@astryxdesign/core 0.5.2-canary.c9c8564 → 0.5.2-canary.e03511b

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 (216) hide show
  1. package/dist/BottomSheet/BottomSheet.d.ts +3 -1
  2. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  3. package/dist/BottomSheet/BottomSheet.js +4 -3
  4. package/dist/BottomSheet/BottomSheetEdgeTint.d.ts +2 -2
  5. package/dist/BottomSheet/BottomSheetEdgeTint.d.ts.map +1 -1
  6. package/dist/BottomSheet/BottomSheetEdgeTint.js +7 -10
  7. package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
  8. package/dist/BottomSheet/BottomSheetSwitcher.js +1 -1
  9. package/dist/Button/Button.d.ts +2 -1
  10. package/dist/Button/Button.d.ts.map +1 -1
  11. package/dist/Button/Button.js +11 -4
  12. package/dist/Carousel/Carousel.d.ts.map +1 -1
  13. package/dist/Carousel/Carousel.js +2 -2
  14. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  15. package/dist/Chat/ChatToolCalls.js +34 -17
  16. package/dist/ContextMenu/ContextMenu.d.ts +14 -3
  17. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  18. package/dist/ContextMenu/ContextMenu.js +149 -26
  19. package/dist/ContextMenu/index.d.ts +1 -0
  20. package/dist/ContextMenu/index.d.ts.map +1 -1
  21. package/dist/DropdownMenu/DropdownMenu.d.ts +24 -5
  22. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  23. package/dist/DropdownMenu/DropdownMenu.js +340 -26
  24. package/dist/DropdownMenu/DropdownMenuContext.d.ts +1 -1
  25. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  26. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  27. package/dist/DropdownMenu/DropdownMenuItem.js +1 -1
  28. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +6 -2
  29. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  30. package/dist/DropdownMenu/DropdownMenuSubMenu.js +43 -14
  31. package/dist/DropdownMenu/MenuBottomSheet.d.ts +20 -0
  32. package/dist/DropdownMenu/MenuBottomSheet.d.ts.map +1 -0
  33. package/dist/DropdownMenu/MenuBottomSheet.js +36 -0
  34. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts +10 -0
  35. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -0
  36. package/dist/DropdownMenu/MenuBottomSheetActionList.js +111 -0
  37. package/dist/DropdownMenu/index.d.ts +2 -1
  38. package/dist/DropdownMenu/index.d.ts.map +1 -1
  39. package/dist/DropdownMenu/menuWidth.d.ts +14 -0
  40. package/dist/DropdownMenu/menuWidth.d.ts.map +1 -0
  41. package/dist/DropdownMenu/menuWidth.js +35 -0
  42. package/dist/DropdownMenu/useMenuOverflow.d.ts +10 -0
  43. package/dist/DropdownMenu/useMenuOverflow.d.ts.map +1 -0
  44. package/dist/DropdownMenu/useMenuOverflow.js +60 -0
  45. package/dist/MoreMenu/MoreMenu.d.ts +8 -2
  46. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  47. package/dist/MoreMenu/MoreMenu.js +2 -0
  48. package/dist/MultiSelector/MultiSelector.d.ts +11 -1
  49. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  50. package/dist/MultiSelector/MultiSelector.js +97 -68
  51. package/dist/MultiSelector/index.d.ts +1 -1
  52. package/dist/MultiSelector/index.d.ts.map +1 -1
  53. package/dist/Popover/Popover.d.ts +10 -6
  54. package/dist/Popover/Popover.d.ts.map +1 -1
  55. package/dist/Popover/Popover.js +141 -16
  56. package/dist/Popover/usePopover.d.ts +4 -0
  57. package/dist/Popover/usePopover.d.ts.map +1 -1
  58. package/dist/Popover/usePopover.js +52 -5
  59. package/dist/Selector/Selector.d.ts +10 -0
  60. package/dist/Selector/Selector.d.ts.map +1 -1
  61. package/dist/Selector/Selector.js +106 -80
  62. package/dist/Selector/SelectorBottomSheet.d.ts +21 -0
  63. package/dist/Selector/SelectorBottomSheet.d.ts.map +1 -0
  64. package/dist/Selector/SelectorBottomSheet.js +83 -0
  65. package/dist/Selector/index.d.ts +1 -1
  66. package/dist/Selector/index.d.ts.map +1 -1
  67. package/dist/Selector/selectorPresentation.stylex.d.ts +16 -0
  68. package/dist/Selector/selectorPresentation.stylex.d.ts.map +1 -0
  69. package/dist/Selector/selectorPresentation.stylex.js +21 -0
  70. package/dist/Selector/useSelectorPresentation.d.ts +32 -0
  71. package/dist/Selector/useSelectorPresentation.d.ts.map +1 -0
  72. package/dist/Selector/useSelectorPresentation.js +90 -0
  73. package/dist/Toast/useToastGesture.d.ts.map +1 -1
  74. package/dist/Toast/useToastGesture.js +46 -9
  75. package/dist/astryx.css +19 -1
  76. package/dist/hooks/useAdaptivePresentation.d.ts +5 -0
  77. package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -0
  78. package/dist/hooks/useAdaptivePresentation.js +19 -0
  79. package/dist/hooks/useFocusReturnVisibility.d.ts +7 -0
  80. package/dist/hooks/useFocusReturnVisibility.d.ts.map +1 -0
  81. package/dist/hooks/useFocusReturnVisibility.js +35 -0
  82. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  83. package/dist/hooks/useFocusTrap.js +12 -0
  84. package/locales/en.json +20 -0
  85. package/locales/pseudo.json +15 -0
  86. package/package.json +2 -2
  87. package/src/BottomSheet/BottomSheet.doc.mjs +37 -0
  88. package/src/BottomSheet/BottomSheet.spec.md +177 -0
  89. package/src/BottomSheet/BottomSheet.test.tsx +29 -0
  90. package/src/BottomSheet/BottomSheet.tsx +7 -4
  91. package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +4 -6
  92. package/src/BottomSheet/BottomSheetEdgeTint.tsx +7 -10
  93. package/src/BottomSheet/BottomSheetSwitcher.tsx +1 -2
  94. package/src/Button/Button.doc.mjs +56 -0
  95. package/src/Button/Button.test.tsx +10 -0
  96. package/src/Button/Button.tsx +13 -5
  97. package/src/ButtonGroup/ButtonGroup.doc.mjs +47 -0
  98. package/src/Carousel/Carousel.test.tsx +83 -0
  99. package/src/Carousel/Carousel.tsx +8 -2
  100. package/src/Chat/ChatToolCalls.test.tsx +44 -0
  101. package/src/Chat/ChatToolCalls.tsx +36 -15
  102. package/src/CheckboxList/CheckboxList.doc.mjs +172 -31
  103. package/src/CheckboxList/CheckboxList.spec.md +214 -0
  104. package/src/CommandPalette/CommandPalette.doc.mjs +72 -0
  105. package/src/CommandPalette/CommandPalette.spec.md +223 -0
  106. package/src/ComplexSelector/ComplexSelector.doc.mjs +42 -0
  107. package/src/ComplexSelector/ComplexSelector.spec.md +189 -0
  108. package/src/ContextMenu/ContextMenu.doc.mjs +61 -1
  109. package/src/ContextMenu/ContextMenu.spec.md +206 -0
  110. package/src/ContextMenu/ContextMenu.test.tsx +131 -1
  111. package/src/ContextMenu/ContextMenu.tsx +199 -32
  112. package/src/ContextMenu/index.ts +1 -0
  113. package/src/Divider/Divider.doc.mjs +24 -0
  114. package/src/Divider/Divider.spec.md +150 -0
  115. package/src/DropdownMenu/DropdownMenu.doc.mjs +305 -40
  116. package/src/DropdownMenu/DropdownMenu.spec.md +245 -0
  117. package/src/DropdownMenu/DropdownMenu.test.tsx +518 -7
  118. package/src/DropdownMenu/DropdownMenu.tsx +459 -28
  119. package/src/DropdownMenu/DropdownMenuContext.tsx +1 -1
  120. package/src/DropdownMenu/DropdownMenuItem.tsx +2 -0
  121. package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +4 -2
  122. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +124 -0
  123. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +72 -17
  124. package/src/DropdownMenu/MenuBottomSheet.tsx +46 -0
  125. package/src/DropdownMenu/MenuBottomSheetActionList.tsx +141 -0
  126. package/src/DropdownMenu/index.ts +2 -0
  127. package/src/DropdownMenu/menuWidth.ts +51 -0
  128. package/src/DropdownMenu/useMenuOverflow.ts +77 -0
  129. package/src/FieldStatus/FieldStatus.doc.mjs +21 -0
  130. package/src/FieldStatus/FieldStatus.spec.md +212 -0
  131. package/src/Grid/Grid.doc.mjs +19 -0
  132. package/src/Grid/Grid.spec.md +161 -0
  133. package/src/Icon/Icon.doc.mjs +196 -48
  134. package/src/Icon/Icon.spec.md +191 -0
  135. package/src/IconButton/IconButton.doc.mjs +38 -0
  136. package/src/Kbd/Kbd.doc.mjs +18 -0
  137. package/src/Kbd/Kbd.spec.md +141 -0
  138. package/src/Layout/Layout.doc.mjs +32 -0
  139. package/src/Layout/Layout.spec.md +199 -0
  140. package/src/Lightbox/Lightbox.doc.mjs +149 -33
  141. package/src/Lightbox/Lightbox.spec.md +197 -0
  142. package/src/Markdown/Markdown.doc.mjs +60 -0
  143. package/src/Markdown/Markdown.spec.md +200 -0
  144. package/src/MobileNav/MobileNav.doc.mjs +140 -32
  145. package/src/MobileNav/MobileNav.spec.md +208 -0
  146. package/src/MoreMenu/MoreMenu.doc.mjs +59 -0
  147. package/src/MoreMenu/MoreMenu.spec.md +221 -0
  148. package/src/MoreMenu/MoreMenu.test.tsx +47 -0
  149. package/src/MoreMenu/MoreMenu.tsx +13 -1
  150. package/src/MultiSelector/MultiSelector.doc.mjs +128 -0
  151. package/src/MultiSelector/MultiSelector.spec.md +266 -0
  152. package/src/MultiSelector/MultiSelector.test.tsx +127 -0
  153. package/src/MultiSelector/MultiSelector.tsx +121 -73
  154. package/src/MultiSelector/index.ts +1 -0
  155. package/src/NavIcon/NavIcon.doc.mjs +17 -0
  156. package/src/NavIcon/NavIcon.spec.md +133 -0
  157. package/src/NavMenu/NavHeadingMenu.spec.md +205 -0
  158. package/src/NavMenu/NavMenu.doc.mjs +37 -0
  159. package/src/Outline/Outline.doc.mjs +35 -0
  160. package/src/Outline/Outline.spec.md +161 -0
  161. package/src/OverflowList/OverflowList.doc.mjs +24 -0
  162. package/src/OverflowList/OverflowList.spec.md +156 -0
  163. package/src/Pagination/Pagination.doc.mjs +74 -0
  164. package/src/Pagination/Pagination.spec.md +225 -0
  165. package/src/Popover/Popover.doc.mjs +11 -11
  166. package/src/Popover/Popover.test.tsx +397 -2
  167. package/src/Popover/Popover.tsx +228 -21
  168. package/src/Popover/usePopover.tsx +92 -15
  169. package/src/ProgressBar/ProgressBar.doc.mjs +198 -38
  170. package/src/ProgressBar/ProgressBar.spec.md +260 -0
  171. package/src/Resizable/Resizable.doc.mjs +59 -16
  172. package/src/Resizable/Resizable.spec.md +194 -0
  173. package/src/Section/Section.doc.mjs +17 -0
  174. package/src/Section/Section.spec.md +138 -0
  175. package/src/SegmentedControl/SegmentedControl.doc.mjs +83 -0
  176. package/src/SegmentedControl/SegmentedControl.spec.md +151 -0
  177. package/src/Selector/Selector.doc.mjs +12 -0
  178. package/src/Selector/Selector.spec.md +202 -0
  179. package/src/Selector/Selector.test.tsx +135 -0
  180. package/src/Selector/Selector.tsx +141 -82
  181. package/src/Selector/SelectorBottomSheet.tsx +97 -0
  182. package/src/Selector/index.ts +1 -0
  183. package/src/Selector/selectorPresentation.stylex.ts +20 -0
  184. package/src/Selector/useSelectorPresentation.ts +147 -0
  185. package/src/Skeleton/Skeleton.doc.mjs +12 -0
  186. package/src/Skeleton/Skeleton.spec.md +126 -0
  187. package/src/Slider/Slider.doc.mjs +184 -30
  188. package/src/Slider/Slider.spec.md +223 -0
  189. package/src/Stack/Stack.doc.mjs +22 -0
  190. package/src/Stack/Stack.spec.md +154 -0
  191. package/src/StatusDot/StatusDot.doc.mjs +17 -0
  192. package/src/StatusDot/StatusDot.spec.md +137 -0
  193. package/src/Switch/Switch.doc.mjs +40 -0
  194. package/src/Switch/Switch.spec.md +174 -0
  195. package/src/Table/Table.doc.mjs +102 -24
  196. package/src/Table/Table.spec.md +283 -0
  197. package/src/Text/Text.doc.mjs +190 -43
  198. package/src/Text/Text.spec.md +205 -0
  199. package/src/TextArea/TextArea.doc.mjs +255 -45
  200. package/src/TextArea/TextArea.spec.md +204 -0
  201. package/src/Toast/ToastViewport.test.tsx +118 -4
  202. package/src/Toast/useToastGesture.ts +61 -8
  203. package/src/ToggleButton/ToggleButton.doc.mjs +56 -0
  204. package/src/Toolbar/Toolbar.doc.mjs +19 -0
  205. package/src/Toolbar/Toolbar.spec.md +173 -0
  206. package/src/Tooltip/Tooltip.doc.mjs +17 -0
  207. package/src/Tooltip/Tooltip.spec.md +141 -0
  208. package/src/TreeList/TreeList.doc.mjs +58 -0
  209. package/src/TreeList/TreeList.spec.md +220 -0
  210. package/src/Typeahead/Typeahead.doc.mjs +83 -1
  211. package/src/Typeahead/Typeahead.spec.md +250 -0
  212. package/src/hooks/useAdaptivePresentation.ts +35 -0
  213. package/src/hooks/useFocusReturnVisibility.ts +45 -0
  214. package/src/hooks/useFocusTrap.test.tsx +40 -0
  215. package/src/hooks/useFocusTrap.ts +13 -0
  216. package/src/theme/themingTargets.test.ts +91 -25
