@astryxdesign/core 0.6.1 → 0.6.2-canary.023c3e4

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 (305) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/BottomSheet/BottomSheet.d.ts +1 -1
  3. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  4. package/dist/BottomSheet/BottomSheet.js +3 -1
  5. package/dist/BottomSheet/BottomSheetPanel.d.ts +7 -5
  6. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  7. package/dist/BottomSheet/BottomSheetPanel.js +56 -19
  8. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  9. package/dist/Chat/ChatComposerInput.js +2 -2
  10. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  11. package/dist/CheckboxInput/CheckboxInput.js +12 -2
  12. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  13. package/dist/Collapsible/Collapsible.js +6 -1
  14. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  15. package/dist/CommandPalette/CommandPaletteInput.js +1 -1
  16. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  17. package/dist/ComplexSelector/ComplexSelector.js +1 -1
  18. package/dist/DateInput/DateInput.d.ts.map +1 -1
  19. package/dist/DateInput/DateInput.js +4 -4
  20. package/dist/DateInput/NativeDateField.d.ts.map +1 -1
  21. package/dist/DateInput/NativeDateField.js +16 -16
  22. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  23. package/dist/DateInput/TouchDateField.js +2 -2
  24. package/dist/DateRangeInput/DateRangeInput.d.ts +3 -0
  25. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  26. package/dist/DateRangeInput/DateRangeInput.js +20 -13
  27. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  28. package/dist/DateTimeInput/DateTimeInput.js +8 -8
  29. package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
  30. package/dist/DateTimeInput/TouchDateTimeField.js +4 -4
  31. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +9 -3
  32. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -1
  33. package/dist/DateTimeInput/nativePickerSegmentStyles.js +3 -3
  34. package/dist/Dialog/DialogHeader.d.ts +1 -1
  35. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  36. package/dist/Dialog/DialogHeader.js +10 -7
  37. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  38. package/dist/Field/PanelSearchInput.js +1 -1
  39. package/dist/FileInput/FileInput.d.ts.map +1 -1
  40. package/dist/FileInput/FileInput.js +16 -10
  41. package/dist/Kbd/Kbd.d.ts +5 -3
  42. package/dist/Kbd/Kbd.d.ts.map +1 -1
  43. package/dist/Kbd/Kbd.js +36 -42
  44. package/dist/Link/Link.d.ts.map +1 -1
  45. package/dist/Link/Link.js +6 -2
  46. package/dist/Markdown/Markdown.d.ts +18 -4
  47. package/dist/Markdown/Markdown.d.ts.map +1 -1
  48. package/dist/Markdown/Markdown.fr23.bench.d.ts +2 -0
  49. package/dist/Markdown/Markdown.fr23.bench.d.ts.map +1 -0
  50. package/dist/Markdown/Markdown.fr23.bench.js +150 -0
  51. package/dist/Markdown/Markdown.fr23.sampling.d.ts +2 -0
  52. package/dist/Markdown/Markdown.fr23.sampling.d.ts.map +1 -0
  53. package/dist/Markdown/Markdown.fr23.sampling.js +43 -0
  54. package/dist/Markdown/Markdown.js +311 -114
  55. package/dist/Markdown/ast.d.ts +162 -0
  56. package/dist/Markdown/ast.d.ts.map +1 -0
  57. package/dist/Markdown/ast.js +74 -0
  58. package/dist/Markdown/index.d.ts +5 -3
  59. package/dist/Markdown/index.d.ts.map +1 -1
  60. package/dist/Markdown/index.js +3 -2
  61. package/dist/Markdown/parser/index.d.ts +10 -0
  62. package/dist/Markdown/parser/index.d.ts.map +1 -0
  63. package/dist/Markdown/parser/index.js +10 -0
  64. package/dist/Markdown/parser.d.ts +220 -38
  65. package/dist/Markdown/parser.d.ts.map +1 -1
  66. package/dist/Markdown/parser.js +1072 -188
  67. package/dist/Markdown/plugins/frontmatter.d.ts +32 -0
  68. package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -0
  69. package/dist/Markdown/plugins/frontmatter.js +132 -0
  70. package/dist/Markdown/plugins/index.d.ts +16 -0
  71. package/dist/Markdown/plugins/index.d.ts.map +1 -0
  72. package/dist/Markdown/plugins/index.js +13 -0
  73. package/dist/Markdown/plugins/protocol.d.ts +191 -0
  74. package/dist/Markdown/plugins/protocol.d.ts.map +1 -0
  75. package/dist/Markdown/plugins/protocol.js +957 -0
  76. package/dist/Markdown/plugins/semanticFence.d.ts +36 -0
  77. package/dist/Markdown/plugins/semanticFence.d.ts.map +1 -0
  78. package/dist/Markdown/plugins/semanticFence.js +213 -0
  79. package/dist/Markdown/plugins/sourceDecoration.d.ts +76 -0
  80. package/dist/Markdown/plugins/sourceDecoration.d.ts.map +1 -0
  81. package/dist/Markdown/plugins/sourceDecoration.js +393 -0
  82. package/dist/Markdown/plugins/textTransform.d.ts +30 -0
  83. package/dist/Markdown/plugins/textTransform.d.ts.map +1 -0
  84. package/dist/Markdown/plugins/textTransform.js +374 -0
  85. package/dist/Markdown/remark.d.ts +196 -0
  86. package/dist/Markdown/remark.d.ts.map +1 -0
  87. package/dist/Markdown/remark.js +1062 -0
  88. package/dist/Markdown/url.d.ts +4 -0
  89. package/dist/Markdown/url.d.ts.map +1 -0
  90. package/dist/Markdown/url.js +25 -0
  91. package/dist/Markdown/utils.d.ts +4 -2
  92. package/dist/Markdown/utils.d.ts.map +1 -1
  93. package/dist/Markdown/utils.js +2 -1
  94. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  95. package/dist/NumberInput/NumberInput.js +4 -4
  96. package/dist/Outline/index.d.ts +1 -0
  97. package/dist/Outline/index.d.ts.map +1 -1
  98. package/dist/Outline/parseOutlineFromMarkdown.d.ts +7 -1
  99. package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
  100. package/dist/Outline/parseOutlineFromMarkdown.js +14 -8
  101. package/dist/Outline/useOutlineFromMarkdown.d.ts +3 -1
  102. package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
  103. package/dist/Outline/useOutlineFromMarkdown.js +8 -3
  104. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  105. package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
  106. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  107. package/dist/ProgressBar/ProgressBar.js +36 -31
  108. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  109. package/dist/RadioList/RadioListItem.js +13 -1
  110. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  111. package/dist/SegmentedControl/SegmentedControlItem.js +5 -5
  112. package/dist/Selector/Selector.d.ts.map +1 -1
  113. package/dist/Selector/Selector.js +1 -1
  114. package/dist/SideNav/SideNav.d.ts +2 -1
  115. package/dist/SideNav/SideNav.d.ts.map +1 -1
  116. package/dist/SideNav/SideNav.js +9 -3
  117. package/dist/Slider/Slider.d.ts.map +1 -1
  118. package/dist/Slider/Slider.js +19 -10
  119. package/dist/Spinner/Spinner.d.ts +1 -1
  120. package/dist/Spinner/Spinner.d.ts.map +1 -1
  121. package/dist/Spinner/Spinner.js +45 -21
  122. package/dist/Switch/Switch.d.ts.map +1 -1
  123. package/dist/Switch/Switch.js +11 -0
  124. package/dist/TabList/Tab.d.ts +1 -1
  125. package/dist/TabList/Tab.d.ts.map +1 -1
  126. package/dist/TabList/Tab.js +20 -7
  127. package/dist/TextArea/TextArea.d.ts.map +1 -1
  128. package/dist/TextArea/TextArea.js +1 -1
  129. package/dist/TextInput/TextInput.d.ts.map +1 -1
  130. package/dist/TextInput/TextInput.js +2 -2
  131. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  132. package/dist/TimeInput/TimeInput.js +4 -4
  133. package/dist/ToggleButton/ToggleButton.d.ts +2 -1
  134. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  135. package/dist/ToggleButton/ToggleButton.js +7 -1
  136. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  137. package/dist/Typeahead/BaseTypeahead.js +16 -7
  138. package/dist/astryx.css +19 -8
  139. package/dist/hooks/scrollKeyboardDelegation.d.ts +3 -0
  140. package/dist/hooks/scrollKeyboardDelegation.d.ts.map +1 -0
  141. package/dist/hooks/scrollKeyboardDelegation.js +146 -0
  142. package/dist/hooks/useScrollableArea.d.ts +6 -2
  143. package/dist/hooks/useScrollableArea.d.ts.map +1 -1
  144. package/dist/hooks/useScrollableArea.js +17 -5
  145. package/dist/utils/interactionOverlay.stylex.d.ts +8 -0
  146. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  147. package/dist/utils/interactionOverlay.stylex.js +9 -0
  148. package/locales/en.json +16 -0
  149. package/locales/pseudo.json +12 -0
  150. package/package.json +22 -5
  151. package/scripts/agent-doc-state.mjs +1 -1
  152. package/src/BottomSheet/BottomSheet.doc.mjs +8 -1
  153. package/src/BottomSheet/BottomSheet.spec.md +46 -20
  154. package/src/BottomSheet/BottomSheet.test.tsx +6 -3
  155. package/src/BottomSheet/BottomSheet.tsx +3 -1
  156. package/src/BottomSheet/BottomSheetKeyboard.test.tsx +195 -0
  157. package/src/BottomSheet/BottomSheetPanel.test.tsx +11 -1
  158. package/src/BottomSheet/BottomSheetPanel.tsx +49 -16
  159. package/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts +344 -0
  160. package/src/Button/__tests__/Button.a11y.chromium.spec.ts +17 -1
  161. package/src/Button/__tests__/Button.a11y.known-failures.ts +0 -29
  162. package/src/Button/__tests__/Button.a11y.renders.tsx +9 -2
  163. package/src/Button/__tests__/Button.a11y.states.ts +9 -0
  164. package/src/Chat/ChatComposerInput.doc.mjs +3 -3
  165. package/src/Chat/ChatComposerInput.tsx +10 -2
  166. package/src/CheckboxInput/CheckboxInput.doc.mjs +11 -0
  167. package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
  168. package/src/CheckboxInput/CheckboxInput.tsx +21 -1
  169. package/src/ClickableCard/ClickableCard.test.tsx +102 -5
  170. package/src/Collapsible/Collapsible.doc.mjs +11 -0
  171. package/src/Collapsible/Collapsible.test.tsx +21 -0
  172. package/src/Collapsible/Collapsible.tsx +5 -0
  173. package/src/CommandPalette/CommandPaletteInput.tsx +6 -1
  174. package/src/ComplexSelector/ComplexSelector.tsx +6 -1
  175. package/src/DateInput/DateInput.tsx +6 -1
  176. package/src/DateInput/NativeDateField.tsx +24 -8
  177. package/src/DateInput/TouchDateField.tsx +6 -2
  178. package/src/DateRangeInput/DateRangeInput.doc.mjs +35 -7
  179. package/src/DateRangeInput/DateRangeInput.spec.md +203 -0
  180. package/src/DateRangeInput/DateRangeInput.test.tsx +100 -4
  181. package/src/DateRangeInput/DateRangeInput.tsx +35 -21
  182. package/src/DateTimeInput/DateTimeInput.tsx +6 -1
  183. package/src/DateTimeInput/TouchDateTimeField.tsx +6 -1
  184. package/src/DateTimeInput/nativePickerSegmentStyles.ts +24 -8
  185. package/src/Dialog/Dialog.doc.mjs +3 -0
  186. package/src/Dialog/Dialog.spec.md +1 -1
  187. package/src/Dialog/DialogHeader.doc.mjs +38 -0
  188. package/src/Dialog/DialogHeader.test.tsx +49 -0
  189. package/src/Dialog/DialogHeader.tsx +23 -4
  190. package/src/Dialog/modules/DialogHeader.spec.md +152 -0
  191. package/src/Field/PanelSearchInput.tsx +5 -2
  192. package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +1 -1
  193. package/src/FileInput/FileInput.doc.mjs +2 -0
  194. package/src/FileInput/FileInput.spec.md +199 -0
  195. package/src/FileInput/FileInput.test.tsx +14 -0
  196. package/src/FileInput/FileInput.tsx +24 -5
  197. package/src/Kbd/Kbd.doc.mjs +3 -3
  198. package/src/Kbd/Kbd.test.tsx +57 -1
  199. package/src/Kbd/Kbd.tsx +57 -37
  200. package/src/Layout/LayoutFooter.doc.mjs +17 -0
  201. package/src/Link/Link.doc.mjs +11 -0
  202. package/src/Link/Link.test.tsx +24 -0
  203. package/src/Link/Link.tsx +5 -0
  204. package/src/Markdown/Markdown.doc.mjs +439 -42
  205. package/src/Markdown/Markdown.fr23.bench.ts +174 -0
  206. package/src/Markdown/Markdown.fr23.perf.test.ts +249 -0
  207. package/src/Markdown/Markdown.fr23.sampling.ts +49 -0
  208. package/src/Markdown/Markdown.helpers.perf.test.ts +489 -0
  209. package/src/Markdown/Markdown.public.test.ts +280 -0
  210. package/src/Markdown/Markdown.spec.md +304 -72
  211. package/src/Markdown/Markdown.test.tsx +118 -3
  212. package/src/Markdown/Markdown.tsx +489 -129
  213. package/src/Markdown/ast.test.ts +307 -0
  214. package/src/Markdown/ast.ts +337 -0
  215. package/src/Markdown/incremental.test.ts +187 -7
  216. package/src/Markdown/index.ts +43 -1
  217. package/src/Markdown/modules/remark.spec.md +269 -0
  218. package/src/Markdown/parser/index.ts +64 -0
  219. package/src/Markdown/parser.perf.test.ts +3 -1
  220. package/src/Markdown/parser.public.test.ts +103 -0
  221. package/src/Markdown/parser.test.ts +122 -0
  222. package/src/Markdown/parser.ts +1721 -248
  223. package/src/Markdown/plugins/frontmatter.test.tsx +125 -0
  224. package/src/Markdown/plugins/frontmatter.ts +198 -0
  225. package/src/Markdown/plugins/index.ts +53 -0
  226. package/src/Markdown/plugins/protocol.test.tsx +1022 -0
  227. package/src/Markdown/plugins/protocol.ts +1618 -0
  228. package/src/Markdown/plugins/semanticFence.test.tsx +546 -0
  229. package/src/Markdown/plugins/semanticFence.ts +424 -0
  230. package/src/Markdown/plugins/sourceDecoration.test.tsx +799 -0
  231. package/src/Markdown/plugins/sourceDecoration.ts +539 -0
  232. package/src/Markdown/plugins/textTransform.test.ts +467 -0
  233. package/src/Markdown/plugins/textTransform.ts +554 -0
  234. package/src/Markdown/remark.test.tsx +1877 -0
  235. package/src/Markdown/remark.ts +1574 -0
  236. package/src/Markdown/url.ts +30 -0
  237. package/src/Markdown/utils.ts +19 -1
  238. package/src/MultiSelector/MultiSelector.test.tsx +1 -35
  239. package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +128 -0
  240. package/src/NumberInput/NumberInput.tsx +6 -1
  241. package/src/Outline/Outline.doc.mjs +7 -5
  242. package/src/Outline/Outline.spec.md +1 -1
  243. package/src/Outline/index.ts +1 -0
  244. package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +142 -0
  245. package/src/Outline/parseOutlineFromMarkdown.ts +40 -10
  246. package/src/Outline/useOutlineFromMarkdown.ts +15 -3
  247. package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
  248. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
  249. package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
  250. package/src/ProgressBar/ProgressBar.test.tsx +29 -11
  251. package/src/ProgressBar/ProgressBar.tsx +59 -72
  252. package/src/RadioList/RadioList.doc.mjs +11 -0
  253. package/src/RadioList/RadioList.test.tsx +32 -0
  254. package/src/RadioList/RadioListItem.tsx +25 -1
  255. package/src/ScrollableArea/modules/useScrollableArea.spec.md +50 -22
  256. package/src/SegmentedControl/SegmentedControl.doc.mjs +2 -2
  257. package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
  258. package/src/SegmentedControl/SegmentedControlItem.tsx +6 -9
  259. package/src/Selector/Selector.spec.md +12 -9
  260. package/src/Selector/Selector.tsx +6 -1
  261. package/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts +130 -0
  262. package/src/Selector/__tests__/Listbox.a11y.dom.ts +38 -0
  263. package/src/Selector/__tests__/Listbox.a11y.inventory.test.ts +63 -0
  264. package/src/Selector/__tests__/Listbox.a11y.known-failures.ts +37 -0
  265. package/src/Selector/__tests__/Listbox.a11y.states.ts +232 -0
  266. package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +121 -0
  267. package/src/SideNav/SideNav.doc.mjs +1 -1
  268. package/src/SideNav/SideNav.test.tsx +10 -0
  269. package/src/SideNav/SideNav.tsx +14 -2
  270. package/src/Slider/Slider.doc.mjs +27 -0
  271. package/src/Slider/Slider.spec.md +61 -47
  272. package/src/Slider/Slider.test.tsx +146 -0
  273. package/src/Slider/Slider.tsx +37 -13
  274. package/src/Spinner/Spinner.doc.mjs +6 -3
  275. package/src/Spinner/Spinner.test.tsx +41 -0
  276. package/src/Spinner/Spinner.tsx +62 -32
  277. package/src/Switch/Switch.doc.mjs +11 -0
  278. package/src/Switch/Switch.test.tsx +16 -0
  279. package/src/Switch/Switch.tsx +28 -0
  280. package/src/TabList/Tab.tsx +35 -7
  281. package/src/TabList/TabList.doc.mjs +11 -0
  282. package/src/TabList/TabList.test.tsx +66 -0
  283. package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +1 -34
  284. package/src/TextArea/TextArea.tsx +6 -1
  285. package/src/TextInput/TextInput.test.tsx +20 -1
  286. package/src/TextInput/TextInput.tsx +6 -1
  287. package/src/TimeInput/TimeInput.tsx +6 -1
  288. package/src/ToggleButton/ToggleButton.test.tsx +133 -0
  289. package/src/ToggleButton/ToggleButton.tsx +9 -2
  290. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
  291. package/src/Tokenizer/Tokenizer.spec.md +142 -75
  292. package/src/TreeList/TreeList.spec.md +123 -75
  293. package/src/Typeahead/BaseTypeahead.spec.md +4 -3
  294. package/src/Typeahead/BaseTypeahead.tsx +21 -7
  295. package/src/Typeahead/Typeahead.test.tsx +53 -0
  296. package/src/__tests__/PressedState.a11y.chromium.spec.ts +813 -0
  297. package/src/__tests__/pressState.ts +93 -0
  298. package/src/hooks/scrollKeyboardDelegation.test.ts +155 -0
  299. package/src/hooks/scrollKeyboardDelegation.ts +233 -0
  300. package/src/hooks/useScrollableArea.doc.mjs +15 -3
  301. package/src/hooks/useScrollableArea.test.tsx +59 -1
  302. package/src/hooks/useScrollableArea.ts +34 -10
  303. package/src/inputFontFloor.test.ts +106 -0
  304. package/src/theme/derivedVarRegistry.test.ts +6 -4
  305. package/src/utils/interactionOverlay.stylex.ts +20 -0
