@astryxdesign/core 0.7.7-canary.f9ac85f → 0.7.7

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 (198) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/dist/AppShell/useCloseMobileDrawerOnActivate.d.ts +13 -0
  3. package/dist/AppShell/useCloseMobileDrawerOnActivate.d.ts.map +1 -0
  4. package/dist/AppShell/useCloseMobileDrawerOnActivate.js +45 -0
  5. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  6. package/dist/BottomSheet/BottomSheet.js +79 -9
  7. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  8. package/dist/BottomSheet/BottomSheetPanel.js +1 -1
  9. package/dist/BottomSheet/bottomSheetStack.d.ts.map +1 -1
  10. package/dist/BottomSheet/bottomSheetStack.js +5 -4
  11. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +2 -1
  12. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  13. package/dist/Breadcrumbs/BreadcrumbItem.js +7 -3
  14. package/dist/Button/Button.d.ts.map +1 -1
  15. package/dist/Button/Button.js +30 -2
  16. package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
  17. package/dist/CheckboxList/CheckboxList.js +7 -1
  18. package/dist/CheckboxList/CheckboxListContext.d.ts +6 -0
  19. package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
  20. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  21. package/dist/CheckboxList/CheckboxListItem.js +10 -2
  22. package/dist/DateInput/DateInput.d.ts.map +1 -1
  23. package/dist/DateInput/DateInput.js +5 -2
  24. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  25. package/dist/DateInput/MonthScroller.js +13 -27
  26. package/dist/DateInput/Wheel.d.ts.map +1 -1
  27. package/dist/DateInput/Wheel.js +22 -41
  28. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  29. package/dist/DateTimeInput/DateTimeInput.js +5 -2
  30. package/dist/Dialog/Dialog.d.ts.map +1 -1
  31. package/dist/Dialog/Dialog.js +4 -3
  32. package/dist/Drawer/Drawer.js +0 -1
  33. package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -1
  34. package/dist/Drawer/useDrawerDialogPresence.js +5 -1
  35. package/dist/Field/PanelSearchInput.d.ts +9 -1
  36. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  37. package/dist/Field/PanelSearchInput.js +3 -1
  38. package/dist/InfoTip/InfoTip.d.ts +2 -2
  39. package/dist/InfoTip/InfoTip.js +4 -4
  40. package/dist/Layer/layerAnimations.stylex.d.ts +7 -8
  41. package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
  42. package/dist/Layer/layerAnimations.stylex.js +25 -20
  43. package/dist/Layer/usePressPin.d.ts +3 -5
  44. package/dist/Layer/usePressPin.d.ts.map +1 -1
  45. package/dist/Layer/usePressPin.js +4 -6
  46. package/dist/MobileNav/MobileNav.js +0 -1
  47. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  48. package/dist/MultiSelector/hooks.js +2 -4
  49. package/dist/Outline/Outline.d.ts +2 -4
  50. package/dist/Outline/Outline.d.ts.map +1 -1
  51. package/dist/Outline/Outline.js +4 -52
  52. package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
  53. package/dist/Outline/useOutlineFromMarkdown.js +1 -0
  54. package/dist/Outline/useScrollSpy.js +3 -3
  55. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  56. package/dist/PowerSearch/PowerSearch.js +134 -179
  57. package/dist/RadioList/RadioList.d.ts.map +1 -1
  58. package/dist/RadioList/RadioList.js +5 -0
  59. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  60. package/dist/Resizable/ResizeHandle.js +118 -260
  61. package/dist/Resizable/resizeGesture.d.ts +91 -0
  62. package/dist/Resizable/resizeGesture.d.ts.map +1 -0
  63. package/dist/Resizable/resizeGesture.js +251 -0
  64. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  65. package/dist/SideNav/SideNavItem.js +3 -9
  66. package/dist/Token/Token.d.ts +2 -2
  67. package/dist/Token/Token.d.ts.map +1 -1
  68. package/dist/Token/Token.js +12 -4
  69. package/dist/Token/TokenNamingContext.d.ts +14 -0
  70. package/dist/Token/TokenNamingContext.d.ts.map +1 -0
  71. package/dist/Token/TokenNamingContext.js +23 -0
  72. package/dist/Tokenizer/Tokenizer.d.ts +46 -59
  73. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  74. package/dist/Tokenizer/Tokenizer.js +35 -58
  75. package/dist/Tokenizer/TokenizerBottomSheet.d.ts +25 -28
  76. package/dist/Tokenizer/TokenizerBottomSheet.d.ts.map +1 -1
  77. package/dist/Tokenizer/TokenizerBottomSheet.js +157 -54
  78. package/dist/Tokenizer/TokenizerItemRemovableContext.d.ts.map +1 -1
  79. package/dist/Tokenizer/TokenizerItemRemovableContext.js +5 -3
  80. package/dist/Tooltip/Tooltip.d.ts +25 -30
  81. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  82. package/dist/Tooltip/Tooltip.js +24 -5
  83. package/dist/Tooltip/index.d.ts +1 -1
  84. package/dist/Tooltip/index.d.ts.map +1 -1
  85. package/dist/Tooltip/useTooltip.d.ts +28 -29
  86. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  87. package/dist/Tooltip/useTooltip.js +20 -8
  88. package/dist/TopNav/TopNavItem.d.ts.map +1 -1
  89. package/dist/TopNav/TopNavItem.js +4 -7
  90. package/dist/TopNav/TopNavMegaMenuItem.d.ts.map +1 -1
  91. package/dist/TopNav/TopNavMegaMenuItem.js +4 -7
  92. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  93. package/dist/TopNav/TopNavMenu.js +4 -7
  94. package/dist/astryx.css +5 -5
  95. package/dist/gestures/GestureHost.d.ts +27 -1
  96. package/dist/gestures/GestureHost.d.ts.map +1 -1
  97. package/dist/gestures/GestureHost.js +90 -8
  98. package/dist/hooks/useHighlightedOptionScroll.d.ts +11 -1
  99. package/dist/hooks/useHighlightedOptionScroll.d.ts.map +1 -1
  100. package/dist/hooks/useHighlightedOptionScroll.js +103 -5
  101. package/dist/hooks/useMenuPress.d.ts.map +1 -1
  102. package/dist/hooks/useMenuPress.js +4 -2
  103. package/dist/i18n/generated-english/PowerSearch/PowerSearch.generated.d.ts.map +1 -1
  104. package/dist/i18n/generated-english/PowerSearch/PowerSearch.generated.js +1 -0
  105. package/dist/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.d.ts.map +1 -1
  106. package/dist/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.js +7 -1
  107. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  108. package/dist/i18n/generated-locales/en.generated.js +1 -0
  109. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  110. package/dist/i18n/generated-locales/pseudo.generated.js +1 -0
  111. package/dist/internal/SheetConfirmation.d.ts +11 -0
  112. package/dist/internal/SheetConfirmation.d.ts.map +1 -0
  113. package/dist/internal/SheetConfirmation.js +92 -0
  114. package/dist/internal/sheetDraft.d.ts +35 -0
  115. package/dist/internal/sheetDraft.d.ts.map +1 -0
  116. package/dist/internal/sheetDraft.js +65 -0
  117. package/dist/internal/touch-sheet.d.ts +21 -0
  118. package/dist/internal/touch-sheet.d.ts.map +1 -0
  119. package/dist/internal/touch-sheet.js +19 -0
  120. package/dist/internal/useDismissSearchOnTouchDrag.d.ts +24 -0
  121. package/dist/internal/useDismissSearchOnTouchDrag.d.ts.map +1 -0
  122. package/dist/internal/useDismissSearchOnTouchDrag.js +56 -0
  123. package/dist/motion/reducedMotion.d.ts +24 -0
  124. package/dist/motion/reducedMotion.d.ts.map +1 -1
  125. package/dist/motion/reducedMotion.js +78 -1
  126. package/dist/utils/clickGuard.d.ts +8 -1
  127. package/dist/utils/clickGuard.d.ts.map +1 -1
  128. package/dist/utils/clickGuard.js +24 -1
  129. package/locales/en.json +4 -0
  130. package/locales/pseudo.json +3 -0
  131. package/package.json +9 -9
  132. package/src/AppShell/useCloseMobileDrawerOnActivate.ts +52 -0
  133. package/src/BottomSheet/BottomSheet.tsx +95 -10
  134. package/src/BottomSheet/BottomSheetPanel.tsx +7 -2
  135. package/src/BottomSheet/bottomSheetStack.ts +8 -4
  136. package/src/Breadcrumbs/BreadcrumbItem.tsx +13 -3
  137. package/src/Button/Button.tsx +37 -1
  138. package/src/CheckboxList/CheckboxList.doc.mjs +4 -2
  139. package/src/CheckboxList/CheckboxList.tsx +6 -0
  140. package/src/CheckboxList/CheckboxListContext.tsx +6 -0
  141. package/src/CheckboxList/CheckboxListItem.tsx +11 -2
  142. package/src/DateInput/DateInput.tsx +8 -2
  143. package/src/DateInput/MonthScroller.tsx +18 -22
  144. package/src/DateInput/Wheel.tsx +30 -32
  145. package/src/DateTimeInput/DateTimeInput.tsx +8 -2
  146. package/src/Dialog/Dialog.tsx +19 -4
  147. package/src/Drawer/Drawer.tsx +1 -1
  148. package/src/Drawer/useDrawerDialogPresence.ts +5 -1
  149. package/src/Field/PanelSearchInput.tsx +11 -0
  150. package/src/InfoTip/InfoTip.doc.mjs +3 -3
  151. package/src/InfoTip/InfoTip.tsx +5 -5
  152. package/src/Layer/layerAnimations.stylex.ts +24 -16
  153. package/src/Layer/usePressPin.ts +5 -11
  154. package/src/MobileNav/MobileNav.tsx +1 -1
  155. package/src/MultiSelector/hooks.ts +2 -4
  156. package/src/Outline/Outline.doc.mjs +177 -51
  157. package/src/Outline/Outline.tsx +5 -57
  158. package/src/Outline/useOutlineFromMarkdown.doc.mjs +157 -0
  159. package/src/Outline/useOutlineFromMarkdown.ts +1 -0
  160. package/src/Outline/useScrollSpy.ts +3 -3
  161. package/src/PowerSearch/PowerSearch.doc.mjs +3 -3
  162. package/src/PowerSearch/PowerSearch.tsx +196 -183
  163. package/src/RadioList/RadioList.doc.mjs +4 -2
  164. package/src/RadioList/RadioList.tsx +4 -0
  165. package/src/Resizable/ResizeHandle.tsx +133 -328
  166. package/src/Resizable/resizeGesture.ts +368 -0
  167. package/src/SideNav/SideNavItem.tsx +3 -7
  168. package/src/Token/Token.tsx +13 -4
  169. package/src/Token/TokenNamingContext.ts +28 -0
  170. package/src/Tokenizer/Tokenizer.doc.mjs +16 -0
  171. package/src/Tokenizer/Tokenizer.tsx +81 -86
  172. package/src/Tokenizer/TokenizerBottomSheet.tsx +219 -93
  173. package/src/Tokenizer/TokenizerItemRemovableContext.ts +5 -3
  174. package/src/Tooltip/Tooltip.doc.mjs +123 -54
  175. package/src/Tooltip/Tooltip.tsx +36 -39
  176. package/src/Tooltip/index.ts +0 -1
  177. package/src/Tooltip/useTooltip.doc.mjs +147 -33
  178. package/src/Tooltip/useTooltip.tsx +39 -32
  179. package/src/TopNav/TopNavItem.tsx +6 -5
  180. package/src/TopNav/TopNavMegaMenuItem.tsx +6 -5
  181. package/src/TopNav/TopNavMenu.tsx +6 -5
  182. package/src/gestures/GestureHost.ts +105 -10
  183. package/src/hooks/useHighlightedOptionScroll.ts +113 -9
  184. package/src/hooks/useMenuPress.ts +8 -2
  185. package/src/i18n/generated-english/PowerSearch/PowerSearch.generated.ts +1 -0
  186. package/src/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.ts +7 -1
  187. package/src/i18n/generated-locales/en.generated.ts +1 -0
  188. package/src/i18n/generated-locales/pseudo.generated.ts +1 -0
  189. package/src/internal/SheetConfirmation.tsx +101 -0
  190. package/src/internal/sheetDraft.ts +80 -0
  191. package/src/internal/touch-sheet.ts +21 -0
  192. package/src/internal/useDismissSearchOnTouchDrag.ts +69 -0
  193. package/src/motion/reducedMotion.ts +79 -1
  194. package/src/utils/clickGuard.ts +37 -1
  195. package/dist/internal/tokenizer-presentation.d.ts +0 -21
  196. package/dist/internal/tokenizer-presentation.d.ts.map +0 -1
  197. package/dist/internal/tokenizer-presentation.js +0 -21
  198. package/src/internal/tokenizer-presentation.ts +0 -25
