@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
|
@@ -729,6 +729,18 @@ export function Tokenizer<T extends SearchableItem>({
|
|
|
729
729
|
[value],
|
|
730
730
|
);
|
|
731
731
|
|
|
732
|
+
// Selected labels, lowercased, for the Create entry's duplicate check. The
|
|
733
|
+
// typed text is a label, not an id: real selections usually carry an opaque
|
|
734
|
+
// id (`usr_123` under the label "Alice"), so comparing the text against ids
|
|
735
|
+
// alone misses an already-tokenized label — and the search results cannot
|
|
736
|
+
// catch it either, because filteredSource has already removed the selected
|
|
737
|
+
// item from them. The result was a "Create Alice" entry that, when picked,
|
|
738
|
+
// added a second "Alice" token under a different id.
|
|
739
|
+
const selectedLabels = useMemo(
|
|
740
|
+
() => new Set(value.map(item => item.label.toLowerCase())),
|
|
741
|
+
[value],
|
|
742
|
+
);
|
|
743
|
+
|
|
732
744
|
const filteredSource: SearchSource<T> = useMemo(
|
|
733
745
|
() => ({
|
|
734
746
|
search: async (query: string) => {
|
|
@@ -759,6 +771,7 @@ export function Tokenizer<T extends SearchableItem>({
|
|
|
759
771
|
}
|
|
760
772
|
const alreadyExists =
|
|
761
773
|
selectedIds.has(trimmed) ||
|
|
774
|
+
selectedLabels.has(trimmed.toLowerCase()) ||
|
|
762
775
|
results.some(
|
|
763
776
|
item => item.label.toLowerCase() === trimmed.toLowerCase(),
|
|
764
777
|
);
|
|
@@ -773,7 +786,7 @@ export function Tokenizer<T extends SearchableItem>({
|
|
|
773
786
|
} as unknown as T,
|
|
774
787
|
];
|
|
775
788
|
},
|
|
776
|
-
[hasCreate, selectedIds],
|
|
789
|
+
[hasCreate, selectedIds, selectedLabels],
|
|
777
790
|
);
|
|
778
791
|
|
|
779
792
|
const emptySource: SearchSource<T> = useMemo(
|
|
@@ -807,7 +820,13 @@ export function Tokenizer<T extends SearchableItem>({
|
|
|
807
820
|
item.id.startsWith(CREATABLE_ID_PREFIX)
|
|
808
821
|
) {
|
|
809
822
|
const createdValue = item.id.slice(CREATABLE_ID_PREFIX.length);
|
|
810
|
-
|
|
823
|
+
// Same domain fix as createEntries: the created value names itself by
|
|
824
|
+
// label, so a selection carrying an opaque id with that label is also a
|
|
825
|
+
// duplicate, not just one whose id happens to equal the text.
|
|
826
|
+
if (
|
|
827
|
+
selectedIds.has(createdValue) ||
|
|
828
|
+
selectedLabels.has(createdValue.toLowerCase())
|
|
829
|
+
) {
|
|
811
830
|
return;
|
|
812
831
|
}
|
|
813
832
|
const base = {id: createdValue, label: createdValue};
|
|
@@ -825,7 +844,16 @@ export function Tokenizer<T extends SearchableItem>({
|
|
|
825
844
|
onChange(newItems, {item, type: 'add'});
|
|
826
845
|
announce(t('@astryx.tokenizer.tokenAdded', {label: item.label}));
|
|
827
846
|
},
|
|
828
|
-
[
|
|
847
|
+
[
|
|
848
|
+
value,
|
|
849
|
+
onChange,
|
|
850
|
+
isAtMax,
|
|
851
|
+
selectedIds,
|
|
852
|
+
selectedLabels,
|
|
853
|
+
hasCreate,
|
|
854
|
+
announce,
|
|
855
|
+
t,
|
|
856
|
+
],
|
|
829
857
|
);
|
|
830
858
|
|
|
831
859
|
// Handle removing an item. Single removal path: both Backspace on an empty
|
|
@@ -2,18 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file Toolbar.test.tsx
|
|
5
|
-
* @input Uses vitest, @testing-library/react, Toolbar
|
|
6
|
-
* @output Unit tests for Toolbar component
|
|
7
|
-
* @position Testing; validates Toolbar implementation
|
|
5
|
+
* @input Uses vitest, @testing-library/react, user-event, Toolbar, MoreMenu
|
|
6
|
+
* @output Unit tests for Toolbar component and nested popover focus ownership
|
|
7
|
+
* @position Testing; validates Toolbar implementation and post-dismissal tab stops
|
|
8
8
|
*
|
|
9
9
|
* SYNC: When Toolbar component changes, update tests to match new behavior
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import {describe, it, expect, vi} from 'vitest';
|
|
13
|
-
import {render, screen} from '@testing-library/react';
|
|
13
|
+
import {act, fireEvent, render, screen, waitFor} from '@testing-library/react';
|
|
14
14
|
import userEvent from '@testing-library/user-event';
|
|
15
15
|
import * as stylex from '@stylexjs/stylex';
|
|
16
16
|
import {Toolbar} from './Toolbar';
|
|
17
|
+
import {MoreMenu} from '../MoreMenu';
|
|
17
18
|
import {
|
|
18
19
|
paddingBlockStyles,
|
|
19
20
|
paddingInlineStyles,
|
|
@@ -377,6 +378,132 @@ describe('Toolbar', () => {
|
|
|
377
378
|
expect(buttons[2]).toHaveAttribute('tabindex', '-1');
|
|
378
379
|
});
|
|
379
380
|
|
|
381
|
+
it('keeps its tab stop after outside dismissal of MoreMenu', async () => {
|
|
382
|
+
const user = userEvent.setup();
|
|
383
|
+
render(
|
|
384
|
+
<>
|
|
385
|
+
<button type="button">Before</button>
|
|
386
|
+
<Toolbar
|
|
387
|
+
label="Actions"
|
|
388
|
+
startContent={<button type="button">Copy</button>}
|
|
389
|
+
endContent={
|
|
390
|
+
<MoreMenu
|
|
391
|
+
label="More actions"
|
|
392
|
+
items={[{label: 'Duplicate'}, {label: 'Delete'}]}
|
|
393
|
+
/>
|
|
394
|
+
}
|
|
395
|
+
/>
|
|
396
|
+
<button type="button">After</button>
|
|
397
|
+
</>,
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
const trigger = screen.getByRole('button', {name: 'More actions'});
|
|
401
|
+
// Seed the trigger's tab stop with keyboard navigation so this tests
|
|
402
|
+
// boundary ownership independently of pointer-focus synchronization.
|
|
403
|
+
act(() => screen.getByRole('button', {name: 'Copy'}).focus());
|
|
404
|
+
await user.keyboard('{ArrowRight}');
|
|
405
|
+
expect(trigger).toHaveFocus();
|
|
406
|
+
await user.click(trigger);
|
|
407
|
+
await waitFor(() =>
|
|
408
|
+
expect(screen.getByRole('menu', {hidden: true})).toHaveFocus(),
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
const item = screen.getByRole('menuitem', {
|
|
412
|
+
name: 'Duplicate',
|
|
413
|
+
hidden: true,
|
|
414
|
+
});
|
|
415
|
+
act(() => item.focus());
|
|
416
|
+
expect(item).toHaveFocus();
|
|
417
|
+
expect(trigger.getAttribute('tabindex')).toBe('0');
|
|
418
|
+
expect(
|
|
419
|
+
screen.getByRole('button', {name: 'Copy'}).getAttribute('tabindex'),
|
|
420
|
+
).toBe('-1');
|
|
421
|
+
expect(
|
|
422
|
+
screen.getByRole('menu', {hidden: true}).getAttribute('tabindex'),
|
|
423
|
+
).not.toBe('0');
|
|
424
|
+
|
|
425
|
+
await user.click(screen.getByRole('button', {name: 'After'}));
|
|
426
|
+
// jsdom has no native light dismiss. Deliver the browser's close event
|
|
427
|
+
// after focus has moved outside, without restoring it to the trigger.
|
|
428
|
+
const popover = item.closest('[popover]');
|
|
429
|
+
fireEvent(
|
|
430
|
+
popover!,
|
|
431
|
+
Object.assign(new Event('toggle'), {newState: 'closed'}),
|
|
432
|
+
);
|
|
433
|
+
await waitFor(() =>
|
|
434
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false'),
|
|
435
|
+
);
|
|
436
|
+
expect(screen.getByRole('button', {name: 'After'})).toHaveFocus();
|
|
437
|
+
await user.tab({shift: true});
|
|
438
|
+
expect(trigger).toHaveFocus();
|
|
439
|
+
await user.tab({shift: true});
|
|
440
|
+
expect(screen.getByRole('button', {name: 'Before'})).toHaveFocus();
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it('leaves navigation inside an open MoreMenu to the menu', async () => {
|
|
444
|
+
const user = userEvent.setup();
|
|
445
|
+
render(
|
|
446
|
+
<Toolbar
|
|
447
|
+
label="Actions"
|
|
448
|
+
startContent={<button type="button">Copy</button>}
|
|
449
|
+
endContent={
|
|
450
|
+
<MoreMenu
|
|
451
|
+
label="More actions"
|
|
452
|
+
items={[{label: 'Duplicate'}, {label: 'Delete'}]}
|
|
453
|
+
/>
|
|
454
|
+
}
|
|
455
|
+
/>,
|
|
456
|
+
);
|
|
457
|
+
const trigger = screen.getByRole('button', {name: 'More actions'});
|
|
458
|
+
act(() => screen.getByRole('button', {name: 'Copy'}).focus());
|
|
459
|
+
await user.keyboard('{ArrowRight}');
|
|
460
|
+
expect(trigger).toHaveFocus();
|
|
461
|
+
await user.click(trigger);
|
|
462
|
+
await waitFor(() =>
|
|
463
|
+
expect(screen.getByRole('menu', {hidden: true})).toHaveFocus(),
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
const first = screen.getByRole('menuitem', {
|
|
467
|
+
name: 'Duplicate',
|
|
468
|
+
hidden: true,
|
|
469
|
+
});
|
|
470
|
+
const last = screen.getByRole('menuitem', {name: 'Delete', hidden: true});
|
|
471
|
+
act(() => first.focus());
|
|
472
|
+
await user.keyboard('{ArrowLeft}');
|
|
473
|
+
expect(first).toHaveFocus();
|
|
474
|
+
await user.keyboard('{ArrowRight}');
|
|
475
|
+
expect(first).toHaveFocus();
|
|
476
|
+
|
|
477
|
+
await user.keyboard('{ArrowDown}');
|
|
478
|
+
expect(last).toHaveFocus();
|
|
479
|
+
|
|
480
|
+
expect(trigger).toHaveAttribute('tabindex', '0');
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('keeps its own arrow navigation when rendered inside a popover', async () => {
|
|
484
|
+
const user = userEvent.setup();
|
|
485
|
+
render(
|
|
486
|
+
<div popover="manual">
|
|
487
|
+
<Toolbar
|
|
488
|
+
label="Actions"
|
|
489
|
+
startContent={
|
|
490
|
+
<>
|
|
491
|
+
<button type="button">Copy</button>
|
|
492
|
+
<button type="button">Paste</button>
|
|
493
|
+
</>
|
|
494
|
+
}
|
|
495
|
+
/>
|
|
496
|
+
</div>,
|
|
497
|
+
);
|
|
498
|
+
// jsdom does not open native popovers; the ancestor still tests ownership.
|
|
499
|
+
const copy = screen.getByRole('button', {name: 'Copy', hidden: true});
|
|
500
|
+
const paste = screen.getByRole('button', {name: 'Paste', hidden: true});
|
|
501
|
+
act(() => copy.focus());
|
|
502
|
+
await user.keyboard('{ArrowRight}');
|
|
503
|
+
expect(paste).toHaveFocus();
|
|
504
|
+
expect(paste.getAttribute('tabindex')).toBe('0');
|
|
505
|
+
});
|
|
506
|
+
|
|
380
507
|
it('does not steal caret keys from a text input mid-line (navigation-4)', async () => {
|
|
381
508
|
const user = userEvent.setup();
|
|
382
509
|
render(
|
package/src/Toolbar/Toolbar.tsx
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* @file Toolbar.tsx
|
|
7
7
|
* @input Uses Section, SizeContext, useListFocus, useKeyboardHint, StyleX, spacingVars, sizeVars
|
|
8
8
|
* @output Exports Toolbar component and ToolbarProps
|
|
9
|
-
* @position Core implementation; consumed by index.ts
|
|
9
|
+
* @position Core implementation; consumed by index.ts. Owns toolbar focus and
|
|
10
|
+
* keyboard navigation, excluding nested toolbars and popover contents.
|
|
10
11
|
*
|
|
11
12
|
* SYNC: When modified, update these files to stay in sync:
|
|
12
13
|
* - /packages/core/src/Toolbar/Toolbar.doc.mjs
|
|
@@ -248,6 +249,7 @@ export function Toolbar({
|
|
|
248
249
|
|
|
249
250
|
const {listRef, handleKeyDown, handleFocus} = useListFocus<HTMLDivElement>({
|
|
250
251
|
itemSelector: 'button, input, [tabindex]',
|
|
252
|
+
boundarySelector: '[role="toolbar"], [popover]',
|
|
251
253
|
orientation,
|
|
252
254
|
hasRovingTabIndex: true,
|
|
253
255
|
hasCaretGuard: true,
|
|
@@ -148,6 +148,21 @@ The styling seam does not change TreeList's tree, treeitem, group, roving-focus,
|
|
|
148
148
|
expansion, selection, disabled, or activation behavior. Styling inputs MUST NOT
|
|
149
149
|
replace component-owned semantic or interaction props.
|
|
150
150
|
|
|
151
|
+
- **AR1 — TreeList adopts the APG Tree View pattern** (`spec:AST-020/FR15`), as
|
|
152
|
+
one composite control: the Tree list is `role="tree"`, named by its Header
|
|
153
|
+
when one is rendered and otherwise by the caller's `aria-label` or
|
|
154
|
+
`aria-labelledby`; every row is `role="treeitem"`, named by its label and described by its
|
|
155
|
+
description, `aria-expanded` when it has children,
|
|
156
|
+
`aria-selected` when it is the selected row, `aria-level` for its depth, and
|
|
157
|
+
`aria-posinset`/`aria-setsize` among its siblings; children sit in a `role="group"`. One row holds the Tab stop
|
|
158
|
+
(roving focus). ArrowDown/ArrowUp move between visible rows; ArrowRight expands
|
|
159
|
+
a closed row, then moves into its first child, and ArrowLeft collapses an open
|
|
160
|
+
row or moves to the parent — mirrored under RTL; Home and End move to the first
|
|
161
|
+
and last visible rows; Enter and Space activate the focused row; typing letters
|
|
162
|
+
moves to the next row whose label starts with them. Selection is caller-owned:
|
|
163
|
+
callers mark at most one row selected (`isSelected`); the tree does not
|
|
164
|
+
enforce it, claims no `aria-multiselectable`, and has no selection keyboard.
|
|
165
|
+
|
|
151
166
|
## Design relationships
|
|
152
167
|
|
|
153
168
|
| Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
|
|
@@ -227,14 +242,15 @@ authorize or prohibit a future target.
|
|
|
227
242
|
|
|
228
243
|
## Verification map
|
|
229
244
|
|
|
230
|
-
| Contract | Verification
|
|
231
|
-
| ------------------- |
|
|
232
|
-
| FR1 | `TreeList.test.tsx` rendering, hierarchy, slot, and accessibility suites plus source inspection
|
|
233
|
-
| FR2 | `TreeList.test.tsx` and `themingTargets.test.ts`
|
|
234
|
-
| FR3 | Source inspection plus Icon public target metadata
|
|
235
|
-
| FR4 | `TreeList.test.tsx` description rendering test and source inspection
|
|
236
|
-
|
|
|
237
|
-
|
|
|
245
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
246
|
+
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ |
|
|
247
|
+
| FR1 | `TreeList.test.tsx` rendering, hierarchy, slot, and accessibility suites plus source inspection | Flat, nested, described, headed, and slotted items | Removing asserted Tree list, Header, Item, Chevron, Item label, Item description, Start content, End content, or Guide behavior fails existing role, content, hierarchy, or state assertions; Chevron glyph presence remains source-inspected only. | `audit:TreeList/anatomy` |
|
|
248
|
+
| FR2 | `TreeList.test.tsx` and `themingTargets.test.ts` | Five local targets and their documented states | Removing or moving any current local target fails focused class assertions or the global inventory. | `audit:TreeList/theming` |
|
|
249
|
+
| FR3 | Source inspection plus Icon public target metadata | Expandable rows and caller-provided slots | Existing focused tests do not assert the composed Icon instance; changing glyph or slot ownership requires this map and the relevant owner metadata to change. | `audit:TreeList/theming` |
|
|
250
|
+
| FR4 | `TreeList.test.tsx` description rendering test and source inspection | Item with description | Removing description fails content coverage; adding a target requires an explicit map update. | `audit:TreeList/anatomy` |
|
|
251
|
+
| AR1 | Tree View accessibility contract (`internal/a11y-spec`, pending) bound to TreeList, in jsdom and Chromium; `TreeList.test.tsx` row-naming cases ("names a parent row by its label alone, not the chevron toggle", "names a plain row by its label and describes it with its description", "names and describes link and action rows themselves, not only their inner control", "keeps start and end content, including controls, out of the row name", "typeahead matches the label, not the description or start content") and `TreeListRowNames.a11y.chromium.spec.ts` for the browser-computed names; `TreeList.test.tsx` structural ARIA cases ("sets aria-level, aria-posinset, and aria-setsize at the top level", "sets aria-level/posinset/setsize correctly at deeper levels", "sets aria-level across three depths"); `TreeList.test.tsx` and `useTreeFocus.test.tsx` keyboard suites | Flat and nested trees; expanded/collapsed; selected/unselected; LTR and RTL; typeahead | A row loses its role, label name, state or position, two rows hold a Tab stop, an arrow key stops moving or stops mirroring under RTL, Home/End or typeahead stop working, or the tree claims multi-select | `audit:TreeList/accessibility` |
|
|
252
|
+
| FR5, FR6, ORD1 | Focused `TreeList.test.tsx` row-identity and styling-composition coverage, required with implementation | One input at a time; all three together; sibling rows | Dropping any mechanism, applying mechanisms to different elements, targeting the `<li>`, changing the merge order, or styling a sibling row fails. | `audit:TreeList/api` |
|
|
253
|
+
| Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and five current local targets | Missing, extra, duplicated, prefixed, or stale mappings fail repository validation. | `audit:TreeList/theming` |
|
|
238
254
|
|
|
239
255
|
Focused target-placement assertions cover all five local targets. Runtime support
|
|
240
256
|
and mutation-sensitive coverage for the item styling seam may land separately,
|
|
@@ -261,6 +277,15 @@ Rejected: applying the seam to the outer semantic `<li>`. That element owns tree
|
|
|
261
277
|
semantics and child grouping, while the row box is the stable visual and hover
|
|
262
278
|
boundary callers need to address.
|
|
263
279
|
|
|
280
|
+
### DEC-2 — The tree is one composite control with single, caller-owned selection
|
|
281
|
+
|
|
282
|
+
**Reference:** `component:TreeList/DEC-2`
|
|
283
|
+
**Decider:** `cixzhang`, `2026-10-13`
|
|
284
|
+
|
|
285
|
+
TreeList already plays the Tree View's role as one widget — one Tab stop, arrows, Home/End, activation, typeahead — so it adopts the pattern's keyboard model except the optional `*` key (AR1), not only the required parts. Callers mark at most one row selected (`isSelected`) and the tree does not enforce it, so the tree never claims `aria-multiselectable` and adds no range or toggle keyboard.
|
|
286
|
+
|
|
287
|
+
Rejected: multi-select (`aria-multiselectable`, Shift/Ctrl ranges); a Tab stop per row.
|
|
288
|
+
|
|
264
289
|
## Open questions
|
|
265
290
|
|
|
266
291
|
None.
|
|
@@ -170,6 +170,78 @@ describe('TreeList', () => {
|
|
|
170
170
|
expect(screen.getByText('Description text')).toBeInTheDocument();
|
|
171
171
|
});
|
|
172
172
|
|
|
173
|
+
// ===========================================================================
|
|
174
|
+
// Row accessible name and description (APG tree view: named by the label)
|
|
175
|
+
// ===========================================================================
|
|
176
|
+
|
|
177
|
+
it('names a parent row by its label alone, not the chevron toggle', () => {
|
|
178
|
+
const items: TreeListItemData[] = [
|
|
179
|
+
{id: 'docs', label: 'Documents', children: [{id: 'a', label: 'Annual'}]},
|
|
180
|
+
];
|
|
181
|
+
render(<TreeList items={items} />);
|
|
182
|
+
const row = screen.getByText('Documents').closest('li')!;
|
|
183
|
+
expect(row).toHaveAccessibleName('Documents');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('names a plain row by its label and describes it with its description', () => {
|
|
187
|
+
const items: TreeListItemData[] = [
|
|
188
|
+
{id: 'a', label: 'Budget', description: 'Updated yesterday'},
|
|
189
|
+
];
|
|
190
|
+
render(<TreeList items={items} />);
|
|
191
|
+
const row = screen.getByText('Budget').closest('li')!;
|
|
192
|
+
expect(row).toHaveAccessibleName('Budget');
|
|
193
|
+
expect(row).toHaveAccessibleDescription('Updated yesterday');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('names and describes link and action rows themselves, not only their inner control', () => {
|
|
197
|
+
const items: TreeListItemData[] = [
|
|
198
|
+
{id: 'l', label: 'Report', description: 'Opens the report', href: '#'},
|
|
199
|
+
{
|
|
200
|
+
id: 'b',
|
|
201
|
+
label: 'Export',
|
|
202
|
+
description: 'Downloads a file',
|
|
203
|
+
onClick: () => {},
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
render(<TreeList items={items} />);
|
|
207
|
+
const link = screen.getByText('Report').closest('li')!;
|
|
208
|
+
expect(link).toHaveAccessibleName('Report');
|
|
209
|
+
expect(link).toHaveAccessibleDescription('Opens the report');
|
|
210
|
+
const action = screen.getByText('Export').closest('li')!;
|
|
211
|
+
expect(action).toHaveAccessibleName('Export');
|
|
212
|
+
expect(action).toHaveAccessibleDescription('Downloads a file');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('keeps start and end content, including controls, out of the row name', () => {
|
|
216
|
+
const items: TreeListItemData[] = [
|
|
217
|
+
{
|
|
218
|
+
id: 'a',
|
|
219
|
+
label: 'Invoices',
|
|
220
|
+
startContent: <span>12</span>,
|
|
221
|
+
endContent: <button type="button">Pin folder</button>,
|
|
222
|
+
},
|
|
223
|
+
];
|
|
224
|
+
render(<TreeList items={items} />);
|
|
225
|
+
const row = screen.getByText('Invoices').closest('li')!;
|
|
226
|
+
expect(row).toHaveAccessibleName('Invoices');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('typeahead matches the label, not the description or start content', async () => {
|
|
230
|
+
const user = userEvent.setup();
|
|
231
|
+
const items: TreeListItemData[] = [
|
|
232
|
+
{id: 'a', label: 'Apple', description: 'Cherry-red fruit'},
|
|
233
|
+
{id: 'b', label: 'Banana', startContent: <span>Curved</span>},
|
|
234
|
+
{id: 'c', label: 'Cherry'},
|
|
235
|
+
];
|
|
236
|
+
render(<TreeList items={items} />);
|
|
237
|
+
const apple = screen.getByText('Apple').closest('li')!;
|
|
238
|
+
apple.focus();
|
|
239
|
+
await user.keyboard('c');
|
|
240
|
+
expect(document.activeElement).toBe(
|
|
241
|
+
screen.getByText('Cherry').closest('li'),
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
|
|
173
245
|
// ===========================================================================
|
|
174
246
|
// Semantic HTML
|
|
175
247
|
// ===========================================================================
|
|
@@ -27,7 +27,7 @@ import type {
|
|
|
27
27
|
TreeListVariant,
|
|
28
28
|
} from './TreeListTypes';
|
|
29
29
|
import {themeProps} from '../utils/themeProps';
|
|
30
|
-
import {
|
|
30
|
+
import {useTreeFocusInternal} from '../hooks/useTreeFocus';
|
|
31
31
|
|
|
32
32
|
// =============================================================================
|
|
33
33
|
// Types
|
|
@@ -255,11 +255,15 @@ export function TreeList({
|
|
|
255
255
|
return false;
|
|
256
256
|
}, []);
|
|
257
257
|
|
|
258
|
-
const {treeRef, handleKeyDown, handleFocus} =
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
258
|
+
const {treeRef, handleKeyDown, handleFocus} =
|
|
259
|
+
useTreeFocusInternal<HTMLUListElement>({
|
|
260
|
+
onToggleExpand: handleToggle,
|
|
261
|
+
onActivate: activateItem,
|
|
262
|
+
hasRovingTabIndex: true,
|
|
263
|
+
// Rows are named by their label (AR1); typeahead matches that label, not
|
|
264
|
+
// the chevron toggle, start content or description.
|
|
265
|
+
matchLabelledBy: true,
|
|
266
|
+
});
|
|
263
267
|
|
|
264
268
|
const handleRootKeyDown = useCallback(
|
|
265
269
|
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
@@ -557,6 +557,11 @@ export function TreeListItem({
|
|
|
557
557
|
return (
|
|
558
558
|
<li
|
|
559
559
|
role="treeitem"
|
|
560
|
+
// The row is named by its label alone and described by its description
|
|
561
|
+
// (APG tree view): without these the name would be the row's whole text,
|
|
562
|
+
// chevron button label and description included.
|
|
563
|
+
aria-labelledby={labelId}
|
|
564
|
+
aria-describedby={description != null ? descriptionId : undefined}
|
|
560
565
|
aria-expanded={hasChildren ? isExpanded : undefined}
|
|
561
566
|
aria-selected={isSelected || undefined}
|
|
562
567
|
aria-disabled={isDisabled || undefined}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file TreeList.a11y.chromium.spec.ts
|
|
5
|
+
* @input Built TreeList tree-view stories and TREE_VIEW_PATTERN in real Chromium
|
|
6
|
+
* @output Browser evidence for every tree-view expectation across the binding's states
|
|
7
|
+
* @position Chromium lane of the TreeList tree-view binding (component:TreeList 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
|
+
TREE_VIEW_PATTERN,
|
|
19
|
+
blockingResults,
|
|
20
|
+
checkAccessibilitySpec,
|
|
21
|
+
formatFailures,
|
|
22
|
+
neverExercised,
|
|
23
|
+
summarize,
|
|
24
|
+
type BindingResult,
|
|
25
|
+
} from '@astryxdesign/a11y-spec';
|
|
26
|
+
import {
|
|
27
|
+
createChromiumHarness,
|
|
28
|
+
holdMotionStill,
|
|
29
|
+
} from '@astryxdesign/a11y-spec/chromium';
|
|
30
|
+
import {
|
|
31
|
+
DEFAULT_STORYBOOK_DIR,
|
|
32
|
+
serveStorybook,
|
|
33
|
+
type StaticServer,
|
|
34
|
+
} from '@astryxdesign/a11y-spec/storybook';
|
|
35
|
+
import {
|
|
36
|
+
TREE_LIST_BINDING_STATES,
|
|
37
|
+
type TreeListBindingState,
|
|
38
|
+
} from './TreeList.a11y.states';
|
|
39
|
+
|
|
40
|
+
let storybook: StaticServer;
|
|
41
|
+
|
|
42
|
+
test.beforeAll(async () => {
|
|
43
|
+
storybook = await serveStorybook(
|
|
44
|
+
process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test.afterAll(async () => {
|
|
49
|
+
await storybook?.close();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
function storyUrl(storyId: string): string {
|
|
53
|
+
return `${storybook.origin}/iframe.html?id=${storyId}&viewMode=story`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function rowLocator(page: Page, id: string): Locator {
|
|
57
|
+
return page.locator(
|
|
58
|
+
`#storybook-root [role="treeitem"][data-tree-id="${id}"]`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function mountState(
|
|
63
|
+
page: Page,
|
|
64
|
+
state: TreeListBindingState,
|
|
65
|
+
): Promise<{subject: Locator; related: Record<string, Locator>}> {
|
|
66
|
+
await page.goto(storyUrl(state.storyId), {waitUntil: 'load'});
|
|
67
|
+
await holdMotionStill(page);
|
|
68
|
+
const subject = rowLocator(page, state.subject);
|
|
69
|
+
await subject.waitFor({state: 'attached'});
|
|
70
|
+
const root = page.locator('#storybook-root');
|
|
71
|
+
const related: Record<string, Locator> = {
|
|
72
|
+
tree: root.getByRole('tree'),
|
|
73
|
+
before: root.getByRole('button', {name: 'Before the tree'}),
|
|
74
|
+
after: root.getByRole('button', {name: 'After the tree'}),
|
|
75
|
+
group: subject.locator('[role="group"]'),
|
|
76
|
+
};
|
|
77
|
+
for (const [name, id] of Object.entries(state.relations)) {
|
|
78
|
+
related[name] = rowLocator(page, id);
|
|
79
|
+
}
|
|
80
|
+
return {subject, related};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function runState(
|
|
84
|
+
page: Page,
|
|
85
|
+
cdp: CDPSession,
|
|
86
|
+
state: TreeListBindingState,
|
|
87
|
+
): Promise<BindingResult> {
|
|
88
|
+
return checkAccessibilitySpec({
|
|
89
|
+
spec: TREE_VIEW_PATTERN,
|
|
90
|
+
binding: 'TreeList',
|
|
91
|
+
state: state.id,
|
|
92
|
+
facts: state.facts,
|
|
93
|
+
mount: async () => {
|
|
94
|
+
const {subject, related} = await mountState(page, state);
|
|
95
|
+
return createChromiumHarness({page, cdp, subject, related});
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
test('every tree-view expectation is exercised by a TreeList state', async ({
|
|
101
|
+
page,
|
|
102
|
+
}) => {
|
|
103
|
+
const cdp = await page.context().newCDPSession(page);
|
|
104
|
+
const results: BindingResult[] = [];
|
|
105
|
+
for (const state of TREE_LIST_BINDING_STATES) {
|
|
106
|
+
results.push(await runState(page, cdp, state));
|
|
107
|
+
}
|
|
108
|
+
expect(neverExercised(results)).toEqual([]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
for (const state of TREE_LIST_BINDING_STATES) {
|
|
112
|
+
test(`TreeList [${state.id}] — ${state.summary}`, async ({page}) => {
|
|
113
|
+
const cdp = await page.context().newCDPSession(page);
|
|
114
|
+
const result = await runState(page, cdp, state);
|
|
115
|
+
const report = summarize(TREE_VIEW_PATTERN, [result]);
|
|
116
|
+
expect(formatFailures(blockingResults([result]))).toBe('');
|
|
117
|
+
expect(
|
|
118
|
+
result.results.filter(
|
|
119
|
+
row => row.status !== 'pass' && row.status !== 'not-applicable',
|
|
120
|
+
),
|
|
121
|
+
).toEqual([]);
|
|
122
|
+
expect(report.unrunLayers).toEqual([]);
|
|
123
|
+
expect(report.counts.unexpectedPass).toBe(0);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file TreeList.a11y.renders.tsx
|
|
5
|
+
* @input One TreeList tree-view binding state
|
|
6
|
+
* @output The React tree every lane renders for that state (the jsdom test directly, the story through Storybook)
|
|
7
|
+
* @position Shared fixture for the TreeList tree-view binding
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {ReactElement} from 'react';
|
|
11
|
+
import {TreeList} from '../TreeList';
|
|
12
|
+
import type {TreeListItemData} from '../TreeListTypes';
|
|
13
|
+
import {
|
|
14
|
+
TREE_LIST_LABEL,
|
|
15
|
+
type TreeListBindingState,
|
|
16
|
+
} from './TreeList.a11y.states';
|
|
17
|
+
|
|
18
|
+
export function renderTreeListState(
|
|
19
|
+
state: Pick<TreeListBindingState, 'expanded' | 'disabled' | 'direction'>,
|
|
20
|
+
): ReactElement {
|
|
21
|
+
const expanded = (id: string) => state.expanded.includes(id);
|
|
22
|
+
const disabled = (id: string) => state.disabled?.includes(id) ?? false;
|
|
23
|
+
const items: TreeListItemData[] = [
|
|
24
|
+
{
|
|
25
|
+
id: 'docs',
|
|
26
|
+
label: 'Documents',
|
|
27
|
+
isExpanded: expanded('docs'),
|
|
28
|
+
isDisabled: disabled('docs'),
|
|
29
|
+
children: [
|
|
30
|
+
{id: 'annual', label: 'Annual report'},
|
|
31
|
+
{id: 'budget', label: 'Budget'},
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
{id: 'photos', label: 'Photos', isDisabled: disabled('photos')},
|
|
35
|
+
{id: 'trash', label: 'Trash', isDisabled: disabled('trash')},
|
|
36
|
+
];
|
|
37
|
+
return (
|
|
38
|
+
<div dir={state.direction ?? 'ltr'}>
|
|
39
|
+
<button type="button">Before the tree</button>
|
|
40
|
+
<TreeList aria-label={TREE_LIST_LABEL} items={items} />
|
|
41
|
+
<button type="button">After the tree</button>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|