@astryxdesign/core 0.6.1 → 0.6.2-canary.0faf070
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/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/DateRangeInput/DateRangeInput.d.ts +3 -0
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +16 -9
- 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/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +9 -3
- 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 +10 -2
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +58 -14
- package/dist/Markdown/index.d.ts +1 -1
- package/dist/Markdown/index.d.ts.map +1 -1
- package/dist/Markdown/parser.d.ts +126 -12
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +369 -34
- package/dist/Markdown/utils.d.ts +1 -1
- package/dist/Markdown/utils.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
- 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/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 +23 -15
- 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/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 +15 -6
- package/dist/astryx.css +13 -2
- 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 +7 -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/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/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 +29 -20
- 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/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 +13 -3
- 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/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 +167 -42
- package/src/Markdown/Markdown.public.test.ts +157 -0
- package/src/Markdown/Markdown.spec.md +255 -71
- package/src/Markdown/Markdown.test.tsx +107 -3
- package/src/Markdown/Markdown.tsx +116 -35
- package/src/Markdown/incremental.test.ts +175 -7
- package/src/Markdown/index.ts +6 -0
- package/src/Markdown/parser.perf.test.ts +3 -1
- package/src/Markdown/parser.test.ts +122 -0
- package/src/Markdown/parser.ts +609 -81
- package/src/Markdown/utils.ts +6 -0
- package/src/Outline/Outline.spec.md +1 -1
- package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +142 -0
- package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
- package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
- 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/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 +37 -0
- package/src/Spinner/Spinner.tsx +31 -14
- 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/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/Typeahead/BaseTypeahead.spec.md +4 -3
- package/src/Typeahead/BaseTypeahead.tsx +15 -6
- 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/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
|
),
|
|
@@ -25,7 +25,7 @@ export const docs = {
|
|
|
25
25
|
{className: 'astryx-side-nav-section'},
|
|
26
26
|
],
|
|
27
27
|
},
|
|
28
|
-
description: 'Container with five zones: header, topContent, children (scrollable), footer, and footerIcons. Supports collapsible
|
|
28
|
+
description: 'Container with five zones: header, topContent, children (scrollable), footer, and footerIcons. Supports collapsible and resizable modes.',
|
|
29
29
|
props: [
|
|
30
30
|
{
|
|
31
31
|
name: 'header',
|
|
@@ -989,6 +989,16 @@ describe('SideNav resizable', () => {
|
|
|
989
989
|
).toBeInTheDocument();
|
|
990
990
|
});
|
|
991
991
|
|
|
992
|
+
it('clips the overlay drag handle within the resizable nav bounds', () => {
|
|
993
|
+
render(<SideNav resizable>Content</SideNav>);
|
|
994
|
+
const nav = screen.getByRole('navigation');
|
|
995
|
+
const container = nav.parentElement;
|
|
996
|
+
const handle = screen.getByTestId('astryx-sidenav-resize-handle');
|
|
997
|
+
|
|
998
|
+
expect(container).toContainElement(handle);
|
|
999
|
+
expect(getComputedStyle(container!).overflow).toBe('clip');
|
|
1000
|
+
});
|
|
1001
|
+
|
|
992
1002
|
it('does not render drag handle without resizable', () => {
|
|
993
1003
|
render(<SideNav>Content</SideNav>);
|
|
994
1004
|
expect(
|
package/src/SideNav/SideNav.tsx
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* Sidebar navigation container with five zones: header + topContent (sticky together),
|
|
12
12
|
* children (scrollable), footer, and footerIcons (sticky bottom).
|
|
13
13
|
*
|
|
14
|
-
* Supports optional resize via drag handle at the inline-end edge.
|
|
14
|
+
* Supports optional resize via a drag handle at the inline-end edge. The
|
|
15
|
+
* resizable wrapper clips handle overflow while keeping its focus ring inside.
|
|
15
16
|
*
|
|
16
17
|
* SYNC: When modified, update these files to stay in sync:
|
|
17
18
|
* - /packages/core/src/SideNav/SideNav.doc.mjs
|
|
@@ -25,7 +26,7 @@ import {useCallback, useImperativeHandle, useRef, type ReactNode} from 'react';
|
|
|
25
26
|
import type {BaseProps} from '../BaseProps';
|
|
26
27
|
import * as stylex from '@stylexjs/stylex';
|
|
27
28
|
import type {StyleXStyles} from '@stylexjs/stylex';
|
|
28
|
-
import {spacingVars} from '../theme/tokens.stylex';
|
|
29
|
+
import {focusVars, spacingVars} from '../theme/tokens.stylex';
|
|
29
30
|
import {mergeProps} from '../utils';
|
|
30
31
|
import {
|
|
31
32
|
SideNavCollapseContext,
|
|
@@ -241,6 +242,16 @@ const styles = stylex.create({
|
|
|
241
242
|
display: 'flex',
|
|
242
243
|
flexShrink: 0,
|
|
243
244
|
height: '100%',
|
|
245
|
+
overflow: 'clip',
|
|
246
|
+
},
|
|
247
|
+
resizableHandle: {
|
|
248
|
+
// Keep the shared ring inside the clipping boundary. The wrapper cannot
|
|
249
|
+
// use overflow-clip-margin: that paint allowance also contributes the
|
|
250
|
+
// overlay's half-pixel spill to AppShell's scrollable overflow.
|
|
251
|
+
outlineOffset: {
|
|
252
|
+
default: '0',
|
|
253
|
+
':focus-visible': `calc(0px - ${focusVars['--focus-outline-width']})`,
|
|
254
|
+
},
|
|
244
255
|
},
|
|
245
256
|
// Topbar mode — horizontal layout for mobile top bar
|
|
246
257
|
topbar: {
|
|
@@ -658,6 +669,7 @@ export function SideNav({
|
|
|
658
669
|
position="overlay"
|
|
659
670
|
pillPlacement="end"
|
|
660
671
|
isAlwaysVisible={false}
|
|
672
|
+
xstyle={styles.resizableHandle}
|
|
661
673
|
resizable={resizableHook.props}
|
|
662
674
|
label={t('@astryx.sideNav.resizeSidebar')}
|
|
663
675
|
/>
|
|
@@ -19,6 +19,12 @@ const anatomy = [
|
|
|
19
19
|
description:
|
|
20
20
|
'Control row containing the track, thumb or thumbs, and optional text value.',
|
|
21
21
|
},
|
|
22
|
+
{
|
|
23
|
+
name: 'Interactive control',
|
|
24
|
+
required: true,
|
|
25
|
+
description:
|
|
26
|
+
'Pointer and keyboard interaction surface containing the rail, fill, marks, and thumbs.',
|
|
27
|
+
},
|
|
22
28
|
{
|
|
23
29
|
name: 'Track',
|
|
24
30
|
required: true,
|
|
@@ -235,6 +241,11 @@ export const docs = {
|
|
|
235
241
|
visualProps: ['orientation'],
|
|
236
242
|
states: ['disabled'],
|
|
237
243
|
},
|
|
244
|
+
{
|
|
245
|
+
className: 'astryx-slider-control',
|
|
246
|
+
visualProps: ['orientation'],
|
|
247
|
+
states: ['disabled'],
|
|
248
|
+
},
|
|
238
249
|
{className: 'astryx-slider-track', visualProps: ['orientation']},
|
|
239
250
|
{
|
|
240
251
|
className: 'astryx-slider-thumb',
|
|
@@ -244,6 +255,17 @@ export const docs = {
|
|
|
244
255
|
],
|
|
245
256
|
},
|
|
246
257
|
usage: {
|
|
258
|
+
accessibility: [
|
|
259
|
+
{
|
|
260
|
+
name: 'Thumb',
|
|
261
|
+
category: 'Color contrast',
|
|
262
|
+
criterion: '1.4.11 Non-text Contrast',
|
|
263
|
+
requirement: '3:1',
|
|
264
|
+
states: ['Rest', 'Hover', 'Pointer down'],
|
|
265
|
+
description:
|
|
266
|
+
'The thumb must have at least 3:1 contrast with the track and the surface behind it. Pointer down is the whole drag: measure the thumb with the pressed overlay applied.',
|
|
267
|
+
},
|
|
268
|
+
],
|
|
247
269
|
anatomy,
|
|
248
270
|
description:
|
|
249
271
|
'A draggable control for selecting a numeric value or range within defined bounds. Supports single value and range selection, tick marks, custom value formatting, and vertical orientation. Use it when users need to explore a continuous range, such as volume, price, or percentage.',
|
|
@@ -416,6 +438,11 @@ export const docsZh = {
|
|
|
416
438
|
visualProps: ['orientation'],
|
|
417
439
|
states: ['disabled'],
|
|
418
440
|
},
|
|
441
|
+
{
|
|
442
|
+
className: 'astryx-slider-control',
|
|
443
|
+
visualProps: ['orientation'],
|
|
444
|
+
states: ['disabled'],
|
|
445
|
+
},
|
|
419
446
|
{className: 'astryx-slider-track', visualProps: ['orientation']},
|
|
420
447
|
{
|
|
421
448
|
className: 'astryx-slider-thumb',
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
schema_version: 3
|
|
3
|
-
template_version:
|
|
3
|
+
template_version: 4
|
|
4
4
|
kind: component
|
|
5
5
|
id: component:Slider
|
|
6
|
-
authority:
|
|
6
|
+
authority: current
|
|
7
7
|
archive_reason: null
|
|
8
8
|
superseded_by: null
|
|
9
|
-
approved_by:
|
|
10
|
-
approved_at:
|
|
9
|
+
approved_by: cixzhang
|
|
10
|
+
approved_at: 2026-09-14
|
|
11
11
|
owners: [cixzhang]
|
|
12
12
|
review_triggers: [theming]
|
|
13
13
|
verified_by:
|
|
@@ -29,14 +29,14 @@ system_specs: []
|
|
|
29
29
|
## Intent
|
|
30
30
|
|
|
31
31
|
Slider presents a labeled control for selecting one numeric value or a bounded
|
|
32
|
-
range. This
|
|
33
|
-
|
|
32
|
+
range. This contract records its consumer anatomy and theming ownership, including
|
|
33
|
+
an additive target for the interactive control surface.
|
|
34
34
|
|
|
35
35
|
## Compatibility and migration
|
|
36
36
|
|
|
37
37
|
- Released default preserved: `yes`
|
|
38
|
-
- Compatibility class: additive
|
|
39
|
-
|
|
38
|
+
- Compatibility class: additive target and state reflection only; runtime,
|
|
39
|
+
default styling, DOM semantics, and public props remain unchanged
|
|
40
40
|
- Controlled/uncontrolled behavior: unchanged; Slider remains controlled
|
|
41
41
|
- Migration decision: none
|
|
42
42
|
|
|
@@ -48,7 +48,8 @@ Consumer migration instructions belong in consumer docs and release notes.
|
|
|
48
48
|
|
|
49
49
|
- The current slider row, background track, filled range, tick marks and labels,
|
|
50
50
|
thumbs, and adjacent text value presentation.
|
|
51
|
-
- The
|
|
51
|
+
- The `slider`, `slider-control`, `slider-track`, and `slider-thumb` public
|
|
52
|
+
targets.
|
|
52
53
|
|
|
53
54
|
**Does not own / non-goals**
|
|
54
55
|
|
|
@@ -61,24 +62,26 @@ Consumer migration instructions belong in consumer docs and release notes.
|
|
|
61
62
|
|
|
62
63
|
## Public concepts
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
This adds one public theming target without adding or changing a component prop,
|
|
66
|
+
value domain, or behavior. Consumer props, modes, states, and usage remain
|
|
67
|
+
documented in `Slider.doc.mjs`.
|
|
66
68
|
|
|
67
69
|
## Behavioral and layout contract
|
|
68
70
|
|
|
69
|
-
| ID | Candidate invariant
|
|
70
|
-
| --- |
|
|
71
|
-
| FR1 | The current render places a filled range, one or two thumbs, and optional tick marks over the background track.
|
|
72
|
-
| FR2 | `Slider`, `Track`, and `Thumb` carry the
|
|
73
|
-
| FR3 | Filled range, tick marks, mark labels, and adjacent text value display are stable rendered parts without their own current Slider target.
|
|
74
|
-
| FR4 | Label and status presentation continue to use Field and FieldStatus; value tooltips continue to use Tooltip.
|
|
71
|
+
| ID | Candidate invariant | Basis | Review state |
|
|
72
|
+
| --- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -------------------------------------------------- |
|
|
73
|
+
| FR1 | The current render places a filled range, one or two thumbs, and optional tick marks over the background track. | Current source, docs, and focused tests | Verified current behavior; no new behavior decided |
|
|
74
|
+
| FR2 | `Slider`, `Interactive control`, `Track`, and `Thumb` carry the `slider`, `slider-control`, `slider-track`, and `slider-thumb` targets respectively. | Current source, public docs, and #6224 | Approved additive target contract |
|
|
75
|
+
| FR3 | Filled range, tick marks, mark labels, and adjacent text value display are stable rendered parts without their own current Slider target. | Current source and public docs | Verified current asymmetry; not ratified as policy |
|
|
76
|
+
| FR4 | Label and status presentation continue to use Field and FieldStatus; value tooltips continue to use Tooltip. | Current source and focused tests | Verified composition boundary |
|
|
75
77
|
|
|
76
78
|
### Observed current target asymmetry
|
|
77
79
|
|
|
78
80
|
ProgressBar currently exposes targets for its fill and marks, while Slider
|
|
79
|
-
exposes targets for its root row, background track, and
|
|
80
|
-
filled range, tick marks, mark labels, or adjacent text value
|
|
81
|
-
implementation evidence for a joint audit, not approval of
|
|
81
|
+
exposes targets for its root row, interactive control, background track, and
|
|
82
|
+
thumbs but not its filled range, tick marks, mark labels, or adjacent text value
|
|
83
|
+
display. This is implementation evidence for a joint audit, not approval of
|
|
84
|
+
either component's remaining target shape.
|
|
82
85
|
|
|
83
86
|
### Allowed variation
|
|
84
87
|
|
|
@@ -108,20 +111,21 @@ implementation evidence for a joint audit, not approval of either target shape.
|
|
|
108
111
|
|
|
109
112
|
## Accessibility contract
|
|
110
113
|
|
|
111
|
-
This
|
|
114
|
+
This contract does not change or extend Slider's existing accessible name, value,
|
|
112
115
|
range-thumb naming, description/status association, keyboard behavior, disabled
|
|
113
116
|
behavior, or value-tooltip behavior.
|
|
114
117
|
|
|
115
118
|
## Design relationships
|
|
116
119
|
|
|
117
|
-
| Anatomy or state
|
|
118
|
-
|
|
|
119
|
-
| Label and description
|
|
120
|
-
| Slider and
|
|
121
|
-
|
|
|
122
|
-
|
|
|
123
|
-
|
|
|
124
|
-
|
|
|
120
|
+
| Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
|
|
121
|
+
| ------------------------------ | ---------------------------------------------------------------------------------------------- | ------------------------------- | -------------- | ------------------ |
|
|
122
|
+
| Label and description | Identify and explain the numeric setting. | Current shared-component source | Supporting | FR4 |
|
|
123
|
+
| Slider and interactive control | Separate the outer row from the pointer/keyboard surface and its composite disabled treatment. | Current source and public docs | Prominent | FR1, FR2 |
|
|
124
|
+
| Track | Shows the available range behind the fill. | Current source and public docs | Prominent | FR1, FR2 |
|
|
125
|
+
| Filled range and thumbs | Show the selected value or interval over the available range. | Current source and public docs | Prominent | FR1, FR2, FR3 |
|
|
126
|
+
| Tick marks and labels | Show optional supplied positions and their text. | Current source and public docs | Supporting | FR1, FR3 |
|
|
127
|
+
| Value presentation | Shows the formatted value as text or a shared Tooltip. | Current source and public docs | Supporting | FR3, FR4 |
|
|
128
|
+
| Status message | Presents shared validation feedback below the slider. | Current shared-component source | Supporting | FR4 |
|
|
125
129
|
|
|
126
130
|
### Theming anatomy
|
|
127
131
|
|
|
@@ -138,6 +142,7 @@ behavior, or value-tooltip behavior.
|
|
|
138
142
|
}
|
|
139
143
|
},
|
|
140
144
|
"Slider": {"target": "slider"},
|
|
145
|
+
"Interactive control": {"target": "slider-control"},
|
|
141
146
|
"Track": {"target": "slider-track"},
|
|
142
147
|
"Filled range": {
|
|
143
148
|
"none": {
|
|
@@ -172,10 +177,12 @@ behavior, or value-tooltip behavior.
|
|
|
172
177
|
}
|
|
173
178
|
```
|
|
174
179
|
|
|
175
|
-
`
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
180
|
+
`Interactive control` names the pointer/keyboard hit surface and the composite
|
|
181
|
+
opacity boundary around the rail, fill, marks, and thumbs. `Filled range`, `Tick
|
|
182
|
+
mark`, `Mark label`, and `Value display` remain stable consumer anatomy, but no
|
|
183
|
+
current Slider target reaches them. The map records those gaps without making
|
|
184
|
+
their absence intentional. `Value display` names the adjacent text mode; the
|
|
185
|
+
separately listed value tooltip retains Tooltip's target.
|
|
179
186
|
|
|
180
187
|
## Family and system relationships
|
|
181
188
|
|
|
@@ -191,24 +198,31 @@ adjacent text mode; the separately listed value tooltip retains Tooltip's target
|
|
|
191
198
|
|
|
192
199
|
## Verification map
|
|
193
200
|
|
|
194
|
-
| Contract | Verification | Representative states
|
|
195
|
-
| ------------------- | -------------------------------------------------------------------------------------- |
|
|
196
|
-
| FR1 | `Slider.test.tsx` structure, range, and marks suites | Single, range, horizontal, vertical, marks
|
|
197
|
-
| FR2 | `themingTargets.test.ts
|
|
198
|
-
| FR3 | Source and consumer-doc review | Filled range, marks, labels, adjacent text value
|
|
199
|
-
| FR4 | Source inspection; focused tests cover label, status, and disabled-reason Tooltip only | Label, status, value tooltip, disabled reason
|
|
200
|
-
| Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and
|
|
201
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
202
|
+
| ------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | ---------------------- |
|
|
203
|
+
| 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` |
|
|
204
|
+
| 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` |
|
|
205
|
+
| 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` |
|
|
206
|
+
| 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` |
|
|
207
|
+
| 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` |
|
|
201
208
|
|
|
202
|
-
The focused Slider suite
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
209
|
+
The focused Slider suite asserts the `slider-control` target's orientation and
|
|
210
|
+
disabled state. Source/metadata guards cover all four target declarations. The
|
|
211
|
+
suite covers only the disabled-reason Tooltip path, not the
|
|
212
|
+
`valueDisplay="tooltip"` composition; that anatomy is source-inspected and
|
|
213
|
+
remains missing focused test coverage.
|
|
207
214
|
|
|
208
215
|
## Decision log
|
|
209
216
|
|
|
210
|
-
|
|
211
|
-
|
|
217
|
+
### DEC-1 — Interactive control is public Slider anatomy
|
|
218
|
+
|
|
219
|
+
**Reference:** `component:Slider/DEC-1`
|
|
220
|
+
**Decider:** cixzhang, 2026-09-14
|
|
221
|
+
|
|
222
|
+
The stable pointer/keyboard interaction and compositing surface is a
|
|
223
|
+
consumer-recognizable part, so it carries `slider-control`. `Thumb` remains
|
|
224
|
+
separate anatomy and continues to carry `slider-thumb`. This adds no default
|
|
225
|
+
behavior or styling change.
|
|
212
226
|
|
|
213
227
|
## Open questions
|
|
214
228
|
|