@@ -22,4 +22,28 @@ export declare function reducedMotionFade(element?: Element | null, options?: {
22
22
  /** The change starts from the release of a touch drag (DR2). */
23
23
  touchRelease?: boolean;
24
24
  }): ReducedMotionFade | null;
25
+ /** A running Reduced Motion dip. */
26
+ export interface ReducedMotionDip {
27
+ /** Stops the dip. A change that has not landed yet never lands. */
28
+ cancel(): void;
29
+ }
30
+ /**
31
+ * Plays `fade` on `element` as a dip, and runs `land` at its middle: the
32
+ * fade-out, the change, then the fade-in. The fade-out holds at 0 until `land`
33
+ * has run, and the fade-in starts only after it. So the change never shows at
34
+ * full opacity, however late the main thread runs. A timer at the middle can
35
+ * fire after the fade-in has begun.
36
+ *
37
+ * `land` must apply the change before it returns: the fade-in starts right
38
+ * after it. `from` is the opacity the fade-out starts at: a leaving element
39
+ * fades out from where it is.
40
+ *
41
+ * @example
42
+ * ```
43
+ * const dip = playReducedMotionDip(scroller, fade, () => scroller.scrollTo({top}));
44
+ * // A newer change replaces it:
45
+ * dip.cancel();
46
+ * ```
47
+ */
48
+ export declare function playReducedMotionDip(element: Element, fade: ReducedMotionFade, land: () => void, from?: number): ReducedMotionDip;
25
49
  //# sourceMappingURL=reducedMotion.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reducedMotion.d.ts","sourceRoot":"","sources":["../../src/motion/reducedMotion.ts"],"names":[],"mappings":"AAqDA,6EAA6E;AAC7E,eAAO,MAAM,yBAAyB,2DACoB,CAAC;AAE3D,gFAAgF;AAChF,eAAO,MAAM,4BAA4B,iEACuB,CAAC;AAEjE;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,EACxB,OAAO,GAAE;IACP,gEAAgE;IAChE,YAAY,CAAC,EAAE,OAAO,CAAC;CACnB,GACL,iBAAiB,GAAG,IAAI,CAmB1B"}
