@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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import {describe, it, expect, vi} from 'vitest';
|
|
4
4
|
import {render, screen, fireEvent} from '@testing-library/react';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
5
6
|
import {ClickableCard} from './ClickableCard';
|
|
6
7
|
|
|
7
8
|
describe('ClickableCard', () => {
|
|
@@ -47,6 +48,47 @@ describe('ClickableCard', () => {
|
|
|
47
48
|
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
48
49
|
});
|
|
49
50
|
|
|
51
|
+
it('calls onClick once when the accessible control itself is clicked', () => {
|
|
52
|
+
const handleClick = vi.fn();
|
|
53
|
+
render(
|
|
54
|
+
<ClickableCard label="Test card" onClick={handleClick}>
|
|
55
|
+
<span>Content</span>
|
|
56
|
+
</ClickableCard>,
|
|
57
|
+
);
|
|
58
|
+
// Pointer activation aimed at the element carrying the role — what
|
|
59
|
+
// speech input, assistive technology, and automation dispatch.
|
|
60
|
+
fireEvent.click(screen.getByRole('button', {name: 'Test card'}));
|
|
61
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('passes the card surface as currentTarget for surface clicks', () => {
|
|
65
|
+
const seen: EventTarget[] = [];
|
|
66
|
+
render(
|
|
67
|
+
<ClickableCard
|
|
68
|
+
label="Test card"
|
|
69
|
+
onClick={e => seen.push(e.currentTarget)}>
|
|
70
|
+
<span>Content</span>
|
|
71
|
+
</ClickableCard>,
|
|
72
|
+
);
|
|
73
|
+
fireEvent.click(screen.getByText('Content'));
|
|
74
|
+
expect(seen).toHaveLength(1);
|
|
75
|
+
expect(seen[0]).toBe(screen.getByText('Content').parentElement);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('calls onClick exactly once when the surface of an href card is clicked without preventDefault', () => {
|
|
79
|
+
const handleClick = vi.fn();
|
|
80
|
+
render(
|
|
81
|
+
<ClickableCard label="Nav card" href="/settings" onClick={handleClick}>
|
|
82
|
+
<span>Content</span>
|
|
83
|
+
</ClickableCard>,
|
|
84
|
+
);
|
|
85
|
+
// The container hook proxies the surface click to the link with
|
|
86
|
+
// `link.click()`; that synthetic click bubbles back through the card and
|
|
87
|
+
// must not run the consumer callback a second time.
|
|
88
|
+
fireEvent.click(screen.getByText('Content'));
|
|
89
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
90
|
+
});
|
|
91
|
+
|
|
50
92
|
it('does NOT call onClick when a nested button is clicked', () => {
|
|
51
93
|
const handleCardClick = vi.fn();
|
|
52
94
|
const handleButtonClick = vi.fn();
|
|
@@ -86,25 +128,80 @@ describe('ClickableCard', () => {
|
|
|
86
128
|
expect(link).toHaveAttribute('target', '_blank');
|
|
87
129
|
});
|
|
88
130
|
|
|
89
|
-
it('disabled button is disabled', () => {
|
|
131
|
+
it('disabled button is disabled and the surface runs nothing', () => {
|
|
90
132
|
const handleClick = vi.fn();
|
|
91
133
|
render(
|
|
92
134
|
<ClickableCard label="Disabled" onClick={handleClick} isDisabled>
|
|
93
|
-
Content
|
|
135
|
+
<span>Content</span>
|
|
94
136
|
</ClickableCard>,
|
|
95
137
|
);
|
|
96
138
|
const button = screen.getByRole('button', {name: 'Disabled'});
|
|
97
139
|
expect(button).toBeDisabled();
|
|
140
|
+
fireEvent.click(screen.getByText('Content'));
|
|
141
|
+
fireEvent.click(button);
|
|
142
|
+
expect(handleClick).not.toHaveBeenCalled();
|
|
98
143
|
});
|
|
99
144
|
|
|
100
|
-
it('disabled link has aria-disabled', () => {
|
|
145
|
+
it('disabled link has aria-disabled, leaves the tab order, and the surface runs nothing', () => {
|
|
146
|
+
const handleClick = vi.fn();
|
|
101
147
|
render(
|
|
102
|
-
<ClickableCard
|
|
103
|
-
|
|
148
|
+
<ClickableCard
|
|
149
|
+
label="Disabled link"
|
|
150
|
+
href="/settings"
|
|
151
|
+
onClick={handleClick}
|
|
152
|
+
isDisabled>
|
|
153
|
+
<span>Content</span>
|
|
104
154
|
</ClickableCard>,
|
|
105
155
|
);
|
|
106
156
|
const link = screen.getByRole('link', {name: 'Disabled link'});
|
|
107
157
|
expect(link).toHaveAttribute('aria-disabled', 'true');
|
|
158
|
+
expect(link).toHaveAttribute('tabindex', '-1');
|
|
159
|
+
fireEvent.click(screen.getByText('Content'));
|
|
160
|
+
expect(handleClick).not.toHaveBeenCalled();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('runs onClick once from the keyboard via the hidden button', async () => {
|
|
164
|
+
const user = userEvent.setup();
|
|
165
|
+
const handleClick = vi.fn();
|
|
166
|
+
render(
|
|
167
|
+
<ClickableCard label="Test card" onClick={handleClick}>
|
|
168
|
+
<span>Content</span>
|
|
169
|
+
</ClickableCard>,
|
|
170
|
+
);
|
|
171
|
+
await user.tab();
|
|
172
|
+
expect(screen.getByRole('button', {name: 'Test card'})).toHaveFocus();
|
|
173
|
+
await user.keyboard('{Enter}');
|
|
174
|
+
await user.keyboard(' ');
|
|
175
|
+
expect(handleClick).toHaveBeenCalledTimes(2);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('does NOT call onClick when a nested input is clicked', () => {
|
|
179
|
+
const handleCardClick = vi.fn();
|
|
180
|
+
render(
|
|
181
|
+
<ClickableCard label="Test card" onClick={handleCardClick}>
|
|
182
|
+
<input aria-label="Quantity" readOnly value="1" />
|
|
183
|
+
</ClickableCard>,
|
|
184
|
+
);
|
|
185
|
+
fireEvent.click(screen.getByRole('textbox', {name: 'Quantity'}));
|
|
186
|
+
expect(handleCardClick).not.toHaveBeenCalled();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('does NOT call onClick when text inside the card is selected', () => {
|
|
190
|
+
const handleClick = vi.fn();
|
|
191
|
+
render(
|
|
192
|
+
<ClickableCard label="Test card" onClick={handleClick}>
|
|
193
|
+
<span>Selectable body text</span>
|
|
194
|
+
</ClickableCard>,
|
|
195
|
+
);
|
|
196
|
+
const text = screen.getByText('Selectable body text');
|
|
197
|
+
const range = document.createRange();
|
|
198
|
+
range.selectNodeContents(text);
|
|
199
|
+
const selection = document.getSelection()!;
|
|
200
|
+
selection.removeAllRanges();
|
|
201
|
+
selection.addRange(range);
|
|
202
|
+
fireEvent.click(text);
|
|
203
|
+
expect(handleClick).not.toHaveBeenCalled();
|
|
204
|
+
selection.removeAllRanges();
|
|
108
205
|
});
|
|
109
206
|
|
|
110
207
|
describe('elevation', () => {
|
|
@@ -82,6 +82,17 @@ export const docs = {
|
|
|
82
82
|
{name: 'CollapsibleGroup'},
|
|
83
83
|
],
|
|
84
84
|
usage: {
|
|
85
|
+
accessibility: [
|
|
86
|
+
{
|
|
87
|
+
name: 'Trigger label',
|
|
88
|
+
category: 'Color contrast',
|
|
89
|
+
criterion: '1.4.3 Contrast (Minimum)',
|
|
90
|
+
requirement: '4.5:1',
|
|
91
|
+
states: ['Rest', 'Pointer down'],
|
|
92
|
+
description:
|
|
93
|
+
'The trigger text must have at least 4.5:1 contrast with the surface behind it. For Pointer down, measure against the pressed overlay the trigger row paints while it is pressed.',
|
|
94
|
+
},
|
|
95
|
+
],
|
|
85
96
|
description: 'Collapsible hides and reveals content behind a trigger button. Use it in settings panels, FAQ pages, or detail views to keep the page scannable while letting users drill into sections they care about. Wrap multiple collapsibles in CollapsibleGroup for accordion behavior. For custom collapsible components, use the `useCollapsible` hook directly (`astryx hook useCollapsible`).',
|
|
86
97
|
bestPractices: [
|
|
87
98
|
{ guidance: true, description: 'Use hasDividers on CollapsibleGroup for FAQ-style lists: built-in row hairlines with themed border tokens, no hand-rolled borders.' },
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import {describe, it, expect, vi} from 'vitest';
|
|
13
13
|
import {render, screen, within} from '@testing-library/react';
|
|
14
14
|
import userEvent from '@testing-library/user-event';
|
|
15
|
+
import {hasPressedArm} from '../__tests__/pressState';
|
|
15
16
|
import {Collapsible} from './Collapsible';
|
|
16
17
|
import {CollapsibleGroup} from './CollapsibleGroup';
|
|
17
18
|
|
|
@@ -615,3 +616,23 @@ describe('Collapsible', () => {
|
|
|
615
616
|
});
|
|
616
617
|
});
|
|
617
618
|
});
|
|
619
|
+
|
|
620
|
+
describe('pressed state', () => {
|
|
621
|
+
it('paints the pressed overlay on the trigger row while it is pressed', () => {
|
|
622
|
+
render(
|
|
623
|
+
<Collapsible trigger="Details">
|
|
624
|
+
<p>Body</p>
|
|
625
|
+
</Collapsible>,
|
|
626
|
+
);
|
|
627
|
+
expect(hasPressedArm(screen.getByRole('button'))).toBe(true);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it('does not press a disabled trigger', () => {
|
|
631
|
+
render(
|
|
632
|
+
<Collapsible trigger="Details" isDisabled>
|
|
633
|
+
<p>Body</p>
|
|
634
|
+
</Collapsible>,
|
|
635
|
+
);
|
|
636
|
+
expect(hasPressedArm(screen.getByRole('button'))).toBe(false);
|
|
637
|
+
});
|
|
638
|
+
});
|
|
@@ -48,6 +48,7 @@ import {mergeProps} from '../utils';
|
|
|
48
48
|
import type {BaseProps} from '../BaseProps';
|
|
49
49
|
import {themeProps} from '../utils/themeProps';
|
|
50
50
|
import {focusOutlineProps} from '../utils/focusOutline.stylex';
|
|
51
|
+
import {interactionOverlayStyles} from '../utils/interactionOverlay.stylex';
|
|
51
52
|
|
|
52
53
|
const styles = stylex.create({
|
|
53
54
|
root: {
|
|
@@ -411,6 +412,10 @@ export function Collapsible({
|
|
|
411
412
|
focusOutlineProps.focusVisible(
|
|
412
413
|
styles.trigger,
|
|
413
414
|
density != null && triggerDensity[density],
|
|
415
|
+
// The system's pressed overlay on the disclosure row. The trigger
|
|
416
|
+
// has no hover surface of its own, so this is the one background
|
|
417
|
+
// it paints, and only while it is pressed.
|
|
418
|
+
!isDisabled && interactionOverlayStyles.pressedBackgroundColor,
|
|
414
419
|
isDisabled && styles.triggerDisabled,
|
|
415
420
|
),
|
|
416
421
|
)}>
|
|
@@ -73,9 +73,14 @@ const styles = stylex.create({
|
|
|
73
73
|
backgroundColor: 'transparent',
|
|
74
74
|
color: colorVars['--color-text-primary'],
|
|
75
75
|
fontFamily: typographyVars['--font-family-body'],
|
|
76
|
+
// The 16px floor is iOS-only: iOS Safari zooms the page when a focused
|
|
77
|
+
// control sits under 16px, and only iOS WebKit implements
|
|
78
|
+
// -webkit-touch-callout to key the coarse-pointer floor to it.
|
|
76
79
|
fontSize: {
|
|
77
80
|
default: typeScaleVars['--text-body-size'],
|
|
78
|
-
'@media (pointer: coarse)':
|
|
81
|
+
'@media (pointer: coarse)': {
|
|
82
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
|
|
83
|
+
},
|
|
79
84
|
},
|
|
80
85
|
lineHeight: typeScaleVars['--text-body-leading'],
|
|
81
86
|
padding: 0,
|
|
@@ -65,9 +65,14 @@ const styles = stylex.create({
|
|
|
65
65
|
paddingBlock: spacingVars['--spacing-2'],
|
|
66
66
|
paddingInline: spacingVars['--spacing-3'],
|
|
67
67
|
fontFamily: typographyVars['--font-family-body'],
|
|
68
|
+
// The 16px floor is iOS-only: iOS Safari zooms the page when a focused
|
|
69
|
+
// control sits under 16px, and only iOS WebKit implements
|
|
70
|
+
// -webkit-touch-callout to key the coarse-pointer floor to it.
|
|
68
71
|
fontSize: {
|
|
69
72
|
default: typeScaleVars['--text-label-size'],
|
|
70
|
-
'@media (pointer: coarse)':
|
|
73
|
+
'@media (pointer: coarse)': {
|
|
74
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-label-size']})`,
|
|
75
|
+
},
|
|
71
76
|
},
|
|
72
77
|
lineHeight: typeScaleVars['--text-label-leading'],
|
|
73
78
|
color: colorVars['--color-text-primary'],
|
|
@@ -99,9 +99,14 @@ const styles = stylex.create({
|
|
|
99
99
|
borderStyle: 'none',
|
|
100
100
|
padding: 0,
|
|
101
101
|
fontFamily: typographyVars['--font-family-body'],
|
|
102
|
+
// The 16px floor is iOS-only: iOS Safari zooms the page when a focused
|
|
103
|
+
// control sits under 16px, and only iOS WebKit implements
|
|
104
|
+
// -webkit-touch-callout to key the coarse-pointer floor to it.
|
|
102
105
|
fontSize: {
|
|
103
106
|
default: typeScaleVars['--text-body-size'],
|
|
104
|
-
'@media (pointer: coarse)':
|
|
107
|
+
'@media (pointer: coarse)': {
|
|
108
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
|
|
109
|
+
},
|
|
105
110
|
},
|
|
106
111
|
lineHeight: typeScaleVars['--text-body-leading'],
|
|
107
112
|
color: colorVars['--color-text-primary'],
|
|
@@ -100,10 +100,14 @@ const styles = stylex.create({
|
|
|
100
100
|
borderStyle: 'none',
|
|
101
101
|
padding: 0,
|
|
102
102
|
fontFamily: typographyVars['--font-family-body'],
|
|
103
|
-
// Below 16px iOS zooms the page when the field takes focus.
|
|
103
|
+
// Below 16px iOS zooms the page when the field takes focus. Only iOS
|
|
104
|
+
// WebKit implements -webkit-touch-callout, so the coarse-pointer floor is
|
|
105
|
+
// keyed to iOS alone.
|
|
104
106
|
fontSize: {
|
|
105
107
|
default: typeScaleVars['--text-body-size'],
|
|
106
|
-
'@media (pointer: coarse)':
|
|
108
|
+
'@media (pointer: coarse)': {
|
|
109
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
|
|
110
|
+
},
|
|
107
111
|
},
|
|
108
112
|
lineHeight: typeScaleVars['--text-body-leading'],
|
|
109
113
|
color: colorVars['--color-text-primary'],
|
|
@@ -112,11 +116,20 @@ const styles = stylex.create({
|
|
|
112
116
|
// A date control's intrinsic height comes from its inner edit fields, not
|
|
113
117
|
// from `line-height`, so it renders ~2px taller than a text input and its
|
|
114
118
|
// value sits off the shared baseline inside the same flex row. One line
|
|
115
|
-
// box is exactly what the text field occupies.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
// box is exactly what the text field occupies. The calc fallback mirrors
|
|
120
|
+
// the iOS-only floor above; browsers with `1lh` track it for free.
|
|
121
|
+
height: {
|
|
122
|
+
default: stylex.firstThatWorks(
|
|
123
|
+
'1lh',
|
|
124
|
+
`calc(${typeScaleVars['--text-body-size']} * ${typeScaleVars['--text-body-leading']})`,
|
|
125
|
+
),
|
|
126
|
+
'@media (pointer: coarse)': {
|
|
127
|
+
'@supports (-webkit-touch-callout: none)': stylex.firstThatWorks(
|
|
128
|
+
'1lh',
|
|
129
|
+
`calc(max(1rem, ${typeScaleVars['--text-body-size']}) * ${typeScaleVars['--text-body-leading']})`,
|
|
130
|
+
),
|
|
131
|
+
},
|
|
132
|
+
},
|
|
120
133
|
// iOS gives date controls their own button-like chrome, with inner
|
|
121
134
|
// spacing and a centred value that no reset of ours can reach.
|
|
122
135
|
WebkitAppearance: 'none',
|
|
@@ -187,9 +200,12 @@ const styles = stylex.create({
|
|
|
187
200
|
// covers — one line of that leading fills its height exactly, which puts
|
|
188
201
|
// the glyphs on the input's own baseline.
|
|
189
202
|
display: 'block',
|
|
203
|
+
// ...with the input's own iOS floor, so the two baselines stay shared.
|
|
190
204
|
fontSize: {
|
|
191
205
|
default: typeScaleVars['--text-body-size'],
|
|
192
|
-
'@media (pointer: coarse)':
|
|
206
|
+
'@media (pointer: coarse)': {
|
|
207
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
|
|
208
|
+
},
|
|
193
209
|
},
|
|
194
210
|
lineHeight: typeScaleVars['--text-body-leading'],
|
|
195
211
|
// A tap has to reach the control underneath — that is what raises the
|
|
@@ -194,10 +194,14 @@ const styles = stylex.create({
|
|
|
194
194
|
padding: 0,
|
|
195
195
|
fontFamily: typographyVars['--font-family-body'],
|
|
196
196
|
// Below 16px iOS zooms the page on focus. The field is focusable even
|
|
197
|
-
// though it is not typable, so it needs the same floor DateInput has
|
|
197
|
+
// though it is not typable, so it needs the same floor DateInput has —
|
|
198
|
+
// keyed to iOS alone, since only iOS WebKit implements
|
|
199
|
+
// -webkit-touch-callout.
|
|
198
200
|
fontSize: {
|
|
199
201
|
default: typeScaleVars['--text-body-size'],
|
|
200
|
-
'@media (pointer: coarse)':
|
|
202
|
+
'@media (pointer: coarse)': {
|
|
203
|
+
'@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
|
|
204
|
+
},
|
|
201
205
|
},
|
|
202
206
|
lineHeight: typeScaleVars['--text-body-leading'],
|
|
203
207
|
color: colorVars['--color-text-primary'],
|
|
@@ -114,7 +114,7 @@ export const docs = {
|
|
|
114
114
|
name: 'presets',
|
|
115
115
|
type: 'Array<DateRangePreset>',
|
|
116
116
|
description:
|
|
117
|
-
'Preset ranges shown as quick-select options beside the calendar.',
|
|
117
|
+
'Preset ranges shown as quick-select options beside the calendar. A preset is disabled when either endpoint violates min, max, or dateConstraints, or when its span violates minRangeSpan or maxRangeSpan.',
|
|
118
118
|
},
|
|
119
119
|
{
|
|
120
120
|
name: 'hasClear',
|
|
@@ -178,9 +178,21 @@ export const docs = {
|
|
|
178
178
|
],
|
|
179
179
|
theming: {
|
|
180
180
|
targets: [
|
|
181
|
-
{
|
|
181
|
+
{
|
|
182
|
+
className: 'astryx-date-range-input',
|
|
183
|
+
visualProps: ['size', 'status'],
|
|
184
|
+
states: ['disabled'],
|
|
185
|
+
},
|
|
182
186
|
{className: 'astryx-date-range-input-toggle-icon', states: ['state']},
|
|
183
|
-
{
|
|
187
|
+
{
|
|
188
|
+
className: 'astryx-date-range-input-clear-icon',
|
|
189
|
+
deprecatedFor: 'input-clear-icon',
|
|
190
|
+
},
|
|
191
|
+
{className: 'astryx-date-range-input-presets'},
|
|
192
|
+
{
|
|
193
|
+
className: 'astryx-date-range-input-preset',
|
|
194
|
+
states: ['selected', 'disabled'],
|
|
195
|
+
},
|
|
184
196
|
],
|
|
185
197
|
},
|
|
186
198
|
usage: {
|
|
@@ -230,6 +242,12 @@ export const docs = {
|
|
|
230
242
|
description:
|
|
231
243
|
'Text above the trigger describing what date range is expected.',
|
|
232
244
|
},
|
|
245
|
+
{
|
|
246
|
+
name: 'Field surface',
|
|
247
|
+
required: true,
|
|
248
|
+
description:
|
|
249
|
+
'Bordered control containing the calendar toggle, range trigger, and end affordances.',
|
|
250
|
+
},
|
|
233
251
|
{
|
|
234
252
|
name: 'Trigger button',
|
|
235
253
|
required: true,
|
|
@@ -252,6 +270,12 @@ export const docs = {
|
|
|
252
270
|
required: false,
|
|
253
271
|
description: 'A list of preset range options beside the calendar.',
|
|
254
272
|
},
|
|
273
|
+
{
|
|
274
|
+
name: 'Preset button',
|
|
275
|
+
required: false,
|
|
276
|
+
description:
|
|
277
|
+
'A quick-select action for one preset range, reflecting current and disabled states.',
|
|
278
|
+
},
|
|
255
279
|
{
|
|
256
280
|
name: 'Clear button',
|
|
257
281
|
required: false,
|
|
@@ -320,7 +344,8 @@ export const docsDense = {
|
|
|
320
344
|
isDisabled: 'disable trigger+picker',
|
|
321
345
|
disabledMessage:
|
|
322
346
|
'reason shown in a tooltip on hover/focus when disabled; keeps trigger focusable via aria-disabled',
|
|
323
|
-
value:
|
|
347
|
+
value:
|
|
348
|
+
'selected range {start, end} or null; import DateRange type from @astryxdesign/core/DateRangeInput (do not redeclare)',
|
|
324
349
|
onChange: 'callback on range change; null on clear',
|
|
325
350
|
min: 'min selectable date: ISODateString template literal type (YYYY-MM-DD); use string literal or cast `as ISODateString`',
|
|
326
351
|
max: 'max selectable date: ISODateString template literal type (YYYY-MM-DD); use string literal or cast `as ISODateString`',
|
|
@@ -329,15 +354,18 @@ export const docsDense = {
|
|
|
329
354
|
'max days a range may span, both endpoints counted (7 = a 7-day window); caps the window from the picked start. Selection-only; does not rewrite an over-wide value',
|
|
330
355
|
minRangeSpan:
|
|
331
356
|
'min days a range must span, both endpoints counted (2 forbids a single-day range); repeated start click commits one day when allowed, otherwise cancels; default 1',
|
|
332
|
-
presets:
|
|
357
|
+
presets:
|
|
358
|
+
'preset ranges as quick-select options; disabled when an endpoint or span violates the corresponding constraints',
|
|
333
359
|
hasClear: 'clear button when range is set (default true)',
|
|
334
360
|
placeholder: 'placeholder when empty',
|
|
335
361
|
size: 'trigger size',
|
|
336
362
|
status: 'error/warning/success status',
|
|
337
|
-
statusVariant:
|
|
363
|
+
statusVariant:
|
|
364
|
+
'How status message is placed: attached overlaps below input; detached floats below w/ spacing; tooltip hides the box and shows it on the status icon.',
|
|
338
365
|
labelTooltip: 'tooltip via info icon at label end',
|
|
339
366
|
numberOfMonths: 'months in calendar (default 2)',
|
|
340
|
-
weekStartsOn:
|
|
367
|
+
weekStartsOn:
|
|
368
|
+
'first day of week in calendar (0=Sunday, or name e.g. "mon")',
|
|
341
369
|
changeAction:
|
|
342
370
|
'async action fired after onChange; drives optimistic UI updates via useTransition',
|
|
343
371
|
isLoading: 'loading state; disables interaction + shows a spinner',
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: 3
|
|
3
|
+
template_version: 4
|
|
4
|
+
kind: component
|
|
5
|
+
id: component:DateRangeInput
|
|
6
|
+
authority: current
|
|
7
|
+
archive_reason: null
|
|
8
|
+
superseded_by: null
|
|
9
|
+
approved_by: cixzhang
|
|
10
|
+
approved_at: 2026-09-14
|
|
11
|
+
owners: [cixzhang]
|
|
12
|
+
review_triggers: [theming]
|
|
13
|
+
verified_by:
|
|
14
|
+
[
|
|
15
|
+
packages/core/src/DateRangeInput/DateRangeInput.test.tsx,
|
|
16
|
+
packages/core/src/theme/themingTargets.test.ts,
|
|
17
|
+
scripts/check-knowledge.mjs,
|
|
18
|
+
]
|
|
19
|
+
modules: []
|
|
20
|
+
families: [family:input-fields, family:overlay-dismissal]
|
|
21
|
+
design_specs: []
|
|
22
|
+
architecture: [architecture:component-theming-surface]
|
|
23
|
+
contributing: []
|
|
24
|
+
system_specs: []
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# DateRangeInput component contract
|
|
28
|
+
|
|
29
|
+
## Intent
|
|
30
|
+
|
|
31
|
+
DateRangeInput presents one controlled date range through a labeled field surface.
|
|
32
|
+
Its trigger opens a Calendar-backed Popover and may include a list of quick-select
|
|
33
|
+
presets beside that calendar.
|
|
34
|
+
|
|
35
|
+
## Compatibility and migration
|
|
36
|
+
|
|
37
|
+
- Released default preserved: `yes`
|
|
38
|
+
- Compatibility class: additive theme targets plus corrected preset constraint
|
|
39
|
+
enforcement; default appearance, DOM semantics, and public props remain unchanged
|
|
40
|
+
- Controlled/uncontrolled behavior: unchanged; DateRangeInput remains controlled
|
|
41
|
+
- Migration decision: none
|
|
42
|
+
|
|
43
|
+
Consumer migration instructions belong in consumer docs and release notes.
|
|
44
|
+
|
|
45
|
+
## Ownership boundary
|
|
46
|
+
|
|
47
|
+
**Owns**
|
|
48
|
+
|
|
49
|
+
- The composite date-range field surface and its range-display trigger.
|
|
50
|
+
- The optional preset list, each preset action, and reflection of a preset's current
|
|
51
|
+
and disabled states.
|
|
52
|
+
- Converting a selected Calendar range or preset into `onChange` and
|
|
53
|
+
`changeAction` output.
|
|
54
|
+
|
|
55
|
+
**Does not own / non-goals**
|
|
56
|
+
|
|
57
|
+
- Label, description, and status presentation — owned by `component:Field` and
|
|
58
|
+
`component:FieldStatus`.
|
|
59
|
+
- Calendar-grid rendering and date-cell interaction — owned by
|
|
60
|
+
`component:Calendar`.
|
|
61
|
+
- Layer hosting and dismissal — owned by `component:Popover` and
|
|
62
|
+
`family:overlay-dismissal`.
|
|
63
|
+
- Shared clear-button presentation — owned by `component:Field`.
|
|
64
|
+
|
|
65
|
+
## Public concepts
|
|
66
|
+
|
|
67
|
+
No public prop or value domain is added. This contract records the existing
|
|
68
|
+
preset-list anatomy and its additive theming surface.
|
|
69
|
+
|
|
70
|
+
## Behavioral and layout contract
|
|
71
|
+
|
|
72
|
+
| ID | Candidate invariant | Basis | Review state |
|
|
73
|
+
| --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | -------------------------- |
|
|
74
|
+
| FR1 | DateRangeInput MUST present the controlled `value` and emit range changes without maintaining a competing selected range. | Current source, docs, and focused tests | Verified current behavior |
|
|
75
|
+
| FR2 | When presets are present, each preset remains an independent button in one labeled group; the applied preset reflects current state and a preset whose endpoint violates `min`, `max`, or `dateConstraints`, or whose range violates `minRangeSpan` or `maxRangeSpan`, reflects disabled state. | Current source, accessibility comments, and focused tests | Verified current behavior |
|
|
76
|
+
| FR3 | The preset group and each preset button expose stable theme targets; selected and disabled are states of the preset-button target rather than separate targets. | `architecture:component-theming-surface`; #5417 demand | Approved additive contract |
|
|
77
|
+
| FR4 | Adding theme targets MUST NOT change the Popover, Calendar, button, focus, or selection semantics those elements already own. | Composition boundary and compatibility goal | Approved additive contract |
|
|
78
|
+
|
|
79
|
+
### Allowed variation
|
|
80
|
+
|
|
81
|
+
- **AV1 — Preset content.** A caller may omit presets or provide any number of
|
|
82
|
+
labeled ranges.
|
|
83
|
+
- **AV2 — Calendar layout.** Calendar month count, date constraints, and range
|
|
84
|
+
bounds may vary without changing preset target identity.
|
|
85
|
+
- **AV3 — Theme output.** Themes may restyle the preset group and buttons while
|
|
86
|
+
the component's button semantics and state remain unchanged.
|
|
87
|
+
|
|
88
|
+
### Representative states
|
|
89
|
+
|
|
90
|
+
| State | Required invariant | Allowed variation |
|
|
91
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
|
|
92
|
+
| No presets | No preset group or preset-button target renders. | Calendar configuration |
|
|
93
|
+
| Presets, no match | Every button carries the preset target with no selected state. | Preset count and labels |
|
|
94
|
+
| Applied preset | The matching button carries `aria-current="true"` and the target's selected state. | Selected range |
|
|
95
|
+
| Disabled preset | A preset that violates an endpoint date constraint or a range-span constraint is natively disabled and carries the target's disabled state. | Constraint source |
|
|
96
|
+
|
|
97
|
+
### Transformation and precedence order
|
|
98
|
+
|
|
99
|
+
- **ORD1 — Preset state.** Resolve each preset's range once, compare it with the
|
|
100
|
+
controlled value, evaluate `min`, `max`, and `dateConstraints` against both
|
|
101
|
+
endpoints, evaluate `minRangeSpan` and `maxRangeSpan` against the full range,
|
|
102
|
+
then reflect selected and disabled state on the same preset-button target.
|
|
103
|
+
- **ORD2 — Preset activation.** An enabled preset emits the already-resolved range;
|
|
104
|
+
activation does not resolve the preset again.
|
|
105
|
+
|
|
106
|
+
Endpoint constraints match Calendar selection: they apply to the preset's start
|
|
107
|
+
and end, not every date between them.
|
|
108
|
+
|
|
109
|
+
### Performance and resources
|
|
110
|
+
|
|
111
|
+
No new performance or resource constraint is introduced.
|
|
112
|
+
|
|
113
|
+
## Accessibility contract
|
|
114
|
+
|
|
115
|
+
- **AR1 — Preset semantics.** Presets remain native buttons in one labeled group,
|
|
116
|
+
navigated independently by Tab.
|
|
117
|
+
- **AR2 — Current state.** The applied preset remains exposed with
|
|
118
|
+
`aria-current="true"`; theme state reflection is additive.
|
|
119
|
+
- **AR3 — Disabled state.** A preset that violates an endpoint date constraint or
|
|
120
|
+
a range-span constraint remains natively disabled; theme state reflection does
|
|
121
|
+
not replace that behavior.
|
|
122
|
+
|
|
123
|
+
## Design relationships
|
|
124
|
+
|
|
125
|
+
| Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
|
|
126
|
+
| ---------------- | --------------------------------------------------------------- | ------------------------------------- | -------------- | ------------------ |
|
|
127
|
+
| Field surface | Presents one coherent input boundary. | Current source and input-field family | Prominent | FR1 |
|
|
128
|
+
| Calendar popover | Provides range selection without changing field ownership. | Popover and Calendar components | Prominent | FR1, FR4 |
|
|
129
|
+
| Preset sidebar | Groups optional shortcuts beside the calendar. | Current source and public docs | Supporting | FR2, FR3 |
|
|
130
|
+
| Preset button | Presents one quick-select range and its current/disabled state. | Current source and public docs | Supporting | FR2–FR4, AR1–AR3 |
|
|
131
|
+
|
|
132
|
+
### Theming anatomy
|
|
133
|
+
|
|
134
|
+
<!-- anatomy-theming:v1 -->
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"Label": {
|
|
139
|
+
"delegatesTo": {"owner": "component:Field", "target": "field-label"}
|
|
140
|
+
},
|
|
141
|
+
"Field surface": {"target": "date-range-input"},
|
|
142
|
+
"Trigger button": {
|
|
143
|
+
"none": {
|
|
144
|
+
"reason": "unsettled: The composite field surface has a target, but the inner range-display button has no separate current target."
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
"Calendar icon": {"target": "date-range-input-toggle-icon"},
|
|
148
|
+
"Calendar popover": {
|
|
149
|
+
"delegatesTo": {"owner": "component:Popover", "target": "popover"}
|
|
150
|
+
},
|
|
151
|
+
"Preset sidebar": {"target": "date-range-input-presets"},
|
|
152
|
+
"Preset button": {"target": "date-range-input-preset"},
|
|
153
|
+
"Clear button": {
|
|
154
|
+
"delegatesTo": {"owner": "component:Field", "target": "input-clear-button"}
|
|
155
|
+
},
|
|
156
|
+
"Status message": {
|
|
157
|
+
"delegatesTo": {"owner": "component:FieldStatus", "target": "field-status"}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The deprecated `date-range-input-clear-icon` alias remains compatibility metadata;
|
|
163
|
+
the shared `input-clear-icon` target owns the current glyph contract.
|
|
164
|
+
|
|
165
|
+
## Family and system relationships
|
|
166
|
+
|
|
167
|
+
- `family:input-fields` owns field sizing, status placement, loading, disabled
|
|
168
|
+
reasons, and end-control geometry.
|
|
169
|
+
- `family:overlay-dismissal` owns the Popover dismissal stack.
|
|
170
|
+
- `architecture:component-theming-surface` owns target admission, state
|
|
171
|
+
reflection, and anatomy mapping.
|
|
172
|
+
|
|
173
|
+
## Verification map
|
|
174
|
+
|
|
175
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
176
|
+
| ------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------ |
|
|
177
|
+
| FR1, FR2, AR1–AR3 | `DateRangeInput.test.tsx` | 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
|
+
|
|
181
|
+
## Decision log
|
|
182
|
+
|
|
183
|
+
### DEC-1 — Preset group and buttons are public theme anatomy
|
|
184
|
+
|
|
185
|
+
**Reference:** `component:DateRangeInput/DEC-1`
|
|
186
|
+
**Decider:** cixzhang, 2026-09-14
|
|
187
|
+
|
|
188
|
+
The optional preset sidebar and each quick-select button are stable,
|
|
189
|
+
consumer-recognizable parts. `date-range-input-presets` belongs on the group
|
|
190
|
+
that owns sidebar presentation; `date-range-input-preset` belongs on each
|
|
191
|
+
button, with selected and disabled reflected as states rather than separate
|
|
192
|
+
targets. The target additions preserve default visuals and button semantics;
|
|
193
|
+
preset constraint enforcement follows FR2 and ORD1–ORD2.
|
|
194
|
+
|
|
195
|
+
## Open questions
|
|
196
|
+
|
|
197
|
+
- **OQ1 — Should the inner range-display trigger receive its own target, or remain
|
|
198
|
+
represented only by the composite field surface?** (`human-api`)
|
|
199
|
+
|
|
200
|
+
## Content boundary
|
|
201
|
+
|
|
202
|
+
This file does not duplicate the consumer prop table, usage examples, Calendar or
|
|
203
|
+
Popover contracts, current audit results, or downstream theme implementation.
|