@astryxdesign/core 0.4.7-canary.fe95cd5 → 0.5.0-canary.07f6c33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/CHANGELOG.md +160 -1
  2. package/dist/Avatar/Avatar.d.ts.map +1 -1
  3. package/dist/Avatar/Avatar.js +15 -10
  4. package/dist/Banner/Banner.d.ts +40 -15
  5. package/dist/Banner/Banner.d.ts.map +1 -1
  6. package/dist/Banner/Banner.js +51 -18
  7. package/dist/Button/Button.d.ts.map +1 -1
  8. package/dist/Button/Button.js +5 -4
  9. package/dist/Chat/ChatTokenizedText.d.ts +2 -0
  10. package/dist/Chat/ChatTokenizedText.d.ts.map +1 -1
  11. package/dist/Chat/ChatTokenizedText.js +11 -6
  12. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  13. package/dist/DateInput/TouchDateField.js +4 -3
  14. package/dist/Markdown/parser.d.ts.map +1 -1
  15. package/dist/Markdown/parser.js +28 -6
  16. package/dist/PowerSearch/PowerSearch.d.ts +8 -2
  17. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  18. package/dist/PowerSearch/PowerSearch.js +16 -2
  19. package/dist/PowerSearch/PowerSearchEditPopover.d.ts +3 -1
  20. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  21. package/dist/PowerSearch/PowerSearchEditPopover.js +8 -0
  22. package/dist/PowerSearch/PowerSearchValueEditor.d.ts +3 -1
  23. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  24. package/dist/PowerSearch/PowerSearchValueEditor.js +19 -9
  25. package/dist/PowerSearch/types.d.ts +2 -0
  26. package/dist/PowerSearch/types.d.ts.map +1 -1
  27. package/dist/PowerSearch/usePowerSearchSource.d.ts +6 -1
  28. package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
  29. package/dist/PowerSearch/usePowerSearchSource.js +14 -5
  30. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  31. package/dist/SideNav/SideNavHeading.js +6 -5
  32. package/dist/Spinner/Spinner.d.ts +3 -3
  33. package/dist/Spinner/Spinner.d.ts.map +1 -1
  34. package/dist/Spinner/Spinner.js +130 -78
  35. package/dist/TabList/Tab.d.ts +15 -2
  36. package/dist/TabList/Tab.d.ts.map +1 -1
  37. package/dist/TabList/Tab.js +32 -7
  38. package/dist/TabList/TabList.d.ts +46 -7
  39. package/dist/TabList/TabList.d.ts.map +1 -1
  40. package/dist/TabList/TabList.js +351 -21
  41. package/dist/TabList/TabListContext.d.ts +11 -1
  42. package/dist/TabList/TabListContext.d.ts.map +1 -1
  43. package/dist/TabList/TabListContext.js +11 -2
  44. package/dist/TabList/TabMenu.d.ts.map +1 -1
  45. package/dist/TabList/TabMenu.js +3 -2
  46. package/dist/TabList/index.d.ts +2 -2
  47. package/dist/TabList/index.d.ts.map +1 -1
  48. package/dist/Table/TableRow.d.ts +16 -5
  49. package/dist/Table/TableRow.d.ts.map +1 -1
  50. package/dist/Table/TableRow.js +16 -5
  51. package/dist/Tokenizer/Tokenizer.d.ts +3 -1
  52. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  53. package/dist/Tokenizer/Tokenizer.js +2 -0
  54. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  55. package/dist/TopNav/TopNavHeading.js +3 -2
  56. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  57. package/dist/TopNav/TopNavMenu.js +4 -3
  58. package/dist/Typeahead/BaseTypeahead.d.ts +2 -0
  59. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  60. package/dist/Typeahead/BaseTypeahead.js +58 -27
  61. package/dist/astryx.css +27 -0
  62. package/dist/utils/groupItems.d.ts +5 -1
  63. package/dist/utils/groupItems.d.ts.map +1 -1
  64. package/dist/utils/groupItems.js +9 -9
  65. package/dist/utils/index.d.ts +1 -1
  66. package/dist/utils/index.d.ts.map +1 -1
  67. package/dist/utils/sharedResizeObserver.d.ts.map +1 -1
  68. package/dist/utils/sharedResizeObserver.js +10 -1
  69. package/package.json +2 -2
  70. package/src/Avatar/Avatar.test.tsx +17 -0
  71. package/src/Avatar/Avatar.tsx +61 -38
  72. package/src/Banner/Banner.doc.mjs +13 -11
  73. package/src/Banner/Banner.test.tsx +102 -15
  74. package/src/Banner/Banner.tsx +72 -25
  75. package/src/Button/Button.test.tsx +13 -0
  76. package/src/Button/Button.tsx +5 -4
  77. package/src/Chat/ChatTokenizedText.tsx +17 -10
  78. package/src/DateInput/TouchDateField.tsx +3 -3
  79. package/src/Markdown/incremental.test.ts +16 -2
  80. package/src/Markdown/parser.perf.test.ts +29 -4
  81. package/src/Markdown/parser.ts +36 -7
  82. package/src/PowerSearch/PowerSearch.doc.mjs +134 -28
  83. package/src/PowerSearch/PowerSearch.test.tsx +240 -1
  84. package/src/PowerSearch/PowerSearch.tsx +23 -2
  85. package/src/PowerSearch/PowerSearchEditPopover.tsx +12 -0
  86. package/src/PowerSearch/PowerSearchValueEditor.test.tsx +47 -1
  87. package/src/PowerSearch/PowerSearchValueEditor.tsx +15 -0
  88. package/src/PowerSearch/types.ts +2 -0
  89. package/src/PowerSearch/usePowerSearchSource.test.ts +82 -2
  90. package/src/PowerSearch/usePowerSearchSource.ts +13 -3
  91. package/src/SideNav/SideNavHeading.tsx +10 -10
  92. package/src/Spinner/Spinner.test.tsx +147 -1
  93. package/src/Spinner/Spinner.tsx +120 -101
  94. package/src/TabList/Tab.doc.mjs +13 -1
  95. package/src/TabList/Tab.tsx +67 -7
  96. package/src/TabList/TabList.doc.mjs +22 -6
  97. package/src/TabList/TabList.test.tsx +559 -0
  98. package/src/TabList/TabList.tsx +488 -21
  99. package/src/TabList/TabListContext.ts +13 -2
  100. package/src/TabList/TabMenu.tsx +3 -2
  101. package/src/TabList/index.ts +6 -2
  102. package/src/Table/Table.doc.mjs +6 -1
  103. package/src/Table/Table.test.tsx +97 -14
  104. package/src/Table/TableBody.doc.mjs +40 -0
  105. package/src/Table/TableFooter.doc.mjs +40 -0
  106. package/src/Table/TableHeader.doc.mjs +40 -0
  107. package/src/Table/TableRow.tsx +16 -5
  108. package/src/Tokenizer/Tokenizer.doc.mjs +11 -0
  109. package/src/Tokenizer/Tokenizer.tsx +4 -0
  110. package/src/TopNav/TopNavHeading.tsx +3 -2
  111. package/src/TopNav/TopNavMenu.tsx +4 -3
  112. package/src/Typeahead/BaseTypeahead.doc.mjs +11 -0
  113. package/src/Typeahead/BaseTypeahead.tsx +75 -32
  114. package/src/utils/groupItems.ts +12 -6
  115. package/src/utils/index.ts +1 -1
  116. package/src/utils/sharedResizeObserver.ts +10 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,164 @@
1
1
  # @xds/core
2
2
 