1
+ {"version":3,"file":"reducedMotion.d.ts","sourceRoot":"","sources":["../../src/motion/reducedMotion.ts"],"names":[],"mappings":"AAqDA,6EAA6E;AAC7E,eAAO,MAAM,yBAAyB,2DACoB,CAAC;AAE3D,gFAAgF;AAChF,eAAO,MAAM,4BAA4B,iEACuB,CAAC;AAEjE;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,EACxB,OAAO,GAAE;IACP,gEAAgE;IAChE,YAAY,CAAC,EAAE,OAAO,CAAC;CACnB,GACL,iBAAiB,GAAG,IAAI,CAmB1B;AAED,oCAAoC;AACpC,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,MAAM,IAAI,IAAI,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,iBAAiB,EACvB,IAAI,EAAE,MAAM,IAAI,EAChB,IAAI,SAAI,GACP,gBAAgB,CAyClB"}
@@ -4,7 +4,7 @@
4
4
  * @file reducedMotion.ts
5
5
  * @input The Reduced Motion preference, the primary pointer, the motion tables
6
6
  * @output reducedMotionFade (the fade the animator would play, for motion it
7
- * does not drive) and the two media queries a StyleX transition spells
7
+ * does not drive), playReducedMotionDip, and the queries a transition spells
8
8
  * @position The one Reduced Motion policy (design:reduced-motion) for code
9
9
  * beside the animator: a raw Web Animations fade, a CSS transition
10
10
  *
@@ -86,4 +86,81 @@ export function reducedMotionFade(element, options = {}) {
86
86
  duration: reducedMotionCrossFade.durationMs,
87
87
  easing: `cubic-bezier(${curve.join(', ')})`
88
88
  };
