@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
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Calendar.a11y.states.ts
|
|
5
|
+
* @input Calendar's representative month-grid states and the facts GRID_PATTERN reads
|
|
6
|
+
* @output CALENDAR_BINDING_STATES, the start-cell neighbourhood for each, and the explicit ownership exclusions for this binding
|
|
7
|
+
* @position Binding inventory shared by the jsdom test, the Chromium spec, and the story (component:Calendar AR1)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {GridStateFacts} from '@astryxdesign/a11y-spec';
|
|
11
|
+
import type {DateRange, ISODateString} from '../Calendar';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Every state starts on a mid-month day with a full neighbourhood inside the
|
|
15
|
+
* same month row: January 14, 2026 is a Wednesday, so its row runs January
|
|
16
|
+
* 11 (Sunday) to January 17 (Saturday), with January 7 above and 21 below.
|
|
17
|
+
*/
|
|
18
|
+
export const START_ISO: ISODateString = '2026-01-14';
|
|
19
|
+
export const NEIGHBOURHOOD = {
|
|
20
|
+
next: '2026-01-15',
|
|
21
|
+
previous: '2026-01-13',
|
|
22
|
+
below: '2026-01-21',
|
|
23
|
+
above: '2026-01-07',
|
|
24
|
+
rowStart: '2026-01-11',
|
|
25
|
+
rowEnd: '2026-01-17',
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
export interface CalendarBindingState {
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly summary: string;
|
|
31
|
+
readonly storyId: string;
|
|
32
|
+
/** The grid's accessible name (its month label). */
|
|
33
|
+
readonly gridName: string;
|
|
34
|
+
readonly facts: GridStateFacts;
|
|
35
|
+
readonly mode: 'single' | 'range';
|
|
36
|
+
readonly value?: ISODateString | DateRange;
|
|
37
|
+
readonly numberOfMonths?: 1 | 2;
|
|
38
|
+
/** Render ISO week numbers in a leading column (`hasWeekNumbers`). */
|
|
39
|
+
readonly hasWeekNumbers?: boolean;
|
|
40
|
+
readonly rtl?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const ALL = [
|
|
44
|
+
'next',
|
|
45
|
+
'previous',
|
|
46
|
+
'below',
|
|
47
|
+
'above',
|
|
48
|
+
'rowStart',
|
|
49
|
+
'rowEnd',
|
|
50
|
+
] as const;
|
|
51
|
+
|
|
52
|
+
function facts(overrides: Partial<GridStateFacts> = {}): GridStateFacts {
|
|
53
|
+
return {
|
|
54
|
+
hasColumnHeaders: true,
|
|
55
|
+
rtl: false,
|
|
56
|
+
multiselectable: false,
|
|
57
|
+
selectedCount: 0,
|
|
58
|
+
enterSelectsStart: true,
|
|
59
|
+
neighbours: ALL,
|
|
60
|
+
...overrides,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const story = (id: string) => `a11y-calendargrid--${id}`;
|
|
65
|
+
|
|
66
|
+
export const CALENDAR_BINDING_STATES = [
|
|
67
|
+
{
|
|
68
|
+
id: 'single-selected',
|
|
69
|
+
summary:
|
|
70
|
+
'a single-mode Calendar with January 20 selected; focus starts on January 14',
|
|
71
|
+
storyId: story('single-selected'),
|
|
72
|
+
gridName: 'January 2026',
|
|
73
|
+
mode: 'single',
|
|
74
|
+
value: '2026-01-20',
|
|
75
|
+
facts: facts({selectedCount: 1}),
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'single-rtl',
|
|
79
|
+
summary:
|
|
80
|
+
'the same Calendar on a right-to-left page: ArrowLeft moves to the next day',
|
|
81
|
+
storyId: story('single-rtl'),
|
|
82
|
+
gridName: 'January 2026',
|
|
83
|
+
mode: 'single',
|
|
84
|
+
value: '2026-01-20',
|
|
85
|
+
rtl: true,
|
|
86
|
+
facts: facts({rtl: true, selectedCount: 1}),
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'single-week-numbers',
|
|
90
|
+
summary:
|
|
91
|
+
'a single-mode Calendar with week numbers: the header row gains a corner cell before the weekday headers',
|
|
92
|
+
storyId: story('single-week-numbers'),
|
|
93
|
+
gridName: 'January 2026',
|
|
94
|
+
mode: 'single',
|
|
95
|
+
value: '2026-01-20',
|
|
96
|
+
hasWeekNumbers: true,
|
|
97
|
+
facts: facts({selectedCount: 1}),
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: 'range-two-months',
|
|
101
|
+
summary:
|
|
102
|
+
'a two-month range Calendar with January 20–22 selected; the first grid is the subject',
|
|
103
|
+
storyId: story('range-two-months'),
|
|
104
|
+
gridName: 'January 2026',
|
|
105
|
+
mode: 'range',
|
|
106
|
+
value: {start: '2026-01-20', end: '2026-01-22'},
|
|
107
|
+
numberOfMonths: 2,
|
|
108
|
+
// Enter on a range Calendar starts a new range rather than selecting the
|
|
109
|
+
// day outright, so the activation row does not apply here.
|
|
110
|
+
facts: facts({
|
|
111
|
+
multiselectable: true,
|
|
112
|
+
selectedCount: 3,
|
|
113
|
+
enterSelectsStart: false,
|
|
114
|
+
}),
|
|
115
|
+
},
|
|
116
|
+
] as const satisfies ReadonlyArray<CalendarBindingState>;
|
|
117
|
+
|
|
118
|
+
export const CALENDAR_EXCLUSIONS = [
|
|
119
|
+
{
|
|
120
|
+
id: 'month-edge-crossing',
|
|
121
|
+
owner: 'component:Calendar FR5 and Calendar.test.tsx (cross-month suites)',
|
|
122
|
+
reason:
|
|
123
|
+
'An arrow at the edge of a month crosses into the neighbouring month and focuses the real day there; the APG grid pattern stops at the edge, and the record defines the crossing. Every binding state therefore starts mid-month, and the crossing stays with the component tests.',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 'disabled-day-arrows',
|
|
127
|
+
owner:
|
|
128
|
+
'Calendar.test.tsx (bounds and dateConstraints suites) and component:Calendar AR1',
|
|
129
|
+
reason:
|
|
130
|
+
"An arrow that meets a day disabled by `min`/`max` or a `dateConstraints` rule skips it and continues in the same direction, crossing the month edge when it must — a behaviour the Calendar record defines (AR1) beyond the APG grid's one-cell step, so this contract's arrow row keeps its states free of disabled days. Calendar.test.tsx proves the skip ('ArrowUp skips a disabled cell in the same column to the next enabled row (complex-2)', the weekdays-only and max-bound arrow cases) and owns it.",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: 'page-keys-and-pointer',
|
|
134
|
+
owner:
|
|
135
|
+
'Calendar.test.tsx: the Page-key cases ("moves the focused day by a month with PageDown and PageUp, into the month now shown", "a page into a month with no enabled day changes nothing and keeps focus", "a page onto a day disabled by a constraint moves the month and focuses its first enabled day", "a page whose target lies past max changes nothing and keeps focus", "in a two-month view PageDown from the second month moves both months and a later Next click selects no stale day") and the pointer selection suites',
|
|
136
|
+
reason:
|
|
137
|
+
'PageUp/PageDown move by a month (author-determined in the APG, defined by component:Calendar AR1) and pointer selection are outside this keyboard-and-structure contract; the component tests own them.',
|
|
138
|
+
},
|
|
139
|
+
] as const;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
/** @vitest-environment jsdom */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @file Calendar.a11y.test.tsx
|
|
6
|
+
* @input GRID_PATTERN bound to Calendar's representative month-grid states
|
|
7
|
+
* @output jsdom-lane proof of the DOM expectations (structure, single Tab stop, selection) and an honest "unrun" report for the browser layers
|
|
8
|
+
* @position Binding test for component:Calendar AR1 (the Chromium spec covers the rest)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {cleanup, render, screen} from '@testing-library/react';
|
|
12
|
+
import {describe, expect, it} from 'vitest';
|
|
13
|
+
import {
|
|
14
|
+
GRID_PATTERN,
|
|
15
|
+
checkAccessibilitySpec,
|
|
16
|
+
createJsdomHarness,
|
|
17
|
+
expectAccessibilitySpec,
|
|
18
|
+
summarize,
|
|
19
|
+
type BindingResult,
|
|
20
|
+
} from '@astryxdesign/a11y-spec';
|
|
21
|
+
import {renderCalendarState} from './Calendar.a11y.renders';
|
|
22
|
+
import {
|
|
23
|
+
CALENDAR_BINDING_STATES,
|
|
24
|
+
CALENDAR_EXCLUSIONS,
|
|
25
|
+
NEIGHBOURHOOD,
|
|
26
|
+
START_ISO,
|
|
27
|
+
type CalendarBindingState,
|
|
28
|
+
} from './Calendar.a11y.states';
|
|
29
|
+
import {CALENDAR_KNOWN_FAILURES} from './Calendar.a11y.known-failures';
|
|
30
|
+
|
|
31
|
+
function grid(state: CalendarBindingState): HTMLElement {
|
|
32
|
+
return screen.getByRole('grid', {name: state.gridName});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function dayButton(iso: string): HTMLElement {
|
|
36
|
+
const button = document.querySelector<HTMLElement>(
|
|
37
|
+
`button[data-date="${iso}"]`,
|
|
38
|
+
);
|
|
39
|
+
if (button == null) {
|
|
40
|
+
throw new Error(`no day button for ${iso}`);
|
|
41
|
+
}
|
|
42
|
+
return button;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function related(): Record<string, HTMLElement> {
|
|
46
|
+
const start = dayButton(START_ISO);
|
|
47
|
+
const startCell = start.closest<HTMLElement>('[role="gridcell"]');
|
|
48
|
+
if (startCell == null) {
|
|
49
|
+
throw new Error('the start day is not inside a gridcell');
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
start,
|
|
53
|
+
startCell,
|
|
54
|
+
...Object.fromEntries(
|
|
55
|
+
Object.entries(NEIGHBOURHOOD).map(([name, iso]) => [
|
|
56
|
+
name,
|
|
57
|
+
dayButton(iso),
|
|
58
|
+
]),
|
|
59
|
+
),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function checkState(state: CalendarBindingState): Promise<BindingResult> {
|
|
64
|
+
return checkAccessibilitySpec({
|
|
65
|
+
spec: GRID_PATTERN,
|
|
66
|
+
binding: 'Calendar',
|
|
67
|
+
state: state.id,
|
|
68
|
+
facts: state.facts,
|
|
69
|
+
knownFailures: CALENDAR_KNOWN_FAILURES,
|
|
70
|
+
mount: async () => {
|
|
71
|
+
render(renderCalendarState(state));
|
|
72
|
+
return createJsdomHarness({subject: grid(state), related: related()});
|
|
73
|
+
},
|
|
74
|
+
unmount: cleanup,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
describe('Calendar — grid pattern, jsdom lane', () => {
|
|
79
|
+
it.each(
|
|
80
|
+
CALENDAR_BINDING_STATES.map(
|
|
81
|
+
state => [state.id, state.summary, state] as const,
|
|
82
|
+
),
|
|
83
|
+
)('%s — %s', async (_id, _summary, state) => {
|
|
84
|
+
await expectAccessibilitySpec({
|
|
85
|
+
spec: GRID_PATTERN,
|
|
86
|
+
binding: 'Calendar',
|
|
87
|
+
state: state.id,
|
|
88
|
+
facts: state.facts,
|
|
89
|
+
knownFailures: CALENDAR_KNOWN_FAILURES,
|
|
90
|
+
render: () => {
|
|
91
|
+
render(renderCalendarState(state));
|
|
92
|
+
},
|
|
93
|
+
subject: () => grid(state),
|
|
94
|
+
related: () => related(),
|
|
95
|
+
cleanup,
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('runs the DOM layer and reports the browser-owned outcomes as unrun', async () => {
|
|
100
|
+
const results: BindingResult[] = [];
|
|
101
|
+
for (const state of CALENDAR_BINDING_STATES) {
|
|
102
|
+
results.push(await checkState(state));
|
|
103
|
+
}
|
|
104
|
+
const report = summarize(GRID_PATTERN, results);
|
|
105
|
+
expect(report.counts.pass).toBeGreaterThan(0);
|
|
106
|
+
expect(report.unrunLayers).toEqual(['accessibility-tree', 'real-browser']);
|
|
107
|
+
expect(report.counts.unexpectedPass).toBe(0);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('Calendar binding inventory', () => {
|
|
112
|
+
it('gives every representative state a distinct checked-in story', () => {
|
|
113
|
+
const stories = CALENDAR_BINDING_STATES.map(state => state.storyId);
|
|
114
|
+
expect(new Set(stories).size).toBe(stories.length);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('keeps edge crossing, Page keys and pointer selection explicitly owned elsewhere', () => {
|
|
118
|
+
expect(CALENDAR_EXCLUSIONS.map(exclusion => exclusion.id)).toEqual([
|
|
119
|
+
'month-edge-crossing',
|
|
120
|
+
'disabled-day-arrows',
|
|
121
|
+
'page-keys-and-pointer',
|
|
122
|
+
]);
|
|
123
|
+
for (const exclusion of CALENDAR_EXCLUSIONS) {
|
|
124
|
+
expect(exclusion.owner.length, exclusion.id).toBeGreaterThan(20);
|
|
125
|
+
expect(exclusion.reason.length, exclusion.id).toBeGreaterThan(80);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('renders the named grid and the whole start neighbourhood for every state', () => {
|
|
130
|
+
for (const state of CALENDAR_BINDING_STATES) {
|
|
131
|
+
render(renderCalendarState(state));
|
|
132
|
+
expect(grid(state), state.id).toBeTruthy();
|
|
133
|
+
expect(Object.keys(related()), state.id).toHaveLength(8);
|
|
134
|
+
cleanup();
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -19,38 +19,75 @@ class MockResizeObserver {
|
|
|
19
19
|
vi.stubGlobal('ResizeObserver', MockResizeObserver);
|
|
20
20
|
|
|
21
21
|
describe('Carousel', () => {
|
|
22
|
-
|
|
22
|
+
// Retained local tests (AST-021 FR6): the carousel contract keeps the
|
|
23
|
+
// aria-roledescription outcomes ADVISORY until a current Carousel record
|
|
24
|
+
// adopts the APG parts, so these component assertions remain the gate for
|
|
25
|
+
// the slide and region role descriptions. Removal blocker: component:Carousel
|
|
26
|
+
// adopting APG Carousel's aria-roledescription as current.
|
|
27
|
+
it('has carousel ARIA attributes', () => {
|
|
23
28
|
render(
|
|
24
|
-
<Carousel aria-label="
|
|
25
|
-
<div
|
|
26
|
-
<div data-testid="item-2">Item 2</div>
|
|
29
|
+
<Carousel aria-label="Photos">
|
|
30
|
+
<div>Item</div>
|
|
27
31
|
</Carousel>,
|
|
28
32
|
);
|
|
29
|
-
|
|
30
|
-
expect(
|
|
33
|
+
const region = screen.getByRole('region', {name: 'Photos'});
|
|
34
|
+
expect(region).toHaveAttribute('aria-roledescription', 'carousel');
|
|
31
35
|
});
|
|
32
36
|
|
|
33
|
-
it('
|
|
37
|
+
it('exposes each slide as a group with aria-roledescription="slide" and a positional name', () => {
|
|
34
38
|
render(
|
|
35
39
|
<Carousel aria-label="Photos">
|
|
36
|
-
<div>
|
|
40
|
+
<div>One</div>
|
|
41
|
+
<div>Two</div>
|
|
42
|
+
<div>Three</div>
|
|
37
43
|
</Carousel>,
|
|
38
44
|
);
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
// APG carousel pattern: each slide container is role=group with
|
|
46
|
+
// aria-roledescription="slide" and an "N of M" accessible name.
|
|
47
|
+
const slides = screen.getAllByRole('group');
|
|
48
|
+
expect(slides).toHaveLength(3);
|
|
49
|
+
slides.forEach((slide, i) => {
|
|
50
|
+
expect(slide).toHaveAttribute('aria-roledescription', 'slide');
|
|
51
|
+
expect(slide).toHaveAccessibleName(`Slide ${i + 1} of 3`);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('renders children', () => {
|
|
56
|
+
render(
|
|
57
|
+
<Carousel aria-label="Test carousel">
|
|
58
|
+
<div data-testid="item-1">Item 1</div>
|
|
59
|
+
<div data-testid="item-2">Item 2</div>
|
|
60
|
+
</Carousel>,
|
|
61
|
+
);
|
|
62
|
+
expect(screen.getByTestId('item-1')).toBeInTheDocument();
|
|
63
|
+
expect(screen.getByTestId('item-2')).toBeInTheDocument();
|
|
41
64
|
});
|
|
42
65
|
|
|
43
|
-
it('
|
|
66
|
+
it('reflects the rendered child count, skipping null and boolean children', () => {
|
|
44
67
|
render(
|
|
45
68
|
<Carousel aria-label="Photos">
|
|
46
|
-
<div>
|
|
69
|
+
<div>One</div>
|
|
70
|
+
{null}
|
|
71
|
+
{false}
|
|
72
|
+
<div>Two</div>
|
|
47
73
|
</Carousel>,
|
|
48
74
|
);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
75
|
+
expect(
|
|
76
|
+
screen.getByRole('group', {name: 'Slide 1 of 2'}),
|
|
77
|
+
).toBeInTheDocument();
|
|
78
|
+
expect(
|
|
79
|
+
screen.getByRole('group', {name: 'Slide 2 of 2'}),
|
|
80
|
+
).toBeInTheDocument();
|
|
81
|
+
// Exactly two slides render: null and false children produce no empty
|
|
82
|
+
// slide (child filtering stays component-owned). The scroller is a named
|
|
83
|
+
// group too, so count the slides by their role description.
|
|
84
|
+
expect(
|
|
85
|
+
screen
|
|
86
|
+
.getAllByRole('group')
|
|
87
|
+
.filter(
|
|
88
|
+
group => group.getAttribute('aria-roledescription') === 'slide',
|
|
89
|
+
),
|
|
90
|
+
).toHaveLength(2);
|
|
54
91
|
});
|
|
55
92
|
|
|
56
93
|
it('applies data-testid', () => {
|
|
@@ -94,59 +131,6 @@ describe('Carousel', () => {
|
|
|
94
131
|
expect(screen.getByLabelText('Scroll right')).toBeInTheDocument();
|
|
95
132
|
});
|
|
96
133
|
|
|
97
|
-
describe('slide semantics', () => {
|
|
98
|
-
it('exposes each slide as a group with aria-roledescription="slide" and a positional name', () => {
|
|
99
|
-
render(
|
|
100
|
-
<Carousel aria-label="Photos">
|
|
101
|
-
<div>One</div>
|
|
102
|
-
<div>Two</div>
|
|
103
|
-
<div>Three</div>
|
|
104
|
-
</Carousel>,
|
|
105
|
-
);
|
|
106
|
-
// APG carousel pattern: each slide container is role=group with
|
|
107
|
-
// aria-roledescription="slide" and an "N of M" accessible name.
|
|
108
|
-
const slides = screen.getAllByRole('group');
|
|
109
|
-
expect(slides).toHaveLength(3);
|
|
110
|
-
slides.forEach((slide, i) => {
|
|
111
|
-
expect(slide).toHaveAttribute('aria-roledescription', 'slide');
|
|
112
|
-
expect(slide).toHaveAccessibleName(`Slide ${i + 1} of 3`);
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('reflects the rendered child count, skipping null and boolean children', () => {
|
|
117
|
-
render(
|
|
118
|
-
<Carousel aria-label="Photos">
|
|
119
|
-
<div>One</div>
|
|
120
|
-
{null}
|
|
121
|
-
{false}
|
|
122
|
-
<div>Two</div>
|
|
123
|
-
</Carousel>,
|
|
124
|
-
);
|
|
125
|
-
const slides = screen.getAllByRole('group');
|
|
126
|
-
expect(slides).toHaveLength(2);
|
|
127
|
-
expect(
|
|
128
|
-
screen.getByRole('group', {name: 'Slide 1 of 2'}),
|
|
129
|
-
).toBeInTheDocument();
|
|
130
|
-
expect(
|
|
131
|
-
screen.getByRole('group', {name: 'Slide 2 of 2'}),
|
|
132
|
-
).toBeInTheDocument();
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('keeps the container region semantics unchanged around labelled slides', () => {
|
|
136
|
-
render(
|
|
137
|
-
<Carousel aria-label="Gallery">
|
|
138
|
-
<div>One</div>
|
|
139
|
-
<div>Two</div>
|
|
140
|
-
</Carousel>,
|
|
141
|
-
);
|
|
142
|
-
const region = screen.getByRole('region', {name: 'Gallery'});
|
|
143
|
-
expect(region).toHaveAttribute('aria-roledescription', 'carousel');
|
|
144
|
-
const slides = screen.getAllByRole('group');
|
|
145
|
-
expect(slides).toHaveLength(2);
|
|
146
|
-
slides.forEach(slide => expect(region).toContainElement(slide));
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
|
|
150
134
|
it('disables edge scroll buttons instead of removing them from the tab order', () => {
|
|
151
135
|
// In jsdom there is no measurable overflow, so both edges are at rest and
|
|
152
136
|
// the scroll buttons are in their hidden/inert state. They must stay
|
|
@@ -932,5 +916,4 @@ describe('Carousel', () => {
|
|
|
932
916
|
);
|
|
933
917
|
});
|
|
934
918
|
});
|
|
935
|
-
|
|
936
919
|
});
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Carousel.a11y.chromium.spec.ts
|
|
5
|
+
* @input Uses the shared Carousel contract, Chromium harness, checked-in stories, and binding inventory
|
|
6
|
+
* @output Real-browser and accessibility-tree evidence for Carousel semantics and focus
|
|
7
|
+
* @position Browser lane required by AST-013; it makes no real-AT claim.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
expect,
|
|
12
|
+
test,
|
|
13
|
+
type CDPSession,
|
|
14
|
+
type Locator,
|
|
15
|
+
type Page,
|
|
16
|
+
} from '@playwright/test';
|
|
17
|
+
import {
|
|
18
|
+
CAROUSEL_PATTERN,
|
|
19
|
+
blockingResults,
|
|
20
|
+
checkAccessibilitySpec,
|
|
21
|
+
formatFailures,
|
|
22
|
+
neverExercised,
|
|
23
|
+
summarize,
|
|
24
|
+
unmatchedKnownFailures,
|
|
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 {CAROUSEL_KNOWN_FAILURES} from './Carousel.a11y.known-failures';
|
|
37
|
+
import {
|
|
38
|
+
CAROUSEL_BINDING_STATES,
|
|
39
|
+
type CarouselBindingRow,
|
|
40
|
+
} from './Carousel.a11y.states';
|
|
41
|
+
|
|
42
|
+
let storybook: StaticServer;
|
|
43
|
+
|
|
44
|
+
test.beforeAll(async () => {
|
|
45
|
+
storybook = await serveStorybook(
|
|
46
|
+
process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test.afterAll(async () => {
|
|
51
|
+
await storybook?.close();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
function storyUrl(storyId: string): string {
|
|
55
|
+
return `${storybook.origin}/iframe.html?id=${storyId}&viewMode=story`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function subjectFor(page: Page, state: CarouselBindingRow): Locator {
|
|
59
|
+
const root = page.locator('#storybook-root');
|
|
60
|
+
if ('selector' in state.subject) {
|
|
61
|
+
return root.locator(state.subject.selector);
|
|
62
|
+
}
|
|
63
|
+
return root.getByRole(state.subject.role, {
|
|
64
|
+
name: state.subject.name,
|
|
65
|
+
includeHidden: true,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function relatedFor(
|
|
70
|
+
page: Page,
|
|
71
|
+
state: CarouselBindingRow,
|
|
72
|
+
): Readonly<Record<string, Locator>> {
|
|
73
|
+
const root = page.locator('#storybook-root');
|
|
74
|
+
return Object.fromEntries(
|
|
75
|
+
Object.entries(state.relations ?? {}).map(([name, relation]) => [
|
|
76
|
+
name,
|
|
77
|
+
root.locator(relation.selector),
|
|
78
|
+
]),
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function mountState(
|
|
83
|
+
page: Page,
|
|
84
|
+
state: CarouselBindingRow,
|
|
85
|
+
): Promise<void> {
|
|
86
|
+
await page.goto(storyUrl(state.storyId), {waitUntil: 'load'});
|
|
87
|
+
await holdMotionStill(page);
|
|
88
|
+
await subjectFor(page, state).waitFor({state: 'attached'});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function runState(
|
|
92
|
+
page: Page,
|
|
93
|
+
cdp: CDPSession,
|
|
94
|
+
state: CarouselBindingRow,
|
|
95
|
+
): Promise<BindingResult> {
|
|
96
|
+
return checkAccessibilitySpec({
|
|
97
|
+
spec: CAROUSEL_PATTERN,
|
|
98
|
+
binding: 'Carousel',
|
|
99
|
+
state: state.id,
|
|
100
|
+
facts: state.facts,
|
|
101
|
+
knownFailures: CAROUSEL_KNOWN_FAILURES,
|
|
102
|
+
mount: async () => {
|
|
103
|
+
await mountState(page, state);
|
|
104
|
+
return createChromiumHarness({
|
|
105
|
+
page,
|
|
106
|
+
subject: subjectFor(page, state),
|
|
107
|
+
cdp,
|
|
108
|
+
related: relatedFor(page, state),
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function bindingState(id: string): CarouselBindingRow {
|
|
115
|
+
const state = CAROUSEL_BINDING_STATES.find(candidate => candidate.id === id);
|
|
116
|
+
if (state == null) {
|
|
117
|
+
throw new Error(`no Carousel binding state "${id}"`);
|
|
118
|
+
}
|
|
119
|
+
return state;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
test('every expectation is exercised by at least one Carousel state', async ({
|
|
123
|
+
page,
|
|
124
|
+
}) => {
|
|
125
|
+
const cdp = await page.context().newCDPSession(page);
|
|
126
|
+
const results: BindingResult[] = [];
|
|
127
|
+
for (const state of CAROUSEL_BINDING_STATES) {
|
|
128
|
+
results.push(await runState(page, cdp, state));
|
|
129
|
+
}
|
|
130
|
+
expect(neverExercised(results)).toEqual([]);
|
|
131
|
+
expect(unmatchedKnownFailures(CAROUSEL_KNOWN_FAILURES, results)).toEqual([]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
for (const state of CAROUSEL_BINDING_STATES) {
|
|
135
|
+
test(`Carousel [${state.id}] — ${state.summary}`, async ({page}) => {
|
|
136
|
+
const cdp = await page.context().newCDPSession(page);
|
|
137
|
+
const result = await runState(page, cdp, state);
|
|
138
|
+
const report = summarize(CAROUSEL_PATTERN, [result]);
|
|
139
|
+
// Required rows block (AST-020 FR9); advisory rows and exact known
|
|
140
|
+
// failures report.
|
|
141
|
+
expect(formatFailures(blockingResults([result]))).toBe('');
|
|
142
|
+
expect(report.unrunLayers).toEqual([]);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
test('the off-screen binding is outside the scroll viewport but remains exposed', async ({
|
|
147
|
+
page,
|
|
148
|
+
}) => {
|
|
149
|
+
const state = bindingState('slide-offscreen');
|
|
150
|
+
await mountState(page, state);
|
|
151
|
+
const slide = subjectFor(page, state);
|
|
152
|
+
const scroller = page.locator(
|
|
153
|
+
'[data-testid="carousel-a11y"] > .astryx-carousel-scroller',
|
|
154
|
+
);
|
|
155
|
+
const [slideBox, scrollerBox] = await Promise.all([
|
|
156
|
+
slide.boundingBox(),
|
|
157
|
+
scroller.boundingBox(),
|
|
158
|
+
]);
|
|
159
|
+
if (slideBox == null || scrollerBox == null) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
'the off-screen geometry fixture did not render measurable boxes',
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
expect(slideBox.x).toBeGreaterThanOrEqual(scrollerBox.x + scrollerBox.width);
|
|
165
|
+
const cdp = await page.context().newCDPSession(page);
|
|
166
|
+
const result = await runState(page, cdp, state);
|
|
167
|
+
expect(
|
|
168
|
+
result.results.find(
|
|
169
|
+
row => row.expectation === 'carousel.slide.offscreen-exposed',
|
|
170
|
+
)?.status,
|
|
171
|
+
).toBe('pass');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('the scroll container pans from the keyboard and honors reduced motion', async ({
|
|
175
|
+
page,
|
|
176
|
+
}) => {
|
|
177
|
+
const state = bindingState('scroller-keyboard');
|
|
178
|
+
await page.emulateMedia({reducedMotion: 'reduce'});
|
|
179
|
+
await page.goto(storyUrl(state.storyId), {waitUntil: 'load'});
|
|
180
|
+
const scroller = subjectFor(page, state);
|
|
181
|
+
await scroller.focus();
|
|
182
|
+
expect(
|
|
183
|
+
await scroller.evaluate(
|
|
184
|
+
element => getComputedStyle(element).scrollBehavior,
|
|
185
|
+
),
|
|
186
|
+
).toBe('auto');
|
|
187
|
+
const before = await scroller.evaluate(element => element.scrollLeft);
|
|
188
|
+
await page.keyboard.press('ArrowRight');
|
|
189
|
+
await expect
|
|
190
|
+
.poll(async () => scroller.evaluate(element => element.scrollLeft))
|
|
191
|
+
.toBeGreaterThan(before);
|
|
192
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Carousel.a11y.known-failures.ts
|
|
5
|
+
* @input The exact carousel-contract failures the shipped Carousel produces today
|
|
6
|
+
* @output CAROUSEL_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 Carousel binding
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {KnownFailure} from '@astryxdesign/a11y-spec';
|
|
11
|
+
|
|
12
|
+
export const CAROUSEL_KNOWN_FAILURES: ReadonlyArray<KnownFailure> = [
|
|
13
|
+
{
|
|
14
|
+
expectation: 'carousel.scroller.role-and-name',
|
|
15
|
+
binding: 'Carousel',
|
|
16
|
+
state: 'scroller-keyboard',
|
|
17
|
+
evidenceLayer: 'accessibility-tree',
|
|
18
|
+
failureEquals:
|
|
19
|
+
'the browser exposes the focusable scroll container with no role',
|
|
20
|
+
standardsReference:
|
|
21
|
+
'WCAG 2.2 4.1.2 Name, Role, Value; Astryx spec:AST-025 FR12 — a viewport that takes keyboard scrolling must provide an appropriate role and accessible name',
|
|
22
|
+
userImpact:
|
|
23
|
+
'A keyboard user who tabs onto the scroll container hears nothing about where they are or what the arrow keys will do; the named region around it is the only clue.',
|
|
24
|
+
reason:
|
|
25
|
+
'This test-only binding records the gap; naming the scroll container is a Carousel markup change that ships as its own spec-backed fix.',
|
|
26
|
+
},
|
|
27
|
+
];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Carousel.a11y.renders.tsx
|
|
5
|
+
* @input Uses Carousel and the binding state ids
|
|
6
|
+
* @output Stable component renders shared by jsdom and Storybook
|
|
7
|
+
* @position Checked-in reproduction seam for the Carousel accessibility contract.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {ReactElement} from 'react';
|
|
11
|
+
import {Carousel} from '../Carousel';
|
|
12
|
+
import type {CarouselBindingRow} from './Carousel.a11y.states';
|
|
13
|
+
|
|
14
|
+
function CarouselFixture() {
|
|
15
|
+
return (
|
|
16
|
+
<Carousel
|
|
17
|
+
data-testid="carousel-a11y"
|
|
18
|
+
hasLoop
|
|
19
|
+
hasSnap
|
|
20
|
+
aria-label="Featured work"
|
|
21
|
+
style={{width: 240}}>
|
|
22
|
+
{['One', 'Two', 'Three', 'Four'].map(label => (
|
|
23
|
+
<div key={label} style={{width: 180, minWidth: 180, height: 80}}>
|
|
24
|
+
{label}
|
|
25
|
+
</div>
|
|
26
|
+
))}
|
|
27
|
+
</Carousel>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const CAROUSEL_STATE_RENDERS: Readonly<
|
|
32
|
+
Record<CarouselBindingRow['id'], () => ReactElement>
|
|
33
|
+
> = {
|
|
34
|
+
'container-labelled': () => <CarouselFixture />,
|
|
35
|
+
'slide-visible': () => <CarouselFixture />,
|
|
36
|
+
'slide-offscreen': () => <CarouselFixture />,
|
|
37
|
+
'scroller-keyboard': () => <CarouselFixture />,
|
|
38
|
+
'next-control-focus': () => <CarouselFixture />,
|
|
39
|
+
};
|