@@ -793,6 +793,11 @@ describe('Toast swipe dismissal', () => {
793
793
  expect(visualToast.style.getPropertyValue('--_toast-swipe-exit-y')).toBe(
794
794
  '120%',
795
795
  );
796
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-y')).toBe('');
797
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-opacity')).toBe(
798
+ '',
799
+ );
800
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-scale')).toBe('');
796
801
  expect(getComputedStyle(visualToast).transform).not.toContain('translateX');
797
802
  });
798
803
 
@@ -1147,11 +1152,9 @@ describe('Toast swipe dismissal', () => {
1147
1152
 
1148
1153
  expect(onHide).toHaveBeenCalledWith('manual');
1149
1154
  expect(visualToast.style.getPropertyValue('--_toast-swipe-opacity')).toBe(
1150
- '0.700',
1151
- );
1152
- expect(visualToast.style.getPropertyValue('--_toast-swipe-scale')).toBe(
1153
- '0.985',
1155
+ '',
1154
1156
  );
1157
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-scale')).toBe('');
1155
1158
  expect(visualToast.style.getPropertyValue('--_toast-swipe-exit-y')).toBe(
1156
1159
  'calc(-1 * 120%)',
1157
1160
  );
@@ -1248,6 +1251,117 @@ describe('Toast swipe dismissal', () => {
1248
1251
  );
1249
1252
  });
1250
1253
 
1254
+ it('abandons a one-finger swipe when a second touch starts', () => {
1255
+ const {visualToast, onHide} = renderSwipeToast();
1256
+ const dispatchTouch = (
1257
+ type: 'touchstart' | 'touchmove',
1258
+ touches: {identifier: number; clientX: number; clientY: number}[],
1259
+ changedTouches = touches,
1260
+ ) => {
1261
+ const event = new Event(type, {bubbles: true, cancelable: true});
1262
+ Object.defineProperty(event, 'touches', {value: touches});
1263
+ Object.defineProperty(event, 'changedTouches', {value: changedTouches});
1264
+ visualToast.dispatchEvent(event);
1265
+ return event;
1266
+ };
1267
+ const first = {identifier: 51, clientX: 10, clientY: 0};
1268
+ const second = {identifier: 52, clientX: 30, clientY: 40};
1269
+
1270
+ act(() => {
1271
+ dispatchTouch('touchstart', [first]);
1272
+ dispatchTouch('touchmove', [{...first, clientY: 40}]);
1273
+ });
1274
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-y')).toBe('40px');
1275
+
1276
+ act(() => {
1277
+ dispatchTouch('touchstart', [first, second], [second]);
1278
+ });
1279
+ const multitouchMove = dispatchTouch('touchmove', [first, second]);
1280
+
1281
+ expect(multitouchMove.defaultPrevented).toBe(false);
1282
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-y')).toBe('');
1283
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-opacity')).toBe(
1284
+ '',
1285
+ );
1286
+ expect(onHide).not.toHaveBeenCalled();
1287
+ });
1288
+
1289
+ it('does not dismiss when another touch remains outside the Toast', () => {
1290
+ const {visualToast, onHide} = renderSwipeToast();
1291
+ const first = {identifier: 61, clientX: 10, clientY: 0};
1292
+ const second = {identifier: 62, clientX: 200, clientY: 200};
1293
+ const dispatchTouch = (
1294
+ type: 'touchstart' | 'touchmove' | 'touchend',
1295
+ touches: (typeof first)[],
1296
+ changedTouches: (typeof first)[],
1297
+ ) => {
1298
+ const event = new Event(type, {bubbles: true, cancelable: true});
1299
+ Object.defineProperty(event, 'touches', {value: touches});
1300
+ Object.defineProperty(event, 'changedTouches', {value: changedTouches});
1301
+ visualToast.dispatchEvent(event);
1302
+ return event;
1303
+ };
1304
+
1305
+ act(() => {
1306
+ dispatchTouch('touchstart', [first], [first]);
1307
+ dispatchTouch(
1308
+ 'touchmove',
1309
+ [{...first, clientY: 60}],
1310
+ [{...first, clientY: 60}],
1311
+ );
1312
+ });
1313
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-y')).toBe('60px');
1314
+
1315
+ // The second contact began outside the Toast, so its touchstart did not
1316
+ // reach the Toast listener. Ending the tracked finger must still cancel
1317
+ // while that other contact remains active.
1318
+ act(() => {
1319
+ dispatchTouch('touchend', [second], [{...first, clientY: 60}]);
1320
+ });
1321
+
1322
+ expect(onHide).not.toHaveBeenCalled();
1323
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-y')).toBe('');
1324
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-opacity')).toBe(
1325
+ '',
1326
+ );
1327
+ });
1328
+
1329
+ it('does not let a pen event with the same numeric ID control a touch gesture', () => {
1330
+ const {visualToast, onHide} = renderSwipeToast();
1331
+ const touch = {identifier: 7, clientX: 10, clientY: 0};
1332
+ const touchStart = new Event('touchstart', {
1333
+ bubbles: true,
1334
+ cancelable: true,
1335
+ });
1336
+ Object.defineProperty(touchStart, 'touches', {value: [touch]});
1337
+ Object.defineProperty(touchStart, 'changedTouches', {value: [touch]});
1338
+
1339
+ act(() => {
1340
+ visualToast.dispatchEvent(touchStart);
1341
+ fireEvent.pointerMove(visualToast, {
1342
+ pointerId: 7,
1343
+ pointerType: 'pen',
1344
+ clientX: 10,
1345
+ clientY: 60,
1346
+ });
1347
+ fireEvent.pointerUp(visualToast, {
1348
+ pointerId: 7,
1349
+ pointerType: 'pen',
1350
+ clientX: 10,
1351
+ clientY: 60,
1352
+ });
1353
+ });
1354
+
1355
+ expect(onHide).not.toHaveBeenCalled();
1356
+ expect(visualToast.style.getPropertyValue('--_toast-swipe-y')).toBe('');
1357
+
1358
+ act(() => {
1359
+ visualToast.dispatchEvent(
1360
+ new Event('touchcancel', {bubbles: true, cancelable: true}),
1361
+ );
1362
+ });
1363
+ });
1364
+
1251
1365
  it('removes native touch listeners when a Toast unmounts', () => {
1252
1366
  const {visualToast, unmount} = renderSwipeToast();
1253
1367
  const removeListener = vi.spyOn(visualToast, 'removeEventListener');
@@ -25,12 +25,14 @@ const SWIPE_ACTIVE_SCALE_MAX = 0.02;
25
25
  export type ToastGestureDirection = 1 | -1;
26
26
 
27
27
  interface GesturePoint {
28
+ source: 'touch' | 'pen';
28
29
  pointerId: number;
29
30
  clientX: number;
30
31
  clientY: number;
31
32
  }
32
33
 
33
34
  interface GestureState {
35
+ source: GesturePoint['source'];
34
36
  pointerId: number;
35
37
  startX: number;
36
38
  startY: number;
@@ -126,6 +128,7 @@ export function useToastGesture({
126
128
  }
127
129
  const surfaceSize = Math.max(root.getBoundingClientRect().height, 1);
128
130
  gestureRef.current = {
131
+ source: point.source,
129
132
  pointerId: point.pointerId,
130
133
  startX: point.clientX,
131
134
  startY: point.clientY,
@@ -159,7 +162,12 @@ export function useToastGesture({
159
162
  ) => {
160
163
  const state = gestureRef.current;
161
164
  const root = rootRef.current;
162
- if (!state || !root || point.pointerId !== state.pointerId) {
165
+ if (
166
+ !state ||
167
+ !root ||
168
+ point.source !== state.source ||
169
+ point.pointerId !== state.pointerId
170
+ ) {
163
171
  return;
164
172
  }
165
173
  const deltaX = point.clientX - state.startX;
@@ -209,7 +217,12 @@ export function useToastGesture({
209
217
  (point: GesturePoint) => {
210
218
  const state = gestureRef.current;
211
219
  const root = rootRef.current;
212
- if (!state || !root || point.pointerId !== state.pointerId) {
220
+ if (
221
+ !state ||
222
+ !root ||
223
+ point.source !== state.source ||
224
+ point.pointerId !== state.pointerId
225
+ ) {
213
226
  return;
214
227
  }
215
228
  const travel = Math.max(
@@ -225,6 +238,10 @@ export function useToastGesture({
225
238
  gestureRef.current = null;
226
239
  root.style.removeProperty('transition-duration');
227
240
  if (isDismissed) {
241
+ // The exit animation owns the final translation. Clear the live-drag
242
+ // transform/fade/scale first so a standalone Toast does not remain
243
+ // partially translated or faded after its dismiss callback fires.
244
+ clearTransientStyles(root);
228
245
  root.style.setProperty(
229
246
  '--_toast-swipe-exit-y',
230
247
  state.direction === 1
@@ -247,7 +264,15 @@ export function useToastGesture({
247
264
  if (
248
265
  event.pointerType === 'pen' &&
249
266
  (event.button == null || event.button === 0) &&
250
- beginGesture(event, event.target)
267
+ beginGesture(
268
+ {
269
+ source: 'pen',
270
+ pointerId: event.pointerId,
271
+ clientX: event.clientX,
272
+ clientY: event.clientY,
273
+ },
274
+ event.target,
275
+ )
251
276
  ) {
252
277
  rootRef.current?.setPointerCapture?.(event.pointerId);
253
278
  }
@@ -261,7 +286,12 @@ export function useToastGesture({
261
286
  return;
262
287
  }
263
288
  moveGesture(
264
- event,
289
+ {
290
+ source: 'pen',
291
+ pointerId: event.pointerId,
292
+ clientX: event.clientX,
293
+ clientY: event.clientY,
294
+ },
265
295
  () => event.preventDefault(),
266
296
  () => rootRef.current?.releasePointerCapture?.(event.pointerId),
267
297
  );
@@ -275,7 +305,12 @@ export function useToastGesture({
275
305
  return;
276
306
  }
277
307
  rootRef.current?.releasePointerCapture?.(event.pointerId);
278
- endGesture(event);
308
+ endGesture({
309
+ source: 'pen',
310
+ pointerId: event.pointerId,
311
+ clientX: event.clientX,
312
+ clientY: event.clientY,
313
+ });
279
314
  },
280
315
  [endGesture],
281
316
  );
@@ -284,6 +319,7 @@ export function useToastGesture({
284
319
  (event: ReactPointerEvent<HTMLDivElement>) => {
285
320
  if (
286
321
  event.pointerType === 'pen' &&
322
+ gestureRef.current?.source === 'pen' &&
287
323
  gestureRef.current?.pointerId === event.pointerId
288
324
  ) {
289
325
  resetGesture(true);
@@ -298,12 +334,14 @@ export function useToastGesture({
298
334
  return;
299
335
  }
300
336
  const point = (touch: Touch): GesturePoint => ({
337
+ source: 'touch',
301
338
  pointerId: touch.identifier,
302
339
  clientX: touch.clientX,
303
340
  clientY: touch.clientY,
304
341
  });
305
342
  const changedTouch = (event: TouchEvent) => {
306
- const pointerId = gestureRef.current?.pointerId;
343
+ const state = gestureRef.current;
344
+ const pointerId = state?.source === 'touch' ? state.pointerId : null;
307
345
  return pointerId == null
308
346
  ? undefined
309
347
  : [...event.changedTouches].find(
@@ -311,12 +349,23 @@ export function useToastGesture({
311
349
  );
312
350
  };
313
351
  const handleTouchStart = (event: TouchEvent) => {
314
- const touch = event.touches.length === 1 ? event.changedTouches[0] : null;
315
- if (touch) {
352
+ if (event.touches?.length !== 1) {
353
+ // A second contact means the browser may be handling pinch zoom or
354
+ // two-finger scrolling. Abandon any accepted one-finger swipe before
355
+ // touchmove can suppress that native gesture.
356
+ resetGesture(true);
357
+ return;
358
+ }
359
+ const touch = event.changedTouches[0];
360
+ if (touch != null) {
316
361
  beginGesture(point(touch), event.target);
317
362
  }
318
363
  };
319
364
  const handleTouchMove = (event: TouchEvent) => {
365
+ if (event.touches?.length !== 1) {
366
+ resetGesture(true);
367
+ return;
368
+ }
320
369
  const touch = changedTouch(event);
321
370
  if (touch) {
322
371
  moveGesture(point(touch), () => {
@@ -327,6 +376,10 @@ export function useToastGesture({
327
376
  }
328
377
  };
329
378
  const handleTouchEnd = (event: TouchEvent) => {
379
+ if (event.touches.length > 0) {
380
+ resetGesture(true);
381
+ return;
382
+ }
330
383
  const touch = changedTouch(event);
331
384
  if (touch) {
332
385
  endGesture(point(touch));
@@ -122,6 +122,62 @@ export const docs = {
122
122
  usage: {
123
123
  description:
124
124
  'ToggleButton switches between selected and unselected states to represent a persistent on/off choice. Use it standalone for binary actions like bold, mute, or favorite, or inside a ToggleButtonGroup for single-select or multi-select toolbar controls.',
125
+ accessibility: [
126
+ {
127
+ name: 'Text label',
128
+ category: 'Color contrast',
129
+ criterion: '1.4.3 Contrast (Minimum)',
130
+ requirement: '4.5:1',
131
+ states: ['Unselected', 'Selected', 'Hover', 'Pointer down'],
132
+ description:
133
+ 'The label must have at least 4.5:1 contrast with the button background when selected and unselected. For Hover and Pointer down, measure the final background after the overlay is applied.',
134
+ },
135
+ {
136
+ name: 'Essential icon or spinner arc',
137
+ category: 'Color contrast',
138
+ criterion: '1.4.11 Non-text Contrast',
139
+ requirement: '3:1',
140
+ states: ['Icon only', 'Loading'],
141
+ description:
142
+ 'An icon-only ToggleButton must have at least 3:1 contrast between its icon and button background. The moving spinner arc must also meet 3:1. An icon beside a visible label does not need its own check.',
143
+ },
144
+ {
145
+ name: 'Selected state indicator',
146
+ category: 'Color contrast',
147
+ criterion: '1.4.11 Non-text Contrast',
148
+ requirement: '3:1 if relied upon',
149
+ states: ['Selected'],
150
+ description:
151
+ 'The selected background must reach 3:1 only when users need it to tell selected from unselected. Label weight or a changed icon can also show selection.',
152
+ },
153
+ {
154
+ name: 'Visible control boundary',
155
+ category: 'Color contrast',
156
+ criterion: '1.4.11 Non-text Contrast',
157
+ requirement: '3:1 if needed',
158
+ states: ['Rest'],
159
+ description:
160
+ 'The button edge needs 3:1 contrast only when users need it to see the control. A visible label or icon can show the control instead.',
161
+ },
162
+ {
163
+ name: 'Keyboard focus indicator',
164
+ category: 'Color contrast',
165
+ criterion: '1.4.11 Non-text Contrast',
166
+ requirement: '3:1',
167
+ states: ['Focus visible'],
168
+ description:
169
+ 'The focus outline must have at least 3:1 contrast with the area around the button. Check both selected and unselected states.',
170
+ },
171
+ {
172
+ name: 'Disabled appearance',
173
+ category: 'Color contrast',
174
+ criterion: '1.4.3 and 1.4.11 exceptions',
175
+ requirement: 'Not required',
176
+ states: ['Disabled'],
177
+ description:
178
+ 'Disabled controls do not need to meet these contrast ratios.',
179
+ },
180
+ ],
125
181
  bestPractices: [
126
182
  {guidance: true, description: 'Use a filled or colored icon for the pressed state so users can see the current state at a glance: an outline star vs a solid star, for example.'},
127
183
  {guidance: true, description: 'Keep the label identical between pressed and unpressed states. Let the visual treatment (icon, weight, background) communicate the change.'},
@@ -1,5 +1,21 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
+ /** @type {import('@astryxdesign/cli/authoring').ComponentAnatomyElement[]} */
4
+ const anatomy = [
5
+ {
6
+ name: 'Outer chrome',
7
+ required: true,
8
+ description:
9
+ 'Section-owned container that supplies the toolbar surface, padding, and selected divider edges.',
10
+ },
11
+ {
12
+ name: 'Toolbar',
13
+ required: true,
14
+ description:
15
+ 'Named toolbar row that owns toolbar semantics and the start, optional center, and end layout.',
16
+ },
17
+ ];
18
+
3
19
  /** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
4
20
 
5
21
  export const docs = {
@@ -147,6 +163,7 @@ export const docs = {
147
163
  ], },
148
164
  ],
149
165
  usage: {
166
+ anatomy,
150
167
  description:
151
168
  'Toolbar is a horizontal bar with left, center, and right areas. Use it for contextual actions within a content area (above a table, inside a card, or in a panel), not as a page-level header. Set the size once on the toolbar and all buttons, inputs, and tabs inside it match automatically.',
152
169
  bestPractices: [
@@ -182,6 +199,7 @@ export const docsZh = {
182
199
  },
183
200
  ],
184
201
  usage: {
202
+ anatomy,
185
203
  description:
186
204
  'Toolbar is a horizontal bar with left, center, and right areas. Use it for contextual actions within a content area (above a table, inside a card, or in a panel), not as a page-level header. Set the size once on the toolbar and all buttons, inputs, and tabs inside it match automatically.',
187
205
  bestPractices: [
@@ -200,6 +218,7 @@ export const docsZh = {
200
218
  export const docsDense = {
201
219
  description: 'Horizontal bar w/ left, center, right areas. For contextual actions in content, not page headers. Size cascades to children.',
202
220
  usage: {
221
+ anatomy,
203
222
  description:
204
223
  'Horizontal bar w/ left, optional center, right. For contextual actions within content (tables, cards, panels), not page-level headers. Size cascades to children.',
205
224
  bestPractices: [
@@ -0,0 +1,173 @@
1
+ ---
2
+ schema_version: 1
3
+ template_version: 3
4
+ kind: component
5
+ id: component:Toolbar
6
+ authority: draft
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: null
10
+ approved_at: null
11
+ owners: [cixzhang]
12
+ review_triggers: [layout, theming, accessibility]
13
+ verified_by:
14
+ [
15
+ packages/core/src/Toolbar/Toolbar.test.tsx,
16
+ packages/core/src/Section/Section.test.tsx,
17
+ packages/core/src/theme/themingTargets.test.ts,
18
+ scripts/check-knowledge.mjs,
19
+ ]
20
+ families: [family:layout-regions]
21
+ design_specs: []
22
+ architecture:
23
+ [
24
+ architecture:component-theming-surface,
25
+ architecture:container-padding,
26
+ architecture:public-component-api,
27
+ ]
28
+ contributing: []
29
+ system_specs: []
30
+ ---
31
+
32
+ # Toolbar component contract
33
+
34
+ ## Intent
35
+
36
+ Toolbar presents a named contextual action region with one semantic toolbar row
37
+ inside Section-owned outer chrome. This draft records the current consumer
38
+ anatomy and theming ownership without changing layout, keyboard behavior,
39
+ targets, or public API.
40
+
41
+ ## Compatibility and migration
42
+
43
+ - Released default preserved: `yes`
44
+ - Compatibility class: additive documentation only; runtime, DOM, styling,
45
+ targets, and public API remain unchanged
46
+ - Controlled/uncontrolled behavior: not applicable
47
+ - Migration decision: none
48
+
49
+ Consumer migration instructions belong in consumer docs and release notes.
50
+
51
+ ## Ownership boundary
52
+
53
+ **Owns**
54
+
55
+ - The named toolbar row, toolbar semantics, start/optional-center/end layout,
56
+ keyboard movement, size cascade, and current `toolbar` target.
57
+
58
+ **Does not own / non-goals**
59
+
60
+ - The painted outer chrome, variant, block padding, and selected divider edges —
61
+ delegated to `component:Section`.
62
+ - Content supplied through the three layout slots — owned by the caller or its
63
+ rendered components.
64
+ - A general arbitrary-child layout primitive contract.
65
+ - New runtime behavior, targets, props, or target placement.
66
+
67
+ ## Public concepts
68
+
69
+ No new public concept is introduced. Consumer props, slots, and usage remain
70
+ documented in `Toolbar.doc.mjs`; `family:layout-regions` owns the shared
71
+ structural-region boundary.
72
+
73
+ ## Behavioral and layout contract
74
+
75
+ | ID | Candidate invariant | Basis | Draft review state |
76
+ | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -------------------------------------------------- |
77
+ | FR1 | The current render contains Section-owned Outer chrome around one named Toolbar carrying `role="toolbar"` and the current `toolbar` target. | Current source, docs, tests, and family | Verified current behavior; no new behavior decided |
78
+ | FR2 | The Toolbar uses its current two-lane layout without center content and its current three-lane layout when center content is supplied. | Current source and focused structure tests | Verified current behavior; no layout change |
79
+ | FR3 | Variant, selected divider edges, and outer padding remain delegated to Section, while Toolbar retains toolbar semantics, keyboard behavior, and internal layout. | Current source, docs, and family contract | Verified current ownership; no target change |
80
+
81
+ ### Allowed variation
82
+
83
+ - Caller content may be absent or supplied in the start, center, and end slots
84
+ without changing Outer chrome or Toolbar ownership.
85
+ - Size and orientation may change the current row geometry and keyboard axis
86
+ without creating separate anatomy parts or targets.
87
+
88
+ ### Representative states
89
+
90
+ | State | Required invariant | Allowed variation |
91
+ | ----------------- | ------------------------------------------------------- | --------------------------------------------------- |
92
+ | Empty toolbar | Outer chrome and named Toolbar remain rendered. | No caller slot content. |
93
+ | Two-lane layout | Start and/or end content use the current flex layout. | Either lane may be absent. |
94
+ | Three-lane layout | Center content selects the current three-column layout. | Start or end content may be absent. |
95
+ | Vertical toolbar | Toolbar semantics and target remain unchanged. | Layout and keyboard movement use the vertical axis. |
96
+
97
+ ### Transformation and precedence order
98
+
99
+ - No new slot, layout, edge-compensation, padding, or event precedence rule is
100
+ introduced.
101
+
102
+ ### Performance and resources
103
+
104
+ - No new performance or resource rule is introduced.
105
+
106
+ ## Accessibility contract
107
+
108
+ This draft does not change or extend Toolbar's current accessible name,
109
+ orientation, roving-tabindex, keyboard-hint, caret-guard, or event-composition
110
+ behavior.
111
+
112
+ ## Design relationships
113
+
114
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
115
+ | ---------------- | -------------------------------------------------------------------------------- | ----------------------------------------- | -------------- | ------------------ |
116
+ | Outer chrome | Supplies the painted surface, padding, and selected boundaries around the row. | `component:Section` | Supporting | FR1, FR3 |
117
+ | Toolbar | Presents the named toolbar semantics and current two- or three-lane arrangement. | Current source, docs, and family contract | Prominent | FR1, FR2, FR3 |
118
+
119
+ The slot contents are caller-owned content inside Toolbar's layout. They do not
120
+ become additional Toolbar-owned anatomy or targets.
121
+
122
+ ### Theming anatomy
123
+
124
+ <!-- anatomy-theming:v1 -->
125
+
126
+ ```json
127
+ {
128
+ "Outer chrome": {
129
+ "delegatesTo": {"owner": "component:Section", "target": "section"}
130
+ },
131
+ "Toolbar": {"target": "toolbar"}
132
+ }
133
+ ```
134
+
135
+ ## Family and system relationships
136
+
137
+ - `family:layout-regions` owns the structural-region vocabulary and records
138
+ Toolbar as a Section-backed contextual action region.
139
+ - `architecture:container-padding` owns the inherited inset and edge-compensation
140
+ geometry used by the composed Section and Toolbar.
141
+ - `architecture:component-theming-surface` owns anatomy qualification, local
142
+ target mapping, and component delegation.
143
+ - `architecture:public-component-api` owns the stable prop and slot surface; this
144
+ documentation adds no API.
145
+
146
+ ## Verification map
147
+
148
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
149
+ | ------------------- | --------------------------------------------------------------- | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | ----------------------- |
150
+ | FR1 | `Toolbar.test.tsx` role, name, pass-through, and Section suites | Empty and populated Toolbar | Removing the role/name, Toolbar target, Section composition, or delegated Section target fails current evidence. | `audit:Toolbar/anatomy` |
151
+ | FR2 | `Toolbar.test.tsx` slot and child-count suites | Start-only, end-only, two-lane, and three-lane | Changing current slot presence or two-/three-lane structure fails focused assertions. | `audit:Toolbar/layout` |
152
+ | FR3 | Toolbar and Section source plus their focused tests | Variant, divider, size, and orientation states | Moving outer chrome ownership into Toolbar or moving toolbar semantics onto Section contradicts source and coverage. | `audit:Toolbar/theming` |
153
+ | Target inventory | `packages/core/src/theme/themingTargets.test.ts` | Toolbar and delegated Section targets | Runtime and documented target metadata drift fails target validation. | `audit:Toolbar/theming` |
154
+ | Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and current local target | Missing, extra, prefixed, or stale mappings fail repository validation. | `audit:Toolbar/theming` |
155
+
156
+ Existing tests assert the semantic row, slot structure, and Section composition.
157
+ They do not prove computed inset or edge-compensation geometry for every parent
158
+ padding state; this documentation does not claim that coverage.
159
+
160
+ ## Decision log
161
+
162
+ None. This draft records current facts and introduces no component-local design,
163
+ layout, accessibility, or API decision.
164
+
165
+ ## Open questions
166
+
167
+ None.
168
+
169
+ ## Content boundary
170
+
171
+ This file does not duplicate consumer prop tables, examples, family layout
172
+ rules, container-padding mechanics, keyboard algorithms, implementation steps,
173
+ or system rules. It links to their owners.
@@ -1,5 +1,19 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
+ /** @type {import('@astryxdesign/cli/authoring').ComponentAnatomyElement[]} */
4
+ const anatomy = [
5
+ {
6
+ name: 'Tooltip surface',
7
+ required: true,
8
+ description: 'Painted overlay surface that presents the tooltip.',
9
+ },
10
+ {
11
+ name: 'Tooltip text',
12
+ required: true,
13
+ description: 'Tooltip content rendered within the surface.',
14
+ },
15
+ ];
16
+
3
17
  /** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
4
18
 
5
19
  export const docs = {
@@ -110,6 +124,7 @@ export const docs = {
110
124
  ],
111
125
  },
112
126
  usage: {
127
+ anatomy,
113
128
  description:
114
129
  'A short text hint that appears on hover or focus, anchored to a trigger element. Use it to describe icon-only buttons, show the full text of truncated labels, or provide supplementary context without cluttering the UI.',
115
130
  bestPractices: [
@@ -217,6 +232,7 @@ export const docsZh = {
217
232
  ],
218
233
  },
219
234
  usage: {
235
+ anatomy,
220
236
  description:
221
237
  'A short text hint that appears on hover or focus, anchored to a trigger element. Use it to describe icon-only buttons, show the full text of truncated labels, or provide supplementary context without cluttering the UI.',
222
238
  bestPractices: [
@@ -233,6 +249,7 @@ export const docsZh = {
233
249
  export const docsDense = {
234
250
  description: 'Hover/focus triggered tooltip for displaying short, non-interactive text anchored to trigger element.',
235
251
  usage: {
252
+ anatomy,
236
253
  description:
237
254
  'A short text hint that appears on hover or focus, anchored to a trigger element. Use it to describe icon-only buttons, show the full text of truncated labels, or provide supplementary context without cluttering the UI.',
238
255
  bestPractices: [