@astryxdesign/core 0.5.2-canary.c9c8564 → 0.5.2-canary.e03511b
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 +3 -1
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +4 -3
- package/dist/BottomSheet/BottomSheetEdgeTint.d.ts +2 -2
- package/dist/BottomSheet/BottomSheetEdgeTint.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetEdgeTint.js +7 -10
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +1 -1
- package/dist/Button/Button.d.ts +2 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +11 -4
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +2 -2
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +34 -17
- package/dist/ContextMenu/ContextMenu.d.ts +14 -3
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +149 -26
- package/dist/ContextMenu/index.d.ts +1 -0
- package/dist/ContextMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.d.ts +24 -5
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +340 -26
- package/dist/DropdownMenu/DropdownMenuContext.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +6 -2
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +43 -14
- package/dist/DropdownMenu/MenuBottomSheet.d.ts +20 -0
- package/dist/DropdownMenu/MenuBottomSheet.d.ts.map +1 -0
- package/dist/DropdownMenu/MenuBottomSheet.js +36 -0
- package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts +10 -0
- package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -0
- package/dist/DropdownMenu/MenuBottomSheetActionList.js +111 -0
- package/dist/DropdownMenu/index.d.ts +2 -1
- package/dist/DropdownMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/menuWidth.d.ts +14 -0
- package/dist/DropdownMenu/menuWidth.d.ts.map +1 -0
- package/dist/DropdownMenu/menuWidth.js +35 -0
- package/dist/DropdownMenu/useMenuOverflow.d.ts +10 -0
- package/dist/DropdownMenu/useMenuOverflow.d.ts.map +1 -0
- package/dist/DropdownMenu/useMenuOverflow.js +60 -0
- package/dist/MoreMenu/MoreMenu.d.ts +8 -2
- package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
- package/dist/MoreMenu/MoreMenu.js +2 -0
- package/dist/MultiSelector/MultiSelector.d.ts +11 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +97 -68
- package/dist/MultiSelector/index.d.ts +1 -1
- package/dist/MultiSelector/index.d.ts.map +1 -1
- package/dist/Popover/Popover.d.ts +10 -6
- package/dist/Popover/Popover.d.ts.map +1 -1
- package/dist/Popover/Popover.js +141 -16
- package/dist/Popover/usePopover.d.ts +4 -0
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +52 -5
- package/dist/Selector/Selector.d.ts +10 -0
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +106 -80
- package/dist/Selector/SelectorBottomSheet.d.ts +21 -0
- package/dist/Selector/SelectorBottomSheet.d.ts.map +1 -0
- package/dist/Selector/SelectorBottomSheet.js +83 -0
- package/dist/Selector/index.d.ts +1 -1
- package/dist/Selector/index.d.ts.map +1 -1
- package/dist/Selector/selectorPresentation.stylex.d.ts +16 -0
- package/dist/Selector/selectorPresentation.stylex.d.ts.map +1 -0
- package/dist/Selector/selectorPresentation.stylex.js +21 -0
- package/dist/Selector/useSelectorPresentation.d.ts +32 -0
- package/dist/Selector/useSelectorPresentation.d.ts.map +1 -0
- package/dist/Selector/useSelectorPresentation.js +90 -0
- package/dist/Toast/useToastGesture.d.ts.map +1 -1
- package/dist/Toast/useToastGesture.js +46 -9
- package/dist/astryx.css +19 -1
- package/dist/hooks/useAdaptivePresentation.d.ts +5 -0
- package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -0
- package/dist/hooks/useAdaptivePresentation.js +19 -0
- package/dist/hooks/useFocusReturnVisibility.d.ts +7 -0
- package/dist/hooks/useFocusReturnVisibility.d.ts.map +1 -0
- package/dist/hooks/useFocusReturnVisibility.js +35 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +12 -0
- package/locales/en.json +20 -0
- package/locales/pseudo.json +15 -0
- package/package.json +2 -2
- package/src/BottomSheet/BottomSheet.doc.mjs +37 -0
- package/src/BottomSheet/BottomSheet.spec.md +177 -0
- package/src/BottomSheet/BottomSheet.test.tsx +29 -0
- package/src/BottomSheet/BottomSheet.tsx +7 -4
- package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +4 -6
- package/src/BottomSheet/BottomSheetEdgeTint.tsx +7 -10
- package/src/BottomSheet/BottomSheetSwitcher.tsx +1 -2
- package/src/Button/Button.doc.mjs +56 -0
- package/src/Button/Button.test.tsx +10 -0
- package/src/Button/Button.tsx +13 -5
- package/src/ButtonGroup/ButtonGroup.doc.mjs +47 -0
- package/src/Carousel/Carousel.test.tsx +83 -0
- package/src/Carousel/Carousel.tsx +8 -2
- package/src/Chat/ChatToolCalls.test.tsx +44 -0
- package/src/Chat/ChatToolCalls.tsx +36 -15
- package/src/CheckboxList/CheckboxList.doc.mjs +172 -31
- package/src/CheckboxList/CheckboxList.spec.md +214 -0
- package/src/CommandPalette/CommandPalette.doc.mjs +72 -0
- package/src/CommandPalette/CommandPalette.spec.md +223 -0
- package/src/ComplexSelector/ComplexSelector.doc.mjs +42 -0
- package/src/ComplexSelector/ComplexSelector.spec.md +189 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +61 -1
- package/src/ContextMenu/ContextMenu.spec.md +206 -0
- package/src/ContextMenu/ContextMenu.test.tsx +131 -1
- package/src/ContextMenu/ContextMenu.tsx +199 -32
- package/src/ContextMenu/index.ts +1 -0
- package/src/Divider/Divider.doc.mjs +24 -0
- package/src/Divider/Divider.spec.md +150 -0
- package/src/DropdownMenu/DropdownMenu.doc.mjs +305 -40
- package/src/DropdownMenu/DropdownMenu.spec.md +245 -0
- package/src/DropdownMenu/DropdownMenu.test.tsx +518 -7
- package/src/DropdownMenu/DropdownMenu.tsx +459 -28
- package/src/DropdownMenu/DropdownMenuContext.tsx +1 -1
- package/src/DropdownMenu/DropdownMenuItem.tsx +2 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +4 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +124 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +72 -17
- package/src/DropdownMenu/MenuBottomSheet.tsx +46 -0
- package/src/DropdownMenu/MenuBottomSheetActionList.tsx +141 -0
- package/src/DropdownMenu/index.ts +2 -0
- package/src/DropdownMenu/menuWidth.ts +51 -0
- package/src/DropdownMenu/useMenuOverflow.ts +77 -0
- package/src/FieldStatus/FieldStatus.doc.mjs +21 -0
- package/src/FieldStatus/FieldStatus.spec.md +212 -0
- package/src/Grid/Grid.doc.mjs +19 -0
- package/src/Grid/Grid.spec.md +161 -0
- package/src/Icon/Icon.doc.mjs +196 -48
- package/src/Icon/Icon.spec.md +191 -0
- package/src/IconButton/IconButton.doc.mjs +38 -0
- package/src/Kbd/Kbd.doc.mjs +18 -0
- package/src/Kbd/Kbd.spec.md +141 -0
- package/src/Layout/Layout.doc.mjs +32 -0
- package/src/Layout/Layout.spec.md +199 -0
- package/src/Lightbox/Lightbox.doc.mjs +149 -33
- package/src/Lightbox/Lightbox.spec.md +197 -0
- package/src/Markdown/Markdown.doc.mjs +60 -0
- package/src/Markdown/Markdown.spec.md +200 -0
- package/src/MobileNav/MobileNav.doc.mjs +140 -32
- package/src/MobileNav/MobileNav.spec.md +208 -0
- package/src/MoreMenu/MoreMenu.doc.mjs +59 -0
- package/src/MoreMenu/MoreMenu.spec.md +221 -0
- package/src/MoreMenu/MoreMenu.test.tsx +47 -0
- package/src/MoreMenu/MoreMenu.tsx +13 -1
- package/src/MultiSelector/MultiSelector.doc.mjs +128 -0
- package/src/MultiSelector/MultiSelector.spec.md +266 -0
- package/src/MultiSelector/MultiSelector.test.tsx +127 -0
- package/src/MultiSelector/MultiSelector.tsx +121 -73
- package/src/MultiSelector/index.ts +1 -0
- package/src/NavIcon/NavIcon.doc.mjs +17 -0
- package/src/NavIcon/NavIcon.spec.md +133 -0
- package/src/NavMenu/NavHeadingMenu.spec.md +205 -0
- package/src/NavMenu/NavMenu.doc.mjs +37 -0
- package/src/Outline/Outline.doc.mjs +35 -0
- package/src/Outline/Outline.spec.md +161 -0
- package/src/OverflowList/OverflowList.doc.mjs +24 -0
- package/src/OverflowList/OverflowList.spec.md +156 -0
- package/src/Pagination/Pagination.doc.mjs +74 -0
- package/src/Pagination/Pagination.spec.md +225 -0
- package/src/Popover/Popover.doc.mjs +11 -11
- package/src/Popover/Popover.test.tsx +397 -2
- package/src/Popover/Popover.tsx +228 -21
- package/src/Popover/usePopover.tsx +92 -15
- package/src/ProgressBar/ProgressBar.doc.mjs +198 -38
- package/src/ProgressBar/ProgressBar.spec.md +260 -0
- package/src/Resizable/Resizable.doc.mjs +59 -16
- package/src/Resizable/Resizable.spec.md +194 -0
- package/src/Section/Section.doc.mjs +17 -0
- package/src/Section/Section.spec.md +138 -0
- package/src/SegmentedControl/SegmentedControl.doc.mjs +83 -0
- package/src/SegmentedControl/SegmentedControl.spec.md +151 -0
- package/src/Selector/Selector.doc.mjs +12 -0
- package/src/Selector/Selector.spec.md +202 -0
- package/src/Selector/Selector.test.tsx +135 -0
- package/src/Selector/Selector.tsx +141 -82
- package/src/Selector/SelectorBottomSheet.tsx +97 -0
- package/src/Selector/index.ts +1 -0
- package/src/Selector/selectorPresentation.stylex.ts +20 -0
- package/src/Selector/useSelectorPresentation.ts +147 -0
- package/src/Skeleton/Skeleton.doc.mjs +12 -0
- package/src/Skeleton/Skeleton.spec.md +126 -0
- package/src/Slider/Slider.doc.mjs +184 -30
- package/src/Slider/Slider.spec.md +223 -0
- package/src/Stack/Stack.doc.mjs +22 -0
- package/src/Stack/Stack.spec.md +154 -0
- package/src/StatusDot/StatusDot.doc.mjs +17 -0
- package/src/StatusDot/StatusDot.spec.md +137 -0
- package/src/Switch/Switch.doc.mjs +40 -0
- package/src/Switch/Switch.spec.md +174 -0
- package/src/Table/Table.doc.mjs +102 -24
- package/src/Table/Table.spec.md +283 -0
- package/src/Text/Text.doc.mjs +190 -43
- package/src/Text/Text.spec.md +205 -0
- package/src/TextArea/TextArea.doc.mjs +255 -45
- package/src/TextArea/TextArea.spec.md +204 -0
- package/src/Toast/ToastViewport.test.tsx +118 -4
- package/src/Toast/useToastGesture.ts +61 -8
- package/src/ToggleButton/ToggleButton.doc.mjs +56 -0
- package/src/Toolbar/Toolbar.doc.mjs +19 -0
- package/src/Toolbar/Toolbar.spec.md +173 -0
- package/src/Tooltip/Tooltip.doc.mjs +17 -0
- package/src/Tooltip/Tooltip.spec.md +141 -0
- package/src/TreeList/TreeList.doc.mjs +58 -0
- package/src/TreeList/TreeList.spec.md +220 -0
- package/src/Typeahead/Typeahead.doc.mjs +83 -1
- package/src/Typeahead/Typeahead.spec.md +250 -0
- package/src/hooks/useAdaptivePresentation.ts +35 -0
- package/src/hooks/useFocusReturnVisibility.ts +45 -0
- package/src/hooks/useFocusTrap.test.tsx +40 -0
- package/src/hooks/useFocusTrap.ts +13 -0
- package/src/theme/themingTargets.test.ts +91 -25
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: 1
|
|
3
|
+
template_version: 3
|
|
4
|
+
kind: component
|
|
5
|
+
id: component:MultiSelector
|
|
6
|
+
authority: draft
|
|
7
|
+
archive_reason: null
|
|
8
|
+
superseded_by: null
|
|
9
|
+
approved_by: null
|
|
10
|
+
approved_at: null
|
|
11
|
+
owners: [cixzhang]
|
|
12
|
+
review_triggers: [theming]
|
|
13
|
+
verified_by:
|
|
14
|
+
[
|
|
15
|
+
packages/core/src/MultiSelector/MultiSelector.test.tsx,
|
|
16
|
+
packages/core/src/BottomSheet/BottomSheet.test.tsx,
|
|
17
|
+
packages/core/src/CheckboxInput/CheckboxInput.test.tsx,
|
|
18
|
+
packages/core/src/Divider/Divider.test.tsx,
|
|
19
|
+
packages/core/src/Field/Field.test.tsx,
|
|
20
|
+
packages/core/src/Icon/Icon.test.tsx,
|
|
21
|
+
packages/core/src/Text/Text.test.tsx,
|
|
22
|
+
scripts/check-knowledge.mjs,
|
|
23
|
+
]
|
|
24
|
+
families: [family:overlay-dismissal]
|
|
25
|
+
design_specs: []
|
|
26
|
+
architecture:
|
|
27
|
+
[architecture:component-theming-surface, architecture:layer-runtime]
|
|
28
|
+
contributing: []
|
|
29
|
+
system_specs: []
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
# MultiSelector component contract
|
|
33
|
+
|
|
34
|
+
## Intent
|
|
35
|
+
|
|
36
|
+
MultiSelector renders a multi-selection control with an optional standalone Field
|
|
37
|
+
shell and one shared panel content tree in either an anchored pointer popup or a
|
|
38
|
+
touch-oriented BottomSheet.
|
|
39
|
+
This draft records current consumer anatomy and theming ownership without changing
|
|
40
|
+
runtime behavior, styling, targets, or public API.
|
|
41
|
+
|
|
42
|
+
## Compatibility and migration
|
|
43
|
+
|
|
44
|
+
- Released default preserved: `yes`
|
|
45
|
+
- Compatibility class: additive documentation only; runtime, DOM, styling,
|
|
46
|
+
targets, and public API remain unchanged
|
|
47
|
+
- Controlled/uncontrolled behavior: unchanged
|
|
48
|
+
- Migration decision: none
|
|
49
|
+
|
|
50
|
+
Consumer migration instructions belong in consumer docs and release notes.
|
|
51
|
+
|
|
52
|
+
## Ownership boundary
|
|
53
|
+
|
|
54
|
+
**Owns**
|
|
55
|
+
|
|
56
|
+
- The painted Trigger and its current `multi-selector` target.
|
|
57
|
+
- Indicator icon, Search row, Option row, Section heading, Empty state, and
|
|
58
|
+
Pointer popup presentation through the other six current MultiSelector targets.
|
|
59
|
+
- One shared panel-content tree containing optional search, option, divider,
|
|
60
|
+
section, and empty-state content regardless of which presentation hosts it.
|
|
61
|
+
|
|
62
|
+
**Does not own / non-goals**
|
|
63
|
+
|
|
64
|
+
- Standalone field-shell presentation — rendered by Field and themed through
|
|
65
|
+
Field's current `field` target; omitted when MultiSelector is inside InputGroup.
|
|
66
|
+
- Shared clear actions — rendered by Field's InputClearButton and themed through
|
|
67
|
+
Field's current `input-clear-button` target.
|
|
68
|
+
- Option checkbox presentation — rendered by CheckboxInput and themed through
|
|
69
|
+
CheckboxInput's current `checkbox-indicator` target.
|
|
70
|
+
- Public option dividers — rendered by Divider and themed through Divider's
|
|
71
|
+
current `divider` target.
|
|
72
|
+
- General Icon presentation for start, search, and status icons — rendered by
|
|
73
|
+
Icon and themed through Icon's current `icon` target.
|
|
74
|
+
- The Touch sheet heading — rendered by Heading and themed through Text's current
|
|
75
|
+
`heading` target.
|
|
76
|
+
- The Touch sheet panel — rendered by BottomSheet and themed through
|
|
77
|
+
BottomSheet's current `bottom-sheet` target.
|
|
78
|
+
- Arbitrary ReactNode start content supplied by the caller.
|
|
79
|
+
- Shared layer lifecycle, positioning, and dismissal behavior.
|
|
80
|
+
|
|
81
|
+
## Public concepts
|
|
82
|
+
|
|
83
|
+
No new public concept is introduced. Consumer props and usage remain documented
|
|
84
|
+
in `MultiSelector.doc.mjs`.
|
|
85
|
+
|
|
86
|
+
## Behavioral and layout contract
|
|
87
|
+
|
|
88
|
+
| ID | Candidate invariant | Basis | Draft review state |
|
|
89
|
+
| --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | -------------------------------------------------- |
|
|
90
|
+
| FR1 | The current standalone presentation contains a Field and painted Trigger; the supported InputGroup path renders the same Trigger without a Field. Optional start content, clear action, status icon, or Indicator icon follows supplied props and state. | Current source, docs, and tests | Verified current behavior; no new behavior decided |
|
|
91
|
+
| FR2 | The shared panel content contains the optional Search row, zero or more Option rows, optional public Option dividers and Section headings, and an Empty state when the applicable result set is empty and the value is not loading. | Current source, docs, and tests | Verified current behavior; no new behavior decided |
|
|
92
|
+
| FR3 | Trigger, Indicator icon, Search row, Option row, Section heading, Empty state, and Pointer popup carry the seven current non-deprecated MultiSelector targets documented in `MultiSelector.doc.mjs`. | Current source, docs, and tests | Verified current behavior; no target change |
|
|
93
|
+
| FR4 | When present, Field, shared clear actions, option checkbox indicators, public Option dividers, general icons, Touch sheet heading, and Touch sheet retain their Field, CheckboxInput, Divider, Icon, Text, and BottomSheet theming owners. | Current composition and owners | Verified current behavior; no target change |
|
|
94
|
+
| FR5 | Pointer popup and Touch sheet are separate surface anatomy rows, but both host the same `panelContent` tree; the Touch sheet additionally renders its Heading. Changing presentation does not create a second search, option, divider, section, or empty-state content model. | Current source and tests | Verified current behavior; no normalization |
|
|
95
|
+
| FR6 | `multi-selector-clear-icon` remains a deprecated compatibility alias for `input-clear-icon`; it is not a current target and does not claim a separate anatomy part. | Current public target metadata | Verified current compatibility state |
|
|
96
|
+
|
|
97
|
+
### Allowed variation
|
|
98
|
+
|
|
99
|
+
- Standalone MultiSelector renders Field; the supported InputGroup path omits it.
|
|
100
|
+
- `presentation="popover"` uses the Pointer popup; `presentation="bottom-sheet"`
|
|
101
|
+
uses the Touch sheet and its Heading; `presentation="adaptive"` selects between
|
|
102
|
+
those existing branches from current modality and viewport behavior.
|
|
103
|
+
- Search row, Option divider, Section heading, Empty state, select-all Option row,
|
|
104
|
+
and both clear actions are optional under their documented prop and data conditions.
|
|
105
|
+
- Semantic names and icon component types render through Icon. Arbitrary ReactNode
|
|
106
|
+
start content renders directly and stays caller-owned.
|
|
107
|
+
- Trigger text, labels, badges, and custom option content may vary inside the
|
|
108
|
+
Trigger or Option row without creating another MultiSelector target.
|
|
109
|
+
|
|
110
|
+
### Representative states
|
|
111
|
+
|
|
112
|
+
| State | Required invariant | Allowed variation |
|
|
113
|
+
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
114
|
+
| Standalone or InputGroup | Field is present only standalone; Trigger anatomy and targets remain the same in both paths. | InputGroup owns grouped layout and labeling support. |
|
|
115
|
+
| Pointer presentation | Pointer popup carries `multi-selector-popup` and hosts the shared panel content. | Search, groups, options, and empty content follow their ordinary states. |
|
|
116
|
+
| Touch presentation | BottomSheet owns the Sheet panel, Text owns its Heading, and the sheet hosts the same shared panel content. | Adaptive or explicit selection may choose this branch. |
|
|
117
|
+
| Search enabled with a query | Search row owns `multi-selector-search`; its icons/actions keep their shared owners. | Results may be flat, grouped, or empty. |
|
|
118
|
+
| Option, select all, selected, disabled | Option row owns `multi-selector-option` and reflects its current size/state metadata. | Checkbox state and custom row content vary independently. |
|
|
119
|
+
| Attached or tooltip status | Status icon replaces the Indicator icon. | Tooltip status wraps the icon in a button without changing Icon owner. |
|
|
120
|
+
|
|
121
|
+
### Transformation and precedence order
|
|
122
|
+
|
|
123
|
+
- No new search, selection, presentation, or styling precedence rule is introduced.
|
|
124
|
+
|
|
125
|
+
### Performance and resources
|
|
126
|
+
|
|
127
|
+
- No new performance or resource rule is introduced.
|
|
128
|
+
|
|
129
|
+
## Accessibility contract
|
|
130
|
+
|
|
131
|
+
This draft does not change or extend MultiSelector's existing field, combobox,
|
|
132
|
+
listbox, checkbox, live-region, focus, or dismissal behavior.
|
|
133
|
+
|
|
134
|
+
## Design relationships
|
|
135
|
+
|
|
136
|
+
| Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
|
|
137
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------ | ----------------- | ------------------ |
|
|
138
|
+
| Field | Provides the label and optional supporting content outside InputGroup; omitted inside InputGroup. | Field component | Supporting | FR1, FR4 |
|
|
139
|
+
| Trigger | Presents the current selection or placeholder and opens the active presentation. | Current source and public docs | Prominent | FR1, FR3 |
|
|
140
|
+
| Icon-rendered start icon | Presents a semantic or component icon through Icon. | Icon component | Supporting | FR1, FR4 |
|
|
141
|
+
| Caller-rendered start content | Presents arbitrary caller content directly in the trigger's start slot. | Caller-supplied content | Context-dependent | FR1 |
|
|
142
|
+
| Trigger clear button | Removes every selected value through the shared field clear action. | Field component | Supporting | FR1, FR4 |
|
|
143
|
+
| Status icon | Presents attached or tooltip validation state through Icon. | Icon component | Supporting | FR1, FR4 |
|
|
144
|
+
| Indicator icon | Presents disclosure state when status presentation does not replace it. | Current source and public docs | Supporting | FR1, FR3 |
|
|
145
|
+
| Search row | Provides query entry at the top of the shared panel content. | Current source and public docs | Supporting | FR2, FR3, FR5 |
|
|
146
|
+
| Search icon | Presents the search affordance through Icon. | Icon component | Supporting | FR2, FR4 |
|
|
147
|
+
| Search clear button | Clears a present query through the shared field clear action. | Field component | Supporting | FR2, FR4 |
|
|
148
|
+
| Option row | Presents one option or the select-all choice and owns row interaction/state. | Current source and public docs | Prominent | FR2, FR3 |
|
|
149
|
+
| Option checkbox indicator | Presents the option's selected, unselected, or indeterminate state through CheckboxInput. | CheckboxInput component | Supporting | FR2, FR4 |
|
|
150
|
+
| Option divider | Separates adjacent groups when a public divider entry is present in the options data. | Divider component | Supporting | FR2, FR4 |
|
|
151
|
+
| Section heading | Labels a visible group of option rows. | Current source and public docs | Supporting | FR2, FR3 |
|
|
152
|
+
| Empty state | Presents no-options or no-results content when the panel has no selectable rows to show. | Current source and public docs | Prominent | FR2, FR3 |
|
|
153
|
+
| Pointer popup | Paints the anchored pointer surface around the shared panel content. | Current source and public docs | Prominent | FR3, FR5 |
|
|
154
|
+
| Touch sheet heading | Names the bottom-sheet presentation above the shared panel content. | Text component | Prominent | FR4, FR5 |
|
|
155
|
+
| Touch sheet | Paints the BottomSheet surface around the same shared panel content. | BottomSheet component | Prominent | FR4, FR5 |
|
|
156
|
+
|
|
157
|
+
The seven current MultiSelector targets remain on their shipped visible elements.
|
|
158
|
+
Nested shared primitives keep their own owners. The Pointer popup and Touch sheet
|
|
159
|
+
are intentionally separate rows because they use different surface owners; the
|
|
160
|
+
Touch sheet adds a Heading owned by Text. Search, option, divider, section, and
|
|
161
|
+
empty-state content remains one shared tree.
|
|
162
|
+
|
|
163
|
+
### Theming anatomy
|
|
164
|
+
|
|
165
|
+
<!-- anatomy-theming:v1 -->
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"Field": {"delegatesTo": {"owner": "component:Field", "target": "field"}},
|
|
170
|
+
"Trigger": {"target": "multi-selector"},
|
|
171
|
+
"Icon-rendered start icon": {
|
|
172
|
+
"delegatesTo": {"owner": "component:Icon", "target": "icon"}
|
|
173
|
+
},
|
|
174
|
+
"Caller-rendered start content": {
|
|
175
|
+
"none": {
|
|
176
|
+
"reason": "intentional: Arbitrary ReactNode content is caller-owned and receives no MultiSelector target."
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
"Trigger clear button": {
|
|
180
|
+
"delegatesTo": {
|
|
181
|
+
"owner": "component:Field",
|
|
182
|
+
"target": "input-clear-button"
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
"Status icon": {
|
|
186
|
+
"delegatesTo": {"owner": "component:Icon", "target": "icon"}
|
|
187
|
+
},
|
|
188
|
+
"Indicator icon": {"target": "multi-selector-indicator-icon"},
|
|
189
|
+
"Search row": {"target": "multi-selector-search"},
|
|
190
|
+
"Search icon": {
|
|
191
|
+
"delegatesTo": {"owner": "component:Icon", "target": "icon"}
|
|
192
|
+
},
|
|
193
|
+
"Search clear button": {
|
|
194
|
+
"delegatesTo": {
|
|
195
|
+
"owner": "component:Field",
|
|
196
|
+
"target": "input-clear-button"
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
"Option row": {"target": "multi-selector-option"},
|
|
200
|
+
"Option checkbox indicator": {
|
|
201
|
+
"delegatesTo": {
|
|
202
|
+
"owner": "component:CheckboxInput",
|
|
203
|
+
"target": "checkbox-indicator"
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
"Option divider": {
|
|
207
|
+
"delegatesTo": {"owner": "component:Divider", "target": "divider"}
|
|
208
|
+
},
|
|
209
|
+
"Section heading": {"target": "multi-selector-section-heading"},
|
|
210
|
+
"Empty state": {"target": "multi-selector-empty-state"},
|
|
211
|
+
"Pointer popup": {"target": "multi-selector-popup"},
|
|
212
|
+
"Touch sheet heading": {
|
|
213
|
+
"delegatesTo": {"owner": "component:Text", "target": "heading"}
|
|
214
|
+
},
|
|
215
|
+
"Touch sheet": {
|
|
216
|
+
"delegatesTo": {
|
|
217
|
+
"owner": "component:BottomSheet",
|
|
218
|
+
"target": "bottom-sheet"
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Family and system relationships
|
|
225
|
+
|
|
226
|
+
- `architecture:component-theming-surface` owns anatomy qualification, local
|
|
227
|
+
target mapping, delegation, and exclusion of deprecated aliases.
|
|
228
|
+
- `architecture:layer-runtime` owns the current Popover host and the distinction
|
|
229
|
+
between browser popover and native-dialog sheet presentation.
|
|
230
|
+
- `family:overlay-dismissal` owns shared Escape and platform-close ordering. The
|
|
231
|
+
Pointer popup participates through its composed Popover owner; the Touch sheet
|
|
232
|
+
inherits BottomSheet's current local-only adoption gap.
|
|
233
|
+
- Field, CheckboxInput, Divider, Icon, Text, and BottomSheet retain ownership of
|
|
234
|
+
their delegated targets and presentation.
|
|
235
|
+
|
|
236
|
+
## Verification map
|
|
237
|
+
|
|
238
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
239
|
+
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
|
|
240
|
+
| FR1, FR2 | `MultiSelector.test.tsx` render, search, divider, grouping, status, select-all, and empty-state suites plus InputGroup source inspection | Standalone/InputGroup, search, divided, selected, empty, status | Removing a documented part or misreporting Field presence conflicts with existing structure, content assertions, or source. | `audit:MultiSelector/anatomy` |
|
|
241
|
+
| FR3, FR6 | Component target suites, source inspection, and public target inventory | All seven current targets and deprecated alias | A current target is missed, invented, moved, or confused with the deprecated clear-icon alias. | `audit:MultiSelector/theming` |
|
|
242
|
+
| FR4 | Composed owner source/tests for Field, CheckboxInput, Divider, Icon, Text, and BottomSheet | Field omission/presence, clear actions, checkbox, divider, icons, heading, sheet | A shared part gains the wrong local owner or its documented owner no longer renders it. | `audit:MultiSelector/theming` |
|
|
243
|
+
| FR5 | `MultiSelector.test.tsx` presentation suites and `panelContent` source inspection | Explicit popover, explicit sheet, adaptive touch | A presentation stops using its owner or receives a divergent panel-content implementation. | `audit:MultiSelector/overlay` |
|
|
244
|
+
| Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and current local targets | Missing, extra, prefixed, stale, or multiply assigned mappings fail repository validation. | `audit:MultiSelector/theming` |
|
|
245
|
+
|
|
246
|
+
Existing component tests directly assert the Trigger, Indicator icon, Search row,
|
|
247
|
+
Option row, Section heading, Empty state, and Pointer popup targets. Presentation
|
|
248
|
+
tests directly distinguish the Popover and BottomSheet branches. Source inspection
|
|
249
|
+
distinguishes standalone Field presence from InputGroup omission and confirms that both surface branches receive the same `panelContent`, while the touch branch
|
|
250
|
+
adds Heading; Divider, Text, BottomSheet, and other shared primitive tests provide
|
|
251
|
+
owner evidence rather than new MultiSelector targets.
|
|
252
|
+
|
|
253
|
+
## Decision log
|
|
254
|
+
|
|
255
|
+
None. This draft records current facts and introduces no component-local design,
|
|
256
|
+
API, theming, or layer-system decision.
|
|
257
|
+
|
|
258
|
+
## Open questions
|
|
259
|
+
|
|
260
|
+
None.
|
|
261
|
+
|
|
262
|
+
## Content boundary
|
|
263
|
+
|
|
264
|
+
This file does not duplicate consumer prop tables, examples, search or selection
|
|
265
|
+
algorithms, implementation steps, or shared layer and theming rules. It links to
|
|
266
|
+
their owners.
|
|
@@ -19,12 +19,15 @@ import {
|
|
|
19
19
|
within,
|
|
20
20
|
} from '@testing-library/react';
|
|
21
21
|
import userEvent from '@testing-library/user-event';
|
|
22
|
+
import * as stylex from '@stylexjs/stylex';
|
|
22
23
|
import {MultiSelector} from './MultiSelector';
|
|
23
24
|
import {Icon} from '../Icon';
|
|
24
25
|
import {InternationalizationProvider} from '../i18n';
|
|
25
26
|
import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
|
|
27
|
+
import {__resetInteractionModalityForTest} from '../utils/interactionModality';
|
|
26
28
|
import {defineTheme} from '../theme/defineTheme';
|
|
27
29
|
import {generateThemeCSS} from '../theme/generateThemeRules';
|
|
30
|
+
import {selectorPresentationStyles} from '../Selector/selectorPresentation.stylex';
|
|
28
31
|
|
|
29
32
|
function generateThemeTestCSS(theme: Parameters<typeof generateThemeCSS>[0]) {
|
|
30
33
|
const {prose, component} = generateThemeCSS(theme);
|
|
@@ -40,6 +43,28 @@ function politeRegion(): HTMLElement | null {
|
|
|
40
43
|
|
|
41
44
|
// Mock showPopover and hidePopover methods since they're not implemented in jsdom
|
|
42
45
|
beforeEach(() => {
|
|
46
|
+
__resetInteractionModalityForTest();
|
|
47
|
+
HTMLDialogElement.prototype.showModal = vi.fn(function (
|
|
48
|
+
this: HTMLDialogElement,
|
|
49
|
+
) {
|
|
50
|
+
this.setAttribute('open', '');
|
|
51
|
+
});
|
|
52
|
+
HTMLDialogElement.prototype.close = vi.fn(function (this: HTMLDialogElement) {
|
|
53
|
+
this.removeAttribute('open');
|
|
54
|
+
});
|
|
55
|
+
vi.stubGlobal(
|
|
56
|
+
'matchMedia',
|
|
57
|
+
vi.fn().mockImplementation((query: string) => ({
|
|
58
|
+
matches: false,
|
|
59
|
+
media: query,
|
|
60
|
+
onchange: null,
|
|
61
|
+
addEventListener: vi.fn(),
|
|
62
|
+
removeEventListener: vi.fn(),
|
|
63
|
+
addListener: vi.fn(),
|
|
64
|
+
removeListener: vi.fn(),
|
|
65
|
+
dispatchEvent: vi.fn(),
|
|
66
|
+
})),
|
|
67
|
+
);
|
|
43
68
|
HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
|
|
44
69
|
this.setAttribute('popover-open', '');
|
|
45
70
|
const event = new Event('toggle', {bubbles: false});
|
|
@@ -75,6 +100,108 @@ const h = {hidden: true} as const;
|
|
|
75
100
|
describe('MultiSelector', () => {
|
|
76
101
|
const defaultOptions = ['Apple', 'Banana', 'Orange'];
|
|
77
102
|
|
|
103
|
+
it('uses a bottom sheet and stays open while toggling options', async () => {
|
|
104
|
+
const user = userEvent.setup();
|
|
105
|
+
const onChange = vi.fn();
|
|
106
|
+
render(
|
|
107
|
+
<MultiSelector
|
|
108
|
+
label="Fruit"
|
|
109
|
+
options={defaultOptions}
|
|
110
|
+
value={[]}
|
|
111
|
+
onChange={onChange}
|
|
112
|
+
presentation="bottom-sheet"
|
|
113
|
+
/>,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const trigger = screen.getByRole('combobox');
|
|
117
|
+
await user.click(trigger);
|
|
118
|
+
|
|
119
|
+
expect(
|
|
120
|
+
await screen.findByRole('dialog', {name: 'Fruit'}),
|
|
121
|
+
).toBeInTheDocument();
|
|
122
|
+
expect(HTMLElement.prototype.showPopover).not.toHaveBeenCalled();
|
|
123
|
+
|
|
124
|
+
await user.click(screen.getByRole('option', {name: 'Banana'}));
|
|
125
|
+
expect(onChange).toHaveBeenCalledWith(['Banana']);
|
|
126
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'true');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('uses a bottom sheet for adaptive presentation on compact touch', async () => {
|
|
130
|
+
vi.stubGlobal(
|
|
131
|
+
'matchMedia',
|
|
132
|
+
vi.fn().mockImplementation((query: string) => ({
|
|
133
|
+
matches: query === '(max-width: 768px) and (pointer: coarse)',
|
|
134
|
+
media: query,
|
|
135
|
+
onchange: null,
|
|
136
|
+
addEventListener: vi.fn(),
|
|
137
|
+
removeEventListener: vi.fn(),
|
|
138
|
+
addListener: vi.fn(),
|
|
139
|
+
removeListener: vi.fn(),
|
|
140
|
+
dispatchEvent: vi.fn(),
|
|
141
|
+
})),
|
|
142
|
+
);
|
|
143
|
+
const user = userEvent.setup();
|
|
144
|
+
render(
|
|
145
|
+
<MultiSelector
|
|
146
|
+
label="Fruit"
|
|
147
|
+
options={defaultOptions}
|
|
148
|
+
value={[]}
|
|
149
|
+
onChange={() => {}}
|
|
150
|
+
presentation="adaptive"
|
|
151
|
+
/>,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
await user.click(screen.getByRole('combobox'));
|
|
155
|
+
expect(
|
|
156
|
+
await screen.findByRole('dialog', {name: 'Fruit'}),
|
|
157
|
+
).toBeInTheDocument();
|
|
158
|
+
expect(HTMLElement.prototype.showPopover).not.toHaveBeenCalled();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('moves keyboard focus into a bottom-sheet listbox', async () => {
|
|
162
|
+
const user = userEvent.setup();
|
|
163
|
+
render(
|
|
164
|
+
<MultiSelector
|
|
165
|
+
label="Fruit"
|
|
166
|
+
options={defaultOptions}
|
|
167
|
+
value={[]}
|
|
168
|
+
onChange={() => {}}
|
|
169
|
+
presentation="bottom-sheet"
|
|
170
|
+
/>,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const trigger = screen.getByRole('combobox');
|
|
174
|
+
trigger.focus();
|
|
175
|
+
await user.keyboard('{Enter}');
|
|
176
|
+
|
|
177
|
+
await waitFor(() => expect(screen.getByRole('listbox')).toHaveFocus());
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('restores touch focus without painting a trigger focus ring', async () => {
|
|
181
|
+
render(
|
|
182
|
+
<MultiSelector
|
|
183
|
+
label="Fruit"
|
|
184
|
+
options={defaultOptions}
|
|
185
|
+
value={[]}
|
|
186
|
+
onChange={() => {}}
|
|
187
|
+
presentation="bottom-sheet"
|
|
188
|
+
/>,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const trigger = screen.getByRole('combobox');
|
|
192
|
+
fireEvent.pointerDown(trigger, {pointerType: 'touch'});
|
|
193
|
+
fireEvent.click(trigger, {detail: 1});
|
|
194
|
+
const dialog = await screen.findByRole('dialog', {name: 'Fruit'});
|
|
195
|
+
fireEvent.pointerDown(dialog, {pointerType: 'touch'});
|
|
196
|
+
fireEvent.click(dialog, {detail: 1});
|
|
197
|
+
|
|
198
|
+
trigger.focus();
|
|
199
|
+
expect(trigger).toHaveFocus();
|
|
200
|
+
expect(trigger.parentElement).toHaveClass(
|
|
201
|
+
stylex.props(selectorPresentationStyles.pointerRestoredFocus).className!,
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
|
|
78
205
|
it('renders with label', () => {
|
|
79
206
|
render(
|
|
80
207
|
<MultiSelector
|