@@ -370,10 +370,15 @@ describe('DateRangeInput', () => {
370
370
  expect(
371
371
  screen.queryByRole('listbox', {hidden: true}),
372
372
  ).not.toBeInTheDocument();
373
- expect(
374
- screen.getByRole('group', {name: 'Preset date ranges', hidden: true}),
375
- ).toBeInTheDocument();
376
- expect(getButton('Last 7 days')).toBeInTheDocument();
373
+ const group = screen.getByRole('group', {
374
+ name: 'Preset date ranges',
375
+ hidden: true,
376
+ });
377
+ expect(group).toBeInTheDocument();
378
+ expect(group).toHaveClass('astryx-date-range-input-presets');
379
+ expect(getButton('Last 7 days')).toHaveClass(
380
+ 'astryx-date-range-input-preset',
381
+ );
377
382
  });
378
383
 
379
384
  it('marks the applied preset with aria-current, not aria-selected', () => {
@@ -386,11 +391,99 @@ describe('DateRangeInput', () => {
386
391
  />,
387
392
  );
388
393
  const active = getButton('Last 7 days');
394
+ expect(active).toHaveClass('astryx-date-range-input-preset');
395
+ expect(active).toHaveAttribute('data-selected', 'selected');
389
396
  expect(active).toHaveAttribute('aria-current', 'true');
390
397
  expect(active).not.toHaveAttribute('aria-selected');
391
398
  const inactive = getButton('This month');
399
+ expect(inactive).toHaveClass('astryx-date-range-input-preset');
400
+ expect(inactive).not.toHaveAttribute('data-selected');
401
+ expect(inactive).not.toHaveAttribute('data-disabled');
392
402
  expect(inactive).not.toHaveAttribute('aria-current');
393
403
  });
404
+
405
+ it('disables a preset when its start is before min', () => {
406
+ const handleChange = vi.fn();
407
+ render(
408
+ <DateRangeInput
409
+ label="Range"
410
+ value={null}
411
+ onChange={handleChange}
412
+ min="2026-03-02"
413
+ presets={[presets[0]]}
414
+ />,
415
+ );
416
+
417
+ const preset = getButton('Last 7 days');
418
+ expect(preset).toBeDisabled();
419
+ fireEvent.click(preset);
420
+ expect(handleChange).not.toHaveBeenCalled();
421
+ });
422
+
423
+ it('disables a preset when its end is after max', () => {
424
+ const handleChange = vi.fn();
425
+ render(
426
+ <DateRangeInput
427
+ label="Range"
428
+ value={null}
429
+ onChange={handleChange}
430
+ max="2026-03-06"
431
+ presets={[presets[0]]}
432
+ />,
433
+ );
434
+
435
+ const preset = getButton('Last 7 days');
436
+ expect(preset).toBeDisabled();
437
+ fireEvent.click(preset);
438
+ expect(handleChange).not.toHaveBeenCalled();
439
+ });
440
+
441
+ it('disables a preset when either endpoint fails dateConstraints', () => {
442
+ const handleChange = vi.fn();
443
+ render(
444
+ <DateRangeInput
445
+ label="Range"
446
+ value={null}
447
+ onChange={handleChange}
448
+ dateConstraints={[date => date.getDate() !== 7]}
449
+ presets={[presets[0]]}
450
+ />,
451
+ );
452
+
453
+ const preset = getButton('Last 7 days');
454
+ expect(preset).toBeDisabled();
455
+ fireEvent.click(preset);
456
+ expect(handleChange).not.toHaveBeenCalled();
457
+ });
458
+
459
+ it('commits an enabled preset using its already-resolved range', () => {
460
+ const range = {start: '2026-03-01', end: '2026-03-07'} as const;
461
+ const getRange = vi.fn(() => range);
462
+ let getRangeCallsAtChange = 0;
463
+ const handleChange = vi.fn(() => {
464
+ getRangeCallsAtChange = getRange.mock.calls.length;
465
+ });
466
+ render(
467
+ <DateRangeInput
468
+ label="Range"
469
+ value={null}
470
+ onChange={handleChange}
471
+ min="2026-03-01"
472
+ max="2026-03-31"
473
+ dateConstraints={[date => date.getDate() !== 13]}
474
+ minRangeSpan={2}
475
+ maxRangeSpan={7}
476
+ presets={[{label: 'Allowed range', getRange}]}
477
+ />,
478
+ );
479
+
480
+ const getRangeCallsBeforeClick = getRange.mock.calls.length;
481
+ const preset = getButton('Allowed range');
482
+ expect(preset).not.toBeDisabled();
483
+ fireEvent.click(preset);
484
+ expect(handleChange).toHaveBeenCalledWith(range);
485
+ expect(getRangeCallsAtChange).toBe(getRangeCallsBeforeClick);
486
+ });
394
487
  });
