@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,108 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** Chromium accessibility-tree and relationship proof for Selector's Combobox binding. */
|
|
4
|
+
|
|
5
|
+
import {expect, test, type Locator, type Page} from '@playwright/test';
|
|
6
|
+
import {
|
|
7
|
+
blockingResults,
|
|
8
|
+
checkAccessibilitySpec,
|
|
9
|
+
formatFailures,
|
|
10
|
+
unmatchedKnownFailures,
|
|
11
|
+
type BindingResult,
|
|
12
|
+
} from '@astryxdesign/a11y-spec';
|
|
13
|
+
import {createChromiumHarness} from '@astryxdesign/a11y-spec/chromium';
|
|
14
|
+
import {
|
|
15
|
+
DEFAULT_STORYBOOK_DIR,
|
|
16
|
+
serveStorybook,
|
|
17
|
+
type StaticServer,
|
|
18
|
+
} from '@astryxdesign/a11y-spec/storybook';
|
|
19
|
+
import {COMBOBOX_PATTERN} from '@astryxdesign/a11y-spec';
|
|
20
|
+
import {
|
|
21
|
+
SELECTOR_COMBOBOX_SCENARIOS,
|
|
22
|
+
type SelectorComboboxScenario,
|
|
23
|
+
} from './Combobox.a11y.states';
|
|
24
|
+
import {SELECTOR_COMBOBOX_KNOWN_FAILURES} from './Combobox.a11y.known-failures';
|
|
25
|
+
|
|
26
|
+
let storybook: StaticServer;
|
|
27
|
+
const observedResults: BindingResult[] = [];
|
|
28
|
+
|
|
29
|
+
test.beforeAll(async () => {
|
|
30
|
+
storybook = await serveStorybook(
|
|
31
|
+
process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test.afterAll(async () => {
|
|
36
|
+
await storybook?.close();
|
|
37
|
+
expect(
|
|
38
|
+
unmatchedKnownFailures(SELECTOR_COMBOBOX_KNOWN_FAILURES, observedResults),
|
|
39
|
+
).toEqual([]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
async function awaitScenario(
|
|
43
|
+
page: Page,
|
|
44
|
+
scenario: SelectorComboboxScenario,
|
|
45
|
+
): Promise<void> {
|
|
46
|
+
if (!scenario.open) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
await expect(page.getByRole('listbox')).toBeVisible();
|
|
50
|
+
if (scenario.highlightFirst) {
|
|
51
|
+
await expect(
|
|
52
|
+
page.getByRole('combobox', {name: scenario.facts.name}),
|
|
53
|
+
).toHaveAttribute('aria-activedescendant', /.+/);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const scenario of SELECTOR_COMBOBOX_SCENARIOS) {
|
|
58
|
+
test(`Selector Combobox contract — ${scenario.id}`, async ({page}) => {
|
|
59
|
+
await page.goto(
|
|
60
|
+
`${storybook.origin}/iframe.html?id=${scenario.storyId}&viewMode=story`,
|
|
61
|
+
{waitUntil: 'load'},
|
|
62
|
+
);
|
|
63
|
+
await awaitScenario(page, scenario);
|
|
64
|
+
|
|
65
|
+
// A modal sheet makes the trigger inert, so find it among hidden nodes too.
|
|
66
|
+
const subject = page.getByRole('combobox', {
|
|
67
|
+
name: scenario.facts.name,
|
|
68
|
+
includeHidden: true,
|
|
69
|
+
});
|
|
70
|
+
if (
|
|
71
|
+
scenario.binding === 'Selector.trigger' &&
|
|
72
|
+
scenario.facts.popupVisible
|
|
73
|
+
) {
|
|
74
|
+
// AR1: the trigger names the popup kind its presentation renders.
|
|
75
|
+
await expect(subject).toHaveAttribute(
|
|
76
|
+
'aria-haspopup',
|
|
77
|
+
scenario.facts.popupRole,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
const related: Record<string, Locator> = {};
|
|
81
|
+
if (scenario.facts.popupVisible) {
|
|
82
|
+
related.popup =
|
|
83
|
+
scenario.facts.popupRole === 'dialog'
|
|
84
|
+
? page.getByRole('dialog', {name: 'Fruit'})
|
|
85
|
+
: page.getByRole('listbox');
|
|
86
|
+
}
|
|
87
|
+
if (scenario.facts.activeDescendant) {
|
|
88
|
+
related['active-descendant'] = page.getByRole('option', {name: 'Apple'});
|
|
89
|
+
}
|
|
90
|
+
const cdp = await page.context().newCDPSession(page);
|
|
91
|
+
const run = await checkAccessibilitySpec({
|
|
92
|
+
spec: COMBOBOX_PATTERN,
|
|
93
|
+
binding: scenario.binding,
|
|
94
|
+
state: scenario.id,
|
|
95
|
+
facts: scenario.facts,
|
|
96
|
+
knownFailures: SELECTOR_COMBOBOX_KNOWN_FAILURES,
|
|
97
|
+
mount: async () => createChromiumHarness({page, cdp, subject, related}),
|
|
98
|
+
});
|
|
99
|
+
observedResults.push(run);
|
|
100
|
+
|
|
101
|
+
expect(formatFailures(blockingResults([run]))).toBe('');
|
|
102
|
+
expect(
|
|
103
|
+
run.results
|
|
104
|
+
.filter(result => result.status === 'unrun')
|
|
105
|
+
.map(result => `${result.expectation}: ${result.detail ?? ''}`),
|
|
106
|
+
).toEqual([]);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** Selector's first binding to the reusable Combobox semantic contract. */
|
|
4
|
+
|
|
5
|
+
import {cleanup, render, screen, waitFor, within} from '@testing-library/react';
|
|
6
|
+
import userEvent from '@testing-library/user-event';
|
|
7
|
+
import {describe, expect, it} from 'vitest';
|
|
8
|
+
import {
|
|
9
|
+
COMBOBOX_PATTERN,
|
|
10
|
+
expectAccessibilitySpec,
|
|
11
|
+
} from '@astryxdesign/a11y-spec';
|
|
12
|
+
import {Selector} from '../Selector';
|
|
13
|
+
import {installListboxDialogStubs} from './Listbox.a11y.dom';
|
|
14
|
+
import {
|
|
15
|
+
SELECTOR_COMBOBOX_EXCLUSIONS,
|
|
16
|
+
SELECTOR_COMBOBOX_SCENARIOS,
|
|
17
|
+
type SelectorComboboxScenario,
|
|
18
|
+
} from './Combobox.a11y.states';
|
|
19
|
+
import {SELECTOR_COMBOBOX_KNOWN_FAILURES} from './Combobox.a11y.known-failures';
|
|
20
|
+
|
|
21
|
+
installListboxDialogStubs();
|
|
22
|
+
|
|
23
|
+
const hidden = {hidden: true} as const;
|
|
24
|
+
|
|
25
|
+
function subjectFor(scenario: SelectorComboboxScenario): HTMLElement {
|
|
26
|
+
return screen.getByRole('combobox', {...hidden, name: scenario.facts.name});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('Selector Combobox semantic binding', () => {
|
|
30
|
+
it.each(SELECTOR_COMBOBOX_SCENARIOS)('$id', async scenario => {
|
|
31
|
+
await expectAccessibilitySpec({
|
|
32
|
+
spec: COMBOBOX_PATTERN,
|
|
33
|
+
binding: scenario.binding,
|
|
34
|
+
state: scenario.id,
|
|
35
|
+
facts: scenario.facts,
|
|
36
|
+
knownFailures: SELECTOR_COMBOBOX_KNOWN_FAILURES,
|
|
37
|
+
render: async () => {
|
|
38
|
+
render(
|
|
39
|
+
<Selector
|
|
40
|
+
label="Fruit"
|
|
41
|
+
options={[
|
|
42
|
+
{value: 'apple', label: 'Apple'},
|
|
43
|
+
{value: 'banana', label: 'Banana'},
|
|
44
|
+
]}
|
|
45
|
+
onChange={() => {}}
|
|
46
|
+
{...scenario.props}
|
|
47
|
+
/>,
|
|
48
|
+
);
|
|
49
|
+
if (!scenario.open) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const user = userEvent.setup();
|
|
53
|
+
await user.click(
|
|
54
|
+
scenario.props.hasSearch
|
|
55
|
+
? screen.getByRole('button', {name: /Fruit/})
|
|
56
|
+
: screen.getByRole('combobox', {name: 'Fruit'}),
|
|
57
|
+
);
|
|
58
|
+
await screen.findByRole('listbox', hidden);
|
|
59
|
+
if (scenario.highlightFirst) {
|
|
60
|
+
const search = subjectFor(scenario);
|
|
61
|
+
await waitFor(() => expect(search).toHaveFocus());
|
|
62
|
+
await user.keyboard('{ArrowDown}');
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
subject: () => subjectFor(scenario),
|
|
66
|
+
related: () => {
|
|
67
|
+
const related: Record<string, Element> = {};
|
|
68
|
+
if (!scenario.facts.popupVisible) {
|
|
69
|
+
return related;
|
|
70
|
+
}
|
|
71
|
+
const listbox = screen.getByRole('listbox', hidden);
|
|
72
|
+
if (scenario.binding === 'Selector.trigger') {
|
|
73
|
+
// AR1: the trigger names the popup kind its presentation renders.
|
|
74
|
+
expect(subjectFor(scenario)).toHaveAttribute(
|
|
75
|
+
'aria-haspopup',
|
|
76
|
+
scenario.facts.popupRole,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
related.popup =
|
|
80
|
+
scenario.facts.popupRole === 'dialog'
|
|
81
|
+
? screen.getByRole('dialog', {...hidden, name: 'Fruit'})
|
|
82
|
+
: listbox;
|
|
83
|
+
if (scenario.facts.activeDescendant) {
|
|
84
|
+
expect(
|
|
85
|
+
subjectFor(scenario).getAttribute('aria-activedescendant'),
|
|
86
|
+
).not.toBeNull();
|
|
87
|
+
related['active-descendant'] = within(listbox).getByRole('option', {
|
|
88
|
+
...hidden,
|
|
89
|
+
name: 'Apple',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return related;
|
|
93
|
+
},
|
|
94
|
+
cleanup,
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('keeps the bounded first-adopter state matrix explicit', () => {
|
|
99
|
+
expect(
|
|
100
|
+
SELECTOR_COMBOBOX_SCENARIOS.map(scenario => [
|
|
101
|
+
scenario.binding,
|
|
102
|
+
scenario.id,
|
|
103
|
+
]),
|
|
104
|
+
).toEqual([
|
|
105
|
+
['Selector.trigger', 'closed'],
|
|
106
|
+
['Selector.trigger', 'open'],
|
|
107
|
+
['Selector.trigger', 'read-only'],
|
|
108
|
+
['Selector.trigger', 'busy'],
|
|
109
|
+
['Selector.trigger', 'sheet-open'],
|
|
110
|
+
['Selector.search', 'search-active'],
|
|
111
|
+
]);
|
|
112
|
+
expect(SELECTOR_COMBOBOX_EXCLUSIONS).toHaveLength(5);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('pins every known failure to exactly one bound state', () => {
|
|
116
|
+
expect(SELECTOR_COMBOBOX_KNOWN_FAILURES).toHaveLength(1);
|
|
117
|
+
for (const record of SELECTOR_COMBOBOX_KNOWN_FAILURES) {
|
|
118
|
+
expect(
|
|
119
|
+
SELECTOR_COMBOBOX_SCENARIOS.filter(
|
|
120
|
+
scenario =>
|
|
121
|
+
scenario.binding === record.binding && scenario.id === record.state,
|
|
122
|
+
),
|
|
123
|
+
).toHaveLength(1);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -989,6 +989,41 @@ describe('SideNav resizable', () => {
|
|
|
989
989
|
).toBeInTheDocument();
|
|
990
990
|
});
|
|
991
991
|
|
|
992
|
+
it('names the nav it resizes with aria-controls (APG window splitter)', () => {
|
|
993
|
+
render(<SideNav resizable>Content</SideNav>);
|
|
994
|
+
const nav = screen.getByRole('navigation');
|
|
995
|
+
const handle = screen.getByRole('separator');
|
|
996
|
+
expect(nav.id).not.toBe('');
|
|
997
|
+
expect(handle).toHaveAttribute('aria-controls', nav.id);
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
it("keeps a consumer's nav id and points the handle at it", () => {
|
|
1001
|
+
render(
|
|
1002
|
+
<SideNav resizable id="primary-nav">
|
|
1003
|
+
Content
|
|
1004
|
+
</SideNav>,
|
|
1005
|
+
);
|
|
1006
|
+
expect(screen.getByRole('navigation')).toHaveAttribute('id', 'primary-nav');
|
|
1007
|
+
expect(screen.getByRole('separator')).toHaveAttribute(
|
|
1008
|
+
'aria-controls',
|
|
1009
|
+
'primary-nav',
|
|
1010
|
+
);
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
it('keeps the generated nav id when a wrapper forwards id={undefined}', () => {
|
|
1014
|
+
render(
|
|
1015
|
+
<SideNav resizable id={undefined}>
|
|
1016
|
+
Content
|
|
1017
|
+
</SideNav>,
|
|
1018
|
+
);
|
|
1019
|
+
const nav = screen.getByRole('navigation');
|
|
1020
|
+
expect(nav.id).not.toBe('');
|
|
1021
|
+
expect(screen.getByRole('separator')).toHaveAttribute(
|
|
1022
|
+
'aria-controls',
|
|
1023
|
+
nav.id,
|
|
1024
|
+
);
|
|
1025
|
+
});
|
|
1026
|
+
|
|
992
1027
|
it('clips the overlay drag handle within the resizable nav bounds', () => {
|
|
993
1028
|
render(<SideNav resizable>Content</SideNav>);
|
|
994
1029
|
const nav = screen.getByRole('navigation');
|
package/src/SideNav/SideNav.tsx
CHANGED
|
@@ -22,7 +22,13 @@
|
|
|
22
22
|
* - /packages/cli/assets/templates/blocks/components/SideNav/ (showcase blocks)
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
useCallback,
|
|
27
|
+
useId,
|
|
28
|
+
useImperativeHandle,
|
|
29
|
+
useRef,
|
|
30
|
+
type ReactNode,
|
|
31
|
+
} from 'react';
|
|
26
32
|
import type {BaseProps} from '../BaseProps';
|
|
27
33
|
import * as stylex from '@stylexjs/stylex';
|
|
28
34
|
import type {StyleXStyles} from '@stylexjs/stylex';
|
|
@@ -440,6 +446,10 @@ export function SideNav({
|
|
|
440
446
|
);
|
|
441
447
|
|
|
442
448
|
const navRef = useRef<HTMLElement>(null);
|
|
449
|
+
// The resize handle names the pane it resizes (APG window splitter), so the
|
|
450
|
+
// nav needs an id; a consumer's own id wins.
|
|
451
|
+
const generatedNavId = useId();
|
|
452
|
+
const navId = props.id ?? generatedNavId;
|
|
443
453
|
const mergedNavRef = useMergedRefs(ref, navRef);
|
|
444
454
|
const collapseStateRef = useRef<SideNavCollapseState>({
|
|
445
455
|
isCollapsed: false,
|
|
@@ -611,7 +621,10 @@ export function SideNav({
|
|
|
611
621
|
className,
|
|
612
622
|
resizableNavStyle,
|
|
613
623
|
)}
|
|
614
|
-
{...props}
|
|
624
|
+
{...props}
|
|
625
|
+
// After the spread: a wrapper forwarding `id={undefined}` must not erase
|
|
626
|
+
// the id the resize handle's aria-controls points at.
|
|
627
|
+
id={navId}>
|
|
615
628
|
{hasStickyTop && (
|
|
616
629
|
<div
|
|
617
630
|
{...stylex.props(
|
|
@@ -672,6 +685,7 @@ export function SideNav({
|
|
|
672
685
|
xstyle={styles.resizableHandle}
|
|
673
686
|
resizable={resizableHook.props}
|
|
674
687
|
label={t('@astryx.sideNav.resizeSidebar')}
|
|
688
|
+
aria-controls={navId}
|
|
675
689
|
/>
|
|
676
690
|
</div>
|
|
677
691
|
) : (
|
|
@@ -35,8 +35,10 @@ an additive target for the interactive control surface.
|
|
|
35
35
|
## Compatibility and migration
|
|
36
36
|
|
|
37
37
|
- Released default preserved: `yes`
|
|
38
|
-
- Compatibility class: additive target and state reflection
|
|
39
|
-
|
|
38
|
+
- Compatibility class: additive target and state reflection; default styling,
|
|
39
|
+
DOM semantics, and public props remain unchanged, and the one keyboard change
|
|
40
|
+
AR1 names — the horizontal arrows follow the track's direction under RTL —
|
|
41
|
+
ships as a behavior fix
|
|
40
42
|
- Controlled/uncontrolled behavior: unchanged; Slider remains controlled
|
|
41
43
|
- Migration decision: none
|
|
42
44
|
|
|
@@ -62,9 +64,9 @@ Consumer migration instructions belong in consumer docs and release notes.
|
|
|
62
64
|
|
|
63
65
|
## Public concepts
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
documented in `Slider.doc.mjs`.
|
|
67
|
+
The record adds one public theming target and names the keyboard model (AR1)
|
|
68
|
+
without adding or changing a component prop or value domain. Consumer props,
|
|
69
|
+
modes, states, and usage remain documented in `Slider.doc.mjs`.
|
|
68
70
|
|
|
69
71
|
## Behavioral and layout contract
|
|
70
72
|
|
|
@@ -102,8 +104,10 @@ either component's remaining target shape.
|
|
|
102
104
|
|
|
103
105
|
### Transformation and precedence order
|
|
104
106
|
|
|
105
|
-
-
|
|
106
|
-
|
|
107
|
+
- ORD1: an arrow or Page key result snaps to the nearest step, then clamps to
|
|
108
|
+
the bounds; Home and End go to `min` and `max` (AR1).
|
|
109
|
+
- The anatomy decision (DEC-1) introduces no value, snapping, geometry,
|
|
110
|
+
interaction, or styling precedence rule of its own.
|
|
107
111
|
|
|
108
112
|
### Performance and resources
|
|
109
113
|
|
|
@@ -111,9 +115,21 @@ either component's remaining target shape.
|
|
|
111
115
|
|
|
112
116
|
## Accessibility contract
|
|
113
117
|
|
|
114
|
-
|
|
115
|
-
range-thumb naming, description/status association,
|
|
116
|
-
|
|
118
|
+
The anatomy decision (DEC-1) does not change Slider's accessible name, value,
|
|
119
|
+
range-thumb naming, description/status association, disabled behavior, or
|
|
120
|
+
value-tooltip behavior; AR1 records the keyboard model and its one correction.
|
|
121
|
+
|
|
122
|
+
- **AR1 — Slider adopts the APG Slider pattern** (`spec:AST-020/FR15`), each
|
|
123
|
+
thumb as one composite control: every thumb is `role="slider"` with an
|
|
124
|
+
accessible name (a range names each thumb), `aria-valuemin`, `aria-valuemax`,
|
|
125
|
+
`aria-valuenow`, `aria-valuetext` when a formatter changes the spoken value,
|
|
126
|
+
`aria-orientation`, and `aria-disabled` when disabled; an enabled thumb, and a
|
|
127
|
+
disabled thumb that carries a disabled message, is a Tab stop.
|
|
128
|
+
ArrowUp and ArrowDown step by `step`; on a horizontal track the horizontal
|
|
129
|
+
arrows follow the track's direction — ArrowRight increases and ArrowLeft
|
|
130
|
+
decreases under LTR, and under RTL ArrowLeft increases and ArrowRight
|
|
131
|
+
decreases; PageUp and PageDown step by ten steps; Home moves to `min` and End to `max`. In a range, a thumb never crosses the other past the minimum
|
|
132
|
+
separation.
|
|
117
133
|
|
|
118
134
|
## Design relationships
|
|
119
135
|
|
|
@@ -198,13 +214,14 @@ separately listed value tooltip retains Tooltip's target.
|
|
|
198
214
|
|
|
199
215
|
## Verification map
|
|
200
216
|
|
|
201
|
-
| Contract | Verification
|
|
202
|
-
| ------------------- |
|
|
203
|
-
| FR1 | `Slider.test.tsx` structure, range, and marks suites
|
|
204
|
-
|
|
|
205
|
-
|
|
|
206
|
-
|
|
|
207
|
-
|
|
|
217
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
218
|
+
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
|
|
219
|
+
| FR1 | `Slider.test.tsx` structure, range, and marks suites | Single, range, horizontal, vertical, marks | Removing or misaligning stable parts breaks existing role, position, or mark assertions. | `audit:Slider/anatomy` |
|
|
220
|
+
| AR1 | Slider accessibility contract (`internal/a11y-spec`, pending) bound to Slider, in jsdom and Chromium; `Slider.test.tsx` keyboard suites, including "follows the mirrored track under RTL: ArrowLeft increases, ArrowRight decreases, Up/Down unchanged" and "keeps ArrowLeft decreasing on a vertical track under RTL"; the Page, Home/End and Up/Down cases under the contract | Single and range; horizontal LTR and RTL; vertical; disabled; formatted value | A thumb loses its role, name or value properties; an arrow, Page, Home or End key stops stepping or steps against the mirrored track; a range thumb crosses the other | `audit:Slider/accessibility` |
|
|
221
|
+
| FR2 | `Slider.test.tsx`, `themingTargets.test.ts`, generated probe theme, and Chromium | Root, horizontal/vertical control, disabled control, track, and thumb | A target class/state is missing, undocumented, or placed outside its owning anatomy. | `audit:Slider/theming` |
|
|
222
|
+
| FR3 | Source and consumer-doc review | Filled range, marks, labels, adjacent text value | A missing target is inaccurately documented as present or intentionally permanent. | `audit:Slider/theming` |
|
|
223
|
+
| FR4 | Source inspection; focused tests cover label, status, and disabled-reason Tooltip only | Label, status, value tooltip, disabled reason | Shared composition or its accessible association disappears; value-tooltip composition still lacks focused coverage. | `audit:Slider/anatomy` |
|
|
224
|
+
| Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and four current local targets | Missing, extra, prefixed, stale, or unclaimed mappings fail repository validation. | `audit:Slider/theming` |
|
|
208
225
|
|
|
209
226
|
The focused Slider suite asserts the `slider-control` target's orientation and
|
|
210
227
|
disabled state. Source/metadata guards cover all four target declarations. The
|
|
@@ -224,6 +241,15 @@ consumer-recognizable part, so it carries `slider-control`. `Thumb` remains
|
|
|
224
241
|
separate anatomy and continues to carry `slider-thumb`. This adds no default
|
|
225
242
|
behavior or styling change.
|
|
226
243
|
|
|
244
|
+
### DEC-2 — Each thumb is an APG slider, and horizontal arrows follow the track
|
|
245
|
+
|
|
246
|
+
**Reference:** `component:Slider/DEC-2`
|
|
247
|
+
**Decider:** `cixzhang`, `2026-10-13`
|
|
248
|
+
|
|
249
|
+
A thumb already plays the slider role as one composite control, so it carries the pattern's value properties and its whole keyboard model (AR1). The horizontal arrows follow the track's painted direction because a key that moves the thumb against the way it travels on screen is a defect, not a convention; the vertical arrows and Up/Down are direction-free.
|
|
250
|
+
|
|
251
|
+
Rejected: keeping ArrowRight as "increase" under RTL; a Tab stop or arrow model per range thumb that differs from the single thumb.
|
|
252
|
+
|
|
227
253
|
## Open questions
|
|
228
254
|
|
|
229
255
|
- **OQ1 — After a joint ProgressBar and Slider family audit, should any shared
|
|
@@ -510,6 +510,69 @@ describe('Slider', () => {
|
|
|
510
510
|
expect(handleChangeEnd).toHaveBeenCalledWith([0.2, 0.7]);
|
|
511
511
|
});
|
|
512
512
|
|
|
513
|
+
it('End reaches an off-grid max from a non-zero min, and Home still reaches min', async () => {
|
|
514
|
+
const user = userEvent.setup();
|
|
515
|
+
const onChange = vi.fn();
|
|
516
|
+
const onChangeEnd = vi.fn();
|
|
517
|
+
// From min 1 with step 3 the grid is 1, 4, …, 100; max 101 is off it.
|
|
518
|
+
render(
|
|
519
|
+
<Slider
|
|
520
|
+
label="Zoom"
|
|
521
|
+
min={1}
|
|
522
|
+
max={101}
|
|
523
|
+
step={3}
|
|
524
|
+
value={49}
|
|
525
|
+
onChange={onChange}
|
|
526
|
+
onChangeEnd={onChangeEnd}
|
|
527
|
+
/>,
|
|
528
|
+
);
|
|
529
|
+
const thumb = screen.getByRole('slider');
|
|
530
|
+
thumb.focus();
|
|
531
|
+
|
|
532
|
+
await user.keyboard('{End}');
|
|
533
|
+
expect(onChange).toHaveBeenLastCalledWith(101);
|
|
534
|
+
expect(onChangeEnd).toHaveBeenLastCalledWith(101);
|
|
535
|
+
|
|
536
|
+
await user.keyboard('{Home}');
|
|
537
|
+
expect(onChange).toHaveBeenLastCalledWith(1);
|
|
538
|
+
expect(onChangeEnd).toHaveBeenLastCalledWith(1);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it('End lands on max when max is off the step grid, while arrows still snap', async () => {
|
|
542
|
+
const user = userEvent.setup();
|
|
543
|
+
const onChange = vi.fn();
|
|
544
|
+
function Owner() {
|
|
545
|
+
const [value, setValue] = useState(48);
|
|
546
|
+
return (
|
|
547
|
+
<Slider
|
|
548
|
+
label="Zoom"
|
|
549
|
+
min={0}
|
|
550
|
+
max={100}
|
|
551
|
+
step={3}
|
|
552
|
+
value={value}
|
|
553
|
+
onChange={(next: number) => {
|
|
554
|
+
onChange(next);
|
|
555
|
+
setValue(next);
|
|
556
|
+
}}
|
|
557
|
+
/>
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
render(<Owner />);
|
|
561
|
+
const thumb = screen.getByRole('slider');
|
|
562
|
+
thumb.focus();
|
|
563
|
+
|
|
564
|
+
await user.keyboard('{End}');
|
|
565
|
+
expect(onChange).toHaveBeenLastCalledWith(100);
|
|
566
|
+
|
|
567
|
+
// ORD1 for arrows and Page keys is unchanged: from max (100, off the
|
|
568
|
+
// grid) ArrowLeft targets 97 and snaps to 96; from 96 PageUp targets 126
|
|
569
|
+
// and clamps to 100, the bound.
|
|
570
|
+
await user.keyboard('{ArrowLeft}');
|
|
571
|
+
expect(onChange).toHaveBeenLastCalledWith(96);
|
|
572
|
+
await user.keyboard('{PageUp}');
|
|
573
|
+
expect(onChange).toHaveBeenLastCalledWith(100);
|
|
574
|
+
});
|
|
575
|
+
|
|
513
576
|
it('fires onChangeEnd on keyboard Home/End with correct value', async () => {
|
|
514
577
|
const user = userEvent.setup();
|
|
515
578
|
const handleChangeEnd = vi.fn();
|
|
@@ -708,6 +771,77 @@ describe('Slider', () => {
|
|
|
708
771
|
gcsSpy.mockRestore();
|
|
709
772
|
});
|
|
710
773
|
|
|
774
|
+
it('follows the mirrored track under RTL: ArrowLeft increases, ArrowRight decreases, Up/Down unchanged', () => {
|
|
775
|
+
const handleChange = vi.fn();
|
|
776
|
+
render(
|
|
777
|
+
<Slider
|
|
778
|
+
label="Volume"
|
|
779
|
+
value={50}
|
|
780
|
+
min={0}
|
|
781
|
+
max={100}
|
|
782
|
+
step={1}
|
|
783
|
+
valueDisplay="none"
|
|
784
|
+
onChange={handleChange}
|
|
785
|
+
/>,
|
|
786
|
+
);
|
|
787
|
+
const thumb = screen.getByRole('slider');
|
|
788
|
+
const track = thumb.parentElement!;
|
|
789
|
+
const realGetComputedStyle = window.getComputedStyle;
|
|
790
|
+
const gcsSpy = vi
|
|
791
|
+
.spyOn(window, 'getComputedStyle')
|
|
792
|
+
.mockImplementation((el: Element, pseudo?: string | null) => {
|
|
793
|
+
if (el === track) {
|
|
794
|
+
return {direction: 'rtl'} as CSSStyleDeclaration;
|
|
795
|
+
}
|
|
796
|
+
return realGetComputedStyle(el, pseudo ?? undefined);
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
fireEvent.keyDown(thumb, {key: 'ArrowLeft'});
|
|
800
|
+
expect(handleChange).toHaveBeenLastCalledWith(51);
|
|
801
|
+
fireEvent.keyDown(thumb, {key: 'ArrowRight'});
|
|
802
|
+
expect(handleChange).toHaveBeenLastCalledWith(49);
|
|
803
|
+
fireEvent.keyDown(thumb, {key: 'ArrowUp'});
|
|
804
|
+
expect(handleChange).toHaveBeenLastCalledWith(51);
|
|
805
|
+
fireEvent.keyDown(thumb, {key: 'ArrowDown'});
|
|
806
|
+
expect(handleChange).toHaveBeenLastCalledWith(49);
|
|
807
|
+
|
|
808
|
+
gcsSpy.mockRestore();
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
it('keeps ArrowLeft decreasing on a vertical track under RTL', () => {
|
|
812
|
+
const handleChange = vi.fn();
|
|
813
|
+
render(
|
|
814
|
+
<Slider
|
|
815
|
+
label="Volume"
|
|
816
|
+
value={50}
|
|
817
|
+
min={0}
|
|
818
|
+
max={100}
|
|
819
|
+
step={1}
|
|
820
|
+
orientation="vertical"
|
|
821
|
+
valueDisplay="none"
|
|
822
|
+
onChange={handleChange}
|
|
823
|
+
/>,
|
|
824
|
+
);
|
|
825
|
+
const thumb = screen.getByRole('slider');
|
|
826
|
+
const track = thumb.parentElement!;
|
|
827
|
+
const realGetComputedStyle = window.getComputedStyle;
|
|
828
|
+
const gcsSpy = vi
|
|
829
|
+
.spyOn(window, 'getComputedStyle')
|
|
830
|
+
.mockImplementation((el: Element, pseudo?: string | null) => {
|
|
831
|
+
if (el === track) {
|
|
832
|
+
return {direction: 'rtl'} as CSSStyleDeclaration;
|
|
833
|
+
}
|
|
834
|
+
return realGetComputedStyle(el, pseudo ?? undefined);
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
fireEvent.keyDown(thumb, {key: 'ArrowLeft'});
|
|
838
|
+
expect(handleChange).toHaveBeenLastCalledWith(49);
|
|
839
|
+
fireEvent.keyDown(thumb, {key: 'ArrowRight'});
|
|
840
|
+
expect(handleChange).toHaveBeenLastCalledWith(51);
|
|
841
|
+
|
|
842
|
+
gcsSpy.mockRestore();
|
|
843
|
+
});
|
|
844
|
+
|
|
711
845
|
// --- Mark label click snapping ---
|
|
712
846
|
|
|
713
847
|
it('clicking a mark label snaps to that mark value, not pointer position', () => {
|
package/src/Slider/Slider.tsx
CHANGED
|
@@ -668,11 +668,18 @@ export function Slider({ref, ...props}: SliderProps) {
|
|
|
668
668
|
);
|
|
669
669
|
|
|
670
670
|
const updateValue = useCallback(
|
|
671
|
-
(thumbIndex: number, newVal: number) => {
|
|
671
|
+
(thumbIndex: number, newVal: number, snap = true) => {
|
|
672
672
|
if (isDisabled) {
|
|
673
673
|
return;
|
|
674
674
|
}
|
|
675
|
-
|
|
675
|
+
// Home and End pass the bound itself (`snap: false`). The step grid is
|
|
676
|
+
// anchored at `min`, so Home always landed there; `max` may lie off the
|
|
677
|
+
// grid (max 100 with step 3 snapped End to 99, not 100).
|
|
678
|
+
const clamped = clamp(
|
|
679
|
+
snap ? snapToStep(newVal, min, step) : newVal,
|
|
680
|
+
min,
|
|
681
|
+
max,
|
|
682
|
+
);
|
|
676
683
|
|
|
677
684
|
if (isRange) {
|
|
678
685
|
const currentValues = [...values] as [number, number];
|
|
@@ -827,12 +834,22 @@ export function Slider({ref, ...props}: SliderProps) {
|
|
|
827
834
|
const currentVal = values[thumbIndex];
|
|
828
835
|
let newVal: number;
|
|
829
836
|
|
|
837
|
+
// On a horizontal track the keyboard follows the track's direction:
|
|
838
|
+
// under RTL the value grows toward the left, so ArrowLeft increases and
|
|
839
|
+
// ArrowRight decreases (APG Slider). Up/Down and a vertical track are
|
|
840
|
+
// direction-free.
|
|
841
|
+
const towardMax =
|
|
842
|
+
isHorizontal && isRtlElement(trackRef.current)
|
|
843
|
+
? 'ArrowLeft'
|
|
844
|
+
: 'ArrowRight';
|
|
845
|
+
const towardMin = towardMax === 'ArrowLeft' ? 'ArrowRight' : 'ArrowLeft';
|
|
846
|
+
|
|
830
847
|
switch (e.key) {
|
|
831
|
-
case
|
|
848
|
+
case towardMax:
|
|
832
849
|
case 'ArrowUp':
|
|
833
850
|
newVal = currentVal + step;
|
|
834
851
|
break;
|
|
835
|
-
case
|
|
852
|
+
case towardMin:
|
|
836
853
|
case 'ArrowDown':
|
|
837
854
|
newVal = currentVal - step;
|
|
838
855
|
break;
|
|
@@ -853,8 +870,13 @@ export function Slider({ref, ...props}: SliderProps) {
|
|
|
853
870
|
}
|
|
854
871
|
|
|
855
872
|
e.preventDefault();
|
|
856
|
-
const
|
|
857
|
-
|
|
873
|
+
const toBound = e.key === 'Home' || e.key === 'End';
|
|
874
|
+
const clamped = clamp(
|
|
875
|
+
toBound ? newVal : snapToStep(newVal, min, step),
|
|
876
|
+
min,
|
|
877
|
+
max,
|
|
878
|
+
);
|
|
879
|
+
updateValue(thumbIndex, newVal, !toBound);
|
|
858
880
|
|
|
859
881
|
// Compute exact post-update values so onChangeEnd reports the correct
|
|
860
882
|
// value even before React batches the state update.
|
|
@@ -876,6 +898,7 @@ export function Slider({ref, ...props}: SliderProps) {
|
|
|
876
898
|
},
|
|
877
899
|
[
|
|
878
900
|
isDisabled,
|
|
901
|
+
isHorizontal,
|
|
879
902
|
isRange,
|
|
880
903
|
values,
|
|
881
904
|
step,
|