3
+ # 0.5.0
4
+
5
+ #### Breaking Changes
6
+
7
+ - Banner: the collapse axis moves onto one `collapsible` prop, and content can opt out of collapsing (#5255)
8
+ Banner inferred its disclosure from its content: any `children` got a chevron in the header and were hidden until it was pressed. There was no way to show content without a toggle — the case a banner most often wants, a list of the three fields that failed validation — and `defaultIsExpanded` was the only knob, with no controlled mode.
9
+
10
+ The whole axis is now one `boolean | CollapsibleConfig` prop, following the boolean-or-config convention `SideNav.collapsible` set, and backed by the shared `useCollapsible` hook rather than Banner's own state:
11
+
12
+ ```tsx
13
+ <Banner status="error" title="3 fields need attention">…</Banner> // unchanged: collapsible, starts closed
14
+ <Banner collapsible={false}>…</Banner> // new: always visible, no toggle
15
+ <Banner collapsible={{defaultIsOpen: true}}>…</Banner> // replaces defaultIsExpanded
16
+ <Banner collapsible={{isOpen, onOpenChange}}>…</Banner> // new: controlled
17
+ ```
18
+
19
+ **The default is unchanged** — a banner that never mentioned `defaultIsExpanded` behaves exactly as it did. The breaking part is the prop itself: `defaultIsExpanded` is removed in favour of the config, which is a type error at every JSX call site that names it.
20
+
21
+ **Codemod:** `npx astryx upgrade --codemod banner-collapsible-content`
22
+
23
+ It rewrites `defaultIsExpanded` to `collapsible={{defaultIsOpen: true}}` and drops `defaultIsExpanded={false}`, which is now the default. Banners that never set the prop are left alone.
24
+
25
+ **One case the codemod and the compiler both miss: a spread.** `defaultIsExpanded` inside a props object is out of the transform's scope. A props object in a typed position still fails to compile — but an inferred one that is spread, `<Banner {...args} />`, does not, because TypeScript does not excess-property-check a spread. The prop then falls through to the DOM and the banner quietly starts collapsed. **Grep for `defaultIsExpanded` after running the codemod** and migrate any spread sites by hand.
26
+
27
+ - Overlays share one dismissal stack, so a single Escape dismisses exactly one layer. Every overlay used to own its own Escape listener, which meant one press could close a popover _and_ the Dialog hosting it, or a modal _and_ the modal it was opened from. `useLayerDismissal` replaces that with a single stack: the stack owns one listener, routes each press to the top-most layer, and suppresses the browser's own close-watcher so nothing dismisses twice. A layer declares what it does with a press via `escapeBehavior` — `close` (default) or `block`, for a `required` Dialog that must swallow the press without closing so nothing behind it dismisses either. Fixes a Tooltip inside a Dialog closing the Dialog rather than the tip, and a HoverCard trigger swallowing Escape whenever it merely had focus. Dismissals the browser starts on its own — the Android back gesture, the platform close watcher — still close a Dialog, and follow the same top-most rule. An Escape that cancels an in-progress IME composition dismisses nothing: the stack claims that press so the browser raises no close request of its own, and a close request that arrives mid-composition anyway is declined, so a CJK user backing out of a half-formed character no longer loses the layer and everything typed into it. One behavior change worth knowing about if you listen for Escape yourself: the stack claims a press with `preventDefault()` but deliberately leaves propagation alone, so a `keydown` listener on `window` now sees an Escape that a focus-trapped layer used to stop — with `defaultPrevented` already `true`, which is how to tell the stack has acted on it. Top-most is resolved from React-tree nesting (which survives portals) rather than DOM containment alone. A layer's place in that order is keyed to the layer's identity rather than to each registration, so a prop change that re-registers it — a Dialog whose `purpose` flips while it is open — never promotes it above the layers opened over it. Controlled layers follow their control state: a controlled `Tooltip` or `HoverCard` stays on the stack and takes the press like any other layer, but answers it by calling `onOpenChange(false)` rather than hiding itself — whether it actually closes is the caller's update to make, exactly as it has always been for `Dialog` (#4881).
28
+
29
+ #### New Components
30
+
31
+ - Allow MultiSelector count labels to be customized (#4032)
32
+ - MultiSelector: rename the unreleased `formatTriggerCount` prop to `formatValue` and widen it to the whole trigger line. It now receives the selected items (`{value, label}[]`, count available as `.length`) and formats the trigger for `triggerDisplay="count"` and `"labels"`; `"badges"` renders elements, so it is not used there. `formatValue` matches NumberInput and Slider, so the same idea has one name across the system. Defaults are unchanged when the prop is absent (#5377).
33
+ - Promote `Stepper` and `Step` from the canary-only Lab package to Core. The stable package now ships their existing horizontal/vertical layouts, separated and on-track indicators, semantic status, density, and non-linear navigation, plus Core documentation and rendered examples. The default `aria-label` is now localized.
34
+ Advancing one step now animates the connector. Every connector the four layouts draw — the separated bars and the on-track segments alike — grows its accent fill out of the segment's leading edge instead of swapping a background color, so moving forward reads as progress travelling the track. That one gesture is the only thing that animates: going back, jumping forward by more than one step, and mounting mid-flow all apply at once, as does any change under `prefers-reduced-motion`. Retreats are deliberately instant — run in reverse the same transition ends on a shrinking stub of accent, and a remnant still on the track reads as unfinished where the identical curve growing forward reads as arrived — and multi-step jumps are instant because a jump is a navigation rather than a progression, so sweeping a front across the crossed segments only makes the user sit out a journey they asked to skip. Where one span is drawn by several segments (the on-track layouts split a span between two steps, three when a content slot sits between them) the segments take abutting slices of the span's time and run linearly, so the fill reads as one line growing at a constant speed rather than pieces lighting in turn.
35
+
36
+ Five visual fixes land with the promotion. Horizontal steps now divide the track evenly instead of sizing to their own labels, so every progress segment is the same width regardless of how long a step is named. Number indicators shrink from 20px to 16px to match the check, ring, and custom-icon indicators, so a step swapping its number for a check as it completes no longer nudges the label beside it. A step description now occupies a 16px box rather than a 24px one — it previously inherited the page's line box instead of applying its own leading, which opened an 8px gap under the label. A step's content slot now starts flush with the label above it at every density: the slot renders outside the density-padded label area, so it was hanging one pad short of it. And a vertical on-track step carrying content keeps its connector unbroken — the content renders below the row that draws the line, so the track used to split open around any step with content (#5201).
37
+
38
+ #### New Features
39
+
40
+ - AspectRatio: emit `ratio` as a class-level declaration instead of a hard inline style, so the ratio can be overridden responsively: StyleX consumers pass an `aspect-ratio` rule via `xstyle` (including under `@media`/`@container` conditions), and plain-CSS/Tailwind consumers override `aspect-ratio` from their own unlayered rules, which beat the `astryx-base` cascade layer regardless of specificity. The mixed-gallery template's hero now switches 3:1 to 3:2 when the grid stacks with a one-line override on a single element, replacing the duplicated hero markup the fixed inline ratio previously forced (#3883, closes #2798)
41
+ - ChatMessageList: add an `align` prop for top-aligned message lists
42
+ (#3933, closes #2572).
43
+ - DateTimeInput: new `timeOptionInterval` prop adds a dropdown of preset times to the time field, at a cadence of `5 | 10 | 15 | 30 | 60` minutes (`60` gives the 12 AM - 11 PM list). The field becomes an APG combobox over a `listbox`: click or Alt+ArrowDown opens it, ArrowUp/ArrowDown move the active option, Enter picks, Escape closes, and typing moves the highlight to the closest option without filtering the list. `min`/`max` trim the options on the boundary date. Style the popup through the `date-time-input-time-listbox` and `date-time-input-time-option` theme targets.
44
+ Opt-in and additive: with `timeOptionInterval` omitted the time field keeps exactly its current behavior and gains no combobox semantics, so existing `getByRole('combobox')` queries still resolve to the date input. With the list closed the arrow keys keep stepping by `timeIncrement` (#4837).
45
+ - Markdown: opt-in source ranges on parsed blocks
46
+ `parseMarkdown(source, {sourceRanges: true})` now gives every top-level block a `range` — `{start, end}`, the character offsets it occupies in the source that was passed in, with `end` exclusive — so a consumer holding that source can `source.slice(range.start, range.end)` for a block instead of reconstructing it from the node (or from the rendered DOM). Reconstruction is lossy in ways slicing is not: escapes, the exact emphasis and fence characters, heading depth beyond the clamp, and alignment all survive a slice unchanged.
47
+
48
+ Off by default and absent unless asked for, so no existing node, snapshot or comparison changes.
49
+
50
+ Two things the offsets get right that a naive implementation does not: link reference definitions are stripped before the block loop runs, and the ranges are reported against the string the caller passed rather than the stripped text; and `parseMarkdownIncremental` parses slices, so blocks report absolute offsets into the whole document as it streams — including a list whose halves arrived in separate chunks and were merged.
51
+
52
+ A range covers a block's own lines verbatim, so slicing it and parsing the result gives the same node back.
53
+
54
+ Blocks nested inside a list item or a blockquote carry no range: their children are parsed from text the parser reassembled with markers and `>` prefixes removed, so an offset into it would not address the document (#5290).
55
+
56
+ - Table: let `useTableSelection` opt out of the checked-row accent wash
57
+ The selection plugin paints checked rows by writing `backgroundColor` straight onto each `<tr>` from its row ref callback. An inline style outranks anything StyleX can layer on, so a product that wanted the row background for its own meaning had no way to reclaim it short of forking the plugin.
58
+
59
+ `hasRowHighlight` turns the wash off. It defaults to `true`, so existing tables are untouched. Only the background is dropped — `aria-selected` is still set and removed exactly as before, since that is the half of the state screen readers read.
60
+
61
+ ````tsx
62
+ useTableSelection({...config, hasRowHighlight: false});
63
+ ``` (#5310)
64
+ ````
65
+
66
+ - TabList: a strip that switches panels in place can now say so with `role="tablist"`, and it speaks the WAI-ARIA tabs pattern — `role="tablist"` on the strip, `role="tab"` and `aria-selected` on the tabs, and `aria-controls` pointing at the panel each tab opens, from a new `panelId` prop on `Tab`. There is no new prop for the switch: `TabList` declares `role?: AriaRole` and reads it, the way `LayoutHeader`, `LayoutContent` and `LayoutPanel` already declare and document theirs. The keyboard behaviour the pattern asks for was already there: arrows move between tabs, Tab leaves the strip. Under the asserted role the strip takes only the horizontal arrows, leaving ArrowUp and ArrowDown to scroll the page.
67
+ `role` already reached the DOM through `{...restProps}`, so a caller could pass `role="tablist"` and get a tablist whose children were still `<button>`s with `aria-current` — invalid markup, no `aria-selected`, and no warning. Reading the role turns that silent breakage into the correct behaviour; declaring it is what puts it in the type, the prop table and the docs.
68
+
69
+ **Nothing changes for a caller who passes no `role`**: the strip is the `<nav>` landmark with `aria-current` it has always been. Any other role still passes through to the element untouched.
70
+
71
+ Two development warnings come with the asserted role, and only with it. A tab with an `href` is a false statement inside a tablist, so the `href` is ignored and the warning says so. And a tab that controls nothing gets asked for a `panelId` — either that or an `aria-controls` you wrote yourself satisfies it, and a hand-written one is never overwritten. `aria-controls` is emitted only when you supply the id: pointing at a panel that does not exist is an invalid attribute value, which is worse than saying nothing. A menu or any other non-tab in a tablist strip is invalid markup, and warns too. The mirror case warns as well: a `panelId` on a strip that is not a tablist has no panel relationship to state, and is dropped (#5349).
72
+
73
+ - TabList: a strip narrower than its tabs now scrolls instead of spilling out of its container. Every tab stays a tab — nothing is hidden behind a menu — the edges fade to show there is more, and pointers that can hover get arrow affordances; keyboard and screen-reader users reach every tab with the arrow keys, which scrolls the focused tab into view. The selected tab is scrolled back into view whenever it would be out of sight, including on mount and when the host changes `value` itself. The new `overflow` prop takes `'auto'` (the default, which today always scrolls), `'scroll'`, or `'visible'` to keep the old spill-out layout. Built on the existing `useScrollOverflow` hook, so there is no new measurement machinery and no `Carousel` in the tab strip — the documented Carousel recipe, which announced every tab as "slide N of M", is no longer needed and the stories now use the built-in behaviour.
74
+ If you followed that recipe, nothing breaks: a `Carousel` still wrapping the tabs renders and behaves exactly as it did before, because its own scroll container absorbs the strip's, which then never overflows. Removing it is worth doing anyway — it drops the `region`/"slide N of M" wrapping from the accessibility tree, and the strip's own scrolling brings a tab that straddles the edge fully into view on focus, which the carousel does not (#5348).
75
+
76
+ #### Fixes
77
+
78
+ - useTablePagination: with `position='both'` the two pagination `<nav>` landmarks now get distinct accessible names — "{label} (top)" above the table and "{label} (bottom)" below it (axe landmark-unique). Consumer-supplied `label` values are interpolated into both names; single-position labels are unchanged (#4692).
79
+ - Table useTableRowExpansion: the chevron gutter's column header now carries a visually hidden localized name ("Row expansion", key `@astryx.tableRowExpansion.columnHeader`) instead of an empty `<th>` (axe empty-table-header, WCAG 1.3.1 best practice). The gutter stays visually blank (#5383).
80
+ - Table useTableRowStatus: the status gutter's column header now carries a visually hidden localized name ("Row status", key `@astryx.table.rowStatus.columnHeader`) instead of an empty `<th>` (axe empty-table-header, WCAG 1.3.1 best practice). The gutter stays visually blank (#4693).
81
+ - BottomSheet: a standalone sheet no longer dismisses when a CJK user presses Escape to cancel an in-progress IME composition. The browser fires that keydown before `compositionend`, so an Escape handler reading a bare `event.key` misread the composition cancel as a dismissal command and closed the sheet — losing whatever had been typed into a `purpose="form"` field inside it. The handler now early-returns on `isImeKeyEvent`, the same guard `Dialog` and `BottomSheetSwitcher` already carry, and claims the key first so the browser raises no close request of its own (#5322).
82
+ - `Breadcrumbs` marks the current item with semibold weight, not colour alone. The current crumb was distinguished only by `--color-text-primary` against its siblings' `--color-text-secondary`, which fails WCAG 1.4.1 (use of colour) and leaves the current position invisible to anyone who cannot separate the two tones (#4605, closes #4421).
83
+ - ButtonGroup: arrow keys pressed inside a member's open menu stay with that menu. A DropdownMenu renders its menu inline inside the group, so ArrowLeft and ArrowRight used to bubble to the group and move focus onto a sibling button while the menu was still open. The group's `elevation` is also reflected as `data-elevation` now, so a theme can target it (#5355).
84
+ - ButtonGroup is a single tab stop. Its members now share one roving tab stop instead of taking one each, so a three-button group costs one Tab press rather than three. Arrow keys move between members along the orientation (flipped in RTL), Home/End jump to the ends, focus wraps, and disabled members are skipped. Two consequences worth knowing: a keyboard script or test that tabbed through a group member by member must use arrow keys now, and a member rendered as a link (`href`) joins the arrow order for the first time. (#5389)
85
+ - Calendar (and DateInput, DateRangeInput, DateTimeInput) now opens on a month inside the min/max window instead of on today
86
+ With no `focusDate` and no selected value, the calendar opened on today's month even when `min`/`max` excluded it — a 2019 audit window or a booking window that opens next spring rendered a grid where every day was disabled, and the only way in was clicking the prev/next arrows once per month.
87
+
88
+ The initial month is now today clamped into the window: today when it is inside, otherwise whichever bound is nearest. An explicit `focusDate` or a selected value still wins, so nothing changes for callers that already say where to look. With `numberOfMonths={2}` a past window lands `max` in the right-hand pane, so neither pane is entirely out of bounds (#5306).
89
+
90
+ - DateInput: clearing on touch no longer jumps the page to the top
91
+ On the touch surface, tapping the clear (✕) threw the user to the top of the page. Clearing unmounts the clear button, and `handleClear` focused the field in that same task — on iOS Safari, focusing an element as the focused button is removed scrolls the whole document to 0. The focus handoff is now deferred past the unmount, which keeps the page where it was and still returns focus to the field.
92
+
93
+ Measured on the iOS 26 simulator against the live docsite (DateInput — Clearable, page at scrollY 2055): synchronous focus → 0, deferred focus → 2055. `preventScroll` alone does not fix it; it is kept for the ordinary scroll-into-view nudge, which is unwanted for the same reason (#5350).
94
+
95
+ - Clamp standard Dialog width to dynamic viewport space with token gutters, add safe-area/fullscreen sizing and fade-only fullscreen motion updates, add opt-in adaptive Dialog/BottomSheet recipes, and add explicit presentation comparison stories (#5352).
96
+ - DropdownMenu: move the `dropdown-menu-indicator-icon` theme target onto the Icon element itself so a theme can restyle the submenu chevron's size and color directly.
97
+ The loading branch no longer carries the target — its `Spinner` has its own `astryx-spinner` target, matching Selector, MultiSelector and ComplexSelector (#4743).
98
+ - Chat: a token in a message bubble now sits on the line the way it does in the composer. `ChatTokenizedText` wraps each token in the same `inline-flex` / `vertical-align: middle` box `ChatComposerInput` uses, so a chip stops lifting off the text the moment the message is sent. Follows #5324, which fixed the composer half (#5402).
99
+ - Chat: composer tokens no longer sit above surrounding text — `vertical-align` changed from `baseline` to `middle`, and `ChatComposerTokenElement` now uses a StyleX class instead of an inline style so consumers can override alignment without `!important` (#5324).
100
+ - useFocusTrap: Tab is only cancelled when focus is actually inside the trapped container. An open layer whose focus legitimately sits outside it — a listbox popup anchored to its own input, as in DateTimeInput, Typeahead, Selector and MultiSelector — no longer swallows Tab for the whole page, so keyboard users move to the next control on the first press. A trapped surface with no tabbable controls and focus on a `tabIndex={-1}` panel still keeps Tab inside it (#5397).
101
+ - `mod` hotkeys and `Kbd` resolve to Cmd on macOS again when client hints report a blank platform
102
+ `useHotkeys` and `Kbd` both prefer `navigator.userAgentData.platform` and fall back to `navigator.platform`, but guarded the preference with `'platform' in uaData`, which is true whenever the key exists at all. A build reporting `platform: ''` therefore committed to the client-hints branch and got `false` without ever reaching the fallback, so on macOS every `mod` combo listened for Ctrl and every `<Kbd>` drew Ctrl. Electron and other embedders that rewrite the app's user-agent identity ship exactly that. A blank platform is now treated as unknown and falls through (#5325).
103
+ - Markdown streaming: `parseMarkdownIncremental` no longer throws away its settled blocks while a code fence is open, or when a chunk happens to end on a newline — both re-parsed the whole document, so a long streamed response got slower the longer it grew. Blocks rebuilt across a streamed 500-paragraph document: 126,756 → 1,869 (#5407).
104
+ - Migrate core components from inline mergeRefs calls to stable useMergedRefs callbacks (#5267).
105
+ - PowerSearch now applies `menuWidth` to the initial field and search menu without letting it shrink below the input width. Value menus shown after selecting a field are unchanged. (#5237)
106
+ - PowerSearch: `maxOperatorMenuItems` now caps suggestions in string, string-list, and entity-list value typeaheads, including values inside nested filters (#5242).
107
+ - PowerSearch: the edit popover now fits narrow viewports — its 400px minimum width yields to the screen width, and the filter row wraps instead of overflowing when long translated operator labels don't fit. An editor anchored near the screen edge now stays on its own side at the width available there, wrapping internally, instead of flipping across the anchor to keep 400px (#4768).
108
+ - PowerSearch now groups fields in the browsing menu using each field's `group` value. Ungrouped fields appear first, while typed search results remain flat. (#5235)
109
+ - PowerSearch now shows up to 1,000 configured fields when the search box is empty, instead of stopping at 10. Typed searches still show 10 ranked results by default; use `maxSearchResults` to change only that limit. (#5233)
110
+ - `Selector` and `MultiSelector` no longer expose their `{type: 'divider'}` separators to assistive technology. `role="listbox"` only permits `option`/`group` children, but the divider previously rendered `role="separator"` as a direct child of the listbox (axe `aria-required-children`, impact critical). The divider is decorative and carries no information the options don't, so it's now hidden from the accessibility tree via `aria-hidden`, matching the pattern already used for section headings (#5107).
111
+ - Keep merged refs stable across Avatar, Button, SideNav, TabList, and TopNav (#5429).
112
+ - Add useMergedRefs and keep Text and Heading refs stable across rerenders (#5266).
113
+ - Table: a plugin can suppress a body cell's content, and grouped rows use it to keep synthetic group headers out of your cell renderers (#5363)
114
+ `useTableGroupedRows` injects section-header rows into the flattened data, and `BaseTable` evaluates every column's `renderCell` against every row. A renderer that keys a lookup off a field — `STATUS_META[item.status].dot` — was therefore handed a row that is not yours and threw, blanking the page the moment grouping was switched on. The header Proxy answering unknown fields with `''` only ever rescued a renderer that _prints_ a field; `''` fails a lookup exactly as `undefined` does.
115
+
116
+ `BodyCellRenderProps` gains `isContentSuppressed?: boolean`. A plugin sets it in `transformBodyCell` for a row whose cells it is about to replace wholesale in `transformBodyRow`, and the table renders that cell empty without calling the column's renderer or the default one. It is decided per cell at render time against the final column list, so it also covers columns other plugins contributed — whatever order the plugins were listed in.
117
+
118
+ - TimeInput parses compact AM/PM values correctly (#4026)
119
+ - Typeahead: Tab out of the field now moves focus to the next control. The result list is dismissed on the Tab keydown rather than from the blur that press produces — hiding a top-layer popover during the focusout makes Chrome abandon the in-flight focus move and drop focus to `<body>`, so the press appeared to do nothing. Selector and MultiSelector already dismissed on the keydown (#5400).
120
+
121
+ #### Documentation
122
+
123
+ - document missing API contract props across Button, Toast, ContextMenu, MoreMenu, Selector, Link, and Dialog (#4315, part of #4163)
124
+ - document missing props across complex components (MultiSelector, Tokenizer, PowerSearch, Typeahead, Layout, DropdownMenu, HoverCard, Tooltip, Link, Lightbox) (#4316, part of #4163)
125
+ - document the missing components prop on Markdown (#4319, part of #4163)
126
+ - document labelID and isGroupLabel props in Field (#4320, part of #4163)
127
+ - document missing props across structural components (CodeBlock, Toolbar) (#4317, part of #4163)
128
+ - Table: document the section components children mode requires
129
+ Children mode stopped wrapping children in a `<tbody>` in #2098, but the docs still described the contract from before it. The `children` prop read "render TableRow/TableCell directly"; `TableRow`'s own `@example` showed a row sitting in `<Table>` with no section around it; and `TableHeader`, `TableBody`, and `TableFooter` — public exports since that change — had no docs at all and were missing from `Table`'s component list. A reader following the component's own documentation wrote `<table><tr>`, which is invalid HTML and mismatches on hydration.
130
+
131
+ The three section components are now documented, listed on `Table`, and named in the `children` prop description, in a best practice, and in `TableRow`'s example (#5278).
132
+
133
+ #### Other Changes
134
+
135
+ - `align?: 'top' | 'bottom'` (default `'bottom'`). `'bottom'` keeps the
136
+ existing behavior: a flex spacer fills free space so a short conversation sits just above the composer. `'top'` omits the spacer so messages start at the top and grow downward — better for log-style or document-style lists.
137
+ - Only changes the resting position of a non-full list. Once messages overflow
138
+ the container the spacer collapses to zero in both modes, so ChatLayout auto-scroll-to-bottom behavior is unchanged.
139
+ - Spinner: the ring is drawn in SVG instead of `<canvas>`. The arc and track take their colours from the cascade (`currentColor` for `shade="inherit"`), so nothing resolves a colour in JS: a colour change after mount now repaints the ring instead of leaving it stale until it remounts, and mounting spinners no longer costs a `getComputedStyle` each. Rings are pinned to the document timeline's origin, so spinners mounted at different times turn in phase. No API, geometry or theme-target change (#5408).
140
+
141
+ #### Contributors
142
+
143
+ Thanks to everyone who contributed to this release:
144
+
145
+ - @AKnassa
146
+ - @Astro-Han
147
+ - @athz
148
+ - @cixzhang
149
+ - @ernestt
150
+ - @freddymeta
151
+ - @gonzoblasco
152
+ - @HelloOjasMutreja
153
+ - @imdreamrunner
154
+ - @jiunshinn
155
+ - @Kevinjohn
156
+ - @lexs
157
+ - @nynexman4464
158
+ - @rubyycheung
159
+
160
+ ---
161
+
3
162
  # 0.4.7
4
163
 
5
164
  #### Fixes
@@ -189,7 +348,7 @@ Thanks to everyone who contributed to this release:
189
348
  - BottomSheet: a pull up from the scroll area now expands the sheet on iOS. Below the tallest detent, dragging up inside the content did nothing on a real device while the grab handle worked — the sheet took the gesture and then froze for the rest of the pull. iOS Safari raises PointerEvents for a finger under the same numeric id it puts in `Touch.identifier`, so the drag the touch path started was keyed to a live pointer: `beginDrag` captured that pointer, WebKit handed the capture straight back, and the `lostpointercapture` a millisecond later cancelled the drag. Touch-driven drags are now marked as such — they take no pointer capture, and `lostpointercapture`, `pointercancel` and `pointermove` for that same finger no longer cancel, end or double-drive them. Browsers that keep the two id spaces apart were never affected, which is why this only showed up on device (#5178).
190
349
  - Calendar weekday headers now use compact CLDR stand-alone-short names for the selected locale, while preserving the existing `Su` / `Mo` / `Tu` English labels.
191
350
  - Render generated id attributes on Markdown headings so Outline hash links scroll to their target. Heading slugs now come from parser helpers shared with parseOutlineFromMarkdown, and the components.heading override receives the generated id (#4765).
192
- - StatusDot: pair each variant with a distinct built-in shape drawn from the system's semantic icon vocabulary — success a check, warning an exclamation, error a cross, neutral a ring, accent the plain filled dot — so status no longer relies on colour alone (WCAG 2.1 SC 1.4.1). The shapes mirror the marks `Banner`/`FieldStatus` render via `defaultIcons`, a different axis of consistency from `AvatarStatusDot`'s presence shapes (the two share only the neutral ring, intentionally). The diagonal check and cross take a slightly heavier stroke so they stay crisp and distinct at 8px. Also adds an `icon` prop for API parity with `AvatarStatusDot`: a rendered icon replaces the built-in glyph, while booleans and empty renders are ignored so `cond && <Icon />` stays safe. The built-in glyphs resolve through the icon registry under scoped `statusdot:<variant>` keys (the `richtext:*` precedent), so themes can reshape a variant's mark everywhere via `defineTheme({icons})` / `registerIcons` — including marks for augmented custom variants — while overrides of the standard 24px semantic icons deliberately do not leak into the 8px field. Themes can also target the new stable `astryx-statusdot-glyph` class and its `data-shape` attribute — a stroked inline `<svg>` painted from the dot's `currentColor` (#4373).
351
+ - StatusDot: add an `icon` prop for API parity with `AvatarStatusDot` — an optional ReactNode rendered centered in the 8px dot, painted from the dot's `currentColor` ink; booleans and empty renders are ignored so `cond && <Icon />` stays safe. Each variant now pairs its plate with a dedicated ink (`--color-on-*`, the Badge precedent) so a passed icon stays legible — notably the fixed dark on-warning ink on the yellow plate, where a light surface ink lands near 2:1. Per design review, the dot itself deliberately stays a plain colored signal: at 8px, built-in per-variant glyphs satisfy WCAG 1.4.1 on paper but are not genuinely readable, so making the status accessible in context (binary signal, visible label, `icon`, or an accessible alternative) is the builder's responsibility — see the StatusDot usage guidance (#4373).
193
352
  - `Table`'s row-expansion chevron now mirrors correctly under RTL. It previously rotated on expand with no RTL handling at all, so the directional glyph pointed the same way regardless of text direction, matching the pattern already used by `TreeListItem`'s chevron (#5153).
194
353
 
195
354
  #### Contributors
@@ -1 +1 @@
1
- {"version":3,"file":"Avatar.d.ts","sourceRoot":"","sources":["../../src/Avatar/Avatar.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAA4C,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAChF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAqB5C,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAC;AA4BrD;;;;;;GAMG;AACH,KAAK,eAAe,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEzD;;GAEG;AACH,KAAK,iBAAiB,GACpB,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAEzE;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,eAAe,GAAG,iBAAiB,CAAC;AAE7D;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAgBpD;AAgID,MAAM,WAAW,WAAY,SAAQ,SAAS,CAAC,cAAc,CAAC;IAC5D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,EAAE,CAAC,EAAE,iBAAiB,CAAC;IACvB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,OAAO,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;CAChD;AAwHD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,MAAM,CAAC,EACrB,GAAG,EACH,aAAa,EAAE,MAAM,EACrB,WAAW,EACX,IAAI,EACJ,IAAW,EACX,GAAG,EACH,MAAM,EACN,OAAc,EACd,IAAI,EACJ,EAAE,EACF,MAAM,EACN,GAAG,EACH,OAAO,EACP,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,WAAW,+BAmUb;yBAtVe,MAAM"}
1
+ {"version":3,"file":"Avatar.d.ts","sourceRoot":"","sources":["../../src/Avatar/Avatar.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAsB5C,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAC;AA4BrD;;;;;;GAMG;AACH,KAAK,eAAe,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEzD;;GAEG;AACH,KAAK,iBAAiB,GACpB,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAEzE;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,eAAe,GAAG,iBAAiB,CAAC;AAE7D;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAgBpD;AAgID,MAAM,WAAW,WAAY,SAAQ,SAAS,CAAC,cAAc,CAAC;IAC5D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,EAAE,CAAC,EAAE,iBAAiB,CAAC;IACvB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,OAAO,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;CAChD;AAwHD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,MAAM,CAAC,EACrB,GAAG,EACH,aAAa,EAAE,MAAM,EACrB,WAAW,EACX,IAAI,EACJ,IAAW,EACX,GAAG,EACH,MAAM,EACN,OAAc,EACd,IAAI,EACJ,EAAE,EACF,MAAM,EACN,GAAG,EACH,OAAO,EACP,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,WAAW,+BAkVb;yBArWe,MAAM"}
@@ -18,14 +18,15 @@
18
18
  *
19
19
  * Last synced props: alt, fallbackSrc, name, size, src, status, href, as, target, rel, onClick
20
20
  */
21
- import { isValidElement, useMemo, useRef, useState } from 'react';
21
+ import { isValidElement, useCallback, useMemo, useRef, useState } from 'react';
22
22
  import * as stylex from '@stylexjs/stylex';
23
23
  import "../theme/tokens.stylex.js";
24
24
  import { colorVars, typographyVars, fontWeightVars, radiusVars } from "../theme/tokens.stylex.js";
25
25
  import { AvatarSizeContext } from "./AvatarSizeContext.js";
26
26
  import { AvatarStatusLabelContext } from "./AvatarStatusLabelContext.js";
27
27
  import { useAvatarGroup } from "../AvatarGroup/AvatarGroupContext.js";
28
- import { mergeProps, mergeRefs } from "../utils/index.js";
28
+ import { mergeProps } from "../utils/index.js";
29
+ import { useMergedRefs } from "../hooks/useMergedRefs.js";
29
30
  import { themeProps } from "../utils/themeProps.js";
30
31
  import { firstCharacter } from "../utils/characters.js";
31
32
  import { focusOutlineProps } from "../utils/focusOutline.stylex.js";
@@ -444,11 +445,15 @@ export function Avatar({
444
445
  label: undefined,
445
446
  update: null
446
447
  });
447
- // A fresh callback ref every render, so React reattaches it on every commit
448
- // and the name is recomposed after React has written this render's props.
448
+ const {
449
+ 'aria-hidden': consumerAriaHidden,
450
+ 'aria-label': consumerAriaLabel,
451
+ role: consumerRole
452
+ } = props;
453
+ // Recompose the name after React has written any relevant prop changes.
449
454
  // `update` covers the other direction: a label that changes while the avatar
450
455
  // itself does not re-render.
451
- const nameRef = element => {
456
+ const nameRef = useCallback(element => {
452
457
  if (element == null) {
453
458
  return;
454
459
  }
@@ -457,16 +462,16 @@ export function Avatar({
457
462
  const composed = composeAccessibleName(t, nameLabel, meaningful(target.label) ?? getStatusLabel(status));
458
463
  // A consumer's own ARIA wins here exactly as it does in render, where
459
464
  // the derived props spread before `{...props}`.
460
- if (props['aria-label'] == null) {
465
+ if (consumerAriaLabel == null) {
461
466
  setAttributeOrRemove(element, 'aria-label', composed);
462
467
  }
463
468
  // An `<a>`/`<button>` root carries its own role and must not be hidden,
464
469
  // so only the name transfers there.
465
470
  if (!isInteractive) {
466
- if (props.role == null) {
471
+ if (consumerRole == null) {
467
472
  element.setAttribute('role', composed ? 'img' : 'presentation');
468
473
  }
469
- if (props['aria-hidden'] == null) {
474
+ if (consumerAriaHidden == null) {
470
475
  setAttributeOrRemove(element, 'aria-hidden', composed ? undefined : 'true');
471
476
  }
472
477
  }
@@ -475,8 +480,8 @@ export function Avatar({
475
480
  return () => {
476
481
  target.update = null;
477
482
  };
478
- };
479
- const rootRef = mergeRefs(ref, showTooltip ? tooltipHook.ref : undefined, nameRef);
483
+ }, [consumerAriaHidden, consumerAriaLabel, consumerRole, isInteractive, nameLabel, status, t]);
484
+ const rootRef = useMergedRefs(ref, showTooltip ? tooltipHook.ref : undefined, nameRef);
480
485
 
481
486
  // The inner visuals are identical across the static and interactive variants.
482
487
  const visualContent = /*#__PURE__*/_jsxs(_Fragment, {
@@ -1,19 +1,23 @@
1
1
  /**
2
2
  * @file Banner.tsx
3
- * @input Uses React useState/useRef/useId, Button, Icon (with registry string names), StyleX
3
+ * @input Uses React useState/useRef/useId, useCollapsible, Button, Icon (with registry string names), StyleX
4
4
  * @output Exports Banner component, BannerProps, BannerStatus, BannerContainer types
5
5
  * @position Core implementation; consumed by index.ts, tested by Banner.test.tsx
6
6
  *
7
7
  * Visual structure:
8
8
  * - Root container: layout-only wrapper (flex column), no visual styling, no theme target
9
9
  * - Header area (themeProps 'banner'): colored status background with icon, title, description, actions, dismiss
10
- * - Content area (themeProps 'banner-content'): collapsible card background for additional content (children)
10
+ * - Content area (themeProps 'banner-content'): card background for additional content (children)
11
11
  * - Status icon (themeProps 'banner-icon'): the target rides on the default
12
12
  * <Icon> itself — the element that paints — so 'status:X' overrides reach
13
13
  * the glyph (#4166); for a custom `icon` node it stays on the layout wrapper
14
14
  * - No left border accent — color is expressed through the full header background
15
15
  * - Each visual area owns its own border-radius (no overflow:clip on the container)
16
- * - When children are provided, a collapse/expand toggle button appears in the end area
16
+ * - Children are collapsible by default: a toggle appears in the header end
17
+ * area and the content starts closed. `collapsible={false}` opts out and
18
+ * pins the content open with no toggle. The whole collapse axis (enabled /
19
+ * default / controlled) lives on that one prop and is driven by the shared
20
+ * `useCollapsible` hook rather than local state.
17
21
  *
18
22
  * A status added through `BannerStatusMap` augmentation has no entry in the
19
23
  * status lookups, so it renders with no status fill, no default glyph and the
@@ -35,6 +39,7 @@
35
39
  */
36
40
  import { type ReactNode } from 'react';
37
41
  import type { BaseProps } from '../BaseProps';
42
+ import type { CollapsibleConfig } from '../Collapsible/useCollapsible';
38
43
  import type { Elevation } from '../utils/types';
39
44
  import type { BannerStatusMap, BannerContainerMap } from './index';
40
45
  /**
@@ -109,15 +114,29 @@ export interface BannerProps extends BaseProps<HTMLDivElement> {
109
114
  */
110
115
  elevation?: Elevation;
111
116
  /**
112
- * Whether the content area (children) starts expanded.
113
- * Only relevant when children are provided.
114
- * @default false
117
+ * Whether the content area (children) sits behind an expand/collapse toggle
118
+ * in the header. On by default, so a banner with children behaves as it
119
+ * always has.
120
+ *
121
+ * - omitted / `true` — collapsible, starts collapsed
122
+ * - `{defaultIsOpen: true}` — collapsible, starts open
123
+ * - `{isOpen, onOpenChange}` — controlled by the consumer
124
+ * - `false` — not collapsible: children are always visible, with no toggle,
125
+ * no `aria-expanded`, and a content region that stays mounted
126
+ *
127
+ * Takes the shared `CollapsibleConfig` so a banner's disclosure is
128
+ * configured exactly like `Collapsible`'s, rather than through a
129
+ * Banner-only vocabulary. Banner's default differs from the hook's on one
130
+ * point — it starts closed, not open — because a banner's message lives in
131
+ * its header and the content is supplementary.
132
+ *
133
+ * @default true
115
134
  */
116
- defaultIsExpanded?: boolean;
135
+ collapsible?: boolean | CollapsibleConfig;
117
136
  /**
118
- * Extra content rendered below the header in a collapsible card-background area.
137
+ * Extra content rendered below the header in a card-background area.
119
138
  * Use for rich content like lists, links, or detailed information.
120
- * When provided, a collapse/expand toggle button appears in the header.
139
+ * Collapsed behind a toggle unless `collapsible={false}`.
121
140
  */
122
141
  children?: ReactNode;
123
142
  }
@@ -126,11 +145,12 @@ export interface BannerProps extends BaseProps<HTMLDivElement> {
126
145
  *
127
146
  * Two-part visual structure:
128
147
  * - Header: colored status background with icon, title, description, and actions
129
- * - Content (optional): collapsible card background area for additional rich content
148
+ * - Content (optional): card background area for additional rich content
130
149
  *
131
- * When children are provided, a collapse/expand chevron button appears in the
132
- * header end area (to the left of the dismiss button if present). Clicking it
133
- * toggles the visibility of the content area.
150
+ * When children are provided, a chevron toggle appears in the header end area
151
+ * (to the left of the dismiss button if present) and the content starts
152
+ * collapsed. Pass `collapsible={false}` for content that is always visible,
153
+ * or a config object to change the initial state or control it.
134
154
  *
135
155
  * Manages its own dismissed state internally — the banner hides on dismiss
136
156
  * even if `onDismiss` is not provided, so product teams don't need to wire
@@ -161,12 +181,17 @@ export interface BannerProps extends BaseProps<HTMLDivElement> {
161
181
  * <Banner
162
182
  * status="warning"
163
183
  * title="Configuration changes"
164
- * defaultIsExpanded>
184
+ * collapsible={{defaultIsOpen: true}}>
165
185
  * <p>Details here...</p>
166
186
  * </Banner>
187
+ * <Banner status="error" title="3 fields need attention" collapsible={false}>
188
+ * <ul>
189
+ * <li>Email address is invalid</li>
190
+ * </ul>
191
+ * </Banner>
167
192
  * ```
168
193
  */
169
- export declare function Banner({ status, title, description, icon, isDismissable, onDismiss, endContent, container, elevation, defaultIsExpanded, children, xstyle, className, style, ref, onFocusCapture, onPointerDownCapture, ...rest }: BannerProps): import("react").JSX.Element | null;
194
+ export declare function Banner({ status, title, description, icon, isDismissable, onDismiss, endContent, container, elevation, collapsible, children, xstyle, className, style, ref, onFocusCapture, onPointerDownCapture, ...rest }: BannerProps): import("react").JSX.Element | null;
170
195
  export declare namespace Banner {
171
196
  var displayName: string;
172
197
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Banner.d.ts","sourceRoot":"","sources":["../../src/Banner/Banner.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAA0B,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAE9D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAgB5C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAI9C,OAAO,KAAK,EAAC,eAAe,EAAE,kBAAkB,EAAC,MAAM,SAAS,CAAC;AAMjE;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,eAAe,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,kBAAkB,CAAC;AAEvD,MAAM,WAAW,WAAY,SAAQ,SAAS,CAAC,cAAc,CAAC;IAC5D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;OAEG;IACH,MAAM,EAAE,YAAY,CAAC;IACrB;;OAEG;IACH,KAAK,EAAE,SAAS,CAAC;IACjB;;OAEG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAmND;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,MAAM,CAAC,EACrB,MAAM,EACN,KAAK,EACL,WAAW,EACX,IAAI,EACJ,aAAqB,EACrB,SAAS,EACT,UAAU,EACV,SAAkB,EAClB,SAAkB,EAClB,iBAAyB,EACzB,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,cAAc,EACd,oBAAoB,EACpB,GAAG,IAAI,EACR,EAAE,WAAW,sCA+Mb;yBAlOe,MAAM"}
1
+ {"version":3,"file":"Banner.d.ts","sourceRoot":"","sources":["../../src/Banner/Banner.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,EAA0B,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAE9D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAgB5C,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,+BAA+B,CAAC;AAErE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAI9C,OAAO,KAAK,EAAC,eAAe,EAAE,kBAAkB,EAAC,MAAM,SAAS,CAAC;AAMjE;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,eAAe,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,kBAAkB,CAAC;AAEvD,MAAM,WAAW,WAAY,SAAQ,SAAS,CAAC,cAAc,CAAC;IAC5D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;OAEG;IACH,MAAM,EAAE,YAAY,CAAC;IACrB;;OAEG;IACH,KAAK,EAAE,SAAS,CAAC;IACjB;;OAEG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;;;;;;;;;;;;;;OAkBG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,iBAAiB,CAAC;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAmND;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,wBAAgB,MAAM,CAAC,EACrB,MAAM,EACN,KAAK,EACL,WAAW,EACX,IAAI,EACJ,aAAqB,EACrB,SAAS,EACT,UAAU,EACV,SAAkB,EAClB,SAAkB,EAClB,WAAkB,EAClB,QAAQ,EACR,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,cAAc,EACd,oBAAoB,EACpB,GAAG,IAAI,EACR,EAAE,WAAW,sCAoOb;yBAvPe,MAAM"}
@@ -4,20 +4,24 @@
4
4
 
5
5
  /**
6
6
  * @file Banner.tsx
7
- * @input Uses React useState/useRef/useId, Button, Icon (with registry string names), StyleX
7
+ * @input Uses React useState/useRef/useId, useCollapsible, Button, Icon (with registry string names), StyleX
8
8
  * @output Exports Banner component, BannerProps, BannerStatus, BannerContainer types
9
9
  * @position Core implementation; consumed by index.ts, tested by Banner.test.tsx
10
10
  *
11
11
  * Visual structure:
12
12
  * - Root container: layout-only wrapper (flex column), no visual styling, no theme target
13
13
  * - Header area (themeProps 'banner'): colored status background with icon, title, description, actions, dismiss
14
- * - Content area (themeProps 'banner-content'): collapsible card background for additional content (children)
14
+ * - Content area (themeProps 'banner-content'): card background for additional content (children)
15
15
  * - Status icon (themeProps 'banner-icon'): the target rides on the default
16
16
  * <Icon> itself — the element that paints — so 'status:X' overrides reach
17
17
  * the glyph (#4166); for a custom `icon` node it stays on the layout wrapper
18
18
  * - No left border accent — color is expressed through the full header background
19
19
  * - Each visual area owns its own border-radius (no overflow:clip on the container)
20
- * - When children are provided, a collapse/expand toggle button appears in the end area
20
+ * - Children are collapsible by default: a toggle appears in the header end
21
+ * area and the content starts closed. `collapsible={false}` opts out and
22
+ * pins the content open with no toggle. The whole collapse axis (enabled /
23
+ * default / controlled) lives on that one prop and is driven by the shared
24
+ * `useCollapsible` hook rather than local state.
21
25
  *
22
26
  * A status added through `BannerStatusMap` augmentation has no entry in the
23
27
  * status lookups, so it renders with no status fill, no default glyph and the
@@ -43,6 +47,7 @@ import { Button } from "../Button/index.js";
43
47
  import { Icon } from "../Icon/index.js";
44
48
  import "../theme/tokens.stylex.js";
45
49
  import { colorVars, spacingVars, radiusVars, fontWeightVars, typeScaleVars, borderVars, durationVars, easeVars, shadowVars } from "../theme/tokens.stylex.js";
50
+ import { useCollapsible } from "../Collapsible/useCollapsible.js";
46
51
  import { composeEventHandlers, isRenderable, mergeProps } from "../utils/index.js";
47
52
  import { edgeCompSlot } from "../Layout/edgeCompensation.stylex.js";
48
53
  import { themeProps } from "../utils/themeProps.js";
@@ -221,11 +226,12 @@ const elevationStyles = {
221
226
  *
222
227
  * Two-part visual structure:
223
228
  * - Header: colored status background with icon, title, description, and actions
224
- * - Content (optional): collapsible card background area for additional rich content
229
+ * - Content (optional): card background area for additional rich content
225
230
  *
226
- * When children are provided, a collapse/expand chevron button appears in the
227
- * header end area (to the left of the dismiss button if present). Clicking it
228
- * toggles the visibility of the content area.
231
+ * When children are provided, a chevron toggle appears in the header end area
232
+ * (to the left of the dismiss button if present) and the content starts
233
+ * collapsed. Pass `collapsible={false}` for content that is always visible,
234
+ * or a config object to change the initial state or control it.
229
235
  *
230
236
  * Manages its own dismissed state internally — the banner hides on dismiss
231
237
  * even if `onDismiss` is not provided, so product teams don't need to wire
@@ -256,9 +262,14 @@ const elevationStyles = {
256
262
  * <Banner
257
263
  * status="warning"
258
264
  * title="Configuration changes"
259
- * defaultIsExpanded>
265
+ * collapsible={{defaultIsOpen: true}}>
260
266
  * <p>Details here...</p>
261
267
  * </Banner>
268
+ * <Banner status="error" title="3 fields need attention" collapsible={false}>
269
+ * <ul>
270
+ * <li>Email address is invalid</li>
271
+ * </ul>
272
+ * </Banner>
262
273
  * ```
263
274
  */
264
275
  export function Banner({
@@ -271,7 +282,7 @@ export function Banner({
271
282
  endContent,
272
283
  container = 'card',
273
284
  elevation = 'none',
274
- defaultIsExpanded = false,
285
+ collapsible = true,
275
286
  children,
276
287
  xstyle,
277
288
  className,
@@ -283,7 +294,25 @@ export function Banner({
283
294
  }) {
284
295
  const t = useTranslator();
285
296
  const [isDismissed, setIsDismissed] = useState(false);
286
- const [isExpanded, setIsExpanded] = useState(defaultIsExpanded);
297
+ // The disclosure state machine is the shared one — Banner owns no collapse
298
+ // state of its own. `collapsible={false}` disables it, and that is what
299
+ // makes the content permanently visible.
300
+ //
301
+ // The one place Banner departs from the hook's defaults: `useCollapsible`
302
+ // opens by default, a banner starts closed. Its header already carries the
303
+ // message, so the content is supplementary — and this is the behaviour
304
+ // Banner has always had.
305
+ const collapsibleConfig = collapsible != null && typeof collapsible === 'object' ? collapsible : {};
306
+ const {
307
+ isEnabled: isCollapsible,
308
+ isOpen: isExpanded,
309
+ toggle: handleToggleExpand
310
+ } = useCollapsible({
311
+ isCollapsible: collapsible !== false && {
312
+ ...collapsibleConfig,
313
+ defaultIsOpen: collapsibleConfig.defaultIsOpen ?? false
314
+ }
315
+ });
287
316
  // The element focus came from before it entered the banner. Dismissing
288
317
  // unmounts the whole banner, dismiss button included, so without a handoff
289
318
  // the browser drops focus to <body> and a keyboard user loses their place.
@@ -292,7 +321,8 @@ export function Banner({
292
321
  // Links the expand/collapse toggle to the content region it shows/hides so
293
322
  // assistive tech can move from the button to its controlled content
294
323
  // (disclosure pattern). The region is conditionally rendered, so aria-controls
295
- // below is set only while it's mounted to avoid a dangling reference.
324
+ // below is set only while it's mounted to avoid a dangling reference. A
325
+ // non-collapsible banner has no toggle, so neither end of the link is used.
296
326
  const contentId = useId();
297
327
  const defaultIconName = defaultIconNames[status];
298
328
  const role = statusRole[status] ?? FALLBACK_ROLE;
@@ -325,17 +355,20 @@ export function Banner({
325
355
  origin.focus();
326
356
  }
327
357
  };
328
- const handleToggleExpand = () => {
329
- setIsExpanded(prev => !prev);
330
- };
331
358
 
359
+ // The toggle exists only for a collapsible banner that actually has content
360
+ // to disclose.
361
+ const hasToggle = isCollapsible && hasChildren;
332
362
  // Show the end area if there are actions, dismiss, or a collapsible toggle
333
- const showEndArea = isRenderable(endContent) || isDismissable || hasChildren;
363
+ const showEndArea = isRenderable(endContent) || isDismissable || hasToggle;
334
364
  // Center items vertically when there's only a title (no description)
335
365
  // and the banner has action buttons
336
366
  const hasActions = isRenderable(endContent) || isDismissable;
337
367
  const isSingleLine = !isRenderable(description) && hasActions;
338
- const showContent = hasChildren && isExpanded;
368
+
369
+ // Non-collapsible children are always shown; collapsible ones follow the
370
+ // hook's open state.
371
+ const showContent = hasChildren && (!isCollapsible || isExpanded);
339
372
  const isCard = container === 'card';
340
373
  return /*#__PURE__*/_jsxs("div", {
341
374
  ref: ref,
@@ -394,7 +427,7 @@ export function Banner({
394
427
  })]
395
428
  }), showEndArea && /*#__PURE__*/_jsxs("div", {
396
429
  ...stylex.props(styles.endArea, edgeCompSlot.inset(spacingVars['--spacing-2'])),
397
- children: [endContent, hasChildren && /*#__PURE__*/_jsx(Button, {
430
+ children: [endContent, hasToggle && /*#__PURE__*/_jsx(Button, {
398
431
  variant: "ghost",
399
432
  size: "sm",
400
433
  label: isExpanded ? t('@astryx.banner.collapse') : t('@astryx.banner.expand'),
@@ -424,7 +457,7 @@ export function Banner({
424
457
  })]
425
458
  })]
426
459
  }), showContent && /*#__PURE__*/_jsx("div", {
427
- id: contentId,
460
+ id: hasToggle ? contentId : undefined,
428
461
  ...mergeProps(themeProps('banner-content', {
429
462
  container,
430
463
  status
@@ -1 +1 @@
1
- {"version":3,"file":"Button.d.ts","sourceRoot":"","sources":["../../src/Button/Button.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAwB,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAC5D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAC,SAAS,EAAE,SAAS,EAAC,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAuB3C,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAC;AAIrD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,SAAS,CAAC;AA0G9C,QAAA,MAAM,UAAU;;;;;;;;;;EAUd,CAAC;AAkFH;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,gBAAgB,CAAC;AAEnD;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,UAAU,CAAC;AAEjD,MAAM,WAAW,WAAY,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IAC/D,wCAAwC;IACxC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IACnC,oDAAoD;IACpD,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;IACrC,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAChD,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB;;;OAGG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,OAAO,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;IACrD;;OAEG;IACH,WAAW,CAAC,EAAE,CACZ,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KACnC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;OAEG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,EAAE,CAAC,EAAE,iBAAiB,CAAC;IACvB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAuJD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,MAAM,CAAC,EACrB,KAAK,EACL,OAAqB,EACrB,IAAI,EAAE,QAAQ,EACd,IAAe,EACf,UAAkB,EAClB,SAAiB,EACjB,eAAuB,EACvB,WAAW,EACX,IAAI,EACJ,UAAkB,EAClB,KAAK,EACL,SAAkB,EAClB,QAAQ,EACR,UAAU,EACV,OAAO,EACP,IAAI,EACJ,EAAE,EACF,MAAM,EACN,GAAG,EACH,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,GAAG,KAAK,EACT,EAAE,WAAW,GAAG,SAAS,CA6OzB;yBAtQe,MAAM"}
1
+ {"version":3,"file":"Button.d.ts","sourceRoot":"","sources":["../../src/Button/Button.tsx"],"names":[],"mappings":"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"}
@@ -27,7 +27,8 @@ import { VisuallyHidden } from "../VisuallyHidden/index.js";
27
27
  import { EDGE_COMP_ATTR } from "../Layout/edgeCompensation.stylex.js";
28
28
  import { useSize } from "../SizeContext/SizeContext.js";
29
29
  import { useButtonGroup } from "../ButtonGroup/ButtonGroupContext.js";
30
- import { mergeProps, mergeRefs } from "../utils/index.js";
30
+ import { mergeProps } from "../utils/index.js";
31
+ import { useMergedRefs } from "../hooks/useMergedRefs.js";
31
32
  import { useLinkComponent } from "../Link/useLinkComponent.js";
32
33
  import { themeProps } from "../utils/themeProps.js";
33
34
  import { focusOutlineProps } from "../utils/focusOutline.stylex.js";
@@ -503,9 +504,9 @@ export function Button({
503
504
  } : null;
504
505
 
505
506
  // Merge the consumer ref with the tooltip hook's trigger ref so both point at
506
- // the same element. mergeRefs tolerates undefined, so this is a no-op for the
507
- // tooltip side when no tooltip is set.
508
- const mergedButtonRef = mergeRefs(ref, tooltip != null ? tooltipHook.ref : undefined);
507
+ // the same element. useMergedRefs tolerates undefined, so this is a no-op for
508
+ // the tooltip side when no tooltip is set.
509
+ const mergedButtonRef = useMergedRefs(ref, tooltip != null ? tooltipHook.ref : undefined);
509
510
  let element;
510
511
  if (renderAsLink) {
511
512
  element = /*#__PURE__*/_jsx(LinkComponent, {
@@ -10,6 +10,8 @@
10
10
  *
11
11
  * SYNC: When modified, update:
12
12
  * - /packages/core/src/Chat/index.ts
13
+ * - /packages/core/src/Chat/ChatComposerInput.tsx (token alignment must match,
14
+ * or a token moves when the message is sent)
13
15
  * - /packages/cli/assets/templates/blocks/components/ChatTokenizedText/ (block examples)
14
16
  */
15
17
  import React from 'react';
@@ -1 +1 @@
1
- {"version":3,"file":"ChatTokenizedText.d.ts","sourceRoot":"","sources":["../../src/Chat/ChatTokenizedText.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAuB,MAAM,OAAO,CAAC;AAG5C,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AAE3D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAiB5C,MAAM,WAAW,sBAAuB,SAAQ,SAAS,CAAC,eAAe,CAAC;IACxE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACjC,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,EAAE,iBAAiB,EAAE,CAAC;CAC9B;AAoBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,GAAG,EACH,QAAQ,EACR,MAAM,EACN,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,IAAI,EACR,EAAE,sBAAsB,qBA+BxB;yBAvCe,iBAAiB"}
1
+ {"version":3,"file":"ChatTokenizedText.d.ts","sourceRoot":"","sources":["../../src/Chat/ChatTokenizedText.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAuB,MAAM,OAAO,CAAC;AAG5C,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AAE3D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAqB5C,MAAM,WAAW,sBAAuB,SAAQ,SAAS,CAAC,eAAe,CAAC;IACxE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACjC,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,EAAE,iBAAiB,EAAE,CAAC;CAC9B;AAoBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,GAAG,EACH,QAAQ,EACR,MAAM,EACN,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,IAAI,EACR,EAAE,sBAAsB,qBA+BxB;yBAvCe,iBAAiB"}