@astryxdesign/core 0.7.2 → 0.7.3-canary.02f5fe4
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/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +8 -4
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +7 -0
- package/dist/Calendar/Calendar.js +3 -2
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +19 -24
- package/dist/ClickableCard/ClickableCard.d.ts +2 -2
- package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
- package/dist/ClickableCard/ClickableCard.js +11 -9
- package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
- package/dist/CodeBlock/CodeBlock.js +11 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +23 -5
- package/dist/DateInput/DateInput.d.ts +4 -4
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +27 -20
- package/dist/DateInput/NativeDateField.d.ts.map +1 -1
- package/dist/DateInput/NativeDateField.js +14 -8
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +14 -10
- package/dist/DateRangeInput/DateRangeInput.d.ts +5 -5
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +27 -18
- package/dist/DateTimeInput/DateTimeInput.d.ts +5 -5
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +25 -27
- package/dist/DateTimeInput/NativeDateSegment.d.ts +3 -2
- package/dist/DateTimeInput/NativeDateSegment.d.ts.map +1 -1
- package/dist/DateTimeInput/NativeDateSegment.js +5 -5
- package/dist/DateTimeInput/NativeTimeSegment.d.ts +3 -2
- package/dist/DateTimeInput/NativeTimeSegment.d.ts.map +1 -1
- package/dist/DateTimeInput/NativeTimeSegment.js +5 -5
- package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
- package/dist/DateTimeInput/TouchDateTimeField.js +12 -9
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +9 -4
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +52 -10
- package/dist/DropdownMenu/DropdownMenuContext.d.ts +34 -1
- package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuContext.js +15 -0
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +21 -2
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +375 -42
- package/dist/DropdownMenu/menuRegionStops.d.ts +2 -2
- package/dist/DropdownMenu/menuRegionStops.js +2 -2
- package/dist/FileInput/FileInput.d.ts.map +1 -1
- package/dist/FileInput/FileInput.js +14 -12
- package/dist/Link/Link.d.ts +2 -3
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +11 -19
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +48 -15
- package/dist/MultiSelector/MultiSelector.d.ts +4 -4
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.disabledFocus.a11y.chromium.spec.d.ts +2 -0
- package/dist/MultiSelector/MultiSelector.disabledFocus.a11y.chromium.spec.d.ts.map +1 -0
- package/dist/MultiSelector/MultiSelector.disabledFocus.a11y.chromium.spec.js +48 -0
- package/dist/MultiSelector/MultiSelector.js +25 -18
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +5 -2
- package/dist/Popover/Popover.d.ts.map +1 -1
- package/dist/Popover/Popover.js +8 -4
- package/dist/Selector/Selector.d.ts +4 -4
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +23 -14
- package/dist/Selector/SelectorOption.d.ts.map +1 -1
- package/dist/Selector/SelectorOption.js +1 -0
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +7 -14
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +25 -17
- package/dist/Switch/switch.markers.stylex.d.ts +2 -0
- package/dist/Switch/switch.markers.stylex.d.ts.map +1 -1
- package/dist/Switch/switch.markers.stylex.js +6 -0
- package/dist/TimeInput/TimeInput.d.ts +7 -6
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +37 -20
- package/dist/TimeInput/TouchTimeField.d.ts.map +1 -1
- package/dist/TimeInput/TouchTimeField.js +17 -6
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +41 -8
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +8 -3
- package/dist/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/Tooltip/Tooltip.js +36 -14
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +11 -2
- package/dist/TopNav/TopNav.d.ts.map +1 -1
- package/dist/TopNav/TopNav.js +2 -2
- package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
- package/dist/TopNav/TopNavHeading.js +1 -0
- package/dist/TreeList/TreeListItem.d.ts.map +1 -1
- package/dist/TreeList/TreeListItem.js +8 -3
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +14 -2
- package/dist/astryx.css +8 -2
- package/dist/hooks/useGridFocus.d.ts.map +1 -1
- package/dist/hooks/useGridFocus.js +10 -1
- package/dist/hooks/useMenuPress.d.ts +11 -1
- package/dist/hooks/useMenuPress.d.ts.map +1 -1
- package/dist/hooks/useMenuPress.js +43 -5
- package/dist/utils/disabledInteraction.d.ts +21 -0
- package/dist/utils/disabledInteraction.d.ts.map +1 -0
- package/dist/utils/disabledInteraction.js +33 -0
- package/dist/utils/timeParser.d.ts +4 -0
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +9 -2
- package/package.json +4 -4
- package/scripts/agent-doc-state.mjs +58 -9
- package/src/Banner/Banner.spec.md +37 -32
- package/src/BottomSheet/BottomSheet.spec.md +34 -54
- package/src/BottomSheet/BottomSheet.test.tsx +29 -0
- package/src/BottomSheet/BottomSheet.tsx +8 -4
- package/src/BottomSheet/BottomSheetSwitcher.spec.md +125 -58
- package/src/BottomSheet/BottomSheetSwitcher.test.tsx +26 -0
- package/src/BottomSheet/BottomSheetSwitcher.tsx +6 -0
- package/src/Calendar/Calendar.test.tsx +68 -24
- package/src/Calendar/Calendar.tsx +2 -1
- package/src/CheckboxInput/CheckboxInput.test.tsx +44 -5
- package/src/CheckboxInput/CheckboxInput.tsx +16 -26
- package/src/CheckboxInput/__tests__/Checkbox.a11y.states.ts +3 -3
- package/src/CheckboxList/CheckboxList.test.tsx +9 -6
- package/src/ClickableCard/ClickableCard.doc.mjs +2 -2
- package/src/ClickableCard/ClickableCard.test.tsx +5 -4
- package/src/ClickableCard/ClickableCard.tsx +9 -9
- package/src/CodeBlock/CodeBlock.highlightStyles.test.tsx +87 -0
- package/src/CodeBlock/CodeBlock.tsx +11 -1
- package/src/ComplexSelector/ComplexSelector.focusableDisabled.test.tsx +76 -0
- package/src/ComplexSelector/ComplexSelector.tsx +21 -4
- package/src/ContextMenu/ContextMenu.test.tsx +41 -0
- package/src/DateInput/DateInput.doc.mjs +9 -8
- package/src/DateInput/DateInput.test.tsx +88 -13
- package/src/DateInput/DateInput.tsx +49 -21
- package/src/DateInput/DateInputTouch.test.tsx +22 -1
- package/src/DateInput/NativeDateField.test.tsx +29 -3
- package/src/DateInput/NativeDateField.tsx +20 -7
- package/src/DateInput/TouchDateField.tsx +24 -9
- package/src/DateRangeInput/DateRangeInput.doc.mjs +6 -5
- package/src/DateRangeInput/DateRangeInput.spec.md +13 -13
- package/src/DateRangeInput/DateRangeInput.test.tsx +82 -16
- package/src/DateRangeInput/DateRangeInput.tsx +40 -20
- package/src/DateTimeInput/DateTimeInput.doc.mjs +9 -8
- package/src/DateTimeInput/DateTimeInput.test.tsx +126 -11
- package/src/DateTimeInput/DateTimeInput.tsx +42 -26
- package/src/DateTimeInput/DateTimeInputTouch.test.tsx +24 -0
- package/src/DateTimeInput/NativeDateSegment.tsx +9 -6
- package/src/DateTimeInput/NativePickerSegments.test.tsx +31 -0
- package/src/DateTimeInput/NativeTimeSegment.tsx +9 -6
- package/src/DateTimeInput/TouchDateTimeField.tsx +22 -7
- package/src/Dialog/Dialog.test.tsx +39 -0
- package/src/Dialog/Dialog.tsx +9 -4
- package/src/DropdownMenu/DropdownMenu.spec.md +33 -2
- package/src/DropdownMenu/DropdownMenu.tsx +79 -8
- package/src/DropdownMenu/DropdownMenuContext.public.test.ts +22 -0
- package/src/DropdownMenu/DropdownMenuContext.tsx +39 -1
- package/src/DropdownMenu/DropdownMenuItem.doc.mjs +66 -7
- package/src/DropdownMenu/DropdownMenuItem.spec.md +209 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +16 -1
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +623 -1
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +556 -99
- package/src/DropdownMenu/__tests__/DropdownMenuItemAudit.a11y.chromium.spec.ts +366 -0
- package/src/DropdownMenu/__tests__/SubMenuHeaderFooter.a11y.chromium.spec.ts +267 -0
- package/src/DropdownMenu/menuRegionStops.ts +2 -2
- package/src/DropdownMenu/modules/useMenuPress.spec.md +9 -0
- package/src/FieldStatus/FieldStatus.spec.md +3 -2
- package/src/FileInput/FileInput.test.tsx +30 -7
- package/src/FileInput/FileInput.tsx +21 -11
- package/src/Link/Link.test.tsx +21 -7
- package/src/Link/Link.tsx +16 -24
- package/src/Link/__tests__/Link.a11y.states.ts +1 -1
- package/src/Markdown/Markdown.rerender.test.tsx +143 -0
- package/src/Markdown/Markdown.tsx +89 -35
- package/src/MultiSelector/MultiSelector.disabledFocus.a11y.chromium.spec.ts +71 -0
- package/src/MultiSelector/MultiSelector.doc.mjs +5 -5
- package/src/MultiSelector/MultiSelector.focusableDisabled.test.tsx +121 -0
- package/src/MultiSelector/MultiSelector.test.tsx +49 -7
- package/src/MultiSelector/MultiSelector.tsx +53 -29
- package/src/Pagination/Pagination.test.tsx +5 -1
- package/src/Pagination/Pagination.tsx +3 -1
- package/src/Popover/Popover.test.tsx +15 -0
- package/src/Popover/Popover.tsx +19 -8
- package/src/PowerSearch/PowerSearchFilterEditor.test.tsx +3 -2
- package/src/PowerSearch/PowerSearchToken.test.tsx +7 -5
- package/src/Selector/Selector.doc.mjs +4 -4
- package/src/Selector/Selector.focusableDisabled.test.tsx +127 -0
- package/src/Selector/Selector.spec.md +11 -11
- package/src/Selector/Selector.test.tsx +64 -6
- package/src/Selector/Selector.tsx +51 -21
- package/src/Selector/SelectorOption.tsx +4 -0
- package/src/SideNav/SideNav.test.tsx +35 -0
- package/src/Slider/Slider.spec.md +11 -11
- package/src/Slider/Slider.test.tsx +3 -3
- package/src/Slider/Slider.tsx +8 -15
- package/src/Switch/Switch.test.tsx +85 -8
- package/src/Switch/Switch.tsx +26 -16
- package/src/Switch/__tests__/Switch.a11y.states.ts +2 -2
- package/src/Switch/switch.markers.stylex.ts +4 -0
- package/src/Table/Table.spec.md +2 -0
- package/src/Table/plugins/infiniteScroll/useTableInfiniteScroll.spec.md +244 -0
- package/src/Table/plugins/selection/useTableSelection.test.tsx +5 -3
- package/src/Table/plugins/virtualization/useTableVirtualization.spec.md +266 -0
- package/src/TimeInput/NativeTimeInput.test.tsx +25 -1
- package/src/TimeInput/Presentation.test.tsx +23 -0
- package/src/TimeInput/TimeInput.doc.mjs +13 -10
- package/src/TimeInput/TimeInput.test.tsx +139 -7
- package/src/TimeInput/TimeInput.tsx +53 -24
- package/src/TimeInput/TouchTimeField.tsx +27 -5
- package/src/Timestamp/Timestamp.doc.mjs +1 -1
- package/src/Timestamp/Timestamp.test.tsx +168 -0
- package/src/Timestamp/Timestamp.tsx +62 -11
- package/src/Token/Token.test.tsx +26 -4
- package/src/Token/Token.tsx +6 -2
- package/src/Tokenizer/Tokenizer.spec.md +28 -28
- package/src/Tooltip/Tooltip.test.tsx +51 -6
- package/src/Tooltip/Tooltip.tsx +41 -18
- package/src/Tooltip/__tests__/Tooltip.a11y.chromium.spec.ts +129 -0
- package/src/Tooltip/__tests__/Tooltip.a11y.renders.tsx +86 -0
- package/src/Tooltip/__tests__/Tooltip.a11y.states.ts +132 -0
- package/src/Tooltip/__tests__/Tooltip.a11y.test.tsx +126 -0
- package/src/Tooltip/__tests__/TooltipHoverBridge.a11y.chromium.spec.ts +74 -0
- package/src/Tooltip/useTooltip.test.tsx +23 -0
- package/src/Tooltip/useTooltip.tsx +22 -9
- package/src/TopNav/TopNav.tsx +11 -1
- package/src/TopNav/TopNavHeading.tsx +1 -0
- package/src/TopNav/__tests__/TopNavMobileLongHeading.a11y.chromium.spec.ts +74 -0
- package/src/TreeList/TreeList.test.tsx +28 -0
- package/src/TreeList/TreeListItem.tsx +6 -2
- package/src/Typeahead/Typeahead.test.tsx +89 -0
- package/src/Typeahead/Typeahead.tsx +15 -2
- package/src/__tests__/DisabledTriggerFocus.a11y.chromium.spec.ts +86 -0
- package/src/hooks/useGridFocus.ts +16 -1
- package/src/hooks/useMenuPress.ts +45 -7
- package/src/utils/disabledInteraction.ts +40 -0
- package/src/utils/timeParser.test.ts +11 -0
- package/src/utils/timeParser.ts +9 -2
|
@@ -90,12 +90,12 @@ preset-list anatomy and its additive theming surface.
|
|
|
90
90
|
|
|
91
91
|
### Representative states
|
|
92
92
|
|
|
93
|
-
| State | Required invariant
|
|
94
|
-
| ----------------- |
|
|
95
|
-
| No presets | No preset group or preset-button target renders.
|
|
96
|
-
| Presets, no match | Every button carries the preset target with no selected state.
|
|
97
|
-
| Applied preset | The matching button carries `aria-current="true"` and the target's selected state.
|
|
98
|
-
| Disabled preset | A preset that violates an endpoint date constraint or a range-span constraint is
|
|
93
|
+
| State | Required invariant | Allowed variation |
|
|
94
|
+
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
|
|
95
|
+
| No presets | No preset group or preset-button target renders. | Calendar configuration |
|
|
96
|
+
| Presets, no match | Every button carries the preset target with no selected state. | Preset count and labels |
|
|
97
|
+
| Applied preset | The matching button carries `aria-current="true"` and the target's selected state. | Selected range |
|
|
98
|
+
| Disabled preset | A preset that violates an endpoint date constraint or a range-span constraint is disabled per `spec:AST-085` and carries the target's disabled state. | Constraint source |
|
|
99
99
|
|
|
100
100
|
### Transformation and precedence order
|
|
101
101
|
|
|
@@ -120,8 +120,8 @@ No new performance or resource constraint is introduced.
|
|
|
120
120
|
- **AR2 — Current state.** The applied preset remains exposed with
|
|
121
121
|
`aria-current="true"`; theme state reflection is additive.
|
|
122
122
|
- **AR3 — Disabled state.** A preset that violates an endpoint date constraint or
|
|
123
|
-
a range-span constraint
|
|
124
|
-
not replace that behavior.
|
|
123
|
+
a range-span constraint is disabled and stays focusable (`spec:AST-085`); theme
|
|
124
|
+
state reflection does not replace that behavior.
|
|
125
125
|
|
|
126
126
|
## Design relationships
|
|
127
127
|
|
|
@@ -172,11 +172,11 @@ No new performance or resource constraint is introduced.
|
|
|
172
172
|
|
|
173
173
|
## Verification map
|
|
174
174
|
|
|
175
|
-
| Contract | Verification
|
|
176
|
-
| ------------------- |
|
|
177
|
-
| FR1, FR2, AR1–AR3 | `DateRangeInput.test.tsx
|
|
178
|
-
| FR3, FR4 | `DateRangeInput.test.tsx`, `themingTargets.test.ts`, generated probe theme
|
|
179
|
-
| Theming anatomy map | `scripts/check-knowledge.mjs`
|
|
175
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
176
|
+
| ------------------- | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------ |
|
|
177
|
+
| FR1, FR2, AR1–AR3 | `DateRangeInput.test.tsx`; disabled-preset focusability (AR3) pending conformance | no match, applied preset, endpoint-invalid preset, span-invalid preset | Semantics or state attributes disappear | `audit:DateRangeInput/presets` |
|
|
178
|
+
| FR3, FR4 | `DateRangeInput.test.tsx`, `themingTargets.test.ts`, generated probe theme | group target, selected button, disabled button | Target class/state is missing, undocumented, or placed on the wrong element | `audit:DateRangeInput/theming` |
|
|
179
|
+
| Theming anatomy map | `scripts/check-knowledge.mjs` | all documented anatomy | Target ownership or anatomy names drift | `audit:DateRangeInput/theming` |
|
|
180
180
|
|
|
181
181
|
## Decision log
|
|
182
182
|
|
|
@@ -23,6 +23,8 @@ function generateThemeTestCSS(theme: Parameters<typeof generateThemeCSS>[0]) {
|
|
|
23
23
|
// made every role+name query compute ~85 accessible names through jsdom's
|
|
24
24
|
// slow getComputedStyle (~450ms per query). See fastRoleQueries.ts.
|
|
25
25
|
import {getButton, queryButton} from '../__tests__/fastRoleQueries';
|
|
26
|
+
import * as stylex from '@stylexjs/stylex';
|
|
27
|
+
import {colorVars} from '../theme/tokens.stylex';
|
|
26
28
|
import {DateRangeInput} from './DateRangeInput';
|
|
27
29
|
import type {DateRange} from './DateRangeInput';
|
|
28
30
|
import {Icon} from '../Icon';
|
|
@@ -155,7 +157,46 @@ describe('DateRangeInput', () => {
|
|
|
155
157
|
/>,
|
|
156
158
|
);
|
|
157
159
|
const trigger = getButton(/Range/);
|
|
158
|
-
expect(trigger).
|
|
160
|
+
expect(trigger).not.toHaveAttribute('disabled');
|
|
161
|
+
expect(trigger).toHaveAttribute('aria-disabled', 'true');
|
|
162
|
+
trigger.focus();
|
|
163
|
+
expect(trigger).toHaveFocus();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('restores the focus ring on a focused disabled field (AST-085 FR6)', () => {
|
|
167
|
+
// The same declarations as the field's ring style compile to the same
|
|
168
|
+
// atomic classes.
|
|
169
|
+
const ringClasses = stylex
|
|
170
|
+
.props(
|
|
171
|
+
stylex.create({
|
|
172
|
+
ring: {
|
|
173
|
+
borderColor: {
|
|
174
|
+
default: colorVars['--color-border-emphasized'],
|
|
175
|
+
':focus-within': colorVars['--color-accent'],
|
|
176
|
+
},
|
|
177
|
+
boxShadow: {
|
|
178
|
+
default: 'none',
|
|
179
|
+
':focus-within': `inset 0px 0px 0px 2px ${colorVars['--color-accent-muted']}`,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
}).ring,
|
|
183
|
+
)
|
|
184
|
+
.className!.split(' ')
|
|
185
|
+
// Keep the atomic classes, not the dev-only debug name.
|
|
186
|
+
.filter(name => !name.includes('__'));
|
|
187
|
+
render(
|
|
188
|
+
<DateRangeInput
|
|
189
|
+
label="Range"
|
|
190
|
+
isDisabled
|
|
191
|
+
value={null}
|
|
192
|
+
onChange={() => {}}
|
|
193
|
+
/>,
|
|
194
|
+
);
|
|
195
|
+
// The shared disabled style alone drops these :focus-within declarations.
|
|
196
|
+
const wrapper = getButton(/Range/).parentElement!;
|
|
197
|
+
for (const name of ringClasses) {
|
|
198
|
+
expect(wrapper).toHaveClass(name);
|
|
199
|
+
}
|
|
159
200
|
});
|
|
160
201
|
|
|
161
202
|
it('is not disabled by default', () => {
|
|
@@ -272,7 +313,8 @@ describe('DateRangeInput', () => {
|
|
|
272
313
|
onChange={() => {}}
|
|
273
314
|
/>,
|
|
274
315
|
);
|
|
275
|
-
expect(getButton('Open calendar')).
|
|
316
|
+
expect(getButton('Open calendar')).not.toHaveAttribute('disabled');
|
|
317
|
+
expect(getButton('Open calendar')).toHaveAttribute('aria-disabled', 'true');
|
|
276
318
|
});
|
|
277
319
|
|
|
278
320
|
it('renders with size="lg"', () => {
|
|
@@ -526,7 +568,8 @@ describe('DateRangeInput', () => {
|
|
|
526
568
|
);
|
|
527
569
|
|
|
528
570
|
const preset = getButton('Last 7 days');
|
|
529
|
-
expect(preset).
|
|
571
|
+
expect(preset).not.toHaveAttribute('disabled');
|
|
572
|
+
expect(preset).toHaveAttribute('aria-disabled', 'true');
|
|
530
573
|
fireEvent.click(preset);
|
|
531
574
|
expect(handleChange).not.toHaveBeenCalled();
|
|
532
575
|
});
|
|
@@ -544,7 +587,8 @@ describe('DateRangeInput', () => {
|
|
|
544
587
|
);
|
|
545
588
|
|
|
546
589
|
const preset = getButton('Last 7 days');
|
|
547
|
-
expect(preset).
|
|
590
|
+
expect(preset).not.toHaveAttribute('disabled');
|
|
591
|
+
expect(preset).toHaveAttribute('aria-disabled', 'true');
|
|
548
592
|
fireEvent.click(preset);
|
|
549
593
|
expect(handleChange).not.toHaveBeenCalled();
|
|
550
594
|
});
|
|
@@ -562,7 +606,8 @@ describe('DateRangeInput', () => {
|
|
|
562
606
|
);
|
|
563
607
|
|
|
564
608
|
const preset = getButton('Last 7 days');
|
|
565
|
-
expect(preset).
|
|
609
|
+
expect(preset).not.toHaveAttribute('disabled');
|
|
610
|
+
expect(preset).toHaveAttribute('aria-disabled', 'true');
|
|
566
611
|
fireEvent.click(preset);
|
|
567
612
|
expect(handleChange).not.toHaveBeenCalled();
|
|
568
613
|
});
|
|
@@ -590,7 +635,7 @@ describe('DateRangeInput', () => {
|
|
|
590
635
|
|
|
591
636
|
const getRangeCallsBeforeClick = getRange.mock.calls.length;
|
|
592
637
|
const preset = getButton('Allowed range');
|
|
593
|
-
expect(preset).not.
|
|
638
|
+
expect(preset).not.toHaveAttribute('aria-disabled');
|
|
594
639
|
fireEvent.click(preset);
|
|
595
640
|
expect(handleChange).toHaveBeenCalledWith(range);
|
|
596
641
|
expect(getRangeCallsAtChange).toBe(getRangeCallsBeforeClick);
|
|
@@ -613,7 +658,10 @@ describe('DateRangeInput', () => {
|
|
|
613
658
|
// accessibility tree; use hidden: true to find it.
|
|
614
659
|
const h = {hidden: true} as const;
|
|
615
660
|
|
|
616
|
-
function renderDisabled(props?: {
|
|
661
|
+
function renderDisabled(props?: {
|
|
662
|
+
disabledMessage?: string;
|
|
663
|
+
onClick?: () => void;
|
|
664
|
+
}) {
|
|
617
665
|
return render(
|
|
618
666
|
<DateRangeInput
|
|
619
667
|
label="Range"
|
|
@@ -699,11 +747,22 @@ describe('DateRangeInput', () => {
|
|
|
699
747
|
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
700
748
|
});
|
|
701
749
|
|
|
702
|
-
it('
|
|
703
|
-
|
|
750
|
+
it('blocks activation and a caller onClick when disabled without a reason (AST-085 FR1, FR2)', async () => {
|
|
751
|
+
const user = userEvent.setup();
|
|
752
|
+
const onClick = vi.fn();
|
|
753
|
+
renderDisabled({onClick});
|
|
704
754
|
const trigger = getButton(/Range:/);
|
|
705
|
-
expect(trigger).
|
|
706
|
-
|
|
755
|
+
expect(trigger).not.toHaveAttribute('disabled');
|
|
756
|
+
await user.click(trigger);
|
|
757
|
+
await user.keyboard('{Enter}');
|
|
758
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
759
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
it('blocks the calendar button while disabled (AST-085 FR2)', () => {
|
|
763
|
+
renderDisabled();
|
|
764
|
+
fireEvent.click(getButton('Open calendar'));
|
|
765
|
+
expect(getButton(/Range:/)).toHaveAttribute('aria-expanded', 'false');
|
|
707
766
|
});
|
|
708
767
|
});
|
|
709
768
|
});
|
|
@@ -988,13 +1047,19 @@ describe('DateRangeInput range-span forwarding', () => {
|
|
|
988
1047
|
fireEvent.click(getButton('Open calendar'));
|
|
989
1048
|
|
|
990
1049
|
// Before a start is picked, a far-off day is selectable.
|
|
991
|
-
expect(dayButton('2026-01-20')).not.
|
|
1050
|
+
expect(dayButton('2026-01-20')).not.toHaveAttribute(
|
|
1051
|
+
'aria-disabled',
|
|
1052
|
+
'true',
|
|
1053
|
+
);
|
|
992
1054
|
|
|
993
1055
|
fireEvent.click(dayButton('2026-01-10') as HTMLButtonElement);
|
|
994
1056
|
|
|
995
1057
|
// A 7-day window spans start ± 6 days: Jan 16 is the edge, Jan 17 is out.
|
|
996
|
-
expect(dayButton('2026-01-16')).not.
|
|
997
|
-
|
|
1058
|
+
expect(dayButton('2026-01-16')).not.toHaveAttribute(
|
|
1059
|
+
'aria-disabled',
|
|
1060
|
+
'true',
|
|
1061
|
+
);
|
|
1062
|
+
expect(dayButton('2026-01-17')).toHaveAttribute('aria-disabled', 'true');
|
|
998
1063
|
});
|
|
999
1064
|
|
|
1000
1065
|
it('disables a preset whose range violates the span cap', () => {
|
|
@@ -1030,9 +1095,10 @@ describe('DateRangeInput range-span forwarding', () => {
|
|
|
1030
1095
|
// The 3-day preset fits the 7-day cap; the 30-day preset can't be committed.
|
|
1031
1096
|
const withinCap = getButton('Last 3 days');
|
|
1032
1097
|
const overCap = getButton('Last 30 days');
|
|
1033
|
-
expect(withinCap).not.
|
|
1098
|
+
expect(withinCap).not.toHaveAttribute('aria-disabled');
|
|
1034
1099
|
expect(withinCap).not.toHaveAttribute('data-disabled');
|
|
1035
|
-
expect(overCap).
|
|
1100
|
+
expect(overCap).not.toHaveAttribute('disabled');
|
|
1101
|
+
expect(overCap).toHaveAttribute('aria-disabled', 'true');
|
|
1036
1102
|
expect(overCap).toHaveClass('astryx-date-range-input-preset');
|
|
1037
1103
|
expect(overCap).toHaveAttribute('data-disabled', 'disabled');
|
|
1038
1104
|
|
|
@@ -97,6 +97,18 @@ export type {
|
|
|
97
97
|
} from '../Field';
|
|
98
98
|
|
|
99
99
|
const styles = stylex.create({
|
|
100
|
+
// The shared disabled style drops the focus ring; restore it while a
|
|
101
|
+
// control inside the field holds focus (spec:AST-085 FR6). Defaults repeat it.
|
|
102
|
+
disabledFocusRing: {
|
|
103
|
+
borderColor: {
|
|
104
|
+
default: colorVars['--color-border-emphasized'],
|
|
105
|
+
':focus-within': colorVars['--color-accent'],
|
|
106
|
+
},
|
|
107
|
+
boxShadow: {
|
|
108
|
+
default: 'none',
|
|
109
|
+
':focus-within': `inset 0px 0px 0px 2px ${colorVars['--color-accent-muted']}`,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
100
112
|
trigger: {
|
|
101
113
|
display: 'block',
|
|
102
114
|
flex: 1,
|
|
@@ -309,12 +321,12 @@ export interface DateRangeInputProps extends Omit<
|
|
|
309
321
|
/**
|
|
310
322
|
* Explains why the input is disabled. When set together with
|
|
311
323
|
* `isDisabled`, the input shows a tooltip with this text on hover and
|
|
312
|
-
* keyboard focus, and the trigger
|
|
313
|
-
*
|
|
314
|
-
*
|
|
324
|
+
* keyboard focus, and the trigger is described by it, so the reason is
|
|
325
|
+
* discoverable by keyboard and assistive technology. Activation stays
|
|
326
|
+
* blocked.
|
|
315
327
|
*
|
|
316
|
-
* Use this instead of wrapping a disabled input in `Tooltip
|
|
317
|
-
*
|
|
328
|
+
* Use this instead of wrapping a disabled input in `Tooltip`, so the
|
|
329
|
+
* reason is tied to the field.
|
|
318
330
|
*
|
|
319
331
|
* @example
|
|
320
332
|
* ```
|
|
@@ -546,12 +558,12 @@ export function DateRangeInput({
|
|
|
546
558
|
const isBusy = isLoading || optimisticValue !== value;
|
|
547
559
|
const isEffectivelyDisabled = isDisabled || isBusy;
|
|
548
560
|
|
|
549
|
-
//
|
|
550
|
-
//
|
|
551
|
-
//
|
|
552
|
-
// disabled
|
|
553
|
-
//
|
|
554
|
-
//
|
|
561
|
+
// A disabled or busy field never renders native `disabled` (spec:AST-085
|
|
562
|
+
// FR1): its buttons stay focusable via aria-disabled, and activation is
|
|
563
|
+
// blocked by the isEffectivelyDisabled guard in handleToggle. The
|
|
564
|
+
// disabled-reason tooltip's listeners attach to the trigger container. Only
|
|
565
|
+
// the persistent isDisabled state (not the transient busy state) surfaces a
|
|
566
|
+
// reason.
|
|
555
567
|
const showsDisabledMessage = isDisabled && !!disabledMessage;
|
|
556
568
|
const disabledMessageTooltip = useTooltip({
|
|
557
569
|
placement: 'above',
|
|
@@ -695,6 +707,7 @@ export function DateRangeInput({
|
|
|
695
707
|
inputWrapperStyles.base,
|
|
696
708
|
sizeStyles[size],
|
|
697
709
|
isEffectivelyDisabled && inputWrapperStyles.disabled,
|
|
710
|
+
isEffectivelyDisabled && styles.disabledFocusRing,
|
|
698
711
|
status && inputStatusBorderStyles[status.type],
|
|
699
712
|
status &&
|
|
700
713
|
!isEffectivelyDisabled &&
|
|
@@ -708,7 +721,7 @@ export function DateRangeInput({
|
|
|
708
721
|
<button
|
|
709
722
|
type="button"
|
|
710
723
|
onClick={handleToggle}
|
|
711
|
-
disabled={isEffectivelyDisabled}
|
|
724
|
+
aria-disabled={isEffectivelyDisabled || undefined}
|
|
712
725
|
aria-label={
|
|
713
726
|
popover.isOpen
|
|
714
727
|
? t('@astryx.dateInput.toggleCalendarClose')
|
|
@@ -734,12 +747,14 @@ export function DateRangeInput({
|
|
|
734
747
|
ref={triggerButtonRef}
|
|
735
748
|
id={id}
|
|
736
749
|
type="button"
|
|
737
|
-
|
|
738
|
-
//
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
750
|
+
// The browser still dispatches a click on an aria-disabled button,
|
|
751
|
+
// so a caller's onClick is skipped too (spec:AST-085 FR2).
|
|
752
|
+
onClick={
|
|
753
|
+
isEffectivelyDisabled
|
|
754
|
+
? undefined
|
|
755
|
+
: chainEventHandlers(handleToggle, onClickProp)
|
|
756
|
+
}
|
|
757
|
+
aria-disabled={isEffectivelyDisabled ? 'true' : undefined}
|
|
743
758
|
// A caller aria-label replaces the trigger's name; a caller
|
|
744
759
|
// aria-labelledby is read after it (label and selected range).
|
|
745
760
|
aria-label={ariaLabelProp ?? triggerAriaLabel}
|
|
@@ -807,8 +822,13 @@ export function DateRangeInput({
|
|
|
807
822
|
// marked with aria-current (not aria-selected, a listbox
|
|
808
823
|
// concept that contradicted the Tab interaction) (forms-5).
|
|
809
824
|
aria-current={isActive ? 'true' : undefined}
|
|
810
|
-
disabled
|
|
811
|
-
|
|
825
|
+
// Focusable while disabled (AR3, spec:AST-085 FR1).
|
|
826
|
+
aria-disabled={isPresetDisabled || undefined}
|
|
827
|
+
onClick={() => {
|
|
828
|
+
if (!isPresetDisabled) {
|
|
829
|
+
handleRangeSelect(presetRange);
|
|
830
|
+
}
|
|
831
|
+
}}
|
|
812
832
|
{...mergeProps(
|
|
813
833
|
themeProps('date-range-input-preset', {
|
|
814
834
|
selected: isActive ? 'selected' : null,
|
|
@@ -51,14 +51,15 @@ export const docs = {
|
|
|
51
51
|
{
|
|
52
52
|
name: 'isDisabled',
|
|
53
53
|
type: 'boolean',
|
|
54
|
-
description:
|
|
54
|
+
description:
|
|
55
|
+
'Disable the input and picker. The fields stay focusable and expose `aria-disabled`; typing and opening are blocked.',
|
|
55
56
|
default: 'false',
|
|
56
57
|
},
|
|
57
58
|
{
|
|
58
59
|
name: 'disabledMessage',
|
|
59
60
|
type: 'string',
|
|
60
61
|
description:
|
|
61
|
-
'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and
|
|
62
|
+
'Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and describes the fields (activation stays blocked). Use this instead of wrapping a disabled DateTimeInput in Tooltip.',
|
|
62
63
|
},
|
|
63
64
|
{
|
|
64
65
|
name: 'value',
|
|
@@ -289,7 +290,7 @@ export const docs = {
|
|
|
289
290
|
{
|
|
290
291
|
guidance: false,
|
|
291
292
|
description:
|
|
292
|
-
'Wrap a disabled DateTimeInput in Tooltip to explain why it is disabled
|
|
293
|
+
'Wrap a disabled DateTimeInput in Tooltip to explain why it is disabled. Use the disabledMessage prop instead, which ties the reason to the field.',
|
|
293
294
|
},
|
|
294
295
|
],
|
|
295
296
|
anatomy: [
|
|
@@ -412,7 +413,7 @@ export const docsZh = {
|
|
|
412
413
|
{
|
|
413
414
|
guidance: false,
|
|
414
415
|
description:
|
|
415
|
-
'Wrap a disabled DateTimeInput in Tooltip to explain why it is disabled
|
|
416
|
+
'Wrap a disabled DateTimeInput in Tooltip to explain why it is disabled. Use the disabledMessage prop instead, which ties the reason to the field.',
|
|
416
417
|
},
|
|
417
418
|
],
|
|
418
419
|
},
|
|
@@ -444,14 +445,14 @@ export const docsZh = {
|
|
|
444
445
|
{
|
|
445
446
|
name: 'isDisabled',
|
|
446
447
|
type: 'boolean',
|
|
447
|
-
description: '
|
|
448
|
+
description: '禁用输入框和选择器。字段仍可聚焦并暴露 `aria-disabled`,但不能输入或打开选择器。',
|
|
448
449
|
default: 'false',
|
|
449
450
|
},
|
|
450
451
|
{
|
|
451
452
|
name: 'disabledMessage',
|
|
452
453
|
type: 'string',
|
|
453
454
|
description:
|
|
454
|
-
'说明输入框为何被禁用。与 isDisabled
|
|
455
|
+
'说明输入框为何被禁用。与 isDisabled 一起使用时,在悬停/键盘聚焦时显示提示,并描述日期和时间字段(仍阻止输入和激活)。请使用此属性,而不是用 Tooltip 包裹已禁用的 DateTimeInput。',
|
|
455
456
|
},
|
|
456
457
|
{
|
|
457
458
|
name: 'value',
|
|
@@ -667,7 +668,7 @@ export const docsDense = {
|
|
|
667
668
|
{
|
|
668
669
|
guidance: false,
|
|
669
670
|
description:
|
|
670
|
-
'Wrap a disabled DateTimeInput in Tooltip to explain why it is disabled
|
|
671
|
+
'Wrap a disabled DateTimeInput in Tooltip to explain why it is disabled. Use the disabledMessage prop instead, which ties the reason to the field.',
|
|
671
672
|
},
|
|
672
673
|
],
|
|
673
674
|
},
|
|
@@ -679,7 +680,7 @@ export const docsDense = {
|
|
|
679
680
|
isRequired: 'mark field required',
|
|
680
681
|
isDisabled: 'disable input+picker',
|
|
681
682
|
disabledMessage:
|
|
682
|
-
'reason shown in a tooltip on hover/focus when disabled;
|
|
683
|
+
'reason shown in a tooltip on hover/focus when disabled; describes the fields',
|
|
683
684
|
value: 'selected datetime ISO 8601',
|
|
684
685
|
onChange: 'callback on datetime change',
|
|
685
686
|
changeAction: 'async action after onChange; drives optimistic UI',
|
|
@@ -14,6 +14,7 @@ import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
|
|
|
14
14
|
import {render, screen, fireEvent, waitFor} from '@testing-library/react';
|
|
15
15
|
import userEvent from '@testing-library/user-event';
|
|
16
16
|
import * as stylex from '@stylexjs/stylex';
|
|
17
|
+
import {colorVars} from '../theme/tokens.stylex';
|
|
17
18
|
import {getButton, queryButton} from '../__tests__/fastRoleQueries';
|
|
18
19
|
import {DateTimeInput} from './DateTimeInput';
|
|
19
20
|
import type {ISODateTimeString} from './DateTimeInput';
|
|
@@ -288,10 +289,52 @@ describe('DateTimeInput', () => {
|
|
|
288
289
|
expect(screen.getByRole('combobox')).not.toHaveAttribute('aria-required');
|
|
289
290
|
});
|
|
290
291
|
|
|
291
|
-
it('
|
|
292
|
+
it('keeps both disabled inputs focusable without native disabled (AST-085 FR1)', () => {
|
|
292
293
|
render(<DateTimeInput label="Meeting" isDisabled onChange={() => {}} />);
|
|
293
|
-
|
|
294
|
-
|
|
294
|
+
for (const input of [
|
|
295
|
+
screen.getByRole('combobox'),
|
|
296
|
+
screen.getByLabelText('Meeting time'),
|
|
297
|
+
]) {
|
|
298
|
+
expect(input).not.toHaveAttribute('disabled');
|
|
299
|
+
expect(input).toHaveAttribute('aria-disabled', 'true');
|
|
300
|
+
expect(input).toHaveAttribute('readonly');
|
|
301
|
+
input.focus();
|
|
302
|
+
expect(input).toHaveFocus();
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('restores the focus ring on a focused disabled field (AST-085 FR6)', () => {
|
|
307
|
+
// The same declarations as the field's ring style compile to the same
|
|
308
|
+
// atomic classes; the shared disabled style alone drops them.
|
|
309
|
+
const ringClasses = stylex
|
|
310
|
+
.props(
|
|
311
|
+
stylex.create({
|
|
312
|
+
ring: {
|
|
313
|
+
borderColor: {
|
|
314
|
+
default: colorVars['--color-border-emphasized'],
|
|
315
|
+
':focus-within': colorVars['--color-accent'],
|
|
316
|
+
},
|
|
317
|
+
boxShadow: {
|
|
318
|
+
default: 'none',
|
|
319
|
+
':focus-within': `inset 0px 0px 0px 2px ${colorVars['--color-accent-muted']}`,
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
}).ring,
|
|
323
|
+
)
|
|
324
|
+
.className!.split(' ')
|
|
325
|
+
.filter(name => !name.includes('__'));
|
|
326
|
+
render(<DateTimeInput label="Meeting" isDisabled onChange={() => {}} />);
|
|
327
|
+
for (const input of [
|
|
328
|
+
screen.getByRole('combobox'),
|
|
329
|
+
screen.getByLabelText('Meeting time'),
|
|
330
|
+
]) {
|
|
331
|
+
const wrapper = input.closest(
|
|
332
|
+
'.astryx-date-time-input-date-segment, .astryx-date-time-input-time-segment',
|
|
333
|
+
)!;
|
|
334
|
+
for (const name of ringClasses) {
|
|
335
|
+
expect(wrapper).toHaveClass(name);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
295
338
|
});
|
|
296
339
|
|
|
297
340
|
it('is not disabled by default', () => {
|
|
@@ -366,14 +409,25 @@ describe('DateTimeInput', () => {
|
|
|
366
409
|
it('calendar button is disabled when isDisabled is true', () => {
|
|
367
410
|
render(<DateTimeInput label="Meeting" isDisabled onChange={() => {}} />);
|
|
368
411
|
const button = getButton('Open calendar');
|
|
369
|
-
expect(button).
|
|
412
|
+
expect(button).not.toHaveAttribute('disabled');
|
|
413
|
+
expect(button).toHaveAttribute('aria-disabled', 'true');
|
|
414
|
+
fireEvent.click(button);
|
|
415
|
+
expect(screen.getByRole('combobox')).toHaveAttribute(
|
|
416
|
+
'aria-expanded',
|
|
417
|
+
'false',
|
|
418
|
+
);
|
|
370
419
|
});
|
|
371
420
|
|
|
372
421
|
it('disables inputs and button when isLoading is true', () => {
|
|
373
422
|
render(<DateTimeInput label="Meeting" isLoading onChange={() => {}} />);
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
423
|
+
for (const el of [
|
|
424
|
+
screen.getByRole('combobox'),
|
|
425
|
+
screen.getByLabelText('Meeting time'),
|
|
426
|
+
getButton('Open calendar'),
|
|
427
|
+
]) {
|
|
428
|
+
expect(el).not.toHaveAttribute('disabled');
|
|
429
|
+
expect(el).toHaveAttribute('aria-disabled', 'true');
|
|
430
|
+
}
|
|
377
431
|
});
|
|
378
432
|
|
|
379
433
|
it('sets aria-busy when isLoading is true', () => {
|
|
@@ -872,6 +926,12 @@ describe('DateTimeInput', () => {
|
|
|
872
926
|
);
|
|
873
927
|
|
|
874
928
|
const tooltip = screen.getByRole('tooltip', h);
|
|
929
|
+
// The calendar button keeps its enabled tab stop (spec:AST-085 FR1).
|
|
930
|
+
await user.tab();
|
|
931
|
+
expect(getButton('Open calendar')).toHaveFocus();
|
|
932
|
+
expect(
|
|
933
|
+
getButton('Open calendar').getAttribute('aria-describedby'),
|
|
934
|
+
).toContain(tooltip.id);
|
|
875
935
|
await user.tab();
|
|
876
936
|
expect(screen.getByRole('combobox')).toHaveFocus();
|
|
877
937
|
await waitFor(() => {
|
|
@@ -948,11 +1008,48 @@ describe('DateTimeInput', () => {
|
|
|
948
1008
|
expect(onChange).not.toHaveBeenCalled();
|
|
949
1009
|
});
|
|
950
1010
|
|
|
951
|
-
it('
|
|
952
|
-
|
|
1011
|
+
it('blocks typing, opening and a caller onClick when disabled without a reason (AST-085 FR2)', async () => {
|
|
1012
|
+
const user = userEvent.setup();
|
|
1013
|
+
const onChange = vi.fn();
|
|
1014
|
+
const onClick = vi.fn();
|
|
1015
|
+
render(
|
|
1016
|
+
<DateTimeInput
|
|
1017
|
+
label="When"
|
|
1018
|
+
onChange={onChange}
|
|
1019
|
+
onClick={onClick}
|
|
1020
|
+
isDisabled
|
|
1021
|
+
/>,
|
|
1022
|
+
);
|
|
1023
|
+
const dateInput = screen.getByRole('combobox');
|
|
1024
|
+
await user.click(dateInput);
|
|
1025
|
+
await user.type(dateInput, '2026-03-15');
|
|
1026
|
+
await user.keyboard('{ArrowDown}');
|
|
1027
|
+
expect(dateInput).toHaveValue('');
|
|
1028
|
+
expect(dateInput).toHaveAttribute('aria-expanded', 'false');
|
|
1029
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
1030
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
it('drops a date draft instead of committing it when the field becomes disabled (AST-085 FR2)', async () => {
|
|
1034
|
+
const user = userEvent.setup();
|
|
1035
|
+
const onChange = vi.fn();
|
|
1036
|
+
const value = '2026-03-10T09:00' as ISODateTimeString;
|
|
1037
|
+
const {rerender} = render(
|
|
1038
|
+
<DateTimeInput label="When" value={value} onChange={onChange} />,
|
|
1039
|
+
);
|
|
953
1040
|
const dateInput = screen.getByRole('combobox');
|
|
954
|
-
|
|
955
|
-
|
|
1041
|
+
await user.click(dateInput);
|
|
1042
|
+
await user.clear(dateInput);
|
|
1043
|
+
rerender(
|
|
1044
|
+
<DateTimeInput
|
|
1045
|
+
label="When"
|
|
1046
|
+
value={value}
|
|
1047
|
+
onChange={onChange}
|
|
1048
|
+
isDisabled
|
|
1049
|
+
/>,
|
|
1050
|
+
);
|
|
1051
|
+
await user.tab();
|
|
1052
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
956
1053
|
});
|
|
957
1054
|
|
|
958
1055
|
it('does not swap in the time format-hint placeholder on focus while disabled', () => {
|
|
@@ -1030,6 +1127,24 @@ describe('DateTimeInput', () => {
|
|
|
1030
1127
|
});
|
|
1031
1128
|
});
|
|
1032
1129
|
|
|
1130
|
+
it('announces no step from a disabled time field (AST-085 FR2)', async () => {
|
|
1131
|
+
render(
|
|
1132
|
+
<DateTimeInput
|
|
1133
|
+
label="Meeting"
|
|
1134
|
+
value={'2026-03-15T14:30' as ISODateTimeString}
|
|
1135
|
+
onChange={() => {}}
|
|
1136
|
+
isDisabled
|
|
1137
|
+
/>,
|
|
1138
|
+
);
|
|
1139
|
+
|
|
1140
|
+
fireEvent.keyDown(screen.getByLabelText('Meeting time'), {
|
|
1141
|
+
key: 'ArrowUp',
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
1145
|
+
expect(politeRegion()?.textContent ?? '').not.toContain('2:31 PM');
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1033
1148
|
it('politely announces the new time after ArrowDown stepping', async () => {
|
|
1034
1149
|
render(
|
|
1035
1150
|
<DateTimeInput
|