89
+ }
90
+
91
+ /** A running Reduced Motion dip. */
92
+
93
+ /**
94
+ * Plays `fade` on `element` as a dip, and runs `land` at its middle: the
95
+ * fade-out, the change, then the fade-in. The fade-out holds at 0 until `land`
96
+ * has run, and the fade-in starts only after it. So the change never shows at
97
+ * full opacity, however late the main thread runs. A timer at the middle can
98
+ * fire after the fade-in has begun.
99
+ *
100
+ * `land` must apply the change before it returns: the fade-in starts right
101
+ * after it. `from` is the opacity the fade-out starts at: a leaving element
102
+ * fades out from where it is.
103
+ *
104
+ * @example
105
+ * ```
106
+ * const dip = playReducedMotionDip(scroller, fade, () => scroller.scrollTo({top}));
107
+ * // A newer change replaces it:
108
+ * dip.cancel();
109
+ * ```
110
+ */
111
+ export function playReducedMotionDip(element, fade, land, from = 1) {
112
+ const half = fade.duration / 2;
113
+ let isCancelled = false;
114
+ let fadeIn = null;
115
+ const fadeOut = element.animate([{
116
+ opacity: from,
117
+ easing: fade.easing
118
+ }, {
119
+ opacity: 0
120
+ }], {
121
+ duration: half,
122
+ fill: 'forwards'
123
+ });
124
+ fadeOut.finished.then(() => {
125
+ if (isCancelled) {
126
+ return;
127
+ }
128
+ land();
129
+ // The fade-in holds 0 until it starts: after a long stall, its start can
130
+ // come after the frame that runs this.
131
+ fadeIn = element.animate([{
132
+ opacity: 0,
133
+ easing: fade.easing
134
+ }, {
135
+ opacity: 1
136
+ }], {
137
+ duration: half,
138
+ fill: 'backwards'
139
+ });
140
+ // The fade-in keeps the dip's own schedule: it starts where the fade-out
141
+ // ended, so the dip keeps its length. The clock is read after the
142
+ // landing, because a frame's timestamp does not count the landing's own
143
+ // work. After a long stall, half of the fade-in still plays.
144
+ const fadeOutEnd = endTime(fadeOut, half);
145
+ if (fadeOutEnd != null) {
146
+ fadeIn.startTime = Math.max(fadeOutEnd, performance.now() - half / 2);
147
+ }
148
+ // The fade-in now holds the opacity, so the held 0 can go.
149
+ fadeOut.cancel();
150
+ },
151
+ // A cancelled fade-out rejects `finished`: nothing lands.
152
+ () => {});
153
+ return {
154
+ cancel() {
155
+ isCancelled = true;
156
+ fadeOut.cancel();
157
+ fadeIn?.cancel();
158
+ }
159
+ };
160
+ }
161
+
162
+ /** When `animation` was due to end, on its timeline (ms), if it has started. */
163
+ function endTime(animation, duration) {
164
+ const start = animation.startTime;
165
+ return typeof start === 'number' ? start + duration : null;
89
166
  }
@@ -18,9 +18,16 @@ export interface ClickGuardOptions {
18
18
  * @default false
19
19
  */
20
20
  readonly takesLateMouse?: boolean;
21
+ /**
22
+ * The menu press model's arm at a finger's release: the guard also cancels
23
+ * that touch's `touchend`, so the browser creates no click for it. A
24
+ * cancelled `touchend` disarms the guard.
25
+ * @default false
26
+ */
27
+ readonly cancelsTouchEnd?: boolean;
21
28
  }
22
29
  /** Swallow the browser's next click on `doc` (see the file header). */
23
- export declare function armClickGuard(doc: Document, { staysInstalled, takesLateMouse }?: ClickGuardOptions): void;
30
+ export declare function armClickGuard(doc: Document, { staysInstalled, takesLateMouse, cancelsTouchEnd, }?: ClickGuardOptions): void;
24
31
  /** Stop swallowing on `doc`; idempotent. */
25
32
  export declare function disarmClickGuard(doc: Document): void;
26
33
  /** Whether the guard on `doc` would swallow the next click. */
@@ -1 +1 @@
1
- {"version":3,"file":"clickGuard.d.ts","sourceRoot":"","sources":["../../src/utils/clickGuard.ts"],"names":[],"mappings":"AAqCA;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAEzC,yCAAyC;AACzC,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;CACnC;AA8BD,uEAAuE;AACvE,wBAAgB,aAAa,CAC3B,GAAG,EAAE,QAAQ,EACb,EAAC,cAAsB,EAAE,cAAsB,EAAC,GAAE,iBAAsB,GACvE,IAAI,CA2BN;AAED,4CAA4C;AAC5C,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAYpD;AAED,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAExD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,CAOzD;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAQ/C"}
1
+ {"version":3,"file":"clickGuard.d.ts","sourceRoot":"","sources":["../../src/utils/clickGuard.ts"],"names":[],"mappings":"AA0CA;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAEzC,yCAAyC;AACzC,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;CACpC;AA8BD,uEAAuE;AACvE,wBAAgB,aAAa,CAC3B,GAAG,EAAE,QAAQ,EACb,EACE,cAAsB,EACtB,cAAsB,EACtB,eAAuB,GACxB,GAAE,iBAAsB,GACxB,IAAI,CA+CN;AAED,4CAA4C;AAC5C,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAYpD;AAED,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAExD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,CAOzD;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAQ/C"}
@@ -26,6 +26,11 @@
26
26
  // `touchend`, aimed at whatever is under the finger by then. They go with
27
27
  // the click, the next `pointerdown` or the window, as the guard does.
28
28
  //
29
+ // The menu press model's arm at a finger's release also cancels that touch's
30
+ // `touchend`, so the browser creates no click and no compatibility mouse
31
+ // events. A busy main thread can otherwise hold that click past the window,
32
+ // when a drill-in has put another row under the finger.
33
+ //
29
34
  // Some clicks always pass:
