@astryxdesign/core 0.6.1 → 0.6.2-canary.023c3e4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/dist/BottomSheet/BottomSheet.d.ts +1 -1
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +3 -1
- package/dist/BottomSheet/BottomSheetPanel.d.ts +7 -5
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +56 -19
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +2 -2
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +12 -2
- package/dist/Collapsible/Collapsible.d.ts.map +1 -1
- package/dist/Collapsible/Collapsible.js +6 -1
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +1 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +1 -1
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +4 -4
- package/dist/DateInput/NativeDateField.d.ts.map +1 -1
- package/dist/DateInput/NativeDateField.js +16 -16
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +2 -2
- package/dist/DateRangeInput/DateRangeInput.d.ts +3 -0
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +20 -13
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +8 -8
- package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
- package/dist/DateTimeInput/TouchDateTimeField.js +4 -4
- package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +9 -3
- package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -1
- package/dist/DateTimeInput/nativePickerSegmentStyles.js +3 -3
- package/dist/Dialog/DialogHeader.d.ts +1 -1
- package/dist/Dialog/DialogHeader.d.ts.map +1 -1
- package/dist/Dialog/DialogHeader.js +10 -7
- package/dist/Field/PanelSearchInput.d.ts.map +1 -1
- package/dist/Field/PanelSearchInput.js +1 -1
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +16 -10
- package/dist/Kbd/Kbd.d.ts +5 -3
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +36 -42
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +6 -2
- package/dist/Markdown/Markdown.d.ts +18 -4
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.fr23.bench.d.ts +2 -0
- package/dist/Markdown/Markdown.fr23.bench.d.ts.map +1 -0
- package/dist/Markdown/Markdown.fr23.bench.js +150 -0
- package/dist/Markdown/Markdown.fr23.sampling.d.ts +2 -0
- package/dist/Markdown/Markdown.fr23.sampling.d.ts.map +1 -0
- package/dist/Markdown/Markdown.fr23.sampling.js +43 -0
- package/dist/Markdown/Markdown.js +311 -114
- package/dist/Markdown/ast.d.ts +162 -0
- package/dist/Markdown/ast.d.ts.map +1 -0
- package/dist/Markdown/ast.js +74 -0
- package/dist/Markdown/index.d.ts +5 -3
- package/dist/Markdown/index.d.ts.map +1 -1
- package/dist/Markdown/index.js +3 -2
- package/dist/Markdown/parser/index.d.ts +10 -0
- package/dist/Markdown/parser/index.d.ts.map +1 -0
- package/dist/Markdown/parser/index.js +10 -0
- package/dist/Markdown/parser.d.ts +220 -38
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +1072 -188
- package/dist/Markdown/plugins/frontmatter.d.ts +32 -0
- package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -0
- package/dist/Markdown/plugins/frontmatter.js +132 -0
- package/dist/Markdown/plugins/index.d.ts +16 -0
- package/dist/Markdown/plugins/index.d.ts.map +1 -0
- package/dist/Markdown/plugins/index.js +13 -0
- package/dist/Markdown/plugins/protocol.d.ts +191 -0
- package/dist/Markdown/plugins/protocol.d.ts.map +1 -0
- package/dist/Markdown/plugins/protocol.js +957 -0
- package/dist/Markdown/plugins/semanticFence.d.ts +36 -0
- package/dist/Markdown/plugins/semanticFence.d.ts.map +1 -0
- package/dist/Markdown/plugins/semanticFence.js +213 -0
- package/dist/Markdown/plugins/sourceDecoration.d.ts +76 -0
- package/dist/Markdown/plugins/sourceDecoration.d.ts.map +1 -0
- package/dist/Markdown/plugins/sourceDecoration.js +393 -0
- package/dist/Markdown/plugins/textTransform.d.ts +30 -0
- package/dist/Markdown/plugins/textTransform.d.ts.map +1 -0
- package/dist/Markdown/plugins/textTransform.js +374 -0
- package/dist/Markdown/remark.d.ts +196 -0
- package/dist/Markdown/remark.d.ts.map +1 -0
- package/dist/Markdown/remark.js +1062 -0
- package/dist/Markdown/url.d.ts +4 -0
- package/dist/Markdown/url.d.ts.map +1 -0
- package/dist/Markdown/url.js +25 -0
- package/dist/Markdown/utils.d.ts +4 -2
- package/dist/Markdown/utils.d.ts.map +1 -1
- package/dist/Markdown/utils.js +2 -1
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +4 -4
- package/dist/Outline/index.d.ts +1 -0
- package/dist/Outline/index.d.ts.map +1 -1
- package/dist/Outline/parseOutlineFromMarkdown.d.ts +7 -1
- package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
- package/dist/Outline/parseOutlineFromMarkdown.js +14 -8
- package/dist/Outline/useOutlineFromMarkdown.d.ts +3 -1
- package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
- package/dist/Outline/useOutlineFromMarkdown.js +8 -3
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +36 -31
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +13 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +5 -5
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +1 -1
- package/dist/SideNav/SideNav.d.ts +2 -1
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +9 -3
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +19 -10
- package/dist/Spinner/Spinner.d.ts +1 -1
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +45 -21
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +11 -0
- package/dist/TabList/Tab.d.ts +1 -1
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +20 -7
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +1 -1
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +2 -2
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +4 -4
- package/dist/ToggleButton/ToggleButton.d.ts +2 -1
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +7 -1
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +16 -7
- package/dist/astryx.css +19 -8
- package/dist/hooks/scrollKeyboardDelegation.d.ts +3 -0
- package/dist/hooks/scrollKeyboardDelegation.d.ts.map +1 -0
- package/dist/hooks/scrollKeyboardDelegation.js +146 -0
- package/dist/hooks/useScrollableArea.d.ts +6 -2
- package/dist/hooks/useScrollableArea.d.ts.map +1 -1
- package/dist/hooks/useScrollableArea.js +17 -5
- package/dist/utils/interactionOverlay.stylex.d.ts +8 -0
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +9 -0
- package/locales/en.json +16 -0
- package/locales/pseudo.json +12 -0
- package/package.json +22 -5
- package/scripts/agent-doc-state.mjs +1 -1
- package/src/BottomSheet/BottomSheet.doc.mjs +8 -1
- package/src/BottomSheet/BottomSheet.spec.md +46 -20
- package/src/BottomSheet/BottomSheet.test.tsx +6 -3
- package/src/BottomSheet/BottomSheet.tsx +3 -1
- package/src/BottomSheet/BottomSheetKeyboard.test.tsx +195 -0
- package/src/BottomSheet/BottomSheetPanel.test.tsx +11 -1
- package/src/BottomSheet/BottomSheetPanel.tsx +49 -16
- package/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts +344 -0
- package/src/Button/__tests__/Button.a11y.chromium.spec.ts +17 -1
- package/src/Button/__tests__/Button.a11y.known-failures.ts +0 -29
- package/src/Button/__tests__/Button.a11y.renders.tsx +9 -2
- package/src/Button/__tests__/Button.a11y.states.ts +9 -0
- package/src/Chat/ChatComposerInput.doc.mjs +3 -3
- package/src/Chat/ChatComposerInput.tsx +10 -2
- package/src/CheckboxInput/CheckboxInput.doc.mjs +11 -0
- package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
- package/src/CheckboxInput/CheckboxInput.tsx +21 -1
- package/src/ClickableCard/ClickableCard.test.tsx +102 -5
- package/src/Collapsible/Collapsible.doc.mjs +11 -0
- package/src/Collapsible/Collapsible.test.tsx +21 -0
- package/src/Collapsible/Collapsible.tsx +5 -0
- package/src/CommandPalette/CommandPaletteInput.tsx +6 -1
- package/src/ComplexSelector/ComplexSelector.tsx +6 -1
- package/src/DateInput/DateInput.tsx +6 -1
- package/src/DateInput/NativeDateField.tsx +24 -8
- package/src/DateInput/TouchDateField.tsx +6 -2
- package/src/DateRangeInput/DateRangeInput.doc.mjs +35 -7
- package/src/DateRangeInput/DateRangeInput.spec.md +203 -0
- package/src/DateRangeInput/DateRangeInput.test.tsx +100 -4
- package/src/DateRangeInput/DateRangeInput.tsx +35 -21
- package/src/DateTimeInput/DateTimeInput.tsx +6 -1
- package/src/DateTimeInput/TouchDateTimeField.tsx +6 -1
- package/src/DateTimeInput/nativePickerSegmentStyles.ts +24 -8
- package/src/Dialog/Dialog.doc.mjs +3 -0
- package/src/Dialog/Dialog.spec.md +1 -1
- package/src/Dialog/DialogHeader.doc.mjs +38 -0
- package/src/Dialog/DialogHeader.test.tsx +49 -0
- package/src/Dialog/DialogHeader.tsx +23 -4
- package/src/Dialog/modules/DialogHeader.spec.md +152 -0
- package/src/Field/PanelSearchInput.tsx +5 -2
- package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +1 -1
- package/src/FileInput/FileInput.doc.mjs +2 -0
- package/src/FileInput/FileInput.spec.md +199 -0
- package/src/FileInput/FileInput.test.tsx +14 -0
- package/src/FileInput/FileInput.tsx +24 -5
- package/src/Kbd/Kbd.doc.mjs +3 -3
- package/src/Kbd/Kbd.test.tsx +57 -1
- package/src/Kbd/Kbd.tsx +57 -37
- package/src/Layout/LayoutFooter.doc.mjs +17 -0
- package/src/Link/Link.doc.mjs +11 -0
- package/src/Link/Link.test.tsx +24 -0
- package/src/Link/Link.tsx +5 -0
- package/src/Markdown/Markdown.doc.mjs +439 -42
- package/src/Markdown/Markdown.fr23.bench.ts +174 -0
- package/src/Markdown/Markdown.fr23.perf.test.ts +249 -0
- package/src/Markdown/Markdown.fr23.sampling.ts +49 -0
- package/src/Markdown/Markdown.helpers.perf.test.ts +489 -0
- package/src/Markdown/Markdown.public.test.ts +280 -0
- package/src/Markdown/Markdown.spec.md +304 -72
- package/src/Markdown/Markdown.test.tsx +118 -3
- package/src/Markdown/Markdown.tsx +489 -129
- package/src/Markdown/ast.test.ts +307 -0
- package/src/Markdown/ast.ts +337 -0
- package/src/Markdown/incremental.test.ts +187 -7
- package/src/Markdown/index.ts +43 -1
- package/src/Markdown/modules/remark.spec.md +269 -0
- package/src/Markdown/parser/index.ts +64 -0
- package/src/Markdown/parser.perf.test.ts +3 -1
- package/src/Markdown/parser.public.test.ts +103 -0
- package/src/Markdown/parser.test.ts +122 -0
- package/src/Markdown/parser.ts +1721 -248
- package/src/Markdown/plugins/frontmatter.test.tsx +125 -0
- package/src/Markdown/plugins/frontmatter.ts +198 -0
- package/src/Markdown/plugins/index.ts +53 -0
- package/src/Markdown/plugins/protocol.test.tsx +1022 -0
- package/src/Markdown/plugins/protocol.ts +1618 -0
- package/src/Markdown/plugins/semanticFence.test.tsx +546 -0
- package/src/Markdown/plugins/semanticFence.ts +424 -0
- package/src/Markdown/plugins/sourceDecoration.test.tsx +799 -0
- package/src/Markdown/plugins/sourceDecoration.ts +539 -0
- package/src/Markdown/plugins/textTransform.test.ts +467 -0
- package/src/Markdown/plugins/textTransform.ts +554 -0
- package/src/Markdown/remark.test.tsx +1877 -0
- package/src/Markdown/remark.ts +1574 -0
- package/src/Markdown/url.ts +30 -0
- package/src/Markdown/utils.ts +19 -1
- package/src/MultiSelector/MultiSelector.test.tsx +1 -35
- package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +128 -0
- package/src/NumberInput/NumberInput.tsx +6 -1
- package/src/Outline/Outline.doc.mjs +7 -5
- package/src/Outline/Outline.spec.md +1 -1
- package/src/Outline/index.ts +1 -0
- package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +142 -0
- package/src/Outline/parseOutlineFromMarkdown.ts +40 -10
- package/src/Outline/useOutlineFromMarkdown.ts +15 -3
- package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
- package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
- package/src/ProgressBar/ProgressBar.test.tsx +29 -11
- package/src/ProgressBar/ProgressBar.tsx +59 -72
- package/src/RadioList/RadioList.doc.mjs +11 -0
- package/src/RadioList/RadioList.test.tsx +32 -0
- package/src/RadioList/RadioListItem.tsx +25 -1
- package/src/ScrollableArea/modules/useScrollableArea.spec.md +50 -22
- package/src/SegmentedControl/SegmentedControl.doc.mjs +2 -2
- package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +6 -9
- package/src/Selector/Selector.spec.md +12 -9
- package/src/Selector/Selector.tsx +6 -1
- package/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts +130 -0
- package/src/Selector/__tests__/Listbox.a11y.dom.ts +38 -0
- package/src/Selector/__tests__/Listbox.a11y.inventory.test.ts +63 -0
- package/src/Selector/__tests__/Listbox.a11y.known-failures.ts +37 -0
- package/src/Selector/__tests__/Listbox.a11y.states.ts +232 -0
- package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +121 -0
- package/src/SideNav/SideNav.doc.mjs +1 -1
- package/src/SideNav/SideNav.test.tsx +10 -0
- package/src/SideNav/SideNav.tsx +14 -2
- package/src/Slider/Slider.doc.mjs +27 -0
- package/src/Slider/Slider.spec.md +61 -47
- package/src/Slider/Slider.test.tsx +146 -0
- package/src/Slider/Slider.tsx +37 -13
- package/src/Spinner/Spinner.doc.mjs +6 -3
- package/src/Spinner/Spinner.test.tsx +41 -0
- package/src/Spinner/Spinner.tsx +62 -32
- package/src/Switch/Switch.doc.mjs +11 -0
- package/src/Switch/Switch.test.tsx +16 -0
- package/src/Switch/Switch.tsx +28 -0
- package/src/TabList/Tab.tsx +35 -7
- package/src/TabList/TabList.doc.mjs +11 -0
- package/src/TabList/TabList.test.tsx +66 -0
- package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +1 -34
- package/src/TextArea/TextArea.tsx +6 -1
- package/src/TextInput/TextInput.test.tsx +20 -1
- package/src/TextInput/TextInput.tsx +6 -1
- package/src/TimeInput/TimeInput.tsx +6 -1
- package/src/ToggleButton/ToggleButton.test.tsx +133 -0
- package/src/ToggleButton/ToggleButton.tsx +9 -2
- package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
- package/src/Tokenizer/Tokenizer.spec.md +142 -75
- package/src/TreeList/TreeList.spec.md +123 -75
- package/src/Typeahead/BaseTypeahead.spec.md +4 -3
- package/src/Typeahead/BaseTypeahead.tsx +21 -7
- package/src/Typeahead/Typeahead.test.tsx +53 -0
- package/src/__tests__/PressedState.a11y.chromium.spec.ts +813 -0
- package/src/__tests__/pressState.ts +93 -0
- package/src/hooks/scrollKeyboardDelegation.test.ts +155 -0
- package/src/hooks/scrollKeyboardDelegation.ts +233 -0
- package/src/hooks/useScrollableArea.doc.mjs +15 -3
- package/src/hooks/useScrollableArea.test.tsx +59 -1
- package/src/hooks/useScrollableArea.ts +34 -10
- package/src/inputFontFloor.test.ts +106 -0
- package/src/theme/derivedVarRegistry.test.ts +6 -4
- package/src/utils/interactionOverlay.stylex.ts +20 -0
|
@@ -10,7 +10,12 @@ approved_by: cixzhang
|
|
|
10
10
|
approved_at: 2026-09-11
|
|
11
11
|
owners: [cixzhang]
|
|
12
12
|
review_triggers: [public-api, behavior, accessibility]
|
|
13
|
-
verified_by:
|
|
13
|
+
verified_by:
|
|
14
|
+
[
|
|
15
|
+
packages/core/src/hooks/useScrollableArea.test.tsx,
|
|
16
|
+
packages/core/src/hooks/scrollKeyboardDelegation.test.ts,
|
|
17
|
+
packages/core/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts,
|
|
18
|
+
]
|
|
14
19
|
parent_component: component:ScrollableArea
|
|
15
20
|
references: [architecture:public-component-api, spec:AST-025/DEC-1]
|
|
16
21
|
---
|
|
@@ -47,24 +52,25 @@ and scroll chaining without inserting structure.
|
|
|
47
52
|
|
|
48
53
|
## Public API and concepts
|
|
49
54
|
|
|
50
|
-
| Concept | Closed values or states
|
|
51
|
-
| -------------- |
|
|
52
|
-
| axis | `inline`, `block`, `both`
|
|
53
|
-
| keyboard owner | `content`, named `viewport`
|
|
54
|
-
| overscroll | `allow`, `contain`
|
|
55
|
-
| axis state | `isScrollable`, `atStart`, `atEnd`
|
|
55
|
+
| Concept | Closed values or states | Meaning | Default | Owner | Stability |
|
|
56
|
+
| -------------- | ------------------------------------------------------ | --------------------------------------- | ------------------------- | ----------------------------------------- | --------- |
|
|
57
|
+
| axis | `inline`, `block`, `both` | requested logical scroll intent | required | `spec:AST-025` | stable |
|
|
58
|
+
| keyboard owner | `content`, named `viewport`, named `contentOrViewport` | fixed or automatic keyboard scroll path | required | `spec:AST-025` | stable |
|
|
59
|
+
| overscroll | `allow`, `contain` | edge propagation on effective axes | `allow` | `spec:AST-025` | stable |
|
|
60
|
+
| axis state | `isScrollable`, `atStart`, `atEnd` | effective ownership and logical edges | inactive, both edges true | `module:ScrollableArea/useScrollableArea` | stable |
|
|
56
61
|
|
|
57
62
|
## Behavioral contract
|
|
58
63
|
|
|
59
|
-
| ID | Invariant
|
|
60
|
-
| --- |
|
|
61
|
-
| FR1 | An axis MUST be effective only when requested, computed overflow is scroll-capable, and geometry exceeds 1px.
|
|
62
|
-
| FR2 | Viewport and content geometry MUST be observed; invalidations MUST coalesce and preserve the last valid state while unmeasurable.
|
|
63
|
-
| FR3 | Logical edges and behavior-owned physical overflow MUST map through direction and horizontal, vertical, or sideways writing modes without requiring adopters to recreate the mapping.
|
|
64
|
-
| FR4 | The nearest registered effective owner MUST win independently by axis.
|
|
65
|
-
| FR5 | Viewport keyboard props and overscroll containment MUST appear only while applicable requested axes are effective.
|
|
66
|
-
| FR6 | Prop getters MUST compose refs and preserve caller handlers/classes/styles/ARIA while behavior-owned accessibility and non-cancellable behavior win conflicts.
|
|
67
|
-
| FR7 | The viewport prop getter MUST consume caller `xstyle` and internally compose geometry-driven `clip`/`auto`/`hidden` overflow plus explicit fitting Sticky containment.
|
|
64
|
+
| ID | Invariant | Basis | Acceptance and implementation state |
|
|
65
|
+
| --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | --------------------------------------- |
|
|
66
|
+
| FR1 | An axis MUST be effective only when requested, computed overflow is scroll-capable, and geometry exceeds 1px. | `spec:AST-025` FR1–FR4 | implemented |
|
|
67
|
+
| FR2 | Viewport and content geometry MUST be observed; invalidations MUST coalesce and preserve the last valid state while unmeasurable. | `spec:AST-025` FR5–FR9 | implemented |
|
|
68
|
+
| FR3 | Logical edges and behavior-owned physical overflow MUST map through direction and horizontal, vertical, or sideways writing modes without requiring adopters to recreate the mapping. | `spec:AST-025` FR1, FR3–FR4 | implemented |
|
|
69
|
+
| FR4 | The nearest registered effective owner MUST win independently by axis. | `spec:AST-025` FR10, FR18 | implemented privately for future Sticky |
|
|
70
|
+
| FR5 | Viewport keyboard props and overscroll containment MUST appear only while applicable requested axes are effective. | `spec:AST-025` FR12–FR14 | implemented |
|
|
71
|
+
| FR6 | Prop getters MUST compose refs and preserve caller handlers/classes/styles/ARIA while behavior-owned accessibility and non-cancellable behavior win conflicts. | `spec:AST-025` IR4 | implemented |
|
|
72
|
+
| FR7 | The viewport prop getter MUST consume caller `xstyle` and internally compose geometry-driven `clip`/`auto`/`hidden` overflow plus explicit fitting Sticky containment. | `spec:AST-025` FR21 | implemented |
|
|
73
|
+
| FR8 | `contentOrViewport` MUST retain the named overflowing viewport, inspect the first sequential descendant on forward Tab entry, and delegate only to a native link/button outside navigation-key-owning surfaces. Reverse traversal from the delegated first child skips the viewport. Pointer/programmatic focus never delegates; eligibility is checked again on the next entry. | `spec:AST-025` FR12–FR13 | implemented |
|
|
68
74
|
|
|
69
75
|
### Transformation and precedence order
|
|
70
76
|
|
|
@@ -82,9 +88,20 @@ and scroll chaining without inserting structure.
|
|
|
82
88
|
|
|
83
89
|
## Accessibility contract
|
|
84
90
|
|
|
85
|
-
- **AR1 — Named viewport owner.**
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
- **AR1 — Named viewport owner.** Fixed viewport and automatic access require a
|
|
92
|
+
label and retain `tabIndex=0` while effectively scrollable. Automatic access
|
|
93
|
+
delegates only on forward keyboard entry; the descendant retains its own name
|
|
94
|
+
inside the named viewport group/region.
|
|
95
|
+
- **AR2 — Focus continuity.** Content, eligibility, and geometry changes never
|
|
96
|
+
move or blur focus. A viewport losing overflow while focused remains
|
|
97
|
+
programmatically focusable. Reverse traversal from the delegated first child
|
|
98
|
+
skips the viewport, including after visiting later descendants.
|
|
99
|
+
- **AR3 — Conservative eligibility.** Inspect the first sequential target; do not
|
|
100
|
+
search past an excluded one for a link/button. Composite roles, editable
|
|
101
|
+
surfaces, native or registered nested scroll owners, positive tab ordering,
|
|
102
|
+
popup triggers, and unproven interactive roles retain the viewport path.
|
|
103
|
+
- **AR4 — Native scrolling.** Delegation MUST preserve Arrow/Page access to both
|
|
104
|
+
scroll edges without implementing JavaScript scrolling.
|
|
88
105
|
|
|
89
106
|
## Design relationships
|
|
90
107
|
|
|
@@ -97,9 +114,9 @@ No visual representation is owned by this hook.
|
|
|
97
114
|
|
|
98
115
|
## Verification map
|
|
99
116
|
|
|
100
|
-
| Contract | Verification
|
|
101
|
-
| ---------------- |
|
|
102
|
-
| FR1–
|
|
117
|
+
| Contract | Verification | Representative states | Mutation or failure expectation |
|
|
118
|
+
| ---------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
119
|
+
| FR1–FR8, AR1–AR4 | `useScrollableArea.test.tsx`, `scrollKeyboardDelegation.test.ts`, and `BottomSheetKeyboard.a11y.browser.spec.ts` | horizontal/vertical/sideways, LTR/RTL, fit/overflow/edge, fixed/automatic keyboard owners, dynamic descendant eligibility, nested owners, focused loss | style-only or geometry-only ownership, physical-edge leakage, duplicate/missing keyboard path, stale state, ref loss, or dead containment fails |
|
|
103
120
|
|
|
104
121
|
## Decision log
|
|
105
122
|
|
|
@@ -111,6 +128,17 @@ No visual representation is owned by this hook.
|
|
|
111
128
|
One returned prop object per owned element keeps refs, accessibility, handlers,
|
|
112
129
|
and behavior together without making consumer spread order part of correctness.
|
|
113
130
|
|
|
131
|
+
### DEC-2 — Automatic ownership is shared behavior
|
|
132
|
+
|
|
133
|
+
**Reference:** `module:ScrollableArea/useScrollableArea/DEC-2`
|
|
134
|
+
**Decider:** cixzhang, 2026-09-13
|
|
135
|
+
|
|
136
|
+
`contentOrViewport` retains the named overflowing viewport and delegates only on
|
|
137
|
+
forward Tab entry to a safe first sequential native link/button. This replaces
|
|
138
|
+
continuous keyboard-eligibility observation. Geometry observation is unchanged;
|
|
139
|
+
keyboard eligibility adds no subtree or ancestor observers and is inspected anew
|
|
140
|
+
at each entry. BottomSheet is a proving composition of this shared behavior.
|
|
141
|
+
|
|
114
142
|
## Open questions
|
|
115
143
|
|
|
116
144
|
None.
|
|
@@ -129,9 +129,9 @@ export const docs = {
|
|
|
129
129
|
category: 'Color contrast',
|
|
130
130
|
criterion: '1.4.3 Contrast (Minimum)',
|
|
131
131
|
requirement: '4.5:1',
|
|
132
|
-
states: ['Rest', 'Hover', 'Selected'],
|
|
132
|
+
states: ['Rest', 'Hover', 'Pointer down', 'Selected'],
|
|
133
133
|
description:
|
|
134
|
-
'Each label must have at least 4.5:1 contrast with its segment background. Check unselected, Hover, and selected colors as they appear on screen.',
|
|
134
|
+
'Each label must have at least 4.5:1 contrast with its segment background. Check unselected, Hover, Pointer down, and selected colors as they appear on screen. For Hover and Pointer down, measure the final background after the overlay is applied.',
|
|
135
135
|
},
|
|
136
136
|
{
|
|
137
137
|
name: 'Essential icon',
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
16
16
|
import {render, screen, fireEvent, waitFor} from '@testing-library/react';
|
|
17
17
|
import userEvent from '@testing-library/user-event';
|
|
18
|
+
import {hasPressedArm} from '../__tests__/pressState';
|
|
18
19
|
import {SegmentedControl} from './SegmentedControl';
|
|
19
20
|
import {SegmentedControlItem} from './SegmentedControlItem';
|
|
20
21
|
import {
|
|
@@ -672,3 +673,33 @@ describe('forced colors (WCAG 1.4.11)', () => {
|
|
|
672
673
|
expect(getAllInjectedCss()).toContain('forced-color-adjust: none;');
|
|
673
674
|
});
|
|
674
675
|
});
|
|
676
|
+
|
|
677
|
+
describe('pressed state', () => {
|
|
678
|
+
it('paints the pressed overlay on a segment while it is pressed', () => {
|
|
679
|
+
render(
|
|
680
|
+
<SegmentedControl value="grid" onChange={() => {}} label="View mode">
|
|
681
|
+
<SegmentedControlItem value="grid" label="Grid" />
|
|
682
|
+
<SegmentedControlItem value="list" label="List" />
|
|
683
|
+
</SegmentedControl>,
|
|
684
|
+
);
|
|
685
|
+
// The unselected segment is the one a press can change; it carries the
|
|
686
|
+
// system's hover and pressed overlay.
|
|
687
|
+
expect(hasPressedArm(screen.getByRole('radio', {name: 'List'}))).toBe(true);
|
|
688
|
+
// The selected segment keeps its raised surface as it is.
|
|
689
|
+
expect(hasPressedArm(screen.getByRole('radio', {name: 'Grid'}))).toBe(
|
|
690
|
+
false,
|
|
691
|
+
);
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
it('does not press a disabled segment', () => {
|
|
695
|
+
render(
|
|
696
|
+
<SegmentedControl value="grid" onChange={() => {}} label="View mode">
|
|
697
|
+
<SegmentedControlItem value="grid" label="Grid" />
|
|
698
|
+
<SegmentedControlItem value="list" label="List" isDisabled />
|
|
699
|
+
</SegmentedControl>,
|
|
700
|
+
);
|
|
701
|
+
expect(hasPressedArm(screen.getByRole('radio', {name: 'List'}))).toBe(
|
|
702
|
+
false,
|
|
703
|
+
);
|
|
704
|
+
});
|
|
705
|
+
});
|
|
@@ -33,6 +33,7 @@ import {mergeProps, composeEventHandlers} from '../utils';
|
|
|
33
33
|
import type {BaseProps} from '../BaseProps';
|
|
34
34
|
import {themeProps} from '../utils/themeProps';
|
|
35
35
|
import {focusOutlineProps} from '../utils/focusOutline.stylex';
|
|
36
|
+
import {interactionOverlayStyles} from '../utils/interactionOverlay.stylex';
|
|
36
37
|
|
|
37
38
|
export interface SegmentedControlItemProps extends BaseProps<HTMLButtonElement> {
|
|
38
39
|
ref?: React.Ref<HTMLButtonElement>;
|
|
@@ -91,14 +92,6 @@ const styles = stylex.create({
|
|
|
91
92
|
transitionDuration: durationVars['--duration-fast'],
|
|
92
93
|
transitionTimingFunction: easeVars['--ease-standard'],
|
|
93
94
|
},
|
|
94
|
-
hover: {
|
|
95
|
-
backgroundColor: {
|
|
96
|
-
default: null,
|
|
97
|
-
':hover:where(:not(:disabled,[aria-disabled="true"]))': {
|
|
98
|
-
'@media (hover: hover)': colorVars['--color-overlay-hover'],
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
95
|
selected: {
|
|
103
96
|
// Forced colors (Windows High Contrast) strips the painted surface fill
|
|
104
97
|
// and box shadow, which would leave the selected segment with no state
|
|
@@ -248,7 +241,11 @@ export function SegmentedControlItem({
|
|
|
248
241
|
sizeStyles[size],
|
|
249
242
|
isFill && styles.fill,
|
|
250
243
|
isSelected && styles.selected,
|
|
251
|
-
|
|
244
|
+
// The shared hover and pressed overlay, on the segments a press can
|
|
245
|
+
// change: the selected segment keeps its raised surface as it is.
|
|
246
|
+
!isSelected &&
|
|
247
|
+
!isItemDisabled &&
|
|
248
|
+
interactionOverlayStyles.backgroundColor,
|
|
252
249
|
isItemDisabled && styles.disabled,
|
|
253
250
|
xstyle,
|
|
254
251
|
),
|
|
@@ -14,6 +14,8 @@ verified_by:
|
|
|
14
14
|
[
|
|
15
15
|
packages/core/src/Selector/Selector.test.tsx,
|
|
16
16
|
packages/core/src/Selector/Selector.source-build.test.mjs,
|
|
17
|
+
packages/core/src/Selector/__tests__/Selector.listbox.a11y.test.tsx,
|
|
18
|
+
packages/core/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts,
|
|
17
19
|
]
|
|
18
20
|
modules: []
|
|
19
21
|
families: [family:input-fields]
|
|
@@ -274,15 +276,16 @@ in `spec:AST-004` as current runtime behavior.
|
|
|
274
276
|
|
|
275
277
|
## Verification map
|
|
276
278
|
|
|
277
|
-
| Contract | Verification
|
|
278
|
-
| --------------------- |
|
|
279
|
-
| FR1, AR2, AR3 | `Selector.test.tsx` selection, focus, and keyboard suites
|
|
280
|
-
| FR2 | placement and selected-item geometry tests
|
|
281
|
-
| FR3, AV1 | `itemMarkColumn` source inspection plus indicator-position and replacement-indicator tests
|
|
282
|
-
| FR4, AR1, AR2 | presentation tests plus `aria-haspopup` source review
|
|
283
|
-
|
|
|
284
|
-
|
|
|
285
|
-
|
|
|
279
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
280
|
+
| --------------------- | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------- |
|
|
281
|
+
| FR1, AR2, AR3 | `Selector.test.tsx` selection, focus, and keyboard suites | closed/open, search/non-search, disabled option | Removing selection wiring, focus movement, or keyboard behavior fails the named interaction tests | `audit:Selector/behavior` |
|
|
282
|
+
| FR2 | placement and selected-item geometry tests | default, explicit, RTL, transformed entry | Using the wrong positioning model fails the expected position-area or margin | `audit:Selector/behavior` |
|
|
283
|
+
| FR3, AV1 | `itemMarkColumn` source inspection plus indicator-position and replacement-indicator tests | start/end, selected/unselected, check/radio | Removing the wrapper fails row-structure tests; changing its reserved width requires source/layout review | `audit:Selector/design-rendered` |
|
|
284
|
+
| FR4, AR1, AR2 | presentation tests plus `aria-haspopup` source review | pointer, compact coarse pointer, search/non-search | Wrong Popover/dialog roles or focus destinations fail tests; `aria-haspopup` values require source/a11y review | `audit:Selector/accessibility` |
|
|
285
|
+
| AR1, AV2 | `Selector.listbox.a11y.test.tsx` and `Listbox.a11y.chromium.spec.ts` against the shared Listbox contract | popover/sheet, flat/grouped, selected/unselected, disabled, search, loading, custom RTL | A new or wider loss of role, listbox/option name, state, or ownership gates; the exact unnamed no-search sheet state remains known debt | `audit:Selector/accessibility` |
|
|
286
|
+
| FR5 | loading, empty-state, and live-region tests | empty options, unmatched search, loading | Empty/no-results output appears or is announced while loading | `audit:Selector/behavior` |
|
|
287
|
+
| FR6, AR4 | read-only interaction, form, ARIA, and theme-state tests | search/non-search, clearable, open→read-only, disabled precedence | A value changes, popup or edit affordance remains, form value disappears, or read-only semantics are absent | `audit:Selector/accessibility` |
|
|
288
|
+
| source-build contract | `Selector.source-build.test.mjs` | package source compiled by consumer Babel | Moving evaluated StyleX values outside the supported source form fails compilation | `audit:Selector/code-health` |
|
|
286
289
|
|
|
287
290
|
## Decision log
|
|
288
291
|
|
|
@@ -102,9 +102,14 @@ const styles = stylex.create({
|
|
|
102
102
|
paddingBlock: spacingVars['--spacing-2'],
|
|
103
103
|
paddingInline: spacingVars['--spacing-3'],
|
|
104
104
|
fontFamily: typographyVars['--font-family-body'],
|
|
105
|
+
// The 16px floor is iOS-only: iOS Safari zooms the page when a focused
|
|
106
|
+
// control sits under 16px, and only iOS WebKit implements
|
|
107
|
+
// -webkit-touch-callout to key the coarse-pointer floor to it.
|
|
105
108
|
fontSize: {
|
|
106
109
|
default: typeScaleVars['--text-label-size'],
|
|
107
|
-
'@media (pointer: coarse)':
|
|
110
|
+
'@media (pointer: coarse)': {
|
|
111
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-label-size']})`,
|
|
112
|
+
},
|
|
108
113
|
},
|
|
109
114
|
// A FIXED line box, not the ratio: the trigger's padding is derived from
|
|
110
115
|
// one line being `--spacing-5` tall, and a ratio makes the line box track
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Listbox.a11y.chromium.spec.ts
|
|
5
|
+
* @input Uses the Listbox contract, browser harness, state inventory, and checked-in stories
|
|
6
|
+
* @output DOM/accessibility-tree evidence for each real Listbox binding part
|
|
7
|
+
* @position Browser binding; keyboard policy, pixels, and real AT remain separate owners.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {expect, test} from '@playwright/test';
|
|
11
|
+
import {
|
|
12
|
+
LISTBOX_PATTERN,
|
|
13
|
+
checkAccessibilitySpec,
|
|
14
|
+
blockingResults,
|
|
15
|
+
formatFailures,
|
|
16
|
+
formatReport,
|
|
17
|
+
summarize,
|
|
18
|
+
neverExercised,
|
|
19
|
+
unmatchedKnownFailures,
|
|
20
|
+
type BindingResult,
|
|
21
|
+
} from '@astryxdesign/a11y-spec';
|
|
22
|
+
import {
|
|
23
|
+
createChromiumHarness,
|
|
24
|
+
holdMotionStill,
|
|
25
|
+
} from '@astryxdesign/a11y-spec/chromium';
|
|
26
|
+
import {
|
|
27
|
+
serveStorybook,
|
|
28
|
+
DEFAULT_STORYBOOK_DIR,
|
|
29
|
+
type StaticServer,
|
|
30
|
+
} from '@astryxdesign/a11y-spec/storybook';
|
|
31
|
+
import {LISTBOX_KNOWN_FAILURES} from './Listbox.a11y.known-failures';
|
|
32
|
+
import {LISTBOX_SCENARIOS, listboxParts} from './Listbox.a11y.states';
|
|
33
|
+
|
|
34
|
+
let storybook: StaticServer;
|
|
35
|
+
const observedResults: BindingResult[] = [];
|
|
36
|
+
test.beforeAll(async () => {
|
|
37
|
+
storybook = await serveStorybook(
|
|
38
|
+
process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
test.afterAll(async () => {
|
|
42
|
+
await storybook?.close();
|
|
43
|
+
expect(
|
|
44
|
+
unmatchedKnownFailures(LISTBOX_KNOWN_FAILURES, observedResults),
|
|
45
|
+
).toEqual([]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
for (const scenario of LISTBOX_SCENARIOS) {
|
|
49
|
+
test(`${scenario.component} — ${scenario.id}`, async ({page}) => {
|
|
50
|
+
test.setTimeout(2 * 60 * 1000);
|
|
51
|
+
const cdp = await page.context().newCDPSession(page);
|
|
52
|
+
const results: BindingResult[] = [];
|
|
53
|
+
const mount = async () => {
|
|
54
|
+
await page.goto(
|
|
55
|
+
`${storybook.origin}/iframe.html?id=a11y-listbox-pattern--${scenario.id}&viewMode=story&globals=direction:${scenario.direction ?? 'ltr'}`,
|
|
56
|
+
{waitUntil: 'load'},
|
|
57
|
+
);
|
|
58
|
+
await holdMotionStill(page);
|
|
59
|
+
await expect(
|
|
60
|
+
page.locator(`[data-listbox-scenario="${scenario.id}"]`),
|
|
61
|
+
).toBeAttached();
|
|
62
|
+
await page.getByRole('listbox').waitFor({state: 'visible'});
|
|
63
|
+
if (scenario.query != null) {
|
|
64
|
+
await page.getByRole('combobox').fill(scenario.query);
|
|
65
|
+
}
|
|
66
|
+
await expect(page.getByRole('listbox').getByRole('option')).toHaveCount(
|
|
67
|
+
scenario.expectedOptions.length,
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
for (const part of listboxParts(scenario)) {
|
|
72
|
+
results.push(
|
|
73
|
+
await checkAccessibilitySpec({
|
|
74
|
+
spec: LISTBOX_PATTERN,
|
|
75
|
+
binding: `${scenario.component}.${part.role}`,
|
|
76
|
+
state: part.state,
|
|
77
|
+
facts: part.facts,
|
|
78
|
+
knownFailures: LISTBOX_KNOWN_FAILURES,
|
|
79
|
+
mount: async () => {
|
|
80
|
+
await mount();
|
|
81
|
+
const listbox = page.getByRole('listbox');
|
|
82
|
+
const subject =
|
|
83
|
+
part.role === 'listbox'
|
|
84
|
+
? listbox
|
|
85
|
+
: part.role === 'group'
|
|
86
|
+
? listbox
|
|
87
|
+
.getByRole('group')
|
|
88
|
+
.nth(scenario.groups.indexOf(part.name ?? ''))
|
|
89
|
+
: listbox.getByRole('option', {
|
|
90
|
+
name: part.name,
|
|
91
|
+
exact: true,
|
|
92
|
+
});
|
|
93
|
+
return createChromiumHarness({
|
|
94
|
+
page,
|
|
95
|
+
cdp,
|
|
96
|
+
subject,
|
|
97
|
+
related: {
|
|
98
|
+
listbox,
|
|
99
|
+
...Object.fromEntries(
|
|
100
|
+
scenario.expectedOptions.map(option => [
|
|
101
|
+
`option:${option.value}`,
|
|
102
|
+
listbox.getByRole('option', {
|
|
103
|
+
name: option.name,
|
|
104
|
+
exact: true,
|
|
105
|
+
}),
|
|
106
|
+
]),
|
|
107
|
+
),
|
|
108
|
+
...Object.fromEntries(
|
|
109
|
+
scenario.groups.map((name, index) => [
|
|
110
|
+
`group:${name}`,
|
|
111
|
+
listbox.getByRole('group').nth(index),
|
|
112
|
+
]),
|
|
113
|
+
),
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
observedResults.push(...results);
|
|
122
|
+
const report = summarize(LISTBOX_PATTERN, results);
|
|
123
|
+
console.log(formatReport(report));
|
|
124
|
+
expect(formatFailures(blockingResults(results))).toBe('');
|
|
125
|
+
expect(report.counts.unrun).toBe(0);
|
|
126
|
+
if (scenario.component === 'Selector' && scenario.groups.length > 0) {
|
|
127
|
+
expect(neverExercised(results)).toEqual([]);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Listbox.a11y.dom.ts
|
|
5
|
+
* @input Uses Vitest lifecycle hooks and the DOM Dialog prototype
|
|
6
|
+
* @output Scoped stubs for Dialog methods missing from jsdom
|
|
7
|
+
* @position DOM-only binding setup; these stubs prove no native modal behavior.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {afterAll, beforeAll} from 'vitest';
|
|
11
|
+
|
|
12
|
+
export function installListboxDialogStubs(): void {
|
|
13
|
+
const originals = new Map<string, PropertyDescriptor | undefined>();
|
|
14
|
+
beforeAll(() => {
|
|
15
|
+
for (const name of ['show', 'showModal', 'close']) {
|
|
16
|
+
originals.set(
|
|
17
|
+
name,
|
|
18
|
+
Object.getOwnPropertyDescriptor(HTMLDialogElement.prototype, name),
|
|
19
|
+
);
|
|
20
|
+
Object.defineProperty(HTMLDialogElement.prototype, name, {
|
|
21
|
+
configurable: true,
|
|
22
|
+
writable: true,
|
|
23
|
+
value: function (this: HTMLDialogElement) {
|
|
24
|
+
this.open = name !== 'close';
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
afterAll(() => {
|
|
30
|
+
for (const [name, descriptor] of originals) {
|
|
31
|
+
if (descriptor == null) {
|
|
32
|
+
Reflect.deleteProperty(HTMLDialogElement.prototype, name);
|
|
33
|
+
} else {
|
|
34
|
+
Object.defineProperty(HTMLDialogElement.prototype, name, descriptor);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Listbox.a11y.inventory.test.ts
|
|
5
|
+
* @input Uses the locked Listbox migration matrix and checklist
|
|
6
|
+
* @output Coverage-boundary and part-inventory regression checks
|
|
7
|
+
* @position Binding metadata proof, separate from runtime conformance results.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {describe, expect, it} from 'vitest';
|
|
11
|
+
import {LISTBOX_PATTERN, unansweredDimensions} from '@astryxdesign/a11y-spec';
|
|
12
|
+
import {LISTBOX_KNOWN_FAILURES} from './Listbox.a11y.known-failures';
|
|
13
|
+
import {
|
|
14
|
+
LISTBOX_SCENARIOS,
|
|
15
|
+
LISTBOX_EXCLUSIONS,
|
|
16
|
+
listboxParts,
|
|
17
|
+
} from './Listbox.a11y.states';
|
|
18
|
+
|
|
19
|
+
describe('Listbox migration inventory', () => {
|
|
20
|
+
it('accounts for every checklist dimension without claiming other evidence layers', () => {
|
|
21
|
+
expect(unansweredDimensions(LISTBOX_PATTERN)).toEqual([]);
|
|
22
|
+
expect(LISTBOX_EXCLUSIONS).toHaveLength(5);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('binds 21 scenarios and all six real semantic parts', () => {
|
|
26
|
+
expect(LISTBOX_SCENARIOS).toHaveLength(21);
|
|
27
|
+
const parts = LISTBOX_SCENARIOS.flatMap(scenario =>
|
|
28
|
+
listboxParts(scenario).map(part => ({
|
|
29
|
+
binding: `${scenario.component}.${part.role}`,
|
|
30
|
+
state: part.state,
|
|
31
|
+
})),
|
|
32
|
+
);
|
|
33
|
+
expect(parts).toHaveLength(89);
|
|
34
|
+
expect(new Set(parts.map(part => part.state)).size).toBe(89);
|
|
35
|
+
expect([...new Set(parts.map(part => part.binding))].sort()).toEqual([
|
|
36
|
+
'MultiSelector.group',
|
|
37
|
+
'MultiSelector.listbox',
|
|
38
|
+
'MultiSelector.option',
|
|
39
|
+
'Selector.group',
|
|
40
|
+
'Selector.listbox',
|
|
41
|
+
'Selector.option',
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('pins each known failure to one exact inventory part', () => {
|
|
46
|
+
const parts = LISTBOX_SCENARIOS.flatMap(scenario =>
|
|
47
|
+
listboxParts(scenario).map(part => ({
|
|
48
|
+
binding: `${scenario.component}.${part.role}`,
|
|
49
|
+
state: part.state,
|
|
50
|
+
})),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(LISTBOX_KNOWN_FAILURES).toHaveLength(2);
|
|
54
|
+
for (const record of LISTBOX_KNOWN_FAILURES) {
|
|
55
|
+
expect(
|
|
56
|
+
parts.filter(
|
|
57
|
+
part =>
|
|
58
|
+
part.binding === record.binding && part.state === record.state,
|
|
59
|
+
),
|
|
60
|
+
).toHaveLength(1);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Listbox.a11y.known-failures.ts
|
|
5
|
+
* @input Uses KnownFailure from @astryxdesign/a11y-spec
|
|
6
|
+
* @output Exact public-safe Selector and MultiSelector listbox migration failures
|
|
7
|
+
* @position AST-021 debt records; operational ownership remains outside public source.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {KnownFailure} from '@astryxdesign/a11y-spec';
|
|
11
|
+
|
|
12
|
+
export const LISTBOX_KNOWN_FAILURES: ReadonlyArray<KnownFailure> = [
|
|
13
|
+
{
|
|
14
|
+
expectation: 'listbox.exposure.identity',
|
|
15
|
+
binding: 'Selector.listbox',
|
|
16
|
+
state: 'single-sheet:listbox',
|
|
17
|
+
evidenceLayer: 'accessibility-tree',
|
|
18
|
+
failureEquals: 'the browser exposes the listbox without an accessible name',
|
|
19
|
+
standardsReference: 'WCAG 2.2 4.1.2 Name, Role, Value (Level A)',
|
|
20
|
+
userImpact:
|
|
21
|
+
'An accessibility consumer encounters an unnamed listbox in the bottom-sheet presentation and cannot identify what set of choices it contains.',
|
|
22
|
+
reason:
|
|
23
|
+
'The no-search bottom-sheet presentation renders a visible sheet heading without assigning that heading, or another label, as the listbox accessible name. This migration records the existing behavior without changing the component.',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
expectation: 'listbox.exposure.identity',
|
|
27
|
+
binding: 'MultiSelector.listbox',
|
|
28
|
+
state: 'multiple-sheet:listbox',
|
|
29
|
+
evidenceLayer: 'accessibility-tree',
|
|
30
|
+
failureEquals: 'the browser exposes the listbox without an accessible name',
|
|
31
|
+
standardsReference: 'WCAG 2.2 4.1.2 Name, Role, Value (Level A)',
|
|
32
|
+
userImpact:
|
|
33
|
+
'An accessibility consumer encounters an unnamed multi-select listbox in the bottom-sheet presentation and cannot identify what set of choices it contains.',
|
|
34
|
+
reason:
|
|
35
|
+
'The no-search bottom-sheet presentation renders a visible sheet heading without assigning that heading, or another label, as the listbox accessible name. This migration records the existing behavior without changing the component.',
|
|
36
|
+
},
|
|
37
|
+
];
|