395
488
  describe('disabledMessage', () => {
396
489
  // jsdom does not implement the Popover API used by the tooltip, so mock
@@ -831,7 +924,10 @@ describe('DateRangeInput range-span forwarding', () => {
831
924
  const withinCap = getButton('Last 3 days');
832
925
  const overCap = getButton('Last 30 days');
833
926
  expect(withinCap).not.toBeDisabled();
927
+ expect(withinCap).not.toHaveAttribute('data-disabled');
834
928
  expect(overCap).toBeDisabled();
929
+ expect(overCap).toHaveClass('astryx-date-range-input-preset');
930
+ expect(overCap).toHaveAttribute('data-disabled', 'disabled');
835
931
 
836
932
  fireEvent.click(overCap);
837
933
  expect(handleChange).not.toHaveBeenCalled();
@@ -54,6 +54,7 @@ import {
54
54
  type DayOfWeek,
55
55
  type DayOfWeekName,
56
56
  } from '../Calendar';
57
+ import {useCalendarConstraints} from '../Calendar/hooks';
57
58
  import {usePopover} from '../Popover';
58
59
  import {useTooltip} from '../Tooltip';
59
60
  import {mergeProps} from '../utils';
@@ -91,9 +92,14 @@ const styles = stylex.create({
91
92
  borderStyle: 'none',
92
93
  padding: 0,
93
94
  fontFamily: typographyVars['--font-family-body'],
95
+ // The 16px floor is iOS-only: iOS Safari zooms the page when a focused
96
+ // control sits under 16px, and only iOS WebKit implements
97
+ // -webkit-touch-callout to key the coarse-pointer floor to it.
94
98
  fontSize: {
95
99
  default: typeScaleVars['--text-body-size'],
96
- '@media (pointer: coarse)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
100
+ '@media (pointer: coarse)': {
101
+ '@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
102
+ },
97
103
  },
98
104
  lineHeight: typeScaleVars['--text-body-leading'],
99
105
  color: colorVars['--color-text-primary'],
@@ -376,6 +382,9 @@ export interface DateRangeInputProps extends Omit<
376
382
 
377
383
  /**
378
384
  * Preset date ranges shown as quick-select options beside the calendar.
385
+ * A preset is disabled when either endpoint violates `min`, `max`, or
386
+ * `dateConstraints`, or when its span violates `minRangeSpan` or
387
+ * `maxRangeSpan`.
379
388
  */
380
389
  presets?: ReadonlyArray<DateRangePreset>;
381
390
 
@@ -542,6 +551,11 @@ export function DateRangeInput({
542
551
  dialogLabel: t('@astryx.dateRangeInput.dialogLabel'),
543
552
  closeButtonLabel: t('@astryx.dateInput.closeCalendar'),
544
553
  });
554
+ const {isDateDisabled} = useCalendarConstraints({
555
+ min,
556
+ max,
557
+ dateConstraints,
558
+ });
545
559
 
546
560
  const fireChange = useCallback(
547
561
  (newValue: DateRange | null) => {
@@ -577,14 +591,6 @@ export function DateRangeInput({
577
591
  [fireChange, popover],
578
592
  );
579
593
 
580
- const handlePresetClick = useCallback(
581
- (preset: DateRangePreset) => {
582
- fireChange(preset.getRange());
583
- popover.hide();
584
- },
585
- [fireChange, popover],
586
- );
587
-
588
594
  const handleClear = useCallback(
589
595
  (e: React.MouseEvent) => {
590
596
  e.stopPropagation();
@@ -713,15 +719,17 @@ export function DateRangeInput({
713
719
  <div
714
720
  role="group"
715
721
  aria-label={t('@astryx.dateRangeInput.presetDateRanges')}
716
- {...stylex.props(styles.presetSidebar)}>
722
+ {...mergeProps(
723
+ themeProps('date-range-input-presets'),
724
+ stylex.props(styles.presetSidebar),
725
+ )}>
717
726
  {presets.map(preset => {
718
727
  const presetRange = preset.getRange();
719
728
  const isActive = isRangeEqual(value, presetRange);
720
- const isPresetDisabled = !isRangeWithinSpan(
721
- presetRange,
722
- maxRangeSpan,
723
- minRangeSpan,
724
- );
729
+ const isPresetDisabled =
730
+ !isRangeWithinSpan(presetRange, maxRangeSpan, minRangeSpan) ||
731
+ isDateDisabled(plainDateFromISO(presetRange.start)) ||
732
+ isDateDisabled(plainDateFromISO(presetRange.end));
725
733
  return (
726
734
  <button
727
735
  key={preset.label}
@@ -733,12 +741,18 @@ export function DateRangeInput({
733
741
  // concept that contradicted the Tab interaction) (forms-5).
734
742
  aria-current={isActive ? 'true' : undefined}
735
743
  disabled={isPresetDisabled}
736
- onClick={() => handlePresetClick(preset)}
737
- {...stylex.props(
738
- focusOutlineStyles.focusVisible,
739
- styles.presetButton,
740
- isActive && styles.presetButtonActive,
741
- isPresetDisabled && styles.presetButtonDisabled,
744
+ onClick={() => handleRangeSelect(presetRange)}
745
+ {...mergeProps(
746
+ themeProps('date-range-input-preset', {
747
+ selected: isActive ? 'selected' : null,
748
+ disabled: isPresetDisabled ? 'disabled' : null,
749
+ }),
750
+ stylex.props(
751
+ focusOutlineStyles.focusVisible,
752
+ styles.presetButton,
753
+ isActive && styles.presetButtonActive,
754
+ isPresetDisabled && styles.presetButtonDisabled,
755
+ ),
742
756
  )}>
743
757
  {preset.label}
744
758
  </button>
@@ -173,9 +173,14 @@ const styles = stylex.create({
173
173
  borderStyle: 'none',
174
174
  padding: 0,
175
175
  fontFamily: typographyVars['--font-family-body'],
176
+ // The 16px floor is iOS-only: iOS Safari zooms the page when a focused
177
+ // control sits under 16px, and only iOS WebKit implements
178
+ // -webkit-touch-callout to key the coarse-pointer floor to it.
176
179
  fontSize: {
177
180
  default: typeScaleVars['--text-body-size'],
178
- '@media (pointer: coarse)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
181
+ '@media (pointer: coarse)': {
182
+ '@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
183
+ },
179
184
  },
180
185
  lineHeight: typeScaleVars['--text-body-leading'],
181
186
  color: colorVars['--color-text-primary'],
@@ -182,9 +182,14 @@ const styles = stylex.create({
182
182
  borderStyle: 'none',
183
183
  padding: 0,
184
184
  fontFamily: typographyVars['--font-family-body'],
185
+ // The 16px floor is iOS-only: iOS Safari zooms the page when a focused
186
+ // control sits under 16px, and only iOS WebKit implements
187
+ // -webkit-touch-callout to key the coarse-pointer floor to it.
185
188
  fontSize: {
186
189
  default: typeScaleVars['--text-body-size'],
187
- '@media (pointer: coarse)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
190
+ '@media (pointer: coarse)': {
191
+ '@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
192
+ },
188
193
  },
189
194
  lineHeight: typeScaleVars['--text-body-leading'],
190
195
  color: colorVars['--color-text-primary'],
@@ -42,21 +42,34 @@ export const nativePickerSegmentStyles = stylex.create({
42
42
  borderStyle: 'none',
43
43
  padding: 0,
44
44
  fontFamily: typographyVars['--font-family-body'],
45
- // Below 16px iOS zooms the page when the field takes focus.
45
+ // Below 16px iOS zooms the page when the field takes focus. Only iOS
46
+ // WebKit implements -webkit-touch-callout, so the coarse-pointer floor is
47
+ // keyed to iOS alone.
46
48
  fontSize: {
47
49
  default: typeScaleVars['--text-body-size'],
48
- '@media (pointer: coarse)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
50
+ '@media (pointer: coarse)': {
51
+ '@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
52
+ },
49
53
  },
50
54
  lineHeight: typeScaleVars['--text-body-leading'],
51
55
  color: colorVars['--color-text-primary'],
52
56
  backgroundColor: 'transparent',
53
57
  outline: 'none',
54
58
  // Native date/time controls size from their internal edit fields. Pin the
55
- // box to one text line so both segments stay aligned.
56
- height: stylex.firstThatWorks(
57
- '1lh',
58
- `calc(max(1rem, ${typeScaleVars['--text-body-size']}) * ${typeScaleVars['--text-body-leading']})`,
59
- ),
59
+ // box to one text line so both segments stay aligned. The calc fallback
60
+ // mirrors the iOS-only floor above; browsers with `1lh` track it for free.
61
+ height: {
62
+ default: stylex.firstThatWorks(
63
+ '1lh',
64
+ `calc(${typeScaleVars['--text-body-size']} * ${typeScaleVars['--text-body-leading']})`,
65
+ ),
66
+ '@media (pointer: coarse)': {
67
+ '@supports (-webkit-touch-callout: none)': stylex.firstThatWorks(
68
+ '1lh',
69
+ `calc(max(1rem, ${typeScaleVars['--text-body-size']}) * ${typeScaleVars['--text-body-leading']})`,
70
+ ),
71
+ },
72
+ },
60
73
  WebkitAppearance: 'none',
61
74
  appearance: 'none',
62
75
  // DateTimeInput renders its own calendar/clock affordances.
@@ -101,9 +114,12 @@ export const nativePickerSegmentStyles = stylex.create({
101
114
  insetInlineEnd: 0,
102
115
  insetBlock: 0,
103
116
  display: 'block',
117
+ // The same iOS-only floor as the input it covers, shared baselines.
104
118
  fontSize: {
105
119
  default: typeScaleVars['--text-body-size'],
106
- '@media (pointer: coarse)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
120
+ '@media (pointer: coarse)': {
121
+ '@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
122
+ },
107
123
  },
108
124
  lineHeight: typeScaleVars['--text-body-leading'],
109
125
  pointerEvents: 'none',
@@ -30,6 +30,9 @@ export const docs = {
30
30
  container: true,
31
31
  targets: [
32
32
  {className: 'astryx-dialog', visualProps: ['variant']},
33
+ {className: 'astryx-dialog-header'},
34
+ {className: 'astryx-dialog-header-title-block'},
35
+ {className: 'astryx-dialog-header-close-icon'},
33
36
  ],
34
37
  vars: [
35
38
  {name: '--_dialog-radius', description: 'Border radius of the dialog', default: 'var(--radius-container)', private: true},
@@ -17,7 +17,7 @@ verified_by:
17
17
  packages/core/src/Dialog/__tests__/Dialog.a11y.test.tsx,
18
18
  packages/core/src/Dialog/__tests__/Dialog.a11y.chromium.spec.ts,
19
19
  ]
20
- modules: []
20
+ modules: [module:Dialog/DialogHeader]
21
21
  families: [family:overlay-dismissal]
22
22
  design_specs: []
23
23
  architecture:
@@ -8,6 +8,14 @@ export const docs = {
8
8
  displayName: 'Dialog Header',
9
9
  isHiddenFromOverview: true,
10
10
  description: 'Header for dialogs with a title, optional subtitle, close button, and start/end content slots.',
11
+ usage: {
12
+ description: 'Use DialogHeader to give a dialog a labelled title area and optional close control.',
13
+ anatomy: [
14
+ {name: 'Header row', required: true, description: 'Arranges the title block, optional start/end content, and close control.'},
15
+ {name: 'Title block', required: true, description: 'Groups the title and optional subtitle.'},
16
+ {name: 'Close icon', required: false, description: 'Visual close glyph inside the close button.'},
17
+ ],
18
+ },
11
19
  props: [
12
20
  {
13
21
  name: 'title',
@@ -72,6 +80,13 @@ export const docs = {
72
80
  hasDivider: true,
73
81
  },
74
82
  },
83
+ theming: {
84
+ targets: [
85
+ {className: 'astryx-dialog-header'},
86
+ {className: 'astryx-dialog-header-title-block'},
87
+ {className: 'astryx-dialog-header-close-icon'},
88
+ ],
89
+ },
75
90
  examples: [
76
91
  {
77
92
  label: 'Basic',
@@ -117,6 +132,14 @@ export const docsZh = {
117
132
  isHiddenFromOverview: true,
118
133
  displayName: 'Dialog Header',
119
134
  description: '对话框头部,包含标题、可选副标题、关闭按钮以及首尾内容插槽。',
135
+ usage: {
136
+ description: '使用 DialogHeader 为对话框提供带标签的标题区和可选的关闭控件。',
137
+ anatomy: [
138
+ {name: 'Header row', required: true, description: '排列标题区、可选的首尾内容和关闭控件。'},
139
+ {name: 'Title block', required: true, description: '组合标题和可选副标题。'},
140
+ {name: 'Close icon', required: false, description: '关闭按钮内的关闭图标。'},
141
+ ],
142
+ },
120
143
  props: [
121
144
  {
122
145
  name: 'title',
@@ -150,6 +173,13 @@ export const docsZh = {
150
173
  default: 'true',
151
174
  },
152
175
  ],
176
+ theming: {
177
+ targets: [
178
+ {className: 'astryx-dialog-header'},
179
+ {className: 'astryx-dialog-header-title-block'},
180
+ {className: 'astryx-dialog-header-close-icon'},
181
+ ],
182
+ },
153
183
  };
154
184
 
155
185
  export const docsDense = {
@@ -157,6 +187,14 @@ export const docsDense = {
157
187
  isHiddenFromOverview: true,
158
188
  displayName: 'Dialog Header',
159
189
  description: 'dialog header w/ title, optional subtitle, close button, start/end content slots',
190
+ usage: {
191
+ description: 'labelled dialog title area + optional close control',
192
+ anatomy: [
193
+ {name: 'Header row', required: true, description: 'arranges title block, optional start/end content, close control'},
194
+ {name: 'Title block', required: true, description: 'groups title + optional subtitle'},
195
+ {name: 'Close icon', required: false, description: 'close glyph inside close button'},
196
+ ],
197
+ },
160
198
  propDescriptions: {
161
199
  title: 'dialog title (focused on open; labels dialog via aria-labelledby)',
162
200
  subtitle: 'subtitle below title',
@@ -14,6 +14,13 @@ import {render, screen} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
15
  import {DialogHeader} from './DialogHeader';
16
16
  import {LayoutDividerContext} from '../Layout/LayoutDividerContext';
17
+ import {defineTheme} from '../theme/defineTheme';
18
+ import {generateThemeCSS} from '../theme/generateThemeRules';
19
+
20
+ function generateThemeTestCSS(theme: Parameters<typeof generateThemeCSS>[0]) {
21
+ const {prose, component} = generateThemeCSS(theme);
22
+ return [prose, component].filter(Boolean).join('\n\n');
23
+ }
17
24
 
18
25
  describe('DialogHeader', () => {
19
26
  it('renders the title', () => {
@@ -56,6 +63,48 @@ describe('DialogHeader', () => {
56
63
  expect(screen.getByRole('button', {name: /close/i})).toBeInTheDocument();
57
64
  });
58
65
 
66
+ it('exposes theme targets for the header row, title block, and close icon', () => {
67
+ const {container} = render(
68
+ <DialogHeader
69
+ title="Title"
70
+ subtitle="Subtitle"
71
+ onOpenChange={() => {}}
72
+ />,
73
+ );
74
+
75
+ expect(container.querySelector('.astryx-dialog-header')).not.toBeNull();
76
+ expect(screen.getByRole('heading', {level: 2}).parentElement).toHaveClass(
77
+ 'astryx-dialog-header-title-block',
78
+ );
79
+
80
+ const closeIcon = screen
81
+ .getByRole('button', {name: /close/i})
82
+ .querySelector('.astryx-dialog-header-close-icon');
83
+ expect(closeIcon).toHaveClass('astryx-icon');
84
+ });
85
+
86
+ it('lets themes set the two internal gaps and close-icon size', () => {
87
+ const theme = defineTheme({
88
+ name: 'dialog-header-targets-test',
89
+ components: {
90
+ 'dialog-header': {base: {gap: '8px'}},
91
+ 'dialog-header-title-block': {base: {gap: '4px'}},
92
+ 'dialog-header-close-icon': {
93
+ base: {width: '16px', height: '16px', fontSize: '16px'},
94
+ },
95
+ },
96
+ });
97
+ const css = generateThemeTestCSS(theme);
98
+
99
+ expect(css).toContain('.astryx-dialog-header {');
100
+ expect(css).toContain('gap: 8px');
101
+ expect(css).toContain('.astryx-dialog-header-title-block {');
102
+ expect(css).toContain('gap: 4px');
103
+ expect(css).toContain('.astryx-dialog-header-close-icon {');
104
+ expect(css).toContain('width: 16px');
105
+ expect(css).toContain('height: 16px');
106
+ });
107
+
59
108
  it('does not render close button when onOpenChange is not provided', () => {
60
109
  render(<DialogHeader title="Title" />);
61
110
  expect(
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file DialogHeader.tsx
7
- * @input Uses React, useEffect, useRef, LayoutHeader, Button, Icon, Heading, Text, DialogContext
7
+ * @input Uses React, StyleX, LayoutHeader, Button, Icon, Heading, Text, DialogContext, mergeProps, themeProps
8
8
  * @output Exports DialogHeader component and DialogHeaderProps
9
9
  * @position Dialog header component; used with Dialog and Layout
10
10
  *
@@ -25,6 +25,8 @@ import {Icon} from '../Icon';
25
25
  import {Heading} from '../Heading/Heading';
26
26
  import {Text} from '../Text/Text';
27
27
  import type {BaseProps} from '../BaseProps';
28
+ import {mergeProps} from '../utils';
29
+ import {themeProps} from '../utils/themeProps';
28
30
  import {useDialogContext} from './DialogContext';
29
31
  import {useTranslator} from '../i18n';
30
32
 
@@ -41,6 +43,9 @@ const styles = stylex.create({
41
43
  marginInlineEnd: `calc(-1 * ${spacingVars['--spacing-2']})`,
42
44
  },
43
45
  titleWrapper: {
46
+ display: 'flex',
47
+ flexDirection: 'column',
48
+ gap: spacingVars['--spacing-0'],
44
49
  flex: 1,
45
50
  minWidth: 0,
46
51
  // Visual centering: align title center with close button center
@@ -161,11 +166,19 @@ export function DialogHeader({
161
166
  className={className}
162
167
  style={style}
163
168
  {...rest}>
164
- <div {...stylex.props(styles.container)}>
169
+ <div
170
+ {...mergeProps(
171
+ themeProps('dialog-header'),
172
+ stylex.props(styles.container),
173
+ )}>
165
174
  {startContent && (
166
175
  <div {...stylex.props(styles.actions)}>{startContent}</div>
167
176
  )}
168
- <div {...stylex.props(styles.titleWrapper)}>
177
+ <div
178
+ {...mergeProps(
179
+ themeProps('dialog-header-title-block'),
180
+ stylex.props(styles.titleWrapper),
181
+ )}>
169
182
  <Heading
170
183
  ref={titleRef}
171
184
  id={titleId}
@@ -192,7 +205,13 @@ export function DialogHeader({
192
205
  variant="ghost"
193
206
  label={t('@astryx.dialog.close')}
194
207
  tooltip={t('@astryx.dialog.close')}
195
- icon={<Icon icon="close" color="inherit" />}
208
+ icon={
209
+ <Icon
210
+ icon="close"
211
+ color="inherit"
212
+ {...themeProps('dialog-header-close-icon')}
213
+ />
214
+ }
196
215
  onClick={() => {
197
216
  onOpenChange?.(false);
198
217
  }}