30
35
  // - a click with `detail` 0: a keyboard's, a screen reader's, or one that the
31
36
  // page dispatches;
@@ -67,7 +72,8 @@ function listenForClicks(view) {
67
72
  /** Swallow the browser's next click on `doc` (see the file header). */
68
73
  export function armClickGuard(doc, {
69
74
  staysInstalled = false,
70
- takesLateMouse = false
75
+ takesLateMouse = false,
76
+ cancelsTouchEnd = false
71
77
  } = {}) {
72
78
  const view = doc.defaultView;
73
79
  if (view == null) {
@@ -96,6 +102,23 @@ export function armClickGuard(doc, {
96
102
  }));
97
103
  }
98
104
  }
105
+ if (cancelsTouchEnd) {
106
+ // A touch sends its pointerup before its touchend, so the next touchend is
107
+ // that touch's own. The next pointerdown disarms the guard first.
108
+ const stopTouchEnd = subscribeInput(view, 'touchend', event => {
109
+ stopTouchEnd();
110
+ // A touchend that the browser took for a scroll is not cancelable.
111
+ // The guard stays armed for a click that may follow it.
112
+ if (event.cancelable) {
113
+ event.preventDefault();
114
+ // No click follows a cancelled touchend.
115
+ disarmClickGuard(doc);
116
+ }
117
+ }, {
118
+ cancels: true
119
+ });
120
+ guard.unsubscribers.push(stopTouchEnd);
121
+ }
99
122
  guard.timer = setTimeout(() => disarmClickGuard(doc), CLICK_GUARD_WINDOW_MS);
100
123
  }
101
124
 
package/locales/en.json CHANGED
@@ -1123,6 +1123,10 @@
1123
1123
  "defaultMessage": "All filters removed",
1124
1124
  "description": "Screen-reader announcement after a PowerSearch field's Clear all removes every filter. Distinct from @astryx.tokenizer.allRemoved: PowerSearch words its items as filters."
1125
1125
  },
