@astryxdesign/core 0.5.1 → 0.5.2-canary.00477a5
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 +19 -0
- 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/dist/i18n/InternationalizationContext.d.ts +2 -0
- package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
- package/dist/i18n/InternationalizationContext.js +3 -1
- package/dist/i18n/InternationalizationProvider.d.ts.map +1 -1
- package/dist/i18n/InternationalizationProvider.js +3 -1
- package/dist/i18n/resolve.d.ts +1 -1
- package/dist/i18n/resolve.d.ts.map +1 -1
- package/dist/i18n/resolve.js +37 -38
- package/dist/i18n/useTranslator.d.ts.map +1 -1
- package/dist/i18n/useTranslator.js +2 -3
- package/locales/af-ZA.json +0 -4
- package/locales/ar-SA.json +0 -4
- package/locales/ca-ES.json +0 -4
- package/locales/cs-CZ.json +0 -4
- package/locales/da-DK.json +0 -4
- package/locales/de-DE.json +0 -4
- package/locales/el-GR.json +0 -4
- package/locales/en.json +20 -0
- package/locales/es-ES.json +0 -4
- package/locales/fi-FI.json +0 -4
- package/locales/fr-FR.json +0 -4
- package/locales/he-IL.json +0 -4
- package/locales/hu-HU.json +0 -4
- package/locales/it-IT.json +0 -4
- package/locales/ja-JP.json +0 -4
- package/locales/ko-KR.json +0 -4
- package/locales/nl-NL.json +0 -4
- package/locales/no-NO.json +0 -4
- package/locales/pl-PL.json +0 -4
- package/locales/pseudo.json +15 -0
- package/locales/pt-BR.json +0 -4
- package/locales/pt-PT.json +0 -4
- package/locales/ro-RO.json +0 -4
- package/locales/ru-RU.json +0 -4
- package/locales/sr-SP.json +0 -4
- package/locales/sv-SE.json +0 -4
- package/locales/tr-TR.json +0 -4
- package/locales/uk-UA.json +0 -4
- package/locales/vi-VN.json +0 -4
- package/locales/zh-CN.json +0 -4
- package/locales/zh-TW.json +0 -4
- package/package.json +3 -3
- 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/Calendar/Calendar.doc.mjs +1 -1
- 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/CheckboxInput/CheckboxInput.doc.mjs +1 -1
- package/src/CheckboxList/CheckboxList.doc.mjs +173 -32
- 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 +44 -2
- package/src/ComplexSelector/ComplexSelector.spec.md +189 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +13 -1
- package/src/ContextMenu/ContextMenu.test.tsx +131 -1
- package/src/ContextMenu/ContextMenu.tsx +199 -32
- package/src/ContextMenu/index.ts +1 -0
- package/src/DateInput/DateInput.doc.mjs +1 -1
- package/src/DateRangeInput/DateRangeInput.doc.mjs +1 -1
- package/src/DateTimeInput/DateTimeInput.doc.mjs +1 -1
- package/src/Divider/Divider.doc.mjs +24 -0
- package/src/Divider/Divider.spec.md +150 -0
- package/src/DropdownMenu/DropdownMenu.doc.mjs +201 -40
- 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/Field/Field.doc.mjs +1 -1
- package/src/FieldStatus/FieldStatus.doc.mjs +22 -1
- package/src/FieldStatus/FieldStatus.spec.md +212 -0
- package/src/FileInput/FileInput.doc.mjs +1 -1
- 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/Indicator/Indicator.doc.mjs +1 -1
- package/src/InputGroup/InputGroup.doc.mjs +1 -1
- 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 +17 -0
- package/src/MoreMenu/MoreMenu.test.tsx +47 -0
- package/src/MoreMenu/MoreMenu.tsx +13 -1
- package/src/MultiSelector/MultiSelector.doc.mjs +129 -1
- 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/NumberInput/NumberInput.doc.mjs +1 -1
- 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/PowerSearch/PowerSearch.doc.mjs +1 -1
- package/src/ProgressBar/ProgressBar.doc.mjs +198 -38
- package/src/ProgressBar/ProgressBar.spec.md +260 -0
- package/src/RadioList/RadioList.doc.mjs +1 -1
- 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 +13 -1
- 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 +186 -32
- 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 +41 -1
- package/src/Switch/Switch.spec.md +174 -0
- package/src/Text/Text.doc.mjs +190 -43
- package/src/Text/Text.spec.md +205 -0
- package/src/TextArea/TextArea.doc.mjs +256 -46
- package/src/TextArea/TextArea.spec.md +204 -0
- package/src/TextInput/TextInput.doc.mjs +1 -1
- package/src/TimeInput/TimeInput.doc.mjs +1 -1
- 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/Tokenizer/Tokenizer.doc.mjs +1 -1
- 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 +84 -2
- 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/i18n/InternationalizationContext.ts +3 -0
- package/src/i18n/InternationalizationProvider.tsx +8 -1
- package/src/i18n/__tests__/resolve.test.ts +23 -1
- package/src/i18n/resolve.ts +46 -49
- package/src/i18n/useTranslator.ts +2 -7
- package/src/theme/themingTargets.test.ts +91 -25
|
@@ -1,12 +1,123 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').ComponentAnatomyElement[]} */
|
|
4
|
+
const anatomy = [
|
|
5
|
+
{
|
|
6
|
+
name: 'Field',
|
|
7
|
+
required: false,
|
|
8
|
+
description:
|
|
9
|
+
'Standalone Field shell that provides the label and optional supporting content; omitted inside InputGroup.',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: 'Trigger',
|
|
13
|
+
required: true,
|
|
14
|
+
description:
|
|
15
|
+
'Painted control that displays the current selection or placeholder and opens the selection surface.',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'Icon-rendered start icon',
|
|
19
|
+
required: false,
|
|
20
|
+
description:
|
|
21
|
+
'Optional leading semantic icon or icon component rendered through Icon.',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'Caller-rendered start content',
|
|
25
|
+
required: false,
|
|
26
|
+
description:
|
|
27
|
+
'Optional arbitrary React content rendered directly at the start of the trigger.',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'Trigger clear button',
|
|
31
|
+
required: false,
|
|
32
|
+
description:
|
|
33
|
+
'Shared clear action that removes every selected value when hasClear is enabled.',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'Status icon',
|
|
37
|
+
required: false,
|
|
38
|
+
description:
|
|
39
|
+
'Status glyph shown in place of the disclosure indicator for attached or tooltip status.',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'Indicator icon',
|
|
43
|
+
required: false,
|
|
44
|
+
description:
|
|
45
|
+
'Trailing chevron shown when status presentation does not replace it; reflects collapsed or expanded state.',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'Search row',
|
|
49
|
+
required: false,
|
|
50
|
+
description:
|
|
51
|
+
'Panel header with a borderless search input and optional clear action.',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'Search icon',
|
|
55
|
+
required: false,
|
|
56
|
+
description:
|
|
57
|
+
'Leading magnifier rendered through Icon inside the search row.',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'Search clear button',
|
|
61
|
+
required: false,
|
|
62
|
+
description:
|
|
63
|
+
'Shared clear action shown in the search row while a query is present.',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'Option row',
|
|
67
|
+
required: false,
|
|
68
|
+
description:
|
|
69
|
+
'Selectable row for an option or the optional select-all choice.',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'Option checkbox indicator',
|
|
73
|
+
required: false,
|
|
74
|
+
description:
|
|
75
|
+
'CheckboxInput indicator that presents each row’s selected, unselected, or indeterminate state.',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'Option divider',
|
|
79
|
+
required: false,
|
|
80
|
+
description:
|
|
81
|
+
'Divider supplied in the public options data to separate adjacent option groups.',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'Section heading',
|
|
85
|
+
required: false,
|
|
86
|
+
description: 'Visible heading for a labeled group of option rows.',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'Empty state',
|
|
90
|
+
required: false,
|
|
91
|
+
description:
|
|
92
|
+
'Message shown when the shared panel content has no options or no search matches.',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'Pointer popup',
|
|
96
|
+
required: false,
|
|
97
|
+
description:
|
|
98
|
+
'Anchored painted surface that hosts the shared panel content for popover presentation.',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'Touch sheet heading',
|
|
102
|
+
required: false,
|
|
103
|
+
description:
|
|
104
|
+
'Heading above the shared panel content in bottom-sheet presentation.',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'Touch sheet',
|
|
108
|
+
required: false,
|
|
109
|
+
description:
|
|
110
|
+
'BottomSheet surface that hosts the same panel content for bottom-sheet presentation.',
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
|
|
3
114
|
/** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
|
|
4
115
|
|
|
5
116
|
export const docs = {
|
|
6
117
|
name: 'MultiSelector',
|
|
7
118
|
displayName: 'Multi Selector',
|
|
8
119
|
group: 'Selector',
|
|
9
|
-
category: '
|
|
120
|
+
category: 'Form Controls',
|
|
10
121
|
keywords: [
|
|
11
122
|
'multiselect',
|
|
12
123
|
'checkbox',
|
|
@@ -224,6 +335,13 @@ export const docs = {
|
|
|
224
335
|
'Which edge of the option row carries the checkbox. end pushes it to the far edge of the row, including on the select-all row.',
|
|
225
336
|
default: "'start'",
|
|
226
337
|
},
|
|
338
|
+
{
|
|
339
|
+
name: 'presentation',
|
|
340
|
+
type: "'popover' | 'bottom-sheet' | 'adaptive'",
|
|
341
|
+
description:
|
|
342
|
+
'How the option list is presented. adaptive uses a bottom sheet on compact touch screens and an anchored popover otherwise.',
|
|
343
|
+
default: "'popover'",
|
|
344
|
+
},
|
|
227
345
|
{
|
|
228
346
|
name: 'width',
|
|
229
347
|
type: 'SizeValue',
|
|
@@ -257,6 +375,7 @@ export const docs = {
|
|
|
257
375
|
},
|
|
258
376
|
],
|
|
259
377
|
usage: {
|
|
378
|
+
anatomy,
|
|
260
379
|
description:
|
|
261
380
|
'A checkbox dropdown for selecting multiple values from a list. Selected items can display as a count, labels, or badges. Use it for filtering or when presenting a finite set of options where multiple choices are needed.',
|
|
262
381
|
bestPractices: [
|
|
@@ -290,6 +409,11 @@ export const docs = {
|
|
|
290
409
|
description:
|
|
291
410
|
'Use variant="ghost" when a multi-selector sits in a toolbar with ghost buttons. If validation status is needed there, prefer statusVariant="tooltip" so the toolbar height stays compact.',
|
|
292
411
|
},
|
|
412
|
+
{
|
|
413
|
+
guidance: true,
|
|
414
|
+
description:
|
|
415
|
+
'Use presentation="adaptive" when the multi-selector should become a bottom sheet on compact touch screens.',
|
|
416
|
+
},
|
|
293
417
|
{
|
|
294
418
|
guidance: false,
|
|
295
419
|
description: 'Use for single-value selection; use Selector instead.',
|
|
@@ -349,6 +473,8 @@ export const docsZh = {
|
|
|
349
473
|
'状态消息的放置方式:attached 直接叠加在输入框下方;detached 作为独立元素浮于下方并留有间距。',
|
|
350
474
|
renderOption:
|
|
351
475
|
'每个可选选项的自定义渲染函数。不会用于分隔线、分组或全选行。',
|
|
476
|
+
presentation:
|
|
477
|
+
'选项列表的呈现方式:锚定弹出框、底部抽屉,或根据紧凑触控屏自适应。',
|
|
352
478
|
xstyle: '布局自定义的 StyleX 样式,必须是 stylex.create() 值。',
|
|
353
479
|
},
|
|
354
480
|
},
|
|
@@ -487,6 +613,8 @@ export const docsDense = {
|
|
|
487
613
|
'status message placement; ghost detaches attached by default; use tooltip for compact toolbars.',
|
|
488
614
|
renderOption:
|
|
489
615
|
'custom render fn per selectable option; not dividers/sections/select-all',
|
|
616
|
+
presentation:
|
|
617
|
+
'popover, bottom-sheet, or compact-touch adaptive presentation',
|
|
490
618
|
xstyle: 'StyleX layout styles; stylex.create() only',
|
|
491
619
|
},
|
|
492
620
|
},
|
|
@@ -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
|