@astryxdesign/core 0.7.1-canary.ff40b78 → 0.7.1
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.
- package/CHANGELOG.md +116 -0
- package/dist/Button/Button.d.ts +1 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +5 -2
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +191 -32
- package/dist/Chat/ChatComposerInput.d.ts +9 -0
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +5 -0
- package/dist/Chat/ChatMessage.d.ts +11 -0
- package/dist/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/Chat/ChatMessage.js +18 -9
- package/dist/Chat/useTriggerMenu.d.ts +9 -2
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.js +222 -50
- package/dist/CodeBlock/CodeBlock.d.ts +10 -1
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +158 -38
- package/dist/CodeBlock/tokenizer.d.ts +39 -0
- package/dist/CodeBlock/tokenizer.d.ts.map +1 -1
- package/dist/CodeBlock/tokenizer.js +108 -0
- package/dist/CommandPalette/CommandPalette.d.ts +9 -5
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPalette.js +38 -20
- package/dist/CommandPalette/CommandPaletteEmpty.d.ts +4 -3
- package/dist/CommandPalette/CommandPaletteEmpty.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteEmpty.js +4 -3
- package/dist/CommandPalette/CommandPaletteInput.d.ts +5 -4
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +35 -18
- package/dist/CommandPalette/CommandPaletteItem.d.ts +11 -6
- package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteItem.js +8 -9
- package/dist/CommandPalette/CommandPaletteList.d.ts +4 -2
- package/dist/CommandPalette/CommandPaletteList.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteList.js +10 -5
- package/dist/ComplexSelector/ComplexSelector.d.ts +8 -5
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +12 -3
- package/dist/ContextMenu/ContextMenu.d.ts +3 -3
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +62 -48
- package/dist/DateInput/DateInput.js +1 -1
- package/dist/DateInput/MonthScroller.d.ts.map +1 -1
- package/dist/DateInput/MonthScroller.js +12 -4
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +12 -2
- package/dist/Dialog/Dialog.d.ts +1 -1
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +16 -5
- package/dist/Dialog/DialogHeader.d.ts +1 -1
- package/dist/Dialog/DialogHeader.d.ts.map +1 -1
- package/dist/Dialog/DialogHeader.js +7 -7
- package/dist/Divider/Divider.d.ts +1 -0
- package/dist/Divider/Divider.d.ts.map +1 -1
- package/dist/Divider/Divider.js +5 -4
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +161 -65
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +7 -5
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +12 -5
- package/dist/Field/InputClearButton.d.ts +8 -4
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +23 -7
- package/dist/FormLayout/FormLayout.d.ts.map +1 -1
- package/dist/FormLayout/FormLayout.js +1 -0
- package/dist/Grid/Grid.d.ts +13 -4
- package/dist/Grid/Grid.d.ts.map +1 -1
- package/dist/Grid/Grid.js +7 -1
- package/dist/HoverCard/useHoverCard.d.ts +1 -1
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +67 -49
- package/dist/InputGroup/InputGroup.d.ts +2 -4
- package/dist/InputGroup/InputGroup.d.ts.map +1 -1
- package/dist/InputGroup/InputGroup.js +2 -4
- package/dist/InputGroup/groupStyles.d.ts +10 -2
- package/dist/InputGroup/groupStyles.d.ts.map +1 -1
- package/dist/InputGroup/groupStyles.js +19 -2
- package/dist/Layer/useLayerDismissal.d.ts +9 -9
- package/dist/Layer/useLayerDismissal.js +9 -9
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +25 -4
- package/dist/MobileNav/MobileNav.d.ts +7 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +45 -2
- package/dist/MultiSelector/MultiSelector.d.ts +7 -4
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +24 -3
- package/dist/NumberInput/NumberInput.js +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +6 -0
- package/dist/Resizable/ResizeHandle.d.ts +1 -1
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +83 -15
- package/dist/Resizable/useResizable.d.ts +23 -0
- package/dist/Resizable/useResizable.d.ts.map +1 -1
- package/dist/Resizable/useResizable.js +78 -0
- package/dist/Selector/Selector.d.ts +6 -3
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +28 -6
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +10 -2
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +18 -7
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +16 -4
- package/dist/Toolbar/Toolbar.d.ts +2 -1
- package/dist/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar/Toolbar.js +3 -1
- package/dist/TreeList/TreeList.d.ts.map +1 -1
- package/dist/TreeList/TreeList.js +6 -3
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +7 -1
- package/dist/astryx.css +22 -0
- package/dist/hooks/useScrollOverflow.d.ts.map +1 -1
- package/dist/hooks/useScrollOverflow.js +55 -12
- package/dist/hooks/useTreeFocus.d.ts +21 -0
- package/dist/hooks/useTreeFocus.d.ts.map +1 -1
- package/dist/hooks/useTreeFocus.js +40 -2
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +3 -0
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +3 -0
- package/dist/reset.a11y.chromium.spec.js +6 -1
- package/dist/reset.until-found.a11y.browser.spec.d.ts +2 -0
- package/dist/reset.until-found.a11y.browser.spec.d.ts.map +1 -0
- package/dist/reset.until-found.a11y.browser.spec.js +71 -0
- package/dist/theme/syntax/SyntaxTheme.d.ts.map +1 -1
- package/dist/theme/syntax/SyntaxTheme.js +43 -2
- package/dist/theme/syntax/defineSyntaxTheme.d.ts +18 -5
- package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
- package/dist/theme/syntax/defineSyntaxTheme.js +38 -9
- package/dist/theme/syntax/index.d.ts +1 -1
- package/dist/theme/syntax/index.d.ts.map +1 -1
- package/dist/theme/syntax/tokens.d.ts +12 -2
- package/dist/theme/syntax/tokens.d.ts.map +1 -1
- package/dist/theme/syntax/tokens.js +25 -4
- package/dist/utils/plainDate.d.ts +12 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +63 -10
- package/locales/en.json +13 -1
- package/locales/pseudo.json +9 -0
- package/package.json +4 -4
- package/src/AlertDialog/AlertDialog.doc.mjs +6 -0
- package/src/AlertDialog/AlertDialog.spec.md +205 -0
- package/src/AlertDialog/AlertDialog.test.tsx +3 -14
- package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +115 -0
- package/src/AlertDialog/__tests__/AlertDialog.a11y.renders.tsx +52 -0
- package/src/AlertDialog/__tests__/AlertDialog.a11y.states.ts +68 -0
- package/src/AlertDialog/__tests__/AlertDialog.a11y.test.tsx +144 -0
- package/src/Button/Button.test.tsx +37 -0
- package/src/Button/Button.tsx +5 -1
- package/src/Calendar/Calendar.doc.mjs +234 -60
- package/src/Calendar/Calendar.spec.md +209 -0
- package/src/Calendar/Calendar.test.tsx +559 -20
- package/src/Calendar/Calendar.tsx +258 -41
- package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +126 -0
- package/src/Calendar/__tests__/Calendar.a11y.known-failures.ts +12 -0
- package/src/Calendar/__tests__/Calendar.a11y.renders.tsx +57 -0
- package/src/Calendar/__tests__/Calendar.a11y.states.ts +139 -0
- package/src/Calendar/__tests__/Calendar.a11y.test.tsx +137 -0
- package/src/Carousel/Carousel.test.tsx +54 -71
- package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +192 -0
- package/src/Carousel/__tests__/Carousel.a11y.known-failures.ts +27 -0
- package/src/Carousel/__tests__/Carousel.a11y.renders.tsx +39 -0
- package/src/Carousel/__tests__/Carousel.a11y.states.ts +139 -0
- package/src/Carousel/__tests__/Carousel.a11y.test.tsx +127 -0
- package/src/Chat/ChatComposerInput.doc.mjs +1 -1
- package/src/Chat/ChatComposerInput.spec.md +94 -55
- package/src/Chat/ChatComposerInput.test.tsx +202 -1
- package/src/Chat/ChatComposerInput.tsx +13 -0
- package/src/Chat/ChatMessage.doc.mjs +31 -16
- package/src/Chat/ChatMessage.spec.md +265 -0
- package/src/Chat/ChatMessage.test.tsx +246 -0
- package/src/Chat/ChatMessage.tsx +30 -7
- package/src/Chat/ChatSystemMessage.spec.md +36 -37
- package/src/Chat/ChatSystemMessage.test.tsx +4 -4
- package/src/Chat/useTriggerMenu.test.tsx +994 -0
- package/src/Chat/useTriggerMenu.tsx +278 -46
- package/src/CodeBlock/CodeBlock.doc.mjs +7 -0
- package/src/CodeBlock/CodeBlock.spec.md +40 -41
- package/src/CodeBlock/CodeBlock.test.tsx +89 -9
- package/src/CodeBlock/CodeBlock.tsx +205 -25
- package/src/CodeBlock/__tests__/CodeBlockOverflowStop.a11y.chromium.spec.ts +125 -0
- package/src/CodeBlock/__tests__/DiffRowPaint.a11y.chromium.spec.ts +93 -0
- package/src/CodeBlock/tokenizer.test.ts +112 -0
- package/src/CodeBlock/tokenizer.ts +106 -0
- package/src/Collapsible/CollapsibleGroup.spec.md +17 -8
- package/src/Collapsible/__tests__/CollapsibleGroup.a11y.renders.tsx +45 -0
- package/src/Collapsible/__tests__/CollapsibleGroup.a11y.states.ts +139 -0
- package/src/Collapsible/__tests__/CollapsibleGroup.a11y.test.tsx +135 -0
- package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +128 -0
- package/src/CommandPalette/CommandPalette.doc.mjs +126 -23
- package/src/CommandPalette/CommandPalette.spec.md +100 -49
- package/src/CommandPalette/CommandPalette.test.tsx +241 -2
- package/src/CommandPalette/CommandPalette.tsx +47 -21
- package/src/CommandPalette/CommandPaletteEmpty.tsx +4 -3
- package/src/CommandPalette/CommandPaletteInput.doc.mjs +61 -4
- package/src/CommandPalette/CommandPaletteInput.spec.md +215 -0
- package/src/CommandPalette/CommandPaletteInput.test.tsx +375 -51
- package/src/CommandPalette/CommandPaletteInput.tsx +74 -21
- package/src/CommandPalette/CommandPaletteItem.doc.mjs +73 -22
- package/src/CommandPalette/CommandPaletteItem.spec.md +223 -0
- package/src/CommandPalette/CommandPaletteItem.test.tsx +200 -5
- package/src/CommandPalette/CommandPaletteItem.tsx +16 -11
- package/src/CommandPalette/CommandPaletteList.doc.mjs +3 -3
- package/src/CommandPalette/CommandPaletteList.spec.md +41 -36
- package/src/CommandPalette/CommandPaletteList.test.tsx +80 -0
- package/src/CommandPalette/CommandPaletteList.tsx +10 -5
- package/src/CommandPalette/__tests__/CommandPalette.a11y.chromium.spec.ts +599 -0
- package/src/ComplexSelector/ComplexSelector.doc.mjs +10 -8
- package/src/ComplexSelector/ComplexSelector.spec.md +14 -9
- package/src/ComplexSelector/ComplexSelector.test.tsx +64 -16
- package/src/ComplexSelector/ComplexSelector.tsx +37 -8
- package/src/ContextMenu/ContextMenu.doc.mjs +6 -0
- package/src/ContextMenu/ContextMenu.spec.md +51 -53
- package/src/ContextMenu/ContextMenu.test.tsx +132 -1
- package/src/ContextMenu/ContextMenu.tsx +78 -48
- package/src/DateInput/DateInput.doc.mjs +52 -0
- package/src/DateInput/DateInput.spec.md +27 -15
- package/src/DateInput/DateInput.test.tsx +38 -0
- package/src/DateInput/DateInput.tsx +1 -1
- package/src/DateInput/DateInputTouch.test.tsx +22 -0
- package/src/DateInput/MonthScroller.tsx +12 -4
- package/src/DateRangeInput/DateRangeInput.test.tsx +49 -0
- package/src/DateRangeInput/DateRangeInput.tsx +20 -2
- package/src/Dialog/Dialog.test.tsx +27 -1
- package/src/Dialog/Dialog.tsx +16 -5
- package/src/Dialog/DialogHeader.test.tsx +27 -2
- package/src/Dialog/DialogHeader.tsx +7 -8
- package/src/Dialog/__tests__/Dialog.a11y.chromium.spec.ts +21 -14
- package/src/Divider/Divider.doc.mjs +5 -3
- package/src/Divider/Divider.spec.md +140 -54
- package/src/Divider/Divider.test.tsx +15 -0
- package/src/Divider/Divider.tsx +6 -4
- package/src/DropdownMenu/DropdownMenu.doc.mjs +2 -2
- package/src/DropdownMenu/DropdownMenu.spec.md +121 -45
- package/src/DropdownMenu/DropdownMenu.test.tsx +288 -23
- package/src/DropdownMenu/DropdownMenu.tsx +212 -81
- package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +38 -2
- package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +32 -18
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +146 -1
- package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +136 -0
- package/src/DropdownMenu/__tests__/MenuButton.a11y.known-failures.ts +12 -0
- package/src/DropdownMenu/__tests__/MenuButton.a11y.renders.tsx +67 -0
- package/src/DropdownMenu/__tests__/MenuButton.a11y.states.ts +141 -0
- package/src/DropdownMenu/__tests__/MenuButton.a11y.test.tsx +138 -0
- package/src/DropdownMenu/__tests__/MenuHeaderFooter.a11y.chromium.spec.ts +40 -5
- package/src/Field/InputClearButton.test.tsx +29 -4
- package/src/Field/InputClearButton.tsx +26 -8
- package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +21 -14
- package/src/FormLayout/FormLayout.tsx +1 -0
- package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +1 -1
- package/src/Grid/Grid.doc.mjs +14 -11
- package/src/Grid/Grid.spec.md +50 -32
- package/src/Grid/Grid.tsx +13 -4
- package/src/HoverCard/HoverCard.test.tsx +46 -1
- package/src/HoverCard/useHoverCard.tsx +68 -48
- package/src/InputGroup/InputGroup.doc.mjs +11 -1
- package/src/InputGroup/InputGroup.tsx +2 -4
- package/src/InputGroup/groupStyles.ts +40 -2
- package/src/Layer/useLayerDismissal.ts +9 -9
- package/src/Link/__tests__/Link.a11y.chromium.spec.ts +142 -0
- package/src/Link/__tests__/Link.a11y.renders.tsx +97 -0
- package/src/Link/__tests__/Link.a11y.states.ts +104 -0
- package/src/Link/__tests__/Link.a11y.test.tsx +127 -0
- package/src/Markdown/Markdown.doc.mjs +1 -1
- package/src/Markdown/Markdown.fr23.perf.test.ts +161 -40
- package/src/Markdown/Markdown.spec.md +45 -23
- package/src/Markdown/Markdown.test.tsx +38 -0
- package/src/Markdown/Markdown.tsx +24 -2
- package/src/Markdown/plugins/semanticFence.test.tsx +3 -2
- package/src/MobileNav/MobileNav.tsx +50 -2
- package/src/MobileNav/MobileNavFocus.test.tsx +553 -0
- package/src/MoreMenu/MoreMenu.spec.md +18 -7
- package/src/MoreMenu/MoreMenu.test.tsx +38 -0
- package/src/MultiSelector/MultiSelector.doc.mjs +8 -8
- package/src/MultiSelector/MultiSelector.spec.md +1 -1
- package/src/MultiSelector/MultiSelector.test.tsx +188 -21
- package/src/MultiSelector/MultiSelector.tsx +50 -11
- package/src/NumberInput/NumberInput.test.tsx +24 -0
- package/src/NumberInput/NumberInput.tsx +1 -1
- package/src/NumberInput/__tests__/NumberInput.a11y.chromium.spec.ts +22 -15
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +78 -0
- package/src/PowerSearch/PowerSearchEditPopover.tsx +6 -0
- package/src/Resizable/Resizable.spec.md +41 -9
- package/src/Resizable/ResizeHandle.test.tsx +565 -2
- package/src/Resizable/ResizeHandle.tsx +126 -21
- package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +119 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.known-failures.ts +39 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.renders.tsx +80 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.states.ts +197 -0
- package/src/Resizable/__tests__/ResizeHandle.a11y.test.tsx +123 -0
- package/src/Resizable/useResizable.ts +95 -0
- package/src/Selector/Selector.doc.mjs +7 -6
- package/src/Selector/Selector.spec.md +37 -24
- package/src/Selector/Selector.test.tsx +176 -10
- package/src/Selector/Selector.tsx +59 -14
- package/src/Selector/__tests__/Combobox.a11y.known-failures.ts +27 -0
- package/src/Selector/__tests__/Combobox.a11y.states.ts +145 -0
- package/src/Selector/__tests__/Selector.combobox.a11y.chromium.spec.ts +108 -0
- package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +126 -0
- package/src/SideNav/SideNav.test.tsx +35 -0
- package/src/SideNav/SideNav.tsx +16 -2
- package/src/Slider/Slider.spec.md +43 -17
- package/src/Slider/Slider.test.tsx +134 -0
- package/src/Slider/Slider.tsx +29 -6
- package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +112 -0
- package/src/Slider/__tests__/Slider.a11y.known-failures.ts +12 -0
- package/src/Slider/__tests__/Slider.a11y.renders.tsx +42 -0
- package/src/Slider/__tests__/Slider.a11y.states.ts +106 -0
- package/src/Slider/__tests__/Slider.a11y.test.tsx +105 -0
- package/src/Switch/__tests__/Switch.a11y.chromium.spec.ts +25 -18
- package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +19 -12
- package/src/TextInput/__tests__/TextInput.a11y.chromium.spec.ts +21 -14
- package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +18 -11
- package/src/Tokenizer/Tokenizer.test.tsx +87 -0
- package/src/Tokenizer/Tokenizer.tsx +31 -3
- package/src/Toolbar/Toolbar.test.tsx +131 -4
- package/src/Toolbar/Toolbar.tsx +3 -1
- package/src/TreeList/TreeList.spec.md +33 -8
- package/src/TreeList/TreeList.test.tsx +72 -0
- package/src/TreeList/TreeList.tsx +10 -6
- package/src/TreeList/TreeListItem.tsx +5 -0
- package/src/TreeList/__tests__/TreeList.a11y.chromium.spec.ts +125 -0
- package/src/TreeList/__tests__/TreeList.a11y.renders.tsx +44 -0
- package/src/TreeList/__tests__/TreeList.a11y.states.ts +164 -0
- package/src/TreeList/__tests__/TreeList.a11y.test.tsx +138 -0
- package/src/TreeList/__tests__/TreeListRowNames.a11y.chromium.spec.ts +82 -0
- package/src/hooks/useScrollOverflow.test.tsx +222 -0
- package/src/hooks/useScrollOverflow.ts +55 -13
- package/src/hooks/useTreeFocus.test.tsx +37 -0
- package/src/hooks/useTreeFocus.ts +55 -2
- package/src/i18n/generated-locales/en.generated.ts +3 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +3 -0
- package/src/reset.a11y.chromium.spec.ts +9 -1
- package/src/reset.css +8 -3
- package/src/reset.until-found.a11y.browser.spec.ts +75 -0
- package/src/reset.until-found.test.ts +32 -0
- package/src/stylexValidation.source-build.test.mjs +42 -0
- package/src/theme/syntax/SyntaxTheme.tsx +46 -5
- package/src/theme/syntax/defineSyntaxTheme.diff.test.ts +91 -0
- package/src/theme/syntax/defineSyntaxTheme.ts +75 -15
- package/src/theme/syntax/index.ts +5 -1
- package/src/theme/syntax/tokens.ts +32 -2
- package/src/theme/syntax/useSyntaxTheme.diff.test.tsx +40 -0
- package/src/utils/plainDate.test.ts +147 -0
- package/src/utils/plainDate.ts +75 -10
|
@@ -51,10 +51,14 @@ import {
|
|
|
51
51
|
plainDateSetFirstOfMonth,
|
|
52
52
|
plainDateAddMonths,
|
|
53
53
|
plainDateAddDays,
|
|
54
|
+
plainDateDiffDays,
|
|
55
|
+
getDaysInMonth,
|
|
56
|
+
plainDateIsAfter,
|
|
54
57
|
plainDateIsBefore,
|
|
55
58
|
plainDateIsEqual,
|
|
56
59
|
plainDateGetWeekNumber,
|
|
57
60
|
plainDateFormat,
|
|
61
|
+
plainDateWeekFieldName,
|
|
58
62
|
DATE_FORMAT_WITH_WEEKDAY,
|
|
59
63
|
DATE_FORMAT_MONTH_YEAR,
|
|
60
64
|
} from '../utils/plainDate';
|
|
@@ -91,9 +95,12 @@ import type {
|
|
|
91
95
|
import {normalizeDayOfWeek} from '../utils/dateTypes';
|
|
92
96
|
import {themeProps} from '../utils/themeProps';
|
|
93
97
|
import {useLocale, useTranslator} from '../i18n';
|
|
98
|
+
import {VisuallyHidden} from '../VisuallyHidden';
|
|
94
99
|
|
|
95
|
-
/**
|
|
100
|
+
/** How a month navigation moves the focused day: by its row offset in days, or by whole months. */
|
|
101
|
+
type NavigationUnit = 'days' | 'months';
|
|
96
102
|
|
|
103
|
+
/** Imperative handle for Calendar handleRef */
|
|
97
104
|
export interface CalendarHandle {
|
|
98
105
|
/** Navigate the calendar to show the month containing the given date */
|
|
99
106
|
navigateTo: (date: ISODateString) => void;
|
|
@@ -214,6 +221,21 @@ interface CalendarRangeProps extends CalendarBaseProps {
|
|
|
214
221
|
|
|
215
222
|
export type CalendarProps = CalendarSingleProps | CalendarRangeProps;
|
|
216
223
|
|
|
224
|
+
/** The first day of `month`'s month that `isDisabled` allows, or null. */
|
|
225
|
+
function firstEnabledDayOfMonth(
|
|
226
|
+
month: PlainDate,
|
|
227
|
+
isDisabled: (pd: PlainDate) => boolean,
|
|
228
|
+
): PlainDate | null {
|
|
229
|
+
const days = getDaysInMonth(month.year, month.month);
|
|
230
|
+
for (let day = 1; day <= days; day += 1) {
|
|
231
|
+
const pd = {year: month.year, month: month.month, day};
|
|
232
|
+
if (!isDisabled(pd)) {
|
|
233
|
+
return pd;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
|
|
217
239
|
// =============================================================================
|
|
218
240
|
// Main Component
|
|
219
241
|
// =============================================================================
|
|
@@ -333,6 +355,39 @@ export function Calendar({ref, ...props}: CalendarProps) {
|
|
|
333
355
|
});
|
|
334
356
|
}, [baseMonth, numberOfMonths]);
|
|
335
357
|
|
|
358
|
+
// A pending target is claimed by the grid that shows its month, or dropped
|
|
359
|
+
// by the next focus move that is not onto the target day: when a controlled
|
|
360
|
+
// owner refuses the month change, no grid shows the target's month, and a
|
|
361
|
+
// stale target would later steal focus (and Enter) when that month does
|
|
362
|
+
// appear. Only a grid showing the target's month receives it; any other
|
|
363
|
+
// focus change (an arrow to another day, the month header's buttons, or out
|
|
364
|
+
// of the calendar) drops it (see onBlur below).
|
|
365
|
+
const claimablePendingFocus = useMemo(() => {
|
|
366
|
+
if (pendingFocus == null) {
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
const target = plainDateFromISO(pendingFocus);
|
|
370
|
+
return visibleMonths.some(
|
|
371
|
+
pd => pd.year === target.year && pd.month === target.month,
|
|
372
|
+
)
|
|
373
|
+
? pendingFocus
|
|
374
|
+
: null;
|
|
375
|
+
}, [pendingFocus, visibleMonths]);
|
|
376
|
+
const handleRootBlur = useCallback(
|
|
377
|
+
(e: React.FocusEvent<HTMLDivElement>) => {
|
|
378
|
+
// Only the move onto the target day itself keeps the target: an arrow
|
|
379
|
+
// to any other day, a move to the header buttons, or focus leaving the
|
|
380
|
+
// calendar all drop it.
|
|
381
|
+
const movesToTarget =
|
|
382
|
+
e.relatedTarget instanceof Element &&
|
|
383
|
+
e.relatedTarget.getAttribute('data-date') === pendingFocus;
|
|
384
|
+
if (pendingFocus != null && !movesToTarget) {
|
|
385
|
+
setPendingFocus(null);
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
[pendingFocus],
|
|
389
|
+
);
|
|
390
|
+
|
|
336
391
|
// Format month header
|
|
337
392
|
const monthYearLabel = useMemo(() => {
|
|
338
393
|
if (numberOfMonths === 1) {
|
|
@@ -363,6 +418,15 @@ export function Calendar({ref, ...props}: CalendarProps) {
|
|
|
363
418
|
}, [monthYearLabel, announce]);
|
|
364
419
|
|
|
365
420
|
// Determine if prev/next navigation is possible based on min/max
|
|
421
|
+
const minDateForKeys = useMemo(
|
|
422
|
+
() => (min ? plainDateFromISO(min) : null),
|
|
423
|
+
[min],
|
|
424
|
+
);
|
|
425
|
+
const maxDateForKeys = useMemo(
|
|
426
|
+
() => (max ? plainDateFromISO(max) : null),
|
|
427
|
+
[max],
|
|
428
|
+
);
|
|
429
|
+
|
|
366
430
|
const canNavigatePrevious = useMemo(() => {
|
|
367
431
|
if (!min) {
|
|
368
432
|
return true;
|
|
@@ -390,26 +454,137 @@ export function Calendar({ref, ...props}: CalendarProps) {
|
|
|
390
454
|
}, [max, baseMonth, numberOfMonths]);
|
|
391
455
|
|
|
392
456
|
// Navigation handlers
|
|
457
|
+
// The same constraint view the grids use, so a key can resolve its target
|
|
458
|
+
// before the month changes.
|
|
459
|
+
const rootRangeAnchor = useMemo(
|
|
460
|
+
() =>
|
|
461
|
+
mode === 'range' && rangeSelectionStart
|
|
462
|
+
? plainDateFromISO(rangeSelectionStart)
|
|
463
|
+
: null,
|
|
464
|
+
[mode, rangeSelectionStart],
|
|
465
|
+
);
|
|
466
|
+
const {isDateDisabled: isRootDateDisabled} = useCalendarConstraints({
|
|
467
|
+
min,
|
|
468
|
+
max,
|
|
469
|
+
dateConstraints,
|
|
470
|
+
maxRangeSpan,
|
|
471
|
+
minRangeSpan,
|
|
472
|
+
rangeAnchor: rootRangeAnchor,
|
|
473
|
+
});
|
|
474
|
+
|
|
393
475
|
const navigateMonth = useCallback(
|
|
394
|
-
(
|
|
395
|
-
|
|
396
|
-
|
|
476
|
+
(
|
|
477
|
+
delta: number,
|
|
478
|
+
focusedDate?: ISODateString,
|
|
479
|
+
offset?: number,
|
|
480
|
+
unit: NavigationUnit = 'days',
|
|
481
|
+
) => {
|
|
482
|
+
const monthIndex = (pd: PlainDate) => pd.year * 12 + pd.month;
|
|
483
|
+
const firstShown = monthIndex(baseMonth);
|
|
484
|
+
const lastShown = firstShown + numberOfMonths - 1;
|
|
485
|
+
|
|
486
|
+
// The Previous/Next buttons move the month with no focused day.
|
|
487
|
+
if (!focusedDate) {
|
|
488
|
+
if (
|
|
489
|
+
(delta < 0 && !canNavigatePrevious) ||
|
|
490
|
+
(delta > 0 && !canNavigateNext)
|
|
491
|
+
) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const newPd = plainDateAddMonths(baseMonth, delta);
|
|
495
|
+
if (onFocusDateChange) {
|
|
496
|
+
onFocusDateChange(plainDateToISO(newPd));
|
|
497
|
+
} else {
|
|
498
|
+
setInternalFocusDate(newPd);
|
|
499
|
+
}
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
397
502
|
|
|
398
|
-
//
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
503
|
+
// A key resolves its TARGET day first and decides by that, so a
|
|
504
|
+
// boundary blocks only out-of-range targets, never the month movement
|
|
505
|
+
// itself (ArrowUp from February 3 reaches January 27 at a January
|
|
506
|
+
// minimum), and a direction with no enabled day left changes nothing
|
|
507
|
+
// rather than dropping focus.
|
|
508
|
+
const currentPd = plainDateFromISO(focusedDate);
|
|
509
|
+
const inRange = (pd: PlainDate) =>
|
|
510
|
+
!(minDateForKeys && plainDateIsBefore(pd, minDateForKeys)) &&
|
|
511
|
+
!(maxDateForKeys && plainDateIsAfter(pd, maxDateForKeys));
|
|
512
|
+
let targetPd: PlainDate;
|
|
513
|
+
if (unit === 'months') {
|
|
514
|
+
// A page targets the same day of the month a month away (clamped to
|
|
515
|
+
// that month's length). Past `min`/`max` nothing moves; a month with
|
|
516
|
+
// no enabled day is no destination either, the same as an arrow with
|
|
517
|
+
// no enabled day in its direction; a target disabled by a constraint
|
|
518
|
+
// lands on that month's first enabled day instead.
|
|
519
|
+
const candidate = plainDateAddMonths(currentPd, delta);
|
|
520
|
+
if (!inRange(candidate)) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (isRootDateDisabled(candidate)) {
|
|
524
|
+
const firstEnabled = firstEnabledDayOfMonth(
|
|
525
|
+
candidate,
|
|
526
|
+
isRootDateDisabled,
|
|
527
|
+
);
|
|
528
|
+
if (firstEnabled == null) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
targetPd = firstEnabled;
|
|
532
|
+
} else {
|
|
533
|
+
targetPd = candidate;
|
|
534
|
+
}
|
|
535
|
+
} else {
|
|
536
|
+
// Walk the arrow's direction to the first enabled day, up to a year
|
|
537
|
+
// away; past `min`/`max` or with every day disabled, nothing moves.
|
|
538
|
+
const step = delta * (offset ?? 7);
|
|
539
|
+
let candidate = plainDateAddDays(currentPd, step);
|
|
540
|
+
let found: PlainDate | null = null;
|
|
541
|
+
while (Math.abs(plainDateDiffDays(currentPd, candidate)) <= 366) {
|
|
542
|
+
if (!inRange(candidate)) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
if (!isRootDateDisabled(candidate)) {
|
|
546
|
+
found = candidate;
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
candidate = plainDateAddDays(candidate, step);
|
|
550
|
+
}
|
|
551
|
+
if (found == null) {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
targetPd = found;
|
|
404
555
|
}
|
|
405
556
|
|
|
557
|
+
// Focus moves to the target; the months change only when the target's
|
|
558
|
+
// month is not already shown. The view shifts the least it can in the
|
|
559
|
+
// key's direction, so the target month becomes the first (backwards)
|
|
560
|
+
// or last (forwards) month shown.
|
|
561
|
+
const resolved: PlainDate = targetPd;
|
|
562
|
+
const targetMonth = monthIndex(resolved);
|
|
563
|
+
setPendingFocus(plainDateToISO(resolved));
|
|
564
|
+
if (targetMonth >= firstShown && targetMonth <= lastShown) {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const targetFirst = {year: resolved.year, month: resolved.month, day: 1};
|
|
568
|
+
const newPd =
|
|
569
|
+
targetMonth < firstShown
|
|
570
|
+
? targetFirst
|
|
571
|
+
: plainDateAddMonths(targetFirst, -(numberOfMonths - 1));
|
|
406
572
|
if (onFocusDateChange) {
|
|
407
|
-
onFocusDateChange(
|
|
573
|
+
onFocusDateChange(plainDateToISO(newPd));
|
|
408
574
|
} else {
|
|
409
575
|
setInternalFocusDate(newPd);
|
|
410
576
|
}
|
|
411
577
|
},
|
|
412
|
-
[
|
|
578
|
+
[
|
|
579
|
+
baseMonth,
|
|
580
|
+
canNavigateNext,
|
|
581
|
+
canNavigatePrevious,
|
|
582
|
+
isRootDateDisabled,
|
|
583
|
+
maxDateForKeys,
|
|
584
|
+
minDateForKeys,
|
|
585
|
+
numberOfMonths,
|
|
586
|
+
onFocusDateChange,
|
|
587
|
+
],
|
|
413
588
|
);
|
|
414
589
|
|
|
415
590
|
// Escape key handler to cancel range selection
|
|
@@ -513,6 +688,7 @@ export function Calendar({ref, ...props}: CalendarProps) {
|
|
|
513
688
|
<div
|
|
514
689
|
ref={ref}
|
|
515
690
|
{...rest}
|
|
691
|
+
onBlur={composeEventHandlers(rest.onBlur, handleRootBlur)}
|
|
516
692
|
{...mergeProps(
|
|
517
693
|
themeProps('calendar', {mode}),
|
|
518
694
|
stylex.props(calendarStyles.calendar, xstyle),
|
|
@@ -587,13 +763,13 @@ export function Calendar({ref, ...props}: CalendarProps) {
|
|
|
587
763
|
setHoveredDate(date ? plainDateToISO(date) : null)
|
|
588
764
|
}
|
|
589
765
|
today={today}
|
|
590
|
-
onNavigatePrevious={(focusedDate, offset) =>
|
|
591
|
-
navigateMonth(-1, focusedDate, offset)
|
|
766
|
+
onNavigatePrevious={(focusedDate, offset, unit) =>
|
|
767
|
+
navigateMonth(-1, focusedDate, offset, unit)
|
|
592
768
|
}
|
|
593
|
-
onNavigateNext={(focusedDate, offset) =>
|
|
594
|
-
navigateMonth(1, focusedDate, offset)
|
|
769
|
+
onNavigateNext={(focusedDate, offset, unit) =>
|
|
770
|
+
navigateMonth(1, focusedDate, offset, unit)
|
|
595
771
|
}
|
|
596
|
-
pendingFocus={
|
|
772
|
+
pendingFocus={claimablePendingFocus}
|
|
597
773
|
onPendingFocusHandled={() => setPendingFocus(null)}
|
|
598
774
|
/>
|
|
599
775
|
))}
|
|
@@ -626,8 +802,16 @@ interface MonthGridProps {
|
|
|
626
802
|
onDayClick: (date: PlainDate) => void;
|
|
627
803
|
onDayHover: (date: PlainDate | null) => void;
|
|
628
804
|
today: PlainDate;
|
|
629
|
-
onNavigatePrevious?: (
|
|
630
|
-
|
|
805
|
+
onNavigatePrevious?: (
|
|
806
|
+
focusedDate: ISODateString,
|
|
807
|
+
offset: number,
|
|
808
|
+
unit?: NavigationUnit,
|
|
809
|
+
) => void;
|
|
810
|
+
onNavigateNext?: (
|
|
811
|
+
focusedDate: ISODateString,
|
|
812
|
+
offset: number,
|
|
813
|
+
unit?: NavigationUnit,
|
|
814
|
+
) => void;
|
|
631
815
|
pendingFocus?: ISODateString | null;
|
|
632
816
|
onPendingFocusHandled?: () => void;
|
|
633
817
|
}
|
|
@@ -773,14 +957,14 @@ function MonthGrid({
|
|
|
773
957
|
const handlePageUp = useCallback(() => {
|
|
774
958
|
const focusedDate = getFocusedDate();
|
|
775
959
|
if (focusedDate) {
|
|
776
|
-
onNavigatePrevious?.(focusedDate,
|
|
960
|
+
onNavigatePrevious?.(focusedDate, 1, 'months');
|
|
777
961
|
}
|
|
778
962
|
}, [getFocusedDate, onNavigatePrevious]);
|
|
779
963
|
|
|
780
964
|
const handlePageDown = useCallback(() => {
|
|
781
965
|
const focusedDate = getFocusedDate();
|
|
782
966
|
if (focusedDate) {
|
|
783
|
-
onNavigateNext?.(focusedDate,
|
|
967
|
+
onNavigateNext?.(focusedDate, 1, 'months');
|
|
784
968
|
}
|
|
785
969
|
}, [getFocusedDate, onNavigateNext]);
|
|
786
970
|
|
|
@@ -800,8 +984,15 @@ function MonthGrid({
|
|
|
800
984
|
} = useGridFocus<HTMLDivElement>({
|
|
801
985
|
columns: 7,
|
|
802
986
|
cellSelector: '[role="gridcell"]',
|
|
987
|
+
// A borrowed adjacent-month day is exposed aria-disabled (its click does
|
|
988
|
+
// nothing, but it can still take pointer focus) and may also be natively
|
|
989
|
+
// disabled by bounds or a constraint; the keyboard skips it either way
|
|
990
|
+
// and crosses into that month through the grid edge instead, so focus
|
|
991
|
+
// lands on the real day in the month now shown (FR5).
|
|
803
992
|
isCellFocusable: cell =>
|
|
804
|
-
cell.querySelector(
|
|
993
|
+
cell.querySelector(
|
|
994
|
+
'button:not([disabled]):not([aria-disabled="true"])',
|
|
995
|
+
) !== null,
|
|
805
996
|
getFocusTarget: cell => cell.querySelector<HTMLElement>('button'),
|
|
806
997
|
hasRovingTabIndex: true,
|
|
807
998
|
onNavigateBefore: handleNavigatePrevious,
|
|
@@ -810,34 +1001,53 @@ function MonthGrid({
|
|
|
810
1001
|
onPageDown: handlePageDown,
|
|
811
1002
|
});
|
|
812
1003
|
|
|
813
|
-
// Handle pending focus after month navigation
|
|
1004
|
+
// Handle pending focus after month navigation. Every mounted month grid
|
|
1005
|
+
// sees the same pending date; only the grid showing that date's month acts,
|
|
1006
|
+
// so a neighbouring grid's fallback never overrides the right target. The
|
|
1007
|
+
// target is the real day in this month, never an outside-day copy; when it
|
|
1008
|
+
// is disabled, the month's first enabled day takes focus instead.
|
|
814
1009
|
useEffect(() => {
|
|
815
1010
|
if (!pendingFocus || !gridRef.current) {
|
|
816
1011
|
return;
|
|
817
1012
|
}
|
|
818
1013
|
|
|
819
|
-
const buttons = gridRef.current.querySelectorAll<HTMLElement>(
|
|
820
|
-
'button:not([disabled])',
|
|
821
|
-
);
|
|
822
|
-
|
|
823
1014
|
const targetPd = plainDateFromISO(pendingFocus);
|
|
1015
|
+
if (targetPd.year !== year || targetPd.month !== month.month) {
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
824
1018
|
const targetIso = plainDateToISO(targetPd);
|
|
825
1019
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
}
|
|
832
|
-
}
|
|
1020
|
+
const buttons = Array.from(
|
|
1021
|
+
gridRef.current.querySelectorAll<HTMLElement>(
|
|
1022
|
+
'button[data-date]:not([disabled]):not([aria-disabled="true"])',
|
|
1023
|
+
),
|
|
1024
|
+
);
|
|
833
1025
|
|
|
834
|
-
|
|
835
|
-
|
|
1026
|
+
// The real target day, else the month's first enabled day, else — in a
|
|
1027
|
+
// month with no enabled day — the first day button that can still take
|
|
1028
|
+
// focus (a borrowed adjacent-month day), so focus stays in the grid
|
|
1029
|
+
// rather than dropping to the page.
|
|
1030
|
+
const targetButton =
|
|
1031
|
+
buttons.find(button => button.getAttribute('data-date') === targetIso) ??
|
|
1032
|
+
buttons[0] ??
|
|
1033
|
+
gridRef.current.querySelector<HTMLElement>(
|
|
1034
|
+
'button[data-date]:not([disabled])',
|
|
1035
|
+
) ??
|
|
1036
|
+
null;
|
|
1037
|
+
|
|
1038
|
+
if (targetButton != null) {
|
|
1039
|
+
// The target becomes this grid's one Tab stop before it takes focus,
|
|
1040
|
+
// the way the hook's own arrow moves do (FR2): a programmatic focus
|
|
1041
|
+
// alone would leave the stop on the day the grid mounted with.
|
|
1042
|
+
for (const button of gridRef.current.querySelectorAll<HTMLElement>(
|
|
1043
|
+
'button[data-date]',
|
|
1044
|
+
)) {
|
|
1045
|
+
button.tabIndex = button === targetButton ? 0 : -1;
|
|
1046
|
+
}
|
|
1047
|
+
targetButton.focus();
|
|
836
1048
|
}
|
|
837
|
-
|
|
838
|
-
targetButton?.focus();
|
|
839
1049
|
onPendingFocusHandled?.();
|
|
840
|
-
}, [pendingFocus, gridRef, onPendingFocusHandled]);
|
|
1050
|
+
}, [pendingFocus, gridRef, month.month, onPendingFocusHandled, year]);
|
|
841
1051
|
|
|
842
1052
|
// Parse selection
|
|
843
1053
|
let selectedDate: PlainDate | null = null;
|
|
@@ -900,12 +1110,19 @@ function MonthGrid({
|
|
|
900
1110
|
same display:contents row so its cells align to the grid columns. */}
|
|
901
1111
|
<div role="row" {...stylex.props(monthGridStyles.weekRow)}>
|
|
902
1112
|
{hasWeekNumbers && (
|
|
1113
|
+
// The corner above the week-number column is a header cell like
|
|
1114
|
+
// the weekday names beside it (APG grid: every cell in a row is a
|
|
1115
|
+
// gridcell, columnheader or rowheader). Its name is the locale's
|
|
1116
|
+
// own word for the week field, as visually hidden text so the
|
|
1117
|
+
// column stays blank and the header is not empty to axe.
|
|
903
1118
|
<div
|
|
1119
|
+
role="columnheader"
|
|
904
1120
|
{...stylex.props(
|
|
905
1121
|
monthGridStyles.dayName,
|
|
906
1122
|
monthGridStyles.weekNumberHeader,
|
|
907
|
-
)}
|
|
908
|
-
|
|
1123
|
+
)}>
|
|
1124
|
+
<VisuallyHidden>{plainDateWeekFieldName(locale)}</VisuallyHidden>
|
|
1125
|
+
</div>
|
|
909
1126
|
)}
|
|
910
1127
|
{dayNameHeaders.map(({dayOfWeek, name}) => (
|
|
911
1128
|
<div
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Calendar.a11y.chromium.spec.ts
|
|
5
|
+
* @input Built Calendar stories and GRID_PATTERN in real Chromium
|
|
6
|
+
* @output Browser evidence for every grid expectation across the binding's states (role and name, arrows in reading order, Home/End, Enter)
|
|
7
|
+
* @position Chromium lane of the Calendar grid binding (component:Calendar AR1)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
expect,
|
|
12
|
+
test,
|
|
13
|
+
type CDPSession,
|
|
14
|
+
type Locator,
|
|
15
|
+
type Page,
|
|
16
|
+
} from '@playwright/test';
|
|
17
|
+
import {
|
|
18
|
+
GRID_PATTERN,
|
|
19
|
+
blockingResults,
|
|
20
|
+
checkAccessibilitySpec,
|
|
21
|
+
formatFailures,
|
|
22
|
+
neverExercised,
|
|
23
|
+
unmatchedKnownFailures,
|
|
24
|
+
summarize,
|
|
25
|
+
type BindingResult,
|
|
26
|
+
} from '@astryxdesign/a11y-spec';
|
|
27
|
+
import {
|
|
28
|
+
createChromiumHarness,
|
|
29
|
+
holdMotionStill,
|
|
30
|
+
} from '@astryxdesign/a11y-spec/chromium';
|
|
31
|
+
import {
|
|
32
|
+
DEFAULT_STORYBOOK_DIR,
|
|
33
|
+
serveStorybook,
|
|
34
|
+
type StaticServer,
|
|
35
|
+
} from '@astryxdesign/a11y-spec/storybook';
|
|
36
|
+
import {
|
|
37
|
+
CALENDAR_BINDING_STATES,
|
|
38
|
+
NEIGHBOURHOOD,
|
|
39
|
+
START_ISO,
|
|
40
|
+
type CalendarBindingState,
|
|
41
|
+
} from './Calendar.a11y.states';
|
|
42
|
+
import {CALENDAR_KNOWN_FAILURES} from './Calendar.a11y.known-failures';
|
|
43
|
+
|
|
44
|
+
let storybook: StaticServer;
|
|
45
|
+
|
|
46
|
+
test.beforeAll(async () => {
|
|
47
|
+
storybook = await serveStorybook(
|
|
48
|
+
process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test.afterAll(async () => {
|
|
53
|
+
await storybook?.close();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function storyUrl(storyId: string): string {
|
|
57
|
+
return `${storybook.origin}/iframe.html?id=${storyId}&viewMode=story`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function mountState(
|
|
61
|
+
page: Page,
|
|
62
|
+
state: CalendarBindingState,
|
|
63
|
+
): Promise<{subject: Locator; related: Record<string, Locator>}> {
|
|
64
|
+
await page.goto(storyUrl(state.storyId), {waitUntil: 'load'});
|
|
65
|
+
await holdMotionStill(page);
|
|
66
|
+
const root = page.locator('#storybook-root');
|
|
67
|
+
const subject = root.getByRole('grid', {name: state.gridName});
|
|
68
|
+
await subject.waitFor({state: 'attached'});
|
|
69
|
+
const day = (iso: string) => root.locator(`button[data-date="${iso}"]`);
|
|
70
|
+
const start = day(START_ISO);
|
|
71
|
+
return {
|
|
72
|
+
subject,
|
|
73
|
+
related: {
|
|
74
|
+
start,
|
|
75
|
+
startCell: root.locator(
|
|
76
|
+
`[role="gridcell"]:has(> button[data-date="${START_ISO}"])`,
|
|
77
|
+
),
|
|
78
|
+
...Object.fromEntries(
|
|
79
|
+
Object.entries(NEIGHBOURHOOD).map(([name, iso]) => [name, day(iso)]),
|
|
80
|
+
),
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function runState(
|
|
86
|
+
page: Page,
|
|
87
|
+
cdp: CDPSession,
|
|
88
|
+
state: CalendarBindingState,
|
|
89
|
+
): Promise<BindingResult> {
|
|
90
|
+
return checkAccessibilitySpec({
|
|
91
|
+
spec: GRID_PATTERN,
|
|
92
|
+
binding: 'Calendar',
|
|
93
|
+
state: state.id,
|
|
94
|
+
facts: state.facts,
|
|
95
|
+
knownFailures: CALENDAR_KNOWN_FAILURES,
|
|
96
|
+
mount: async () => {
|
|
97
|
+
const {subject, related} = await mountState(page, state);
|
|
98
|
+
return createChromiumHarness({page, cdp, subject, related});
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
test('every grid expectation is exercised by a Calendar state', async ({
|
|
104
|
+
page,
|
|
105
|
+
}) => {
|
|
106
|
+
const cdp = await page.context().newCDPSession(page);
|
|
107
|
+
const results: BindingResult[] = [];
|
|
108
|
+
for (const state of CALENDAR_BINDING_STATES) {
|
|
109
|
+
results.push(await runState(page, cdp, state));
|
|
110
|
+
}
|
|
111
|
+
expect(neverExercised(results)).toEqual([]);
|
|
112
|
+
expect(unmatchedKnownFailures(CALENDAR_KNOWN_FAILURES, results)).toEqual([]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
for (const state of CALENDAR_BINDING_STATES) {
|
|
116
|
+
test(`Calendar [${state.id}] — ${state.summary}`, async ({page}) => {
|
|
117
|
+
const cdp = await page.context().newCDPSession(page);
|
|
118
|
+
const result = await runState(page, cdp, state);
|
|
119
|
+
const report = summarize(GRID_PATTERN, [result]);
|
|
120
|
+
// Required rows block (AST-020 FR9); advisory rows and exact known
|
|
121
|
+
// failures report.
|
|
122
|
+
expect(formatFailures(blockingResults([result]))).toBe('');
|
|
123
|
+
expect(report.unrunLayers).toEqual([]);
|
|
124
|
+
expect(report.counts.unexpectedPass).toBe(0);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Calendar.a11y.known-failures.ts
|
|
5
|
+
* @input The exact grid-contract failures the shipped Calendar produces today
|
|
6
|
+
* @output CALENDAR_KNOWN_FAILURES — records both lanes reconcile against, so a fix shows up as an unexpected pass and any other failure stays a failure
|
|
7
|
+
* @position Known-failure ledger for the Calendar binding
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {KnownFailure} from '@astryxdesign/a11y-spec';
|
|
11
|
+
|
|
12
|
+
export const CALENDAR_KNOWN_FAILURES: ReadonlyArray<KnownFailure> = [];
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file Calendar.a11y.renders.tsx
|
|
7
|
+
* @input One Calendar binding state
|
|
8
|
+
* @output The React tree every lane renders for that state (the jsdom test directly, the story through Storybook)
|
|
9
|
+
* @position Shared fixture for the Calendar grid binding
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {useState, type ReactElement} from 'react';
|
|
13
|
+
import {Calendar} from '../Calendar';
|
|
14
|
+
import type {DateRange, ISODateString} from '../Calendar';
|
|
15
|
+
import {START_ISO, type CalendarBindingState} from './Calendar.a11y.states';
|
|
16
|
+
|
|
17
|
+
function BoundCalendar({state}: {state: CalendarBindingState}) {
|
|
18
|
+
const [single, setSingle] = useState<ISODateString | null>(
|
|
19
|
+
state.mode === 'single' && typeof state.value === 'string'
|
|
20
|
+
? state.value
|
|
21
|
+
: null,
|
|
22
|
+
);
|
|
23
|
+
const [range, setRange] = useState<DateRange | null>(
|
|
24
|
+
state.mode === 'range' && typeof state.value === 'object'
|
|
25
|
+
? state.value
|
|
26
|
+
: null,
|
|
27
|
+
);
|
|
28
|
+
if (state.mode === 'range') {
|
|
29
|
+
return (
|
|
30
|
+
<Calendar
|
|
31
|
+
mode="range"
|
|
32
|
+
value={range}
|
|
33
|
+
onChange={setRange}
|
|
34
|
+
focusDate={START_ISO}
|
|
35
|
+
numberOfMonths={state.numberOfMonths ?? 1}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return (
|
|
40
|
+
<Calendar
|
|
41
|
+
mode="single"
|
|
42
|
+
value={single}
|
|
43
|
+
onChange={setSingle}
|
|
44
|
+
focusDate={START_ISO}
|
|
45
|
+
numberOfMonths={state.numberOfMonths ?? 1}
|
|
46
|
+
hasWeekNumbers={state.hasWeekNumbers}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function renderCalendarState(state: CalendarBindingState): ReactElement {
|
|
52
|
+
return (
|
|
53
|
+
<div dir={state.rtl ? 'rtl' : 'ltr'} style={{padding: 16}}>
|
|
54
|
+
<BoundCalendar state={state} />
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|