1126
+ "@astryx.powersearch.editableFiltersRemoved": {
1127
+ "defaultMessage": "Editable filters removed",
1128
+ "description": "Screen-reader announcement after a PowerSearch field's Clear all removes the filters the user can edit and keeps its read-only filters. Used instead of @astryx.powersearch.allFiltersRemoved while a read-only filter remains."
1129
+ },
1126
1130
  "@astryx.resizable.collapsed": {
1127
1131
  "defaultMessage": "Collapsed",
1128
1132
  "description": "aria-valuetext for the Resizable drag handle (role=separator) while its panel is collapsed to zero size. Replaces the numeric value announcement, since the numeric value is clamped to the minimum while collapsed. Adjective describing the panel state, not a verb/command."
@@ -842,6 +842,9 @@
842
842
  "@astryx.powersearch.allFiltersRemoved": {
843
843
  "defaultMessage": "⟦Àłł ƒíłţéřš řéɱóṽéð⟧"
844
844
  },
845
+ "@astryx.powersearch.editableFiltersRemoved": {
846
+ "defaultMessage": "⟦Éðíţàƀłé ƒíłţéřš řéɱóṽéð⟧"
847
+ },
845
848
  "@astryx.resizable.collapsed": {
846
849
  "defaultMessage": "⟦Çółłàþšéð⟧"
847
850
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/core",
3
- "version": "0.7.7-canary.f9ac85f",
3
+ "version": "0.7.7",
4
4
  "displayName": "Astryx Core",
5
5
  "description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
6
6
  "author": "Meta Open Source",
@@ -829,10 +829,10 @@
829
829
  "types": "./dist/internal/touch.d.ts",
830
830
  "default": "./dist/internal/touch.js"
831
831
  },
832
- "./internal/tokenizer-presentation": {
833
- "source": "./src/internal/tokenizer-presentation.ts",
834
- "types": "./dist/internal/tokenizer-presentation.d.ts",
835
- "default": "./dist/internal/tokenizer-presentation.js"
832
+ "./internal/touch-sheet": {
833
+ "source": "./src/internal/touch-sheet.ts",
834
+ "types": "./dist/internal/touch-sheet.d.ts",
835
+ "default": "./dist/internal/touch-sheet.js"
836
836
  }
837
837
  },
838
838
  "files": [
@@ -865,9 +865,6 @@
865
865
  "react-dom": ">=19.0.0"
866
866
  },
867
867
  "devDependencies": {
868
- "@astryxdesign/a11y-spec": "0.7.7-canary.f9ac85f",
869
- "@astryxdesign/cli": "0.7.7-canary.f9ac85f",
870
- "@astryxdesign/interaction-harness": "0.7.7-canary.f9ac85f",
871
868
  "@babel/cli": "^7.29.7",
872
869
  "@babel/core": "^7.29.7",
873
870
  "@babel/preset-react": "^7.29.7",
@@ -878,7 +875,10 @@
878
875
  "@testing-library/jest-dom": "^6.6.0",
879
876
  "@testing-library/react": "^16.3.2",
880
877
  "@types/babel__core": "^7.20.5",
881
- "tailwindcss": "^4.3.2"
878
+ "tailwindcss": "^4.3.2",
879
+ "@astryxdesign/a11y-spec": "0.0.0",
880
+ "@astryxdesign/interaction-harness": "0.0.0",
881
+ "@astryxdesign/cli": "0.7.7"
882
882
  },
883
883
  "dependencies": {
884
884
  "intl-messageformat": "^11.2.9"
@@ -0,0 +1,52 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file useCloseMobileDrawerOnActivate.ts
7
+ * @input AppShellMobileContext, TopNav and SideNav render modes
8
+ * @output useCloseMobileDrawerOnActivate, useIsInAppShellMobileDrawer
9
+ * @position Internal AppShell helper for rows in the mobile drawer. Not
10
+ * exported from the package.
11
+ */
12
+
13
+ import {useAppShellMobile} from './AppShellMobileContext';
14
+ import {useSideNavRenderMode} from '../SideNav/SideNavRenderContext';
15
+ import {useTopNavRenderMode} from '../TopNav/TopNavRenderContext';
16
+
17
+ /**
18
+ * Returns a function that wraps a row's click handler. While
19
+ * `isInMobileDrawer` is true, the wrapped handler calls the row's handler and
20
+ * then closes the AppShell mobile drawer. Otherwise the row's handler comes
21
+ * back unchanged. Every drawer row closes the drawer through this one rule.
22
+ */
23
+ export function useCloseMobileDrawerOnActivate(
24
+ isInMobileDrawer: boolean,
25
+ ): <Args extends unknown[]>(
26
+ onClick: ((...args: Args) => void) | undefined,
27
+ ) => ((...args: Args) => void) | undefined {
28
+ const {closeMobileNav} = useAppShellMobile();
29
+ return onClick => {
30
+ if (!isInMobileDrawer) {
31
+ return onClick;
32
+ }
33
+ return (...args) => {
34
+ onClick?.(...args);
35
+ closeMobileNav();
36
+ };
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Whether the caller renders inside the AppShell mobile drawer: in TopNav
42
+ * content or SideNav content shown there.
43
+ */
44
+ export function useIsInAppShellMobileDrawer(): boolean {
45
+ const topNavRenderMode = useTopNavRenderMode();
46
+ const sideNavRenderMode = useSideNavRenderMode();
47
+ return (
48
+ topNavRenderMode === 'drawer' ||
49
+ sideNavRenderMode === 'drawer' ||
50
+ sideNavRenderMode === 'drawer-content'
51
+ );
52
+ }
@@ -75,6 +75,7 @@ import {
75
75
  import {useDevWarning} from '../hooks/useDevWarning';
76
76
  import {useScrollLock} from '../hooks/useScrollLock';
77
77
  import {useMergedRefs} from '../hooks/useMergedRefs';
78
+ import {observeMutations} from '../utils/sharedMutationObserver';
78
79
  import {mergeProps} from '../utils/mergeProps';
79
80
  import {LayerDepthProvider} from '../Layer/LayerDepthContext';
80
81
  import {ModalAnnounceProvider} from '../hooks/ModalAnnounceProvider';
@@ -147,12 +148,17 @@ const styles = stylex.create({
147
148
  },
148
149
  // An open standalone dialog positions its panel: the panel sits at its
149
150
  // block end, centred, with no wrapper. A tap beside a narrower panel lands
150
- // on the dialog, which light dismiss reads. The stack's recede applies to
151
- // this dialog too, so a covered sheet shrinks and lifts as one.
151
+ // on the dialog, which light dismiss reads. A flex row, not a grid: WebKit
152
+ // lays a grid host out again on each render inside the sheet, which makes
153
+ // each update slower. Not a column: WebKit then drops the content's
154
+ // block-end padding from the scroll range. In a row the panel's height is
155
+ // its own cross size, so a sheet taller than the viewport runs past the top
156
+ // edge, as in a grid. The stack's recede applies to this dialog too, so a
157
+ // covered sheet shrinks and lifts as one.
152
158
  dialogPanelHost: {
153
- display: 'grid',
154
- alignContent: 'end',
155
- justifyItems: 'center',
159
+ display: 'flex',
160
+ justifyContent: 'center',
161
+ alignItems: 'flex-end',
156
162
  },
157
163
  // A non-modal sheet is in the top layer as a manual popover, so it has no
158
164
  // page z-index (AST-027). Below the Popover API floor, `show()` leaves it
@@ -207,6 +213,10 @@ const styles = stylex.create({
207
213
  transitionTimingFunction: 'linear',
208
214
  },
209
215
  },
216
+ // A closing sheet that keeps focus takes no pointer input during its exit.
217
+ exitHoldingFocus: {
218
+ pointerEvents: 'none',
219
+ },
210
220
  positioner: {
211
221
  position: 'absolute',
212
222
  insetInline: 0,
@@ -255,6 +265,24 @@ function stackPositionerStyles(view: BottomSheetStackView) {
255
265
  }
256
266
 
257
267
  /** The `data-sheet-stack` value for a host: only set while sheets stack. */
268
+ /** Stops a click, from any input, before it reaches its target. */
269
+ function ignoreActivation(event: React.MouseEvent): void {
270
+ event.preventDefault();
271
+ event.stopPropagation();
272
+ }
273
+
274
+ /**
275
+ * Stops every key except Tab before it reaches the focused control, so no
276
+ * key handler runs and no text changes (a search field's Enter, a typed
277
+ * letter, Backspace, an arrow that reopens a list). Tab still moves focus.
278
+ */
279
+ function ignoreKeyDuringExit(event: React.KeyboardEvent): void {
280
+ if (event.key !== 'Tab') {
281
+ event.preventDefault();
282
+ event.stopPropagation();
283
+ }
284
+ }
285
+
258
286
  function stackStateAttribute(
259
287
  view: BottomSheetStackView,
260
288
  isOpen: boolean,
@@ -445,10 +473,14 @@ function StandaloneBottomSheet({
445
473
  // `isOpen` restarts the flag during render, and the layout effect below
446
474
  // settles a close.
447
475
  const [isCloseSettled, setIsCloseSettled] = useState(false);
476
+ // Whether focus was inside the sheet when it closed. Such a sheet stays out
477
+ // of `inert` and `aria-hidden` until its exit ends and focus has left.
478
+ const [isExitFocusHeld, setIsExitFocusHeld] = useState(false);
448
479
  const [settledIsOpen, setSettledIsOpen] = useState(isOpen);
449
480
  if (settledIsOpen !== isOpen) {
450
481
  setSettledIsOpen(isOpen);
451
482
  setIsCloseSettled(false);
483
+ setIsExitFocusHeld(false);
452
484
  }
453
485
  const panelState: BottomSheetPanelState = isOpen
454
486
  ? {kind: 'open', entering: stackView.isReplacing}
@@ -576,9 +608,8 @@ function StandaloneBottomSheet({
576
608
  }, [hasScrim, shouldPresent, writeScrim]);
577
609
  useLayoutEffect(() => {
578
610
  if (!isOpen) {
579
- // Read before the browser's focus fixup for the exiting sheet's
580
- // `inert` runs (at the next rendering opportunity), so this is where
581
- // focus was when the person closed the sheet.
611
+ // Where focus was when the person closed the sheet. The closing sheet
612
+ // is not inert yet, so focus has not moved.
582
613
  const dialog = dialogRef.current;
583
614
  hadFocusAtCloseRef.current =
584
615
  dialog != null && dialog.contains(dialog.ownerDocument.activeElement);
@@ -591,6 +622,8 @@ function StandaloneBottomSheet({
591
622
  // that was not paired as a replace is an exit from here on.
592
623
  // eslint-disable-next-line @eslint-react/set-state-in-effect -- settles the close once the registry has answered
593
624
  setIsCloseSettled(true);
625
+ // Read with the close, before the sheet can go inert.
626
+ setIsExitFocusHeld(hadFocusAtCloseRef.current);
594
627
  }
595
628
  }, [hasScrim, isOpen, shouldPresent]);
596
629
 
@@ -694,7 +727,44 @@ function StandaloneBottomSheet({
694
727
  );
695
728
 
696
729
  const {ref: modalityRef} = useInteractionModalityTracking();
697
- const dialogComposedRef = useMergedRefs(dialogRef, modalityRef);
730
+ // Ends the exit hold once focus has left the sheet: moved elsewhere, or
731
+ // dropped to the page because the focused control was removed or disabled.
732
+ // Native listeners, because React drops the `focusout` a browser sends while
733
+ // React removes the focused element, and Firefox and WebKit send no
734
+ // `focusout` for a removed element at all, so a subtree change is read too.
735
+ // Each reads the focus after the event. They attach only while a closing
736
+ // sheet holds focus, so a closed or open sheet observes nothing.
737
+ const isWatchingExitFocus =
738
+ !isOpen && isPresented && !isReplaced && isExitFocusHeld;
739
+ const exitFocusRef = useCallback(
740
+ (dialog: HTMLDialogElement | null) => {
741
+ if (dialog == null || !isWatchingExitFocus) {
742
+ return;
743
+ }
744
+ const releaseIfFocusLeft = () => {
745
+ if (!dialog.contains(dialog.ownerDocument.activeElement)) {
746
+ setIsExitFocusHeld(false);
747
+ }
748
+ };
749
+ const onFocusOut = () => queueMicrotask(releaseIfFocusLeft);
750
+ dialog.addEventListener('focusout', onFocusOut);
751
+ const unobserve = observeMutations(
752
+ dialog,
753
+ {
754
+ childList: true,
755
+ subtree: true,
756
+ attributeFilter: ['disabled', 'hidden', 'inert'],
757
+ },
758
+ releaseIfFocusLeft,
759
+ );
760
+ return () => {
761
+ dialog.removeEventListener('focusout', onFocusOut);
762
+ unobserve();
763
+ };
764
+ },
765
+ [isWatchingExitFocus],
766
+ );
767
+ const dialogComposedRef = useMergedRefs(dialogRef, modalityRef, exitFocusRef);
698
768
  useScrollLock(shouldPresent && hasScrim);
699
769
  useDevWarning(
700
770
  'BottomSheet',
@@ -764,7 +834,16 @@ function StandaloneBottomSheet({
764
834
  // that contains no presented sheet is marked inert and hidden as well, so
765
835
  // the state is explicit; one that does contain the top sheet is left alone,
766
836
  // because inert on it would reach the top sheet too.
767
- const isInert = isExiting || stackView.isBlocked;
837
+ // A sheet that closes with focus inside it is neither inert nor hidden
838
+ // while that focus stays: `inert` would drop the focus to the page, and
839
+ // `aria-hidden` would hide the focused control. When the exit ends, the
840
+ // dialog closes and focus returns in the same task. When focus leaves the
841
+ // sheet first (into a sheet opened above it, for example), the sheet goes
842
+ // inert at once. Until the close settles, no closing sheet is inert, so the
843
+ // first commit of a close never hides focus.
844
+ const isExitHoldingFocus =
845
+ isExiting && !isReplaced && (!isCloseSettled || isExitFocusHeld);
846
+ const isInert = (isExiting && !isExitHoldingFocus) || stackView.isBlocked;
768
847
 
769
848
  return (
770
849
  <dialog
@@ -779,6 +858,7 @@ function StandaloneBottomSheet({
779
858
  hasScrim && styles.scrim,
780
859
  hasScrim && isEntrySettled && styles.scrimSettled,
781
860
  hasScrim && isExiting && styles.scrimClosing,
861
+ isExitHoldingFocus && styles.exitHoldingFocus,
782
862
  !hasScrim && styles.dialogNonModal,
783
863
  )}
784
864
  ref={dialogComposedRef}
@@ -800,6 +880,11 @@ function StandaloneBottomSheet({
800
880
  hasScrim && stackView.depth > 0 ? stackView.depth : undefined
801
881
  }
802
882
  onCancel={handleCancel}
883
+ // A closing sheet that keeps focus takes no activation during its
884
+ // exit: no click and no key but Tab reaches the control that has focus.
885
+ onClickCapture={isExitHoldingFocus ? ignoreActivation : undefined}
886
+ onKeyDownCapture={isExitHoldingFocus ? ignoreKeyDuringExit : undefined}
887
+ onKeyUpCapture={isExitHoldingFocus ? ignoreKeyDuringExit : undefined}
803
888
  // Presses inside the sheet do not reach handlers around where it is
804
889
  // written. The dialog, not the panel, is the root, as in a switcher.
805
890
  {...layerClickBoundary({onClick: handleClick})}>
@@ -296,14 +296,19 @@ const styles = stylex.create({
296
296
  },
297
297
  content: {
298
298
  // Preserve the body's block formatting (including margin/float isolation)
299
- // and its definite percentage-height basis. min-content lets this real
299
+ // and its definite percentage-height basis. fit-content lets this real
300
300
  // observed box grow with async content beyond a fixed-height viewport.
301
301
  // This box is also the sheet's container: it carries the padding and
302
302
  // publishes it, so a lone Section child escapes it exactly as in Dialog.
303
303
  display: 'flow-root',
304
304
  boxSizing: 'border-box',
305
305
  height: '100%',
306
- minHeight: 'min-content',
306
+ // fit-content, not min-content: they are one size in the block axis, but
307
+ // WebKit ignores min-content on a block's min-height. There the box keeps
308
+ // its body's height on a budget-height sheet. Content placed directly in
309
+ // it then overflows the box, and its block-end padding leaves the scroll
310
+ // range.
311
+ minHeight: 'fit-content',
307
312
  },
308
313
  tallKeyboardBody: {
309
314
  scrollPaddingBlockEnd: MOBILE_KEYBOARD_BOTTOM_CLEARANCE,
@@ -866,12 +866,16 @@ function openModalsOutside(element: Element): HTMLDialogElement[] {
866
866
  if (dialog === element || dialog.contains(element)) {
867
867
  continue;
868
868
  }
869
- // A modal on its way out (a closing sheet keeps its dialog modal through
870
- // the exit animation, inert and hidden) blocks nothing any more: it is
871
- // gone for this decision.
869
+ // A modal on its way out blocks nothing any more: it is gone for this
870
+ // decision. A closing sheet keeps its dialog modal through the exit
871
+ // animation; it is inert and hidden then, unless focus was inside it at
872
+ // the close, so the registry's own record of the close counts too.
872
873
  if (
873
874
  dialog.hasAttribute('inert') ||
874
- dialog.getAttribute('aria-hidden') === 'true'
875
+ dialog.getAttribute('aria-hidden') === 'true' ||
876
+ state.entries.some(
877
+ entry => !entry.isOpen && entry.getElement() === dialog,
878
+ )
875
879
  ) {
876
880
  continue;
877
881
  }
@@ -7,7 +7,8 @@
7
7
  * @input Uses React use/useRef/useEffect, stylex, theme tokens, BreadcrumbContext,
8
8
  * rtlStyles for the contextual default slash, and (for the menu trigger)
9
9
  * usePopover + the shared DropdownMenu item API (renderDropdownItems,
10
- * DropdownMenuContext, MENU_ITEM_SELECTOR, useListFocus)
10
+ * DropdownMenuContext, MENU_ITEM_SELECTOR, useListFocus), and
11
+ * the AppShell mobile drawer helpers
11
12
  * @output Exports BreadcrumbItem component and BreadcrumbItemProps
12
13
  * @position Individual breadcrumb item; used inside Breadcrumbs
13
14
  *
@@ -72,6 +73,10 @@ import {
72
73
  import type {DropdownMenuOption} from '../DropdownMenu/DropdownMenu';
73
74
 
74
75
  import {useMergedRefs} from '../hooks/useMergedRefs';
76
+ import {
77
+ useCloseMobileDrawerOnActivate,
78
+ useIsInAppShellMobileDrawer,
79
+ } from '../AppShell/useCloseMobileDrawerOnActivate';
75
80
  import {useInteractionModalityTracking} from '../utils/interactionModality';
76
81
  import {isRenderable} from '../utils/isRenderable';
77
82
  // =============================================================================
@@ -306,6 +311,11 @@ export function BreadcrumbItem({
306
311
  // aria-current on this instead of guessing the <li>'s last child.
307
312
  const contentRef = useRef<HTMLElement>(null);
308
313
 
314
+ const closeDrawerAfter = useCloseMobileDrawerOnActivate(
315
+ useIsInAppShellMobileDrawer(),
316
+ );
317
+ const handleActivate = closeDrawerAfter(onClick);
318
+
309
319
  const isCurrent = isCurrentProp === true;
310
320
  const isAutoCandidate = isCurrentProp == null;
311
321
  const hasMenu = menu != null;
@@ -456,7 +466,7 @@ export function BreadcrumbItem({
456
466
  <LinkComponent
457
467
  ref={contentRef}
458
468
  href={href}
459
- onClick={onClick}
469
+ onClick={handleActivate}
460
470
  {...focusOutlineProps.focusVisible(
461
471
  itemStyles.link,
462
472
  isSupporting ? itemStyles.supportingLink : itemStyles.defaultLink,
@@ -467,7 +477,7 @@ export function BreadcrumbItem({
467
477
  <button
468
478
  ref={contentRef as React.RefObject<HTMLButtonElement | null>}
469
479
  type="button"
470
- onClick={onClick}
480
+ onClick={handleActivate}
471
481
  {...focusOutlineProps.focusVisible(
472
482
  itemStyles.link,
473
483
  itemStyles.buttonReset,