-
Notifications
You must be signed in to change notification settings - Fork 1.1k
fix(core): render plain Link anchors inline so an ancestor Text clamp can truncate them - #6038
fix(core): render plain Link anchors inline so an ancestor Text clamp can truncate them #6038ManoharPaturi wants to merge 4 commits into
Conversation
... can truncate them
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Hi @ManoharPaturi!
Thank you for your pull request and welcome to our community.
Action Required
In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.
Process
In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.
Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.
If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The newly added CSS-introspection test helper matches selectors via substring checks, which can produce incorrect/flaky assertions and should be made selector-token-accurate.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Updates Link’s default anchor layout so it can be truncated by an ancestor <Text maxLines> clamp, while preserving inline-flex where it’s required (external-link icon and button-rendered form). Also adds documentation and tests to prevent regressions around the clamp behavior described in #6021.
Changes:
- Render plain
<a>links asdisplay: inline(instead ofinline-flex) to allow ancestor-webkit-line-clamptruncation. - Introduce a dedicated
flexLayoutstyle applied only to external-link icon and button-rendered forms. - Add docs + tests covering the truncation behavior and the external-link limitation.
File summaries
| File | Description |
|---|---|
| packages/core/src/Text/Text.doc.mjs | Documents that external-link Link children won’t be truncated by an ancestor Text maxLines. |
| packages/core/src/Link/Link.tsx | Switches base anchor display to inline and scopes inline-flex to icon/button cases via flexLayout. |
| packages/core/src/Link/Link.test.tsx | Adds tests asserting the emitted display mode for plain/external/button Link forms. |
| packages/core/src/Link/Link.doc.mjs | Adds Link guidance for when ancestor Text maxLines truncation will/won’t work. |
| .changeset/link-anchor-clamp.md | Publishes the behavioral change as a patch changeset for @astryxdesign/core. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The displayDeclarationsFor helper added in facebook#6038 matched an element's classList against raw selector text via String.includes, which is a substring check on hashed atomic class names (e.g. StyleX's x1a2b3c style classes). A shorter class name can be a substring of an unrelated longer one, so the check can both false-positive and false-negative depending on what else StyleX has generated for the test run. Rewrite it to walk the real CSSOM: parse each CSSStyleRule's selectorText into class tokens, compare those tokens against the element's classList by exact match, and read the declaration via rule.style.getPropertyValue('display') instead of a body-text regex. Also descends into grouping rules (e.g. @media) so nested declarations aren't missed. Flagged by Copilot review on facebook#6038: facebook#6038 (review) All 63 Link tests and 189 Text tests still pass; tsc --noEmit is clean.
parinith-web
commented
Sep 4, 2026
Hey @ManoharPaturi, nice fix — the base/flexLayout split is exactly the right shape for this.
I took a stab at the one thing Copilot flagged (the displayDeclarationsFor helper in Link.test.tsx matching classes via String.includes on raw selector text, which can mis-match on hashed atomic class names). Reworked it to walk the real CSSOM instead — parses selectorText into class tokens and compares those exactly against the element's classList, reading the value via rule.style.getPropertyValue('display') rather than a regex over the rule body. Also descends into grouping rules like @media so nothing nested gets missed.
Ran it against your branch locally: all 63 Link tests, 189 Text tests, and 640 eslint-plugin-astryx tests still pass, and tsc --noEmit is clean.
Opened it as a PR on top of your branch here: #6043 — happy to fold it directly into this PR instead if you'd rather take the diff yourself, just say the word. Wanted to flag it here first rather than just have a second PR show up with no context.
@cixzhang
cixzhang
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Existing block Links lose keyboard focus visibility in this change. In Chromium, <Link display="block"> changes from a 124px inline-flex focus box to an inline anchor split around a 260px block child; the 2px focus outline computes but paints nothing. Please preserve the old focusable box for block/own-clamped Link variants while keeping only unclamped plain anchors inline.
[Reviewed by Robohands]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
display="block" now splits this inline anchor around its block child, so its keyboard focus outline disappears.
The displayDeclarationsFor helper added in facebook#6038 matched an element's classList against raw selector text via String.includes, which is a substring check on hashed atomic class names (e.g. StyleX's x1a2b3c style classes). A shorter class name can be a substring of an unrelated longer one, so the check can both false-positive and false-negative depending on what else StyleX has generated for the test run. Rewrite it to walk the real CSSOM: parse each CSSStyleRule's selectorText into class tokens, compare those tokens against the element's classList by exact match, and read the declaration via rule.style.getPropertyValue('display') instead of a body-text regex. Also descends into grouping rules (e.g. @media) so nested declarations aren't missed. Flagged by Copilot review on facebook#6038: facebook#6038 (review) All 63 Link tests and 189 Text tests still pass; tsc --noEmit is clean.
ManoharPaturi
commented
Sep 5, 2026
@parinith-web thank you for this — the CSSOM-token matching is a real improvement over my substring approach (exact class comparison + proper descent into grouping rules), and I appreciate you flagging it here first rather than surprising us with a stacked PR. Folded your commit into this branch as 1abf92c (kept your authorship). #6043 can be closed whenever convenient — all 63 Link tests green on the updated branch.
@cixzhang
cixzhang
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for tightening the test helper. The earlier keyboard-focus regression is still present in Chromium: both <Link display="block"> and <Link maxLines={2}> compute a 2px outline but paint no visible focus box because the anchor remains inline around a block-level Text child. Please preserve a visible root focus box for those forms while keeping only the ancestor-clamped plain anchor inline.
[Reviewed by Robohands]
...lf-clamped Link forms
ManoharPaturi
commented
Sep 5, 2026
Thanks @cixzhang — fixed in 2a36cb1. The inline anchor is now reserved for the one composition this PR exists for: the unclamped plain Link under an ancestor <Text maxLines>. Any Link that establishes its own box keeps the inline-flex root and its visible focus outline:
display="block"→ inline-flex root- own
maxLines > 0(self-clamping) → inline-flex root - external-link icon form → inline-flex root (unchanged)
- button-rendered form → inline-flex root (unchanged)
So <Link display="block"> and <Link maxLines={2}> compute and paint the 2px focus box exactly as before this PR, while <Text maxLines={2}><Link>plain</Link></Text> — with no props on the Link — stays inline and truncable by the ancestor clamp. (One subtlety caught by the new tests: maxLines destructures with a default of 0, so the own-clamp condition is maxLines > 0, not a null check.)
Two new test cases assert the block and self-clamped forms keep inline-flex, on top of the existing plain-inline case. 254/254 Link + Text tests, core typecheck, and the full check:repo battery green on the updated branch.
@cixzhang
cixzhang
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, the ancestor clamp and both requested focus cases now work in Chromium. One compatibility path remains: existing Links with a block-level child (for example, HStack plus an icon) still compute a 2px outline but paint no keyboard focus ring. Please keep those composed Links visibly focused too while leaving text-only ancestor-clamped Links inline.
[Reviewed by Robohands]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needsRootBox misses existing block-level children, so those composed Links still paint no focus ring.
Body:
Fixes #6021
Summary
A plain
<Text maxLines={2}><Link>...</Link></Text>silently did not truncate:the anchor was
display: inline-flex, and an inline-flex box establishes itsown formatting context that an ancestor
-webkit-line-clampcannot reach.This changes the plain anchor to
display: inline, so it participates in thesurrounding line boxes and an ancestor clamp truncates it normally. The flex
layout is not lost where it is load-bearing — it moves to a dedicated style
applied only when needed:
isExternalLink+ anchor form) still lay out asinline-flexwithalignItems: centerand the icon gap, exactly as beforeinline-flexlayoutAn external link inside a clamped
Textstill cannot be truncated by theancestor (the icon needs the flex layout), so this also documents the
composition on both sides — Link guidance (do: plain links clamp; don't:
expect it for external links — pass
maxLinesto the Link) in all three docvariants (EN/zh/dense) and matching Text guidance (EN/zh + dense).
Tests
Three new cases in
Link.test.tsxassert the emitted display declarationsper form (via the injected-CSS helper pattern used by forced-colors tests):
inline, and notinline-flexinline-flexinline-flexTesting
vitest run packages/core/src/Link— 63 passedvitest run packages/core/src/Text— 189 passedvitest run internal/eslint-plugin-astryx— 640 passed (docblock format)tsc --noEmit(core) clean, eslint clean,check:changesetsandcheck:syncpass