@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
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: 1
|
|
3
|
+
template_version: 3
|
|
4
|
+
kind: component
|
|
5
|
+
id: component:ComplexSelector
|
|
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/ComplexSelector/ComplexSelector.test.tsx,
|
|
16
|
+
packages/core/src/Field/Field.test.tsx,
|
|
17
|
+
packages/core/src/Icon/Icon.test.tsx,
|
|
18
|
+
scripts/check-knowledge.mjs,
|
|
19
|
+
]
|
|
20
|
+
families: [family:overlay-dismissal]
|
|
21
|
+
design_specs: []
|
|
22
|
+
architecture:
|
|
23
|
+
[architecture:component-theming-surface, architecture:layer-runtime]
|
|
24
|
+
contributing: []
|
|
25
|
+
system_specs: []
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
# ComplexSelector component contract
|
|
29
|
+
|
|
30
|
+
## Intent
|
|
31
|
+
|
|
32
|
+
ComplexSelector renders a field-owned shell, an owned trigger, and a dialog
|
|
33
|
+
popup for caller-provided rich selection content. This draft records current
|
|
34
|
+
consumer anatomy and theming ownership without changing runtime behavior,
|
|
35
|
+
styling, targets, or public API.
|
|
36
|
+
|
|
37
|
+
## Compatibility and migration
|
|
38
|
+
|
|
39
|
+
- Released default preserved: `yes`
|
|
40
|
+
- Compatibility class: additive documentation only; runtime, DOM, styling,
|
|
41
|
+
targets, and public API remain unchanged
|
|
42
|
+
- Controlled/uncontrolled behavior: unchanged
|
|
43
|
+
- Migration decision: none
|
|
44
|
+
|
|
45
|
+
Consumer migration instructions belong in consumer docs and release notes.
|
|
46
|
+
|
|
47
|
+
## Ownership boundary
|
|
48
|
+
|
|
49
|
+
**Owns**
|
|
50
|
+
|
|
51
|
+
- The trigger and its current `complex-selector` target.
|
|
52
|
+
- The trailing indicator icon and its current
|
|
53
|
+
`complex-selector-indicator-icon` target.
|
|
54
|
+
- The painted popup surface and its current `complex-selector-popup` target.
|
|
55
|
+
|
|
56
|
+
**Does not own / non-goals**
|
|
57
|
+
|
|
58
|
+
- Field-shell presentation — rendered by Field and themed through Field's
|
|
59
|
+
current `field` target.
|
|
60
|
+
- General Icon presentation — rendered by Icon for semantic icon names and icon
|
|
61
|
+
component types and themed through Icon's current `icon` target.
|
|
62
|
+
- Arbitrary ReactNode content supplied through `startIcon` — rendered directly
|
|
63
|
+
and owned by the product callsite.
|
|
64
|
+
- The selector-specific structure supplied through `children` — owned by the
|
|
65
|
+
product callsite.
|
|
66
|
+
- Shared layer lifecycle, positioning, and dismissal behavior.
|
|
67
|
+
|
|
68
|
+
## Public concepts
|
|
69
|
+
|
|
70
|
+
No new public concept is introduced. Consumer props and usage remain documented
|
|
71
|
+
in `ComplexSelector.doc.mjs`.
|
|
72
|
+
|
|
73
|
+
## Behavioral and layout contract
|
|
74
|
+
|
|
75
|
+
| ID | Candidate invariant | Basis | Draft review state |
|
|
76
|
+
| --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | -------------------------------------------------- |
|
|
77
|
+
| FR1 | The current render contains a Field, Trigger, Indicator icon, and mounted Popup; one optional start slot may contain either an Icon-rendered start icon or caller-rendered start content. | Current source, docs, and tests | Verified current behavior; no new behavior decided |
|
|
78
|
+
| FR2 | Trigger, Indicator icon, and Popup carry `complex-selector`, `complex-selector-indicator-icon`, and `complex-selector-popup`, respectively. | Current source, docs, and tests | Verified current behavior; no target change |
|
|
79
|
+
| FR3 | Field delegates to Field's `field` target, and a semantic name or icon component in the start slot delegates to Icon's `icon` target. | Current source and owner tests | Verified current behavior; no target change |
|
|
80
|
+
| FR4 | Arbitrary ReactNode start content renders directly and carries no ComplexSelector-owned target. | Current source | Verified current behavior; no target change |
|
|
81
|
+
|
|
82
|
+
### Allowed variation
|
|
83
|
+
|
|
84
|
+
- Caller-provided popup content may vary without becoming a new
|
|
85
|
+
ComplexSelector-owned anatomy part or target.
|
|
86
|
+
- The optional start slot has two factual branches: semantic names and icon
|
|
87
|
+
component types render through Icon, while arbitrary ReactNode content renders
|
|
88
|
+
directly under caller ownership.
|
|
89
|
+
|
|
90
|
+
### Representative states
|
|
91
|
+
|
|
92
|
+
- Input and ghost variants use the same six-part anatomy and current targets.
|
|
93
|
+
- The start slot is absent when `startIcon` is omitted. When present, exactly
|
|
94
|
+
one of Icon-rendered start icon or Caller-rendered start content applies.
|
|
95
|
+
- The Popup remains mounted in both states: it is hidden while closed and shown
|
|
96
|
+
while open. The Indicator icon remains present and reflects collapsed or
|
|
97
|
+
expanded state on its current target.
|
|
98
|
+
|
|
99
|
+
### Transformation and precedence order
|
|
100
|
+
|
|
101
|
+
- No new value, open-state, positioning, or style precedence rule is introduced.
|
|
102
|
+
|
|
103
|
+
### Performance and resources
|
|
104
|
+
|
|
105
|
+
- No new performance or resource rule is introduced.
|
|
106
|
+
|
|
107
|
+
## Accessibility contract
|
|
108
|
+
|
|
109
|
+
This draft does not change or extend ComplexSelector's existing field, trigger,
|
|
110
|
+
dialog, focus, or keyboard behavior.
|
|
111
|
+
|
|
112
|
+
## Design relationships
|
|
113
|
+
|
|
114
|
+
| Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
|
|
115
|
+
| ----------------------------- | -------------------------------------------------------------------------------- | ------------------------------ | ----------------- | ------------------ |
|
|
116
|
+
| Field | Provides the field shell around the owned trigger. | Field component | Supporting | FR1, FR3 |
|
|
117
|
+
| Trigger | Displays the current value or placeholder and opens the popup. | Current source and public docs | Prominent | FR1, FR2 |
|
|
118
|
+
| Icon-rendered start icon | Optionally presents a semantic or component icon through Icon. | Icon component | Supporting | FR1, FR3 |
|
|
119
|
+
| Caller-rendered start content | Optionally presents arbitrary React content directly in the start slot. | Caller-supplied content | Context-dependent | FR1, FR4 |
|
|
120
|
+
| Indicator icon | Presents the trailing disclosure glyph and reflects collapsed or expanded state. | Current source and public docs | Supporting | FR1, FR2 |
|
|
121
|
+
| Popup | Keeps the dialog surface mounted, hidden while closed and painted while open. | Current source and public docs | Prominent | FR1, FR2 |
|
|
122
|
+
|
|
123
|
+
The Field and Icon delegations preserve their existing owners. Arbitrary
|
|
124
|
+
ReactNode start content remains caller-owned and receives no local target. The
|
|
125
|
+
local Indicator icon target remains distinct because it carries the selector's
|
|
126
|
+
expanded/collapsed state and rotation on the glyph itself. Caller-provided popup
|
|
127
|
+
content remains outside the owned anatomy inventory.
|
|
128
|
+
|
|
129
|
+
### Theming anatomy
|
|
130
|
+
|
|
131
|
+
<!-- anatomy-theming:v1 -->
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"Field": {"delegatesTo": {"owner": "component:Field", "target": "field"}},
|
|
136
|
+
"Trigger": {"target": "complex-selector"},
|
|
137
|
+
"Icon-rendered start icon": {
|
|
138
|
+
"delegatesTo": {"owner": "component:Icon", "target": "icon"}
|
|
139
|
+
},
|
|
140
|
+
"Caller-rendered start content": {
|
|
141
|
+
"none": {
|
|
142
|
+
"reason": "intentional: Arbitrary ReactNode content is caller-owned and receives no ComplexSelector target."
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"Indicator icon": {"target": "complex-selector-indicator-icon"},
|
|
146
|
+
"Popup": {"target": "complex-selector-popup"}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Family and system relationships
|
|
151
|
+
|
|
152
|
+
- `architecture:component-theming-surface` owns anatomy qualification, local
|
|
153
|
+
target mapping, and delegation rules.
|
|
154
|
+
- `architecture:layer-runtime` owns the current `usePopover` host, positioning,
|
|
155
|
+
native light-dismiss, and visibility reconciliation used by the Popup.
|
|
156
|
+
- `family:overlay-dismissal` owns shared Escape and platform-close ordering;
|
|
157
|
+
ComplexSelector participates through its composed Popover owner.
|
|
158
|
+
- Field and Icon retain ownership of their delegated targets and presentation.
|
|
159
|
+
|
|
160
|
+
## Verification map
|
|
161
|
+
|
|
162
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
163
|
+
| ------------------- | -------------------------------------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------- | ------------------------------- |
|
|
164
|
+
| FR1 | `ComplexSelector.test.tsx` render, ghost-trigger, and popup suites | Default closed, ghost start branches, open Popup | Removing a documented part fails existing role, content, or structure assertions. | `audit:ComplexSelector/anatomy` |
|
|
165
|
+
| FR2 | Component target suites, source inspection, and theming target inventories | Closed/open Popup; expanded Indicator icon | Removing or renaming a current local target fails component assertions or target inventories. | `audit:ComplexSelector/theming` |
|
|
166
|
+
| FR3, FR4 | Icon owner tests and `renderIconSlot` source inspection | Semantic/component icon; arbitrary ReactNode | A branch gains the wrong owner, loses its target, or receives an invented local target. | `audit:ComplexSelector/theming` |
|
|
167
|
+
| 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:ComplexSelector/theming` |
|
|
168
|
+
|
|
169
|
+
Current component tests directly assert the Trigger and Popup targets. Source
|
|
170
|
+
inspection confirms that non-lazy `useLayer` keeps the Popup mounted while
|
|
171
|
+
closed. The Indicator icon target is covered by source inspection and shared
|
|
172
|
+
target inventories rather than a dedicated component assertion. `renderIconSlot`
|
|
173
|
+
and Icon owner tests distinguish the Icon-rendered branch from arbitrary
|
|
174
|
+
caller-owned ReactNode content.
|
|
175
|
+
|
|
176
|
+
## Decision log
|
|
177
|
+
|
|
178
|
+
None. This draft records current facts and introduces no component-local design,
|
|
179
|
+
API, theming, or layer-system decision.
|
|
180
|
+
|
|
181
|
+
## Open questions
|
|
182
|
+
|
|
183
|
+
None.
|
|
184
|
+
|
|
185
|
+
## Content boundary
|
|
186
|
+
|
|
187
|
+
This file does not duplicate consumer prop tables, examples, caller-provided
|
|
188
|
+
popup structure, implementation steps, or shared layer and theming rules. It
|
|
189
|
+
links to their owners.
|
|
@@ -32,7 +32,7 @@ export const docs = {
|
|
|
32
32
|
{
|
|
33
33
|
name: 'items',
|
|
34
34
|
type: 'ContextMenuOption[]',
|
|
35
|
-
description: 'Array of menu entries. Each entry is one of: an action item `{label, onClick?, icon?, isDisabled?, variant?}` (variant `"destructive"` renders it in the error color), a divider `{type: "divider"}`, or a section `{type: "section", title?, items: [...action items]}`.',
|
|
35
|
+
description: 'Array of menu entries. Each entry is one of: an action item `{label, onClick?, icon?, isDisabled?, variant?, items?}` (nested `items` open a flyout in popover presentation and drill into a new view in bottom-sheet presentation; variant `"destructive"` renders it in the error color), a divider `{type: "divider"}`, or a section `{type: "section", title?, items: [...action items]}`.',
|
|
36
36
|
required: true,
|
|
37
37
|
},
|
|
38
38
|
{
|
|
@@ -63,6 +63,12 @@ export const docs = {
|
|
|
63
63
|
type: '(isOpen: boolean) => void',
|
|
64
64
|
description: 'Callback fired when the menu opens or closes.',
|
|
65
65
|
},
|
|
66
|
+
{
|
|
67
|
+
name: 'presentation',
|
|
68
|
+
type: "'popover' | 'bottom-sheet' | 'adaptive'",
|
|
69
|
+
description: 'Presentation policy. `popover` opens at the pointer position, `bottom-sheet` always uses an action sheet, and `adaptive` uses the BottomSheet at 768px and below when the primary pointer is coarse.',
|
|
70
|
+
default: "'popover'",
|
|
71
|
+
},
|
|
66
72
|
{
|
|
67
73
|
name: 'isDisabled',
|
|
68
74
|
type: 'boolean',
|
|
@@ -78,7 +84,9 @@ export const docs = {
|
|
|
78
84
|
bestPractices: [
|
|
79
85
|
{ guidance: true, description: 'Keep menu items concise and action-oriented; users expect quick access to contextual actions.' },
|
|
80
86
|
{ guidance: true, description: 'Use sections and dividers to group related actions when the menu has many items.' },
|
|
87
|
+
{ guidance: true, description: 'Use `presentation="adaptive"` when right-click should remain cursor-positioned on desktop while long-press opens a reachable BottomSheet on compact touch devices.' },
|
|
81
88
|
{ guidance: true, description: 'Ensure all context menu actions are also accessible via other UI elements for keyboard-only users.' },
|
|
89
|
+
{ guidance: true, description: 'Keep a visible MoreMenu or equivalent trigger for important mobile actions; long-press must not be the only route.' },
|
|
82
90
|
{ guidance: false, description: 'Use a ContextMenu as the only way to access important actions; not all users know to right-click.' },
|
|
83
91
|
{ guidance: false, description: 'Place more than 10–12 items in a single menu without grouping them into sections.' },
|
|
84
92
|
],
|
|
@@ -107,7 +115,9 @@ export const docsZh = {
|
|
|
107
115
|
bestPractices: [
|
|
108
116
|
{ guidance: true, description: '保持菜单项简洁和面向操作。' },
|
|
109
117
|
{ guidance: true, description: '有很多项时使用分组和分隔线。' },
|
|
118
|
+
{ guidance: true, description: '在桌面端需要光标定位、紧凑触控设备上需要 BottomSheet 时,使用 `presentation="adaptive"`。' },
|
|
110
119
|
{ guidance: true, description: '确保所有上下文菜单操作也可通过其他 UI 元素访问。' },
|
|
120
|
+
{ guidance: true, description: '重要的移动端操作还应提供可见的 MoreMenu 或同等入口,不要只依赖长按。' },
|
|
111
121
|
{ guidance: false, description: '将上下文菜单作为访问重要操作的唯一方式。' },
|
|
112
122
|
{ guidance: false, description: '在单个菜单中放置超过 10-12 个项而不分组。' },
|
|
113
123
|
],
|
|
@@ -122,7 +132,9 @@ export const docsDense = {
|
|
|
122
132
|
bestPractices: [
|
|
123
133
|
{ guidance: true, description: 'Keep items concise and action-oriented.' },
|
|
124
134
|
{ guidance: true, description: 'Group related actions with sections and dividers.' },
|
|
135
|
+
{ guidance: true, description: 'Use adaptive presentation for cursor popover on desktop and BottomSheet on compact touch.' },
|
|
125
136
|
{ guidance: true, description: 'Ensure actions are also accessible via other UI elements.' },
|
|
137
|
+
{ guidance: true, description: 'Keep a visible mobile entry point for important actions.' },
|
|
126
138
|
{ guidance: false, description: 'Use as the only way to access important actions.' },
|
|
127
139
|
{ guidance: false, description: 'Place more than 10–12 items without grouping.' },
|
|
128
140
|
],
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
12
|
-
import {render, screen, fireEvent, act} from '@testing-library/react';
|
|
12
|
+
import {render, screen, fireEvent, act, waitFor} from '@testing-library/react';
|
|
13
13
|
import userEvent from '@testing-library/user-event';
|
|
14
|
+
import {readFileSync} from 'node:fs';
|
|
14
15
|
import {ContextMenu} from './ContextMenu';
|
|
15
16
|
import {
|
|
16
17
|
ContextMenuItem,
|
|
@@ -23,6 +24,30 @@ import {DropdownMenuItem} from '../DropdownMenu/DropdownMenuItem';
|
|
|
23
24
|
import {Divider} from '../Divider';
|
|
24
25
|
|
|
25
26
|
beforeEach(() => {
|
|
27
|
+
HTMLDialogElement.prototype.showModal = vi.fn(function (
|
|
28
|
+
this: HTMLDialogElement,
|
|
29
|
+
) {
|
|
30
|
+
this.setAttribute('open', '');
|
|
31
|
+
});
|
|
32
|
+
HTMLDialogElement.prototype.show = vi.fn(function (this: HTMLDialogElement) {
|
|
33
|
+
this.setAttribute('open', '');
|
|
34
|
+
});
|
|
35
|
+
HTMLDialogElement.prototype.close = vi.fn(function (this: HTMLDialogElement) {
|
|
36
|
+
this.removeAttribute('open');
|
|
37
|
+
});
|
|
38
|
+
vi.stubGlobal(
|
|
39
|
+
'matchMedia',
|
|
40
|
+
vi.fn().mockImplementation((query: string) => ({
|
|
41
|
+
matches: false,
|
|
42
|
+
media: query,
|
|
43
|
+
onchange: null,
|
|
44
|
+
addEventListener: vi.fn(),
|
|
45
|
+
removeEventListener: vi.fn(),
|
|
46
|
+
addListener: vi.fn(),
|
|
47
|
+
removeListener: vi.fn(),
|
|
48
|
+
dispatchEvent: vi.fn(),
|
|
49
|
+
})),
|
|
50
|
+
);
|
|
26
51
|
HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
|
|
27
52
|
this.setAttribute('popover-open', '');
|
|
28
53
|
const event = new Event('toggle', {bubbles: false});
|
|
@@ -123,6 +148,100 @@ describe('ContextMenu', () => {
|
|
|
123
148
|
expect(HTMLElement.prototype.showPopover).toHaveBeenCalled();
|
|
124
149
|
});
|
|
125
150
|
|
|
151
|
+
it('opens the real menu content in a BottomSheet when requested', async () => {
|
|
152
|
+
render(
|
|
153
|
+
<ContextMenu
|
|
154
|
+
presentation="bottom-sheet"
|
|
155
|
+
label="Row actions"
|
|
156
|
+
items={[{label: 'Edit'}]}>
|
|
157
|
+
<div>Right-click me</div>
|
|
158
|
+
</ContextMenu>,
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
fireEvent.contextMenu(screen.getByText('Right-click me'));
|
|
162
|
+
await waitFor(() =>
|
|
163
|
+
expect(HTMLDialogElement.prototype.showModal).toHaveBeenCalledOnce(),
|
|
164
|
+
);
|
|
165
|
+
const dialog = screen.getByRole('dialog', {name: 'Row actions'});
|
|
166
|
+
const actionList = dialog.querySelector('[data-autofocus]');
|
|
167
|
+
expect(actionList).not.toBeNull();
|
|
168
|
+
await waitFor(() => expect(actionList).toHaveFocus());
|
|
169
|
+
expect(screen.getByRole('button', {name: 'Edit'})).not.toHaveFocus();
|
|
170
|
+
expect(HTMLElement.prototype.showPopover).not.toHaveBeenCalled();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('drills into nested data items inside the BottomSheet', async () => {
|
|
174
|
+
const user = userEvent.setup();
|
|
175
|
+
const onMove = vi.fn();
|
|
176
|
+
|
|
177
|
+
render(
|
|
178
|
+
<ContextMenu
|
|
179
|
+
presentation="bottom-sheet"
|
|
180
|
+
label="Row actions"
|
|
181
|
+
items={[
|
|
182
|
+
{
|
|
183
|
+
label: 'Move to project',
|
|
184
|
+
items: [{label: 'Apollo launch', onClick: onMove}],
|
|
185
|
+
},
|
|
186
|
+
]}>
|
|
187
|
+
<div>Right-click me</div>
|
|
188
|
+
</ContextMenu>,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
fireEvent.contextMenu(screen.getByText('Right-click me'));
|
|
192
|
+
await user.click(screen.getByRole('button', {name: 'Move to project'}));
|
|
193
|
+
|
|
194
|
+
const submenuHeading = screen.getByRole('heading', {
|
|
195
|
+
name: 'Move to project',
|
|
196
|
+
});
|
|
197
|
+
expect(submenuHeading).toBeInTheDocument();
|
|
198
|
+
await waitFor(() => expect(submenuHeading).toHaveFocus());
|
|
199
|
+
expect(HTMLElement.prototype.showPopover).not.toHaveBeenCalled();
|
|
200
|
+
|
|
201
|
+
await user.click(screen.getByRole('button', {name: 'Apollo launch'}));
|
|
202
|
+
expect(onMove).toHaveBeenCalledOnce();
|
|
203
|
+
await waitFor(() =>
|
|
204
|
+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument(),
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('keeps only the top content padding in a menu BottomSheet', () => {
|
|
209
|
+
const source = readFileSync(
|
|
210
|
+
'packages/core/src/DropdownMenu/MenuBottomSheet.tsx',
|
|
211
|
+
'utf8',
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
expect(source).toContain('paddingBlockStart={4}');
|
|
215
|
+
expect(source).toContain('paddingBlockEnd={0}');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('uses the BottomSheet for adaptive presentation on compact touch', async () => {
|
|
219
|
+
vi.stubGlobal(
|
|
220
|
+
'matchMedia',
|
|
221
|
+
vi.fn().mockImplementation((query: string) => ({
|
|
222
|
+
matches: query === '(max-width: 768px) and (pointer: coarse)',
|
|
223
|
+
media: query,
|
|
224
|
+
onchange: null,
|
|
225
|
+
addEventListener: vi.fn(),
|
|
226
|
+
removeEventListener: vi.fn(),
|
|
227
|
+
addListener: vi.fn(),
|
|
228
|
+
removeListener: vi.fn(),
|
|
229
|
+
dispatchEvent: vi.fn(),
|
|
230
|
+
})),
|
|
231
|
+
);
|
|
232
|
+
render(
|
|
233
|
+
<ContextMenu presentation="adaptive" items={[{label: 'Edit'}]}>
|
|
234
|
+
<div>Right-click me</div>
|
|
235
|
+
</ContextMenu>,
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
fireEvent.contextMenu(screen.getByText('Right-click me'));
|
|
239
|
+
await waitFor(() =>
|
|
240
|
+
expect(HTMLDialogElement.prototype.showModal).toHaveBeenCalledOnce(),
|
|
241
|
+
);
|
|
242
|
+
expect(HTMLElement.prototype.showPopover).not.toHaveBeenCalled();
|
|
243
|
+
});
|
|
244
|
+
|
|
126
245
|
it('closes on Escape even when opened without auto-focus', () => {
|
|
127
246
|
render(
|
|
128
247
|
<ContextMenu items={[{label: 'Item 1'}]}>
|
|
@@ -273,6 +392,17 @@ describe('ContextMenu', () => {
|
|
|
273
392
|
}
|
|
274
393
|
});
|
|
275
394
|
|
|
395
|
+
it('suppresses native text selection on the long-press trigger', () => {
|
|
396
|
+
const source = readFileSync(
|
|
397
|
+
'packages/core/src/ContextMenu/ContextMenu.tsx',
|
|
398
|
+
'utf8',
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
expect(source).toMatch(
|
|
402
|
+
/trigger:[\s\S]*?WebkitTouchCallout: 'none',[\s\S]*?WebkitUserSelect: 'none',[\s\S]*?userSelect: 'none'/,
|
|
403
|
+
);
|
|
404
|
+
});
|
|
405
|
+
|
|
276
406
|
it('cancels the long-press when the finger moves past the threshold', () => {
|
|
277
407
|
vi.useFakeTimers();
|
|
278
408
|
try {
|