@proyecto-viviana/solidaria-components 0.2.9 → 0.3.1
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/README.md +39 -272
- package/dist/ActionBar.d.ts +21 -13
- package/dist/ActionBar.d.ts.map +1 -1
- package/dist/ActionGroup.d.ts +8 -8
- package/dist/ActionGroup.d.ts.map +1 -1
- package/dist/Alert.d.ts +5 -5
- package/dist/Alert.d.ts.map +1 -1
- package/dist/Autocomplete.d.ts +5 -5
- package/dist/Autocomplete.d.ts.map +1 -1
- package/dist/Breadcrumbs.d.ts +18 -7
- package/dist/Breadcrumbs.d.ts.map +1 -1
- package/dist/Button.d.ts +24 -5
- package/dist/Button.d.ts.map +1 -1
- package/dist/Calendar.d.ts +38 -7
- package/dist/Calendar.d.ts.map +1 -1
- package/dist/Checkbox.d.ts +32 -7
- package/dist/Checkbox.d.ts.map +1 -1
- package/dist/Collection.d.ts +19 -14
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Color.d.ts +103 -14
- package/dist/Color.d.ts.map +1 -1
- package/dist/ColorEditor.d.ts +6 -6
- package/dist/ColorEditor.d.ts.map +1 -1
- package/dist/ComboBox.d.ts +85 -19
- package/dist/ComboBox.d.ts.map +1 -1
- package/dist/ContextualHelpTrigger.d.ts +2 -2
- package/dist/ContextualHelpTrigger.d.ts.map +1 -1
- package/dist/DateField.d.ts +8 -6
- package/dist/DateField.d.ts.map +1 -1
- package/dist/DatePicker.d.ts +53 -22
- package/dist/DatePicker.d.ts.map +1 -1
- package/dist/DateRangePickerContext.d.ts +30 -0
- package/dist/DateRangePickerContext.d.ts.map +1 -0
- package/dist/Dialog.d.ts +5 -5
- package/dist/Dialog.d.ts.map +1 -1
- package/dist/Disclosure.d.ts +23 -5
- package/dist/Disclosure.d.ts.map +1 -1
- package/dist/DragAndDrop.d.ts +6 -6
- package/dist/DragAndDrop.d.ts.map +1 -1
- package/dist/DragPreview.d.ts +2 -2
- package/dist/DragPreview.d.ts.map +1 -1
- package/dist/DropZone.d.ts +4 -4
- package/dist/DropZone.d.ts.map +1 -1
- package/dist/FieldError.d.ts +9 -5
- package/dist/FieldError.d.ts.map +1 -1
- package/dist/FileTrigger.d.ts +3 -3
- package/dist/FileTrigger.d.ts.map +1 -1
- package/dist/Focusable.d.ts +2 -2
- package/dist/Focusable.d.ts.map +1 -1
- package/dist/Form.d.ts +18 -4
- package/dist/Form.d.ts.map +1 -1
- package/dist/GridList.d.ts +32 -12
- package/dist/GridList.d.ts.map +1 -1
- package/dist/HiddenDateInput.d.ts +26 -0
- package/dist/HiddenDateInput.d.ts.map +1 -0
- package/dist/HiddenTimeInput.d.ts +25 -0
- package/dist/HiddenTimeInput.d.ts.map +1 -0
- package/dist/Icon.d.ts +5 -5
- package/dist/Icon.d.ts.map +1 -1
- package/dist/Keyboard.d.ts +1 -1
- package/dist/Landmark.d.ts +3 -3
- package/dist/Landmark.d.ts.map +1 -1
- package/dist/Link.d.ts +10 -4
- package/dist/Link.d.ts.map +1 -1
- package/dist/ListBox.d.ts +32 -12
- package/dist/ListBox.d.ts.map +1 -1
- package/dist/ListDropTargetDelegate.d.ts +6 -6
- package/dist/ListDropTargetDelegate.d.ts.map +1 -1
- package/dist/Menu.d.ts +65 -14
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Meter.d.ts +3 -3
- package/dist/Meter.d.ts.map +1 -1
- package/dist/Modal.d.ts +5 -5
- package/dist/Modal.d.ts.map +1 -1
- package/dist/NumberField.d.ts +8 -12
- package/dist/NumberField.d.ts.map +1 -1
- package/dist/Popover.d.ts +28 -5
- package/dist/Popover.d.ts.map +1 -1
- package/dist/Pressable.d.ts +2 -2
- package/dist/Pressable.d.ts.map +1 -1
- package/dist/ProgressBar.d.ts +5 -3
- package/dist/ProgressBar.d.ts.map +1 -1
- package/dist/RadioGroup.d.ts +43 -9
- package/dist/RadioGroup.d.ts.map +1 -1
- package/dist/RangeCalendar.d.ts +34 -7
- package/dist/RangeCalendar.d.ts.map +1 -1
- package/dist/RouterProvider.d.ts +2 -2
- package/dist/RouterProvider.d.ts.map +1 -1
- package/dist/SearchField.d.ts +23 -20
- package/dist/SearchField.d.ts.map +1 -1
- package/dist/Select.d.ts +41 -11
- package/dist/Select.d.ts.map +1 -1
- package/dist/SelectionIndicator.d.ts +3 -3
- package/dist/SelectionIndicator.d.ts.map +1 -1
- package/dist/Separator.d.ts +9 -3
- package/dist/Separator.d.ts.map +1 -1
- package/dist/SharedElementTransition.d.ts +6 -4
- package/dist/SharedElementTransition.d.ts.map +1 -1
- package/dist/Slider.d.ts +12 -8
- package/dist/Slider.d.ts.map +1 -1
- package/dist/StepList.d.ts +90 -0
- package/dist/StepList.d.ts.map +1 -0
- package/dist/Switch.d.ts +11 -5
- package/dist/Switch.d.ts.map +1 -1
- package/dist/Table.d.ts +187 -23
- package/dist/Table.d.ts.map +1 -1
- package/dist/Tabs.d.ts +45 -9
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/TagGroup.d.ts +12 -10
- package/dist/TagGroup.d.ts.map +1 -1
- package/dist/Text.d.ts +2 -2
- package/dist/TextField.d.ts +15 -11
- package/dist/TextField.d.ts.map +1 -1
- package/dist/TimeField.d.ts +6 -6
- package/dist/TimeField.d.ts.map +1 -1
- package/dist/Toast.d.ts +29 -14
- package/dist/Toast.d.ts.map +1 -1
- package/dist/ToggleButton.d.ts +11 -5
- package/dist/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButtonGroup.d.ts +7 -7
- package/dist/ToggleButtonGroup.d.ts.map +1 -1
- package/dist/Toolbar.d.ts +7 -3
- package/dist/Toolbar.d.ts.map +1 -1
- package/dist/Tooltip.d.ts +50 -8
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/Tree.d.ts +66 -17
- package/dist/Tree.d.ts.map +1 -1
- package/dist/Virtualizer.d.ts +12 -12
- package/dist/Virtualizer.d.ts.map +1 -1
- package/dist/VirtualizerLayouts.d.ts +2 -2
- package/dist/VirtualizerLayouts.d.ts.map +1 -1
- package/dist/VisuallyHidden.d.ts +1 -1
- package/dist/VisuallyHidden.d.ts.map +1 -1
- package/dist/contexts.d.ts +5 -1
- package/dist/contexts.d.ts.map +1 -1
- package/dist/index.d.ts +73 -71
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23253 -18564
- package/dist/index.js.map +1 -1
- package/dist/index.jsx +18116 -0
- package/dist/index.jsx.map +1 -0
- package/dist/useDragAndDrop.d.ts +13 -13
- package/dist/useDragAndDrop.d.ts.map +1 -1
- package/dist/utils.d.ts +2 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/virtualizer/Layout.d.ts +1 -1
- package/dist/virtualizer/Layout.d.ts.map +1 -1
- package/package.json +31 -32
- package/src/ActionBar.tsx +75 -72
- package/src/ActionGroup.tsx +53 -61
- package/src/Alert.tsx +17 -42
- package/src/Autocomplete.tsx +39 -44
- package/src/Breadcrumbs.tsx +149 -80
- package/src/Button.tsx +267 -70
- package/src/Calendar.tsx +218 -138
- package/src/Checkbox.tsx +413 -121
- package/src/Collection.tsx +67 -58
- package/src/Color.tsx +803 -380
- package/src/ColorEditor.tsx +131 -149
- package/src/ComboBox.tsx +414 -249
- package/src/ContextualHelpTrigger.tsx +86 -74
- package/src/DateField.tsx +185 -91
- package/src/DatePicker.tsx +524 -213
- package/src/DateRangePickerContext.tsx +44 -0
- package/src/Dialog.tsx +156 -118
- package/src/Disclosure.tsx +127 -80
- package/src/DragAndDrop.tsx +60 -54
- package/src/DragPreview.tsx +13 -11
- package/src/DropZone.tsx +42 -22
- package/src/FieldError.tsx +45 -23
- package/src/FileTrigger.tsx +19 -19
- package/src/Focusable.tsx +21 -24
- package/src/Form.tsx +71 -16
- package/src/GridList.tsx +273 -197
- package/src/HiddenDateInput.tsx +153 -0
- package/src/HiddenTimeInput.tsx +133 -0
- package/src/Icon.tsx +22 -43
- package/src/Keyboard.tsx +3 -3
- package/src/Landmark.tsx +37 -63
- package/src/Link.tsx +125 -75
- package/src/ListBox.tsx +332 -233
- package/src/ListDropTargetDelegate.ts +81 -80
- package/src/Menu.tsx +1023 -274
- package/src/Meter.tsx +38 -56
- package/src/Modal.tsx +251 -176
- package/src/NumberField.tsx +139 -143
- package/src/Popover.tsx +396 -234
- package/src/Pressable.tsx +21 -21
- package/src/ProgressBar.tsx +48 -57
- package/src/RadioGroup.tsx +524 -122
- package/src/RangeCalendar.tsx +157 -90
- package/src/RouterProvider.tsx +30 -47
- package/src/SearchField.tsx +362 -143
- package/src/Select.tsx +656 -233
- package/src/SelectionIndicator.tsx +18 -15
- package/src/Separator.tsx +47 -49
- package/src/SharedElementTransition.tsx +103 -97
- package/src/Slider.tsx +138 -98
- package/src/StepList.tsx +272 -0
- package/src/Switch.tsx +93 -46
- package/src/Table.tsx +1308 -342
- package/src/Tabs.tsx +324 -103
- package/src/TagGroup.tsx +139 -126
- package/src/Text.tsx +3 -3
- package/src/TextField.tsx +389 -79
- package/src/TimeField.tsx +136 -76
- package/src/Toast.tsx +216 -158
- package/src/ToggleButton.tsx +47 -37
- package/src/ToggleButtonGroup.tsx +39 -34
- package/src/Toolbar.tsx +54 -69
- package/src/Tooltip.tsx +387 -119
- package/src/Tree.tsx +651 -368
- package/src/Virtualizer.tsx +208 -180
- package/src/VirtualizerLayouts.ts +45 -30
- package/src/VisuallyHidden.tsx +19 -19
- package/src/contexts.ts +29 -37
- package/src/index.ts +110 -195
- package/src/useDragAndDrop.ts +87 -71
- package/src/utils.tsx +49 -60
- package/src/virtualizer/Layout.ts +14 -22
- package/dist/index.ssr.js +0 -16996
- package/dist/index.ssr.js.map +0 -1
package/src/Menu.tsx
CHANGED
|
@@ -11,11 +11,13 @@ import {
|
|
|
11
11
|
createEffect,
|
|
12
12
|
createMemo,
|
|
13
13
|
createSignal,
|
|
14
|
+
createUniqueId,
|
|
15
|
+
onCleanup,
|
|
14
16
|
splitProps,
|
|
15
17
|
useContext,
|
|
16
18
|
For,
|
|
17
19
|
Show,
|
|
18
|
-
} from
|
|
20
|
+
} from "solid-js";
|
|
19
21
|
import {
|
|
20
22
|
createMenu,
|
|
21
23
|
createMenuItem,
|
|
@@ -29,24 +31,29 @@ import {
|
|
|
29
31
|
type AriaMenuProps,
|
|
30
32
|
type AriaMenuItemProps,
|
|
31
33
|
type AriaMenuTriggerProps,
|
|
32
|
-
} from
|
|
34
|
+
} from "@proyecto-viviana/solidaria";
|
|
33
35
|
import {
|
|
36
|
+
createSelectionState,
|
|
34
37
|
createMenuState,
|
|
35
38
|
createMenuTriggerState,
|
|
36
39
|
type MenuState,
|
|
40
|
+
type MenuStateProps,
|
|
37
41
|
type OverlayTriggerState,
|
|
38
42
|
type Key,
|
|
39
43
|
type DropTarget,
|
|
40
|
-
|
|
44
|
+
type SelectionMode,
|
|
45
|
+
type SelectionStateProps,
|
|
46
|
+
} from "@proyecto-viviana/solid-stately";
|
|
41
47
|
import {
|
|
42
48
|
type RenderChildren,
|
|
43
49
|
type ClassNameOrFunction,
|
|
44
50
|
type StyleOrFunction,
|
|
45
51
|
type SlotProps,
|
|
46
52
|
useRenderProps,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
import {
|
|
53
|
+
filterDOMProps,
|
|
54
|
+
} from "./utils";
|
|
55
|
+
import { SharedElementTransition } from "./SharedElementTransition";
|
|
56
|
+
import { type DragAndDropHooks } from "./useDragAndDrop";
|
|
50
57
|
import {
|
|
51
58
|
CollectionRendererContext,
|
|
52
59
|
Section,
|
|
@@ -58,31 +65,41 @@ import {
|
|
|
58
65
|
useCollectionRenderer,
|
|
59
66
|
flattenCollectionEntries,
|
|
60
67
|
isCollectionSection,
|
|
61
|
-
} from
|
|
62
|
-
import { useVirtualizerContext } from
|
|
68
|
+
} from "./Collection";
|
|
69
|
+
import { useVirtualizerContext } from "./Virtualizer";
|
|
63
70
|
import {
|
|
64
71
|
getNormalizedDropTargetKey,
|
|
65
72
|
mergePersistedKeysIntoVirtualRange,
|
|
66
73
|
useDndPersistedKeys,
|
|
67
74
|
useRenderDropIndicator,
|
|
68
|
-
} from
|
|
69
|
-
|
|
70
|
-
// ============================================
|
|
71
|
-
// TYPES
|
|
72
|
-
// ============================================
|
|
75
|
+
} from "./DragAndDrop";
|
|
76
|
+
import { PopoverTriggerContext } from "./contexts";
|
|
73
77
|
|
|
74
78
|
export interface MenuRenderProps {
|
|
75
79
|
/** Whether the menu is focused. */
|
|
76
80
|
isFocused: boolean;
|
|
77
81
|
/** Whether the menu is open. */
|
|
78
82
|
isOpen: boolean;
|
|
83
|
+
/** Whether the menu has no items. */
|
|
84
|
+
isEmpty: boolean;
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
export interface MenuProps<T>
|
|
82
|
-
extends
|
|
83
|
-
|
|
88
|
+
extends
|
|
89
|
+
Omit<AriaMenuProps, "children">,
|
|
90
|
+
SlotProps,
|
|
91
|
+
Pick<
|
|
92
|
+
MenuStateProps<T>,
|
|
93
|
+
| "selectionMode"
|
|
94
|
+
| "selectionBehavior"
|
|
95
|
+
| "disallowEmptySelection"
|
|
96
|
+
| "selectedKeys"
|
|
97
|
+
| "defaultSelectedKeys"
|
|
98
|
+
| "onSelectionChange"
|
|
99
|
+
| "allowDuplicateSelectionEvents"
|
|
100
|
+
> {
|
|
84
101
|
/** The items to render in the menu. */
|
|
85
|
-
items
|
|
102
|
+
items?: CollectionEntry<T>[];
|
|
86
103
|
/** Function to get the key from an item. */
|
|
87
104
|
getKey?: (item: T) => Key;
|
|
88
105
|
/** Function to get the text value from an item. */
|
|
@@ -96,11 +113,24 @@ export interface MenuProps<T>
|
|
|
96
113
|
/** Handler called when the menu should close. */
|
|
97
114
|
onClose?: () => void;
|
|
98
115
|
/** The children of the component. A function may be provided to render each item. */
|
|
99
|
-
children
|
|
116
|
+
children?: JSX.Element | ((item: T) => JSX.Element);
|
|
117
|
+
/** Internal lazy static children accessor used when collection children need menu context. */
|
|
118
|
+
staticChildren?: () => JSX.Element | undefined;
|
|
100
119
|
/** The CSS className for the element. */
|
|
101
120
|
class?: ClassNameOrFunction<MenuRenderProps>;
|
|
102
121
|
/** The inline style for the element. */
|
|
103
122
|
style?: StyleOrFunction<MenuRenderProps>;
|
|
123
|
+
/** Content to display when the menu has no items. */
|
|
124
|
+
renderEmptyState?: () => JSX.Element;
|
|
125
|
+
/** Whether the menu should close when an item is selected. */
|
|
126
|
+
shouldCloseOnSelect?: boolean;
|
|
127
|
+
/** Ref for the menu element. */
|
|
128
|
+
ref?: RefLike<HTMLUListElement>;
|
|
129
|
+
/** Custom renderer for the menu element. */
|
|
130
|
+
render?: (
|
|
131
|
+
props: JSX.HTMLAttributes<HTMLUListElement>,
|
|
132
|
+
renderProps: MenuRenderProps,
|
|
133
|
+
) => JSX.Element;
|
|
104
134
|
/** Drag and drop hooks from `useDragAndDrop`. */
|
|
105
135
|
dragAndDropHooks?: DragAndDropHooks<T>;
|
|
106
136
|
}
|
|
@@ -108,6 +138,8 @@ export interface MenuProps<T>
|
|
|
108
138
|
export interface MenuItemRenderProps {
|
|
109
139
|
/** Whether the item is selected. */
|
|
110
140
|
isSelected: boolean;
|
|
141
|
+
/** The parent menu selection mode. */
|
|
142
|
+
selectionMode: SelectionMode;
|
|
111
143
|
/** Whether the item is focused. */
|
|
112
144
|
isFocused: boolean;
|
|
113
145
|
/** Whether the item has keyboard focus. */
|
|
@@ -118,11 +150,13 @@ export interface MenuItemRenderProps {
|
|
|
118
150
|
isHovered: boolean;
|
|
119
151
|
/** Whether the item is disabled. */
|
|
120
152
|
isDisabled: boolean;
|
|
153
|
+
/** Whether the item opens a submenu. */
|
|
154
|
+
hasSubmenu: boolean;
|
|
155
|
+
/** Whether the submenu is currently open. */
|
|
156
|
+
isOpen: boolean;
|
|
121
157
|
}
|
|
122
158
|
|
|
123
|
-
export interface MenuItemProps<T>
|
|
124
|
-
extends Omit<AriaMenuItemProps, 'children' | 'key'>,
|
|
125
|
-
SlotProps {
|
|
159
|
+
export interface MenuItemProps<T> extends Omit<AriaMenuItemProps, "children" | "key">, SlotProps {
|
|
126
160
|
/** The unique key for the item. */
|
|
127
161
|
id: Key;
|
|
128
162
|
/** The item value. */
|
|
@@ -137,6 +171,27 @@ export interface MenuItemProps<T>
|
|
|
137
171
|
textValue?: string;
|
|
138
172
|
/** Handler called when the item is activated. */
|
|
139
173
|
onAction?: () => void;
|
|
174
|
+
/** A URL to link to. Turns the menu item into a link. */
|
|
175
|
+
href?: string;
|
|
176
|
+
/** The target window for the link. */
|
|
177
|
+
target?: string;
|
|
178
|
+
/** The relationship between the linked resource and the current page. */
|
|
179
|
+
rel?: string;
|
|
180
|
+
/** Causes the browser to download the linked URL. */
|
|
181
|
+
download?: boolean | string;
|
|
182
|
+
/** Handler called when hover starts. */
|
|
183
|
+
onHoverStart?: () => void;
|
|
184
|
+
/** Handler called when hover ends. */
|
|
185
|
+
onHoverEnd?: () => void;
|
|
186
|
+
/** Handler called when hover state changes. */
|
|
187
|
+
onHoverChange?: (isHovered: boolean) => void;
|
|
188
|
+
/** Ref for the menu item element. */
|
|
189
|
+
ref?: RefLike<HTMLLIElement>;
|
|
190
|
+
/** Custom renderer for the menu item element. */
|
|
191
|
+
render?: (
|
|
192
|
+
props: JSX.HTMLAttributes<HTMLLIElement>,
|
|
193
|
+
renderProps: MenuItemRenderProps,
|
|
194
|
+
) => JSX.Element;
|
|
140
195
|
}
|
|
141
196
|
|
|
142
197
|
export interface MenuTriggerRenderProps {
|
|
@@ -154,7 +209,7 @@ export interface MenuTriggerRenderProps {
|
|
|
154
209
|
isDisabled: boolean;
|
|
155
210
|
}
|
|
156
211
|
|
|
157
|
-
export interface MenuTriggerProps extends Omit<AriaMenuTriggerProps,
|
|
212
|
+
export interface MenuTriggerProps extends Omit<AriaMenuTriggerProps, "children">, SlotProps {
|
|
158
213
|
/** The children of the trigger (typically a Button and Menu). */
|
|
159
214
|
children: JSX.Element;
|
|
160
215
|
/** Whether the menu trigger is disabled. */
|
|
@@ -167,11 +222,18 @@ export interface MenuTriggerProps extends Omit<AriaMenuTriggerProps, 'children'>
|
|
|
167
222
|
onOpenChange?: (isOpen: boolean) => void;
|
|
168
223
|
}
|
|
169
224
|
|
|
170
|
-
export interface SubmenuTriggerProps extends
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
225
|
+
export interface SubmenuTriggerProps extends SlotProps {
|
|
226
|
+
/** The trigger item followed by submenu content. */
|
|
227
|
+
children: JSX.Element;
|
|
228
|
+
/** Delay before opening the submenu on hover. */
|
|
229
|
+
delay?: number;
|
|
230
|
+
/** Whether the submenu is open (controlled). */
|
|
231
|
+
isOpen?: boolean;
|
|
232
|
+
/** Whether the submenu is open by default. */
|
|
233
|
+
defaultOpen?: boolean;
|
|
234
|
+
/** Handler called when the submenu open state changes. */
|
|
235
|
+
onOpenChange?: (isOpen: boolean) => void;
|
|
236
|
+
}
|
|
175
237
|
|
|
176
238
|
interface MenuContextValue<T> {
|
|
177
239
|
state: MenuState<T>;
|
|
@@ -181,28 +243,74 @@ interface MenuContextValue<T> {
|
|
|
181
243
|
dropState?: unknown;
|
|
182
244
|
}
|
|
183
245
|
|
|
246
|
+
type MenuSelectionEvent = { shiftKey?: boolean; ctrlKey?: boolean; metaKey?: boolean };
|
|
247
|
+
|
|
184
248
|
interface MenuTriggerContextValue {
|
|
185
249
|
state: OverlayTriggerState;
|
|
186
250
|
triggerProps: JSX.HTMLAttributes<HTMLElement>;
|
|
187
251
|
menuProps: JSX.HTMLAttributes<HTMLElement>;
|
|
188
252
|
}
|
|
189
253
|
|
|
254
|
+
interface MenuItemContextValue {
|
|
255
|
+
props?: () => JSX.HTMLAttributes<HTMLElement>;
|
|
256
|
+
closeOnSelect?: boolean;
|
|
257
|
+
onAction?: () => void;
|
|
258
|
+
setItemRef?: (el: HTMLLIElement | null) => void;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
interface StaticMenuCollectionItem {
|
|
262
|
+
id: Key;
|
|
263
|
+
textValue?: string;
|
|
264
|
+
isDisabled?: boolean;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
interface StaticMenuCollectionContextValue {
|
|
268
|
+
registerItem(item: StaticMenuCollectionItem): void;
|
|
269
|
+
unregisterItem(id: Key): void;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
interface MenuSectionSelectionContextValue {
|
|
273
|
+
selectionMode: () => SelectionMode;
|
|
274
|
+
isSelected(key: Key): boolean;
|
|
275
|
+
isDisabled(key: Key): boolean;
|
|
276
|
+
select(key: Key, event?: MenuSelectionEvent): void;
|
|
277
|
+
shouldCloseOnSelect(): boolean | undefined;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
interface MenuSectionSelectionRegistryContextValue {
|
|
281
|
+
registerItem(key: Key, selection: MenuSectionSelectionContextValue): void;
|
|
282
|
+
unregisterItem(key: Key, selection: MenuSectionSelectionContextValue): void;
|
|
283
|
+
selectItem(key: Key, event?: MenuSelectionEvent): boolean;
|
|
284
|
+
}
|
|
285
|
+
|
|
190
286
|
export const MenuContext = createContext<MenuContextValue<unknown> | null>(null);
|
|
191
287
|
export const MenuStateContext = createContext<MenuState<unknown> | null>(null);
|
|
192
288
|
export const MenuTriggerContext = createContext<MenuTriggerContextValue | null>(null);
|
|
193
289
|
export const RootMenuTriggerStateContext = createContext<OverlayTriggerState | null>(null);
|
|
290
|
+
const MenuItemContext = createContext<MenuItemContextValue | null>(null);
|
|
291
|
+
const StaticMenuCollectionContext = createContext<StaticMenuCollectionContextValue | null>(null);
|
|
292
|
+
const MenuSectionSelectionContext = createContext<MenuSectionSelectionContextValue | null>(null);
|
|
293
|
+
const MenuSectionSelectionRegistryContext =
|
|
294
|
+
createContext<MenuSectionSelectionRegistryContextValue | null>(null);
|
|
295
|
+
|
|
296
|
+
type RefLike<T> = ((el: T) => void) | { current?: T | null } | undefined;
|
|
194
297
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
298
|
+
function assignRef<T>(ref: RefLike<T>, el: T): void {
|
|
299
|
+
if (!ref) return;
|
|
300
|
+
if (typeof ref === "function") ref(el);
|
|
301
|
+
else ref.current = el;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function resolveBoolean(value: unknown): boolean {
|
|
305
|
+
return typeof value === "function" ? Boolean((value as () => unknown)()) : Boolean(value);
|
|
306
|
+
}
|
|
198
307
|
|
|
199
308
|
/**
|
|
200
309
|
* A menu trigger wraps a button and menu, handling the open/close state.
|
|
201
310
|
*/
|
|
202
311
|
export function MenuTrigger(props: MenuTriggerProps): JSX.Element {
|
|
203
|
-
const [local, stateProps] = splitProps(props, [
|
|
312
|
+
const [local, stateProps] = splitProps(props, ["slot"]);
|
|
204
313
|
|
|
205
|
-
// Create trigger state
|
|
206
314
|
const state = createMenuTriggerState({
|
|
207
315
|
get isOpen() {
|
|
208
316
|
return stateProps.isOpen;
|
|
@@ -215,14 +323,13 @@ export function MenuTrigger(props: MenuTriggerProps): JSX.Element {
|
|
|
215
323
|
},
|
|
216
324
|
});
|
|
217
325
|
|
|
218
|
-
|
|
219
|
-
const { menuTriggerProps, menuProps } = createMenuTrigger(
|
|
326
|
+
const menuTrigger = createMenuTrigger(
|
|
220
327
|
{
|
|
221
328
|
get isDisabled() {
|
|
222
329
|
return stateProps.isDisabled;
|
|
223
330
|
},
|
|
224
331
|
},
|
|
225
|
-
state
|
|
332
|
+
state,
|
|
226
333
|
);
|
|
227
334
|
|
|
228
335
|
return (
|
|
@@ -230,8 +337,12 @@ export function MenuTrigger(props: MenuTriggerProps): JSX.Element {
|
|
|
230
337
|
<MenuTriggerContext.Provider
|
|
231
338
|
value={{
|
|
232
339
|
state,
|
|
233
|
-
triggerProps
|
|
234
|
-
|
|
340
|
+
get triggerProps() {
|
|
341
|
+
return menuTrigger.menuTriggerProps;
|
|
342
|
+
},
|
|
343
|
+
get menuProps() {
|
|
344
|
+
return menuTrigger.menuProps;
|
|
345
|
+
},
|
|
235
346
|
}}
|
|
236
347
|
>
|
|
237
348
|
{props.children}
|
|
@@ -241,13 +352,124 @@ export function MenuTrigger(props: MenuTriggerProps): JSX.Element {
|
|
|
241
352
|
}
|
|
242
353
|
|
|
243
354
|
export function SubmenuTrigger(props: SubmenuTriggerProps): JSX.Element {
|
|
244
|
-
|
|
355
|
+
const children = () =>
|
|
356
|
+
(Array.isArray(props.children) ? props.children : [props.children]) as JSX.Element[];
|
|
357
|
+
const trigger = () => children()[0];
|
|
358
|
+
const content = () => children()[1];
|
|
359
|
+
const parentMenuItemContext = useContext(MenuItemContext);
|
|
360
|
+
const state = createMenuTriggerState({
|
|
361
|
+
get isOpen() {
|
|
362
|
+
return props.isOpen;
|
|
363
|
+
},
|
|
364
|
+
get defaultOpen() {
|
|
365
|
+
return props.defaultOpen;
|
|
366
|
+
},
|
|
367
|
+
get onOpenChange() {
|
|
368
|
+
return props.onOpenChange;
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
let triggerRef: HTMLLIElement | null = null;
|
|
373
|
+
const triggerId = createUniqueId();
|
|
374
|
+
const menuId = createUniqueId();
|
|
375
|
+
let hoverTimeout: number | undefined;
|
|
376
|
+
const delay = () => props.delay ?? 200;
|
|
377
|
+
|
|
378
|
+
const clearHoverTimeout = () => {
|
|
379
|
+
if (hoverTimeout != null) {
|
|
380
|
+
window.clearTimeout(hoverTimeout);
|
|
381
|
+
hoverTimeout = undefined;
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const openSubmenu = () => {
|
|
386
|
+
clearHoverTimeout();
|
|
387
|
+
state.open();
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
const scheduleOpen = () => {
|
|
391
|
+
clearHoverTimeout();
|
|
392
|
+
hoverTimeout = window.setTimeout(() => {
|
|
393
|
+
hoverTimeout = undefined;
|
|
394
|
+
state.open();
|
|
395
|
+
}, delay());
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
onCleanup(clearHoverTimeout);
|
|
399
|
+
|
|
400
|
+
const menuTriggerContext = createMemo<MenuTriggerContextValue>(() => ({
|
|
401
|
+
state,
|
|
402
|
+
triggerProps: {},
|
|
403
|
+
menuProps: {
|
|
404
|
+
id: menuId,
|
|
405
|
+
"aria-labelledby": triggerId,
|
|
406
|
+
},
|
|
407
|
+
}));
|
|
408
|
+
|
|
409
|
+
const popoverTriggerContext = createMemo(() => ({
|
|
410
|
+
state: {
|
|
411
|
+
isOpen: () => state.isOpen(),
|
|
412
|
+
open: () => state.open(),
|
|
413
|
+
close: () => state.close(),
|
|
414
|
+
toggle: () => state.toggle(),
|
|
415
|
+
},
|
|
416
|
+
triggerRef: () => triggerRef,
|
|
417
|
+
setTriggerRef: (el: HTMLElement | null) => {
|
|
418
|
+
triggerRef = el as HTMLLIElement | null;
|
|
419
|
+
},
|
|
420
|
+
triggerId,
|
|
421
|
+
trigger: "SubmenuTrigger",
|
|
422
|
+
}));
|
|
423
|
+
|
|
424
|
+
const itemContext = createMemo<MenuItemContextValue>(() => ({
|
|
425
|
+
closeOnSelect: false,
|
|
426
|
+
onAction: () => openSubmenu(),
|
|
427
|
+
setItemRef: (el) => {
|
|
428
|
+
triggerRef = el;
|
|
429
|
+
},
|
|
430
|
+
props: () => ({
|
|
431
|
+
id: triggerId,
|
|
432
|
+
"aria-haspopup": "menu",
|
|
433
|
+
"aria-expanded": state.isOpen() || undefined,
|
|
434
|
+
"aria-controls": state.isOpen() ? menuId : undefined,
|
|
435
|
+
onPointerEnter: (event: PointerEvent) => {
|
|
436
|
+
if (event.pointerType === "touch") return;
|
|
437
|
+
scheduleOpen();
|
|
438
|
+
},
|
|
439
|
+
onPointerOver: (event: PointerEvent) => {
|
|
440
|
+
if (event.pointerType === "touch") return;
|
|
441
|
+
scheduleOpen();
|
|
442
|
+
},
|
|
443
|
+
onMouseEnter: () => scheduleOpen(),
|
|
444
|
+
onKeyDown: (event: KeyboardEvent) => {
|
|
445
|
+
if (event.key === "ArrowRight" || event.key === "Enter" || event.key === " ") {
|
|
446
|
+
event.preventDefault();
|
|
447
|
+
openSubmenu();
|
|
448
|
+
} else if (event.key === "ArrowLeft" && state.isOpen()) {
|
|
449
|
+
event.preventDefault();
|
|
450
|
+
state.close();
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
}),
|
|
454
|
+
}));
|
|
455
|
+
|
|
456
|
+
return (
|
|
457
|
+
<PopoverTriggerContext.Provider value={popoverTriggerContext()}>
|
|
458
|
+
<MenuTriggerContext.Provider value={menuTriggerContext()}>
|
|
459
|
+
<MenuItemContext.Provider value={{ ...parentMenuItemContext, ...itemContext() }}>
|
|
460
|
+
{trigger()}
|
|
461
|
+
</MenuItemContext.Provider>
|
|
462
|
+
{content()}
|
|
463
|
+
</MenuTriggerContext.Provider>
|
|
464
|
+
</PopoverTriggerContext.Provider>
|
|
465
|
+
);
|
|
245
466
|
}
|
|
246
467
|
|
|
247
468
|
/**
|
|
248
469
|
* A button that opens a menu.
|
|
249
470
|
*/
|
|
250
|
-
export interface MenuButtonProps
|
|
471
|
+
export interface MenuButtonProps
|
|
472
|
+
extends SlotProps, Omit<JSX.HTMLAttributes<HTMLButtonElement>, "class" | "style" | "children"> {
|
|
251
473
|
/** The children of the button. A function may be provided to receive render props. */
|
|
252
474
|
children?: RenderChildren<MenuTriggerRenderProps>;
|
|
253
475
|
/** The CSS className for the element. */
|
|
@@ -258,19 +480,34 @@ export interface MenuButtonProps extends SlotProps, Omit<JSX.HTMLAttributes<HTML
|
|
|
258
480
|
isDisabled?: boolean;
|
|
259
481
|
}
|
|
260
482
|
|
|
261
|
-
export interface MenuSectionProps
|
|
483
|
+
export interface MenuSectionProps
|
|
484
|
+
extends
|
|
485
|
+
SectionProps,
|
|
486
|
+
Pick<
|
|
487
|
+
SelectionStateProps,
|
|
488
|
+
| "selectionMode"
|
|
489
|
+
| "selectionBehavior"
|
|
490
|
+
| "disallowEmptySelection"
|
|
491
|
+
| "selectedKeys"
|
|
492
|
+
| "defaultSelectedKeys"
|
|
493
|
+
| "onSelectionChange"
|
|
494
|
+
| "disabledKeys"
|
|
495
|
+
| "disabledBehavior"
|
|
496
|
+
| "allowDuplicateSelectionEvents"
|
|
497
|
+
> {
|
|
498
|
+
/** Whether menu items in this section should close the menu when selected. */
|
|
499
|
+
shouldCloseOnSelect?: boolean;
|
|
500
|
+
}
|
|
262
501
|
|
|
263
502
|
export function MenuButton(props: MenuButtonProps): JSX.Element {
|
|
264
|
-
const [local, domProps] = splitProps(props, [
|
|
503
|
+
const [local, domProps] = splitProps(props, ["class", "style", "slot", "isDisabled", "children"]);
|
|
265
504
|
|
|
266
|
-
// Get trigger context
|
|
267
505
|
const context = useContext(MenuTriggerContext);
|
|
268
506
|
if (!context) {
|
|
269
|
-
throw new Error(
|
|
507
|
+
throw new Error("MenuButton must be used within a MenuTrigger");
|
|
270
508
|
}
|
|
271
|
-
const { state
|
|
509
|
+
const { state } = context;
|
|
272
510
|
|
|
273
|
-
// Create button aria props for proper press handling
|
|
274
511
|
const buttonAria = createButton({
|
|
275
512
|
get isDisabled() {
|
|
276
513
|
return local.isDisabled;
|
|
@@ -280,17 +517,14 @@ export function MenuButton(props: MenuButtonProps): JSX.Element {
|
|
|
280
517
|
},
|
|
281
518
|
});
|
|
282
519
|
|
|
283
|
-
// Create focus ring
|
|
284
520
|
const { isFocused, isFocusVisible, focusProps } = createFocusRing();
|
|
285
521
|
|
|
286
|
-
// Create hover
|
|
287
522
|
const { isHovered, hoverProps } = createHover({
|
|
288
523
|
get isDisabled() {
|
|
289
524
|
return local.isDisabled;
|
|
290
525
|
},
|
|
291
526
|
});
|
|
292
527
|
|
|
293
|
-
// Render props values
|
|
294
528
|
const renderValues = createMemo<MenuTriggerRenderProps>(() => ({
|
|
295
529
|
isOpen: state.isOpen(),
|
|
296
530
|
isFocused: isFocused(),
|
|
@@ -300,20 +534,26 @@ export function MenuButton(props: MenuButtonProps): JSX.Element {
|
|
|
300
534
|
isDisabled: !!local.isDisabled,
|
|
301
535
|
}));
|
|
302
536
|
|
|
303
|
-
// Resolve render props
|
|
304
537
|
const renderProps = useRenderProps(
|
|
305
538
|
{
|
|
306
539
|
children: props.children,
|
|
307
540
|
class: local.class,
|
|
308
541
|
style: local.style,
|
|
309
|
-
defaultClassName:
|
|
542
|
+
defaultClassName: "solidaria-MenuButton",
|
|
310
543
|
},
|
|
311
|
-
renderValues
|
|
544
|
+
renderValues,
|
|
312
545
|
);
|
|
313
546
|
|
|
314
|
-
|
|
547
|
+
const resolvedTriggerProps = () => context.triggerProps as Record<string, unknown>;
|
|
315
548
|
const cleanTriggerProps = () => {
|
|
316
|
-
const {
|
|
549
|
+
const {
|
|
550
|
+
ref: _ref1,
|
|
551
|
+
"aria-haspopup": _ariaHasPopup,
|
|
552
|
+
"aria-expanded": _ariaExpanded,
|
|
553
|
+
"aria-controls": _ariaControls,
|
|
554
|
+
"aria-disabled": _ariaDisabled,
|
|
555
|
+
...rest
|
|
556
|
+
} = resolvedTriggerProps();
|
|
317
557
|
return rest;
|
|
318
558
|
};
|
|
319
559
|
const cleanButtonProps = () => {
|
|
@@ -339,6 +579,10 @@ export function MenuButton(props: MenuButtonProps): JSX.Element {
|
|
|
339
579
|
type="button"
|
|
340
580
|
class={renderProps.class()}
|
|
341
581
|
style={renderProps.style()}
|
|
582
|
+
aria-haspopup={resolvedTriggerProps()["aria-haspopup"] as "menu" | "listbox" | undefined}
|
|
583
|
+
aria-expanded={resolvedTriggerProps()["aria-expanded"] as boolean | undefined}
|
|
584
|
+
aria-controls={resolvedTriggerProps()["aria-controls"] as string | undefined}
|
|
585
|
+
aria-disabled={resolvedTriggerProps()["aria-disabled"] as boolean | undefined}
|
|
342
586
|
data-open={state.isOpen() || undefined}
|
|
343
587
|
data-focused={isFocused() || undefined}
|
|
344
588
|
data-focus-visible={isFocusVisible() || undefined}
|
|
@@ -357,41 +601,147 @@ export function MenuButton(props: MenuButtonProps): JSX.Element {
|
|
|
357
601
|
export function Menu<T>(props: MenuProps<T>): JSX.Element {
|
|
358
602
|
const [local, stateProps, ariaProps] = splitProps(
|
|
359
603
|
props,
|
|
360
|
-
[
|
|
361
|
-
|
|
604
|
+
[
|
|
605
|
+
"children",
|
|
606
|
+
"class",
|
|
607
|
+
"style",
|
|
608
|
+
"render",
|
|
609
|
+
"slot",
|
|
610
|
+
"renderEmptyState",
|
|
611
|
+
"shouldCloseOnSelect",
|
|
612
|
+
"ref",
|
|
613
|
+
"staticChildren",
|
|
614
|
+
],
|
|
615
|
+
[
|
|
616
|
+
"items",
|
|
617
|
+
"getKey",
|
|
618
|
+
"getTextValue",
|
|
619
|
+
"getDisabled",
|
|
620
|
+
"disabledKeys",
|
|
621
|
+
"selectionMode",
|
|
622
|
+
"selectionBehavior",
|
|
623
|
+
"disallowEmptySelection",
|
|
624
|
+
"selectedKeys",
|
|
625
|
+
"defaultSelectedKeys",
|
|
626
|
+
"onSelectionChange",
|
|
627
|
+
"allowDuplicateSelectionEvents",
|
|
628
|
+
"onAction",
|
|
629
|
+
"onClose",
|
|
630
|
+
"dragAndDropHooks",
|
|
631
|
+
],
|
|
362
632
|
);
|
|
363
633
|
|
|
364
|
-
// Get trigger context if available
|
|
365
634
|
const triggerContext = useContext(MenuTriggerContext);
|
|
366
635
|
|
|
367
|
-
// Ref for the menu element (for click outside detection)
|
|
368
636
|
const [menuRef, setMenuRef] = createSignal<HTMLUListElement | null>(null);
|
|
637
|
+
const [staticItems, setStaticItems] = createSignal<StaticMenuCollectionItem[]>([]);
|
|
638
|
+
const staticItemMap = new Map<Key, StaticMenuCollectionItem>();
|
|
639
|
+
const sectionSelectionMap = new Map<Key, MenuSectionSelectionContextValue>();
|
|
640
|
+
const usesStaticChildren = () => local.staticChildren != null || stateProps.items == null;
|
|
641
|
+
|
|
642
|
+
const syncStaticItems = () => {
|
|
643
|
+
setStaticItems(Array.from(staticItemMap.values()));
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
const staticCollectionContext: StaticMenuCollectionContextValue = {
|
|
647
|
+
registerItem(item) {
|
|
648
|
+
const previous = staticItemMap.get(item.id);
|
|
649
|
+
if (
|
|
650
|
+
previous &&
|
|
651
|
+
previous.textValue === item.textValue &&
|
|
652
|
+
previous.isDisabled === item.isDisabled
|
|
653
|
+
) {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
staticItemMap.set(item.id, item);
|
|
658
|
+
syncStaticItems();
|
|
659
|
+
},
|
|
660
|
+
unregisterItem(id) {
|
|
661
|
+
if (staticItemMap.delete(id)) {
|
|
662
|
+
syncStaticItems();
|
|
663
|
+
}
|
|
664
|
+
},
|
|
665
|
+
};
|
|
666
|
+
const sectionSelectionRegistry: MenuSectionSelectionRegistryContextValue = {
|
|
667
|
+
registerItem(key, selection) {
|
|
668
|
+
sectionSelectionMap.set(key, selection);
|
|
669
|
+
},
|
|
670
|
+
unregisterItem(key, selection) {
|
|
671
|
+
if (sectionSelectionMap.get(key) === selection) {
|
|
672
|
+
sectionSelectionMap.delete(key);
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
selectItem(key, event) {
|
|
676
|
+
const selection = sectionSelectionMap.get(key);
|
|
677
|
+
if (!selection || selection.selectionMode() === "none") {
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
selection.select(key, event);
|
|
682
|
+
return true;
|
|
683
|
+
},
|
|
684
|
+
};
|
|
685
|
+
const handleAction = (key: Key) => {
|
|
686
|
+
sectionSelectionRegistry.selectItem(key);
|
|
687
|
+
stateProps.onAction?.(key);
|
|
688
|
+
};
|
|
369
689
|
|
|
370
690
|
const flatItems = createMemo<T[]>(() => {
|
|
371
|
-
return flattenCollectionEntries(stateProps.items);
|
|
691
|
+
return flattenCollectionEntries(stateProps.items ?? []);
|
|
372
692
|
});
|
|
373
693
|
|
|
374
|
-
const hasSections = createMemo(() =>
|
|
694
|
+
const hasSections = createMemo(() =>
|
|
695
|
+
(stateProps.items ?? []).some((item) => isCollectionSection(item)),
|
|
696
|
+
);
|
|
375
697
|
|
|
376
|
-
// Create menu state
|
|
377
698
|
const state = createMenuState<T>({
|
|
378
699
|
get items() {
|
|
379
|
-
return flatItems();
|
|
700
|
+
return usesStaticChildren() ? (staticItems() as T[]) : flatItems();
|
|
380
701
|
},
|
|
381
702
|
get getKey() {
|
|
382
|
-
return
|
|
703
|
+
return usesStaticChildren()
|
|
704
|
+
? (item: T) => (item as StaticMenuCollectionItem).id
|
|
705
|
+
: stateProps.getKey;
|
|
383
706
|
},
|
|
384
707
|
get getTextValue() {
|
|
385
|
-
return
|
|
708
|
+
return usesStaticChildren()
|
|
709
|
+
? (item: T) =>
|
|
710
|
+
(item as StaticMenuCollectionItem).textValue ??
|
|
711
|
+
String((item as StaticMenuCollectionItem).id)
|
|
712
|
+
: stateProps.getTextValue;
|
|
386
713
|
},
|
|
387
714
|
get getDisabled() {
|
|
388
|
-
return
|
|
715
|
+
return usesStaticChildren()
|
|
716
|
+
? (item: T) => Boolean((item as StaticMenuCollectionItem).isDisabled)
|
|
717
|
+
: stateProps.getDisabled;
|
|
389
718
|
},
|
|
390
719
|
get disabledKeys() {
|
|
391
720
|
return stateProps.disabledKeys;
|
|
392
721
|
},
|
|
722
|
+
get selectionMode() {
|
|
723
|
+
return stateProps.selectionMode;
|
|
724
|
+
},
|
|
725
|
+
get selectionBehavior() {
|
|
726
|
+
return stateProps.selectionBehavior;
|
|
727
|
+
},
|
|
728
|
+
get disallowEmptySelection() {
|
|
729
|
+
return stateProps.disallowEmptySelection;
|
|
730
|
+
},
|
|
731
|
+
get selectedKeys() {
|
|
732
|
+
return stateProps.selectedKeys;
|
|
733
|
+
},
|
|
734
|
+
get defaultSelectedKeys() {
|
|
735
|
+
return stateProps.defaultSelectedKeys;
|
|
736
|
+
},
|
|
737
|
+
get onSelectionChange() {
|
|
738
|
+
return stateProps.onSelectionChange;
|
|
739
|
+
},
|
|
740
|
+
get allowDuplicateSelectionEvents() {
|
|
741
|
+
return stateProps.allowDuplicateSelectionEvents;
|
|
742
|
+
},
|
|
393
743
|
get onAction() {
|
|
394
|
-
return
|
|
744
|
+
return handleAction;
|
|
395
745
|
},
|
|
396
746
|
get onClose() {
|
|
397
747
|
return stateProps.onClose ?? (() => triggerContext?.state.close());
|
|
@@ -400,13 +750,12 @@ export function Menu<T>(props: MenuProps<T>): JSX.Element {
|
|
|
400
750
|
|
|
401
751
|
const resolveDisabled = (): boolean => {
|
|
402
752
|
const disabled = ariaProps.isDisabled;
|
|
403
|
-
if (typeof disabled ===
|
|
753
|
+
if (typeof disabled === "function") {
|
|
404
754
|
return (disabled as () => boolean)();
|
|
405
755
|
}
|
|
406
756
|
return !!disabled;
|
|
407
757
|
};
|
|
408
758
|
|
|
409
|
-
// Create menu aria props
|
|
410
759
|
const { menuProps, labelProps } = createMenu(
|
|
411
760
|
{
|
|
412
761
|
get isDisabled() {
|
|
@@ -416,28 +765,26 @@ export function Menu<T>(props: MenuProps<T>): JSX.Element {
|
|
|
416
765
|
return ariaProps.label;
|
|
417
766
|
},
|
|
418
767
|
get onAction() {
|
|
419
|
-
return
|
|
768
|
+
return handleAction;
|
|
420
769
|
},
|
|
421
770
|
get onClose() {
|
|
422
771
|
return stateProps.onClose ?? (() => triggerContext?.state.close());
|
|
423
772
|
},
|
|
424
|
-
get
|
|
425
|
-
return ariaProps[
|
|
773
|
+
get "aria-label"() {
|
|
774
|
+
return ariaProps["aria-label"];
|
|
426
775
|
},
|
|
427
|
-
get
|
|
428
|
-
return ariaProps[
|
|
776
|
+
get "aria-labelledby"() {
|
|
777
|
+
return ariaProps["aria-labelledby"];
|
|
429
778
|
},
|
|
430
|
-
get
|
|
431
|
-
return ariaProps[
|
|
779
|
+
get "aria-describedby"() {
|
|
780
|
+
return ariaProps["aria-describedby"];
|
|
432
781
|
},
|
|
433
782
|
},
|
|
434
|
-
state
|
|
783
|
+
state,
|
|
435
784
|
);
|
|
436
785
|
|
|
437
|
-
// Create focus ring
|
|
438
786
|
const { isFocused, focusProps } = createFocusRing();
|
|
439
787
|
|
|
440
|
-
// Handle click outside to close menu
|
|
441
788
|
createInteractOutside({
|
|
442
789
|
ref: () => menuRef(),
|
|
443
790
|
onInteractOutside: () => {
|
|
@@ -450,23 +797,21 @@ export function Menu<T>(props: MenuProps<T>): JSX.Element {
|
|
|
450
797
|
},
|
|
451
798
|
});
|
|
452
799
|
|
|
453
|
-
// Render props values
|
|
454
800
|
const renderValues = createMemo<MenuRenderProps>(() => ({
|
|
455
801
|
isFocused: state.isFocused() || isFocused(),
|
|
456
802
|
isOpen: triggerContext?.state.isOpen() ?? true,
|
|
803
|
+
isEmpty: state.collection().size === 0,
|
|
457
804
|
}));
|
|
458
805
|
|
|
459
|
-
// Resolve render props
|
|
460
806
|
const renderProps = useRenderProps(
|
|
461
807
|
{
|
|
462
808
|
class: local.class,
|
|
463
809
|
style: local.style,
|
|
464
|
-
defaultClassName:
|
|
810
|
+
defaultClassName: "solidaria-Menu",
|
|
465
811
|
},
|
|
466
|
-
renderValues
|
|
812
|
+
renderValues,
|
|
467
813
|
);
|
|
468
814
|
|
|
469
|
-
// Remove ref from spread props
|
|
470
815
|
const cleanMenuProps = () => {
|
|
471
816
|
const { ref: _ref1, ...rest } = menuProps as Record<string, unknown>;
|
|
472
817
|
return rest;
|
|
@@ -480,28 +825,42 @@ export function Menu<T>(props: MenuProps<T>): JSX.Element {
|
|
|
480
825
|
const { ref: _ref3, ...rest } = focusProps as Record<string, unknown>;
|
|
481
826
|
return rest;
|
|
482
827
|
};
|
|
828
|
+
const domProps = createMemo(() =>
|
|
829
|
+
filterDOMProps(ariaProps as Record<string, unknown>, { global: true }),
|
|
830
|
+
);
|
|
483
831
|
const cleanLabelProps = () => {
|
|
484
832
|
const { ref: _ref4, ...rest } = labelProps as Record<string, unknown>;
|
|
485
833
|
return rest;
|
|
486
834
|
};
|
|
835
|
+
const setResolvedMenuRef = (el: HTMLUListElement): void => {
|
|
836
|
+
setMenuRef(el);
|
|
837
|
+
assignRef(local.ref, el);
|
|
838
|
+
};
|
|
487
839
|
|
|
488
840
|
// If inside a MenuTrigger, only render when open
|
|
489
841
|
// If standalone (no trigger context), always render
|
|
490
|
-
const shouldRender = () => triggerContext ? triggerContext.state.isOpen() : true;
|
|
842
|
+
const shouldRender = () => (triggerContext ? triggerContext.state.isOpen() : true);
|
|
491
843
|
const parentCollectionRenderer = useCollectionRenderer<unknown>();
|
|
492
844
|
const virtualizer = useVirtualizerContext();
|
|
493
|
-
const getItemNodes = createMemo(() =>
|
|
494
|
-
|
|
845
|
+
const getItemNodes = createMemo(() =>
|
|
846
|
+
Array.from(state.collection()).filter((node) => node.type === "item"),
|
|
847
|
+
);
|
|
848
|
+
const getDropTargetByIndex = (
|
|
849
|
+
index: number,
|
|
850
|
+
position: "before" | "after" | "on",
|
|
851
|
+
): DropTarget | null => {
|
|
495
852
|
const node = getItemNodes()[index];
|
|
496
853
|
if (!node) return null;
|
|
497
|
-
return { type:
|
|
854
|
+
return { type: "item", key: node.key, dropPosition: position };
|
|
498
855
|
};
|
|
499
856
|
const hasDroppableDnd = createMemo(() => {
|
|
500
857
|
const hooks = stateProps.dragAndDropHooks;
|
|
501
858
|
return Boolean(
|
|
502
859
|
hooks?.useDroppableCollectionState &&
|
|
503
860
|
hooks.useDroppableCollection &&
|
|
504
|
-
(hooks.dropTargetDelegate ||
|
|
861
|
+
(hooks.dropTargetDelegate ||
|
|
862
|
+
parentCollectionRenderer?.dropTargetDelegate ||
|
|
863
|
+
hooks.ListDropTargetDelegate),
|
|
505
864
|
);
|
|
506
865
|
});
|
|
507
866
|
const hasDraggableDnd = createMemo(() => {
|
|
@@ -522,11 +881,12 @@ export function Menu<T>(props: MenuProps<T>): JSX.Element {
|
|
|
522
881
|
{ focusedKey: state.focusedKey },
|
|
523
882
|
stateProps.dragAndDropHooks,
|
|
524
883
|
dropState(),
|
|
525
|
-
state.collection()
|
|
884
|
+
state.collection(),
|
|
526
885
|
);
|
|
527
886
|
const virtualRange = createMemo(() => {
|
|
528
887
|
if (!virtualizer || !parentCollectionRenderer?.isVirtualized || hasSections()) return null;
|
|
529
|
-
const
|
|
888
|
+
const dynamicItems = stateProps.items ?? [];
|
|
889
|
+
const baseRange = virtualizer.getVisibleRange(dynamicItems.length);
|
|
530
890
|
const itemNodes = getItemNodes();
|
|
531
891
|
const persistedIndexes = Array.from(persistedKeys())
|
|
532
892
|
.map((key) => itemNodes.findIndex((node) => node.key === key))
|
|
@@ -534,21 +894,32 @@ export function Menu<T>(props: MenuProps<T>): JSX.Element {
|
|
|
534
894
|
const dropTarget = dropState()?.target;
|
|
535
895
|
const normalizedDropKey = getNormalizedDropTargetKey(dropTarget, state.collection());
|
|
536
896
|
const focusedKey = state.focusedKey();
|
|
537
|
-
const focusedIndex =
|
|
897
|
+
const focusedIndex =
|
|
898
|
+
focusedKey != null ? itemNodes.findIndex((node) => node.key === focusedKey) : -1;
|
|
538
899
|
const forceIncludeIndexes = [
|
|
539
|
-
dropTarget?.type ===
|
|
540
|
-
normalizedDropKey != null
|
|
541
|
-
|
|
900
|
+
dropTarget?.type === "item" ? itemNodes.findIndex((node) => node.key === dropTarget.key) : -1,
|
|
901
|
+
normalizedDropKey != null
|
|
902
|
+
? itemNodes.findIndex((node) => node.key === normalizedDropKey)
|
|
903
|
+
: -1,
|
|
904
|
+
dropTarget?.type === "item" ? -1 : focusedIndex,
|
|
542
905
|
].filter((index) => index >= 0);
|
|
543
|
-
return mergePersistedKeysIntoVirtualRange(
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
906
|
+
return mergePersistedKeysIntoVirtualRange(
|
|
907
|
+
baseRange,
|
|
908
|
+
persistedIndexes,
|
|
909
|
+
dynamicItems.length,
|
|
910
|
+
virtualizer,
|
|
911
|
+
80,
|
|
912
|
+
{
|
|
913
|
+
forceIncludeIndexes,
|
|
914
|
+
forceIncludeMaxSpan: 320,
|
|
915
|
+
},
|
|
916
|
+
);
|
|
547
917
|
});
|
|
548
918
|
const visibleItems = createMemo(() => {
|
|
549
919
|
const range = virtualRange();
|
|
550
|
-
|
|
551
|
-
|
|
920
|
+
const items = stateProps.items ?? [];
|
|
921
|
+
if (!range) return items;
|
|
922
|
+
return items.slice(range.start, range.end);
|
|
552
923
|
});
|
|
553
924
|
createEffect(() => {
|
|
554
925
|
if (!hasDraggableDnd()) return;
|
|
@@ -562,22 +933,27 @@ export function Menu<T>(props: MenuProps<T>): JSX.Element {
|
|
|
562
933
|
const hooks = stateProps.dragAndDropHooks;
|
|
563
934
|
const activeDropState = dropState();
|
|
564
935
|
if (!hooks?.useDroppableCollection || !activeDropState) return undefined;
|
|
565
|
-
const resolveDirection = ():
|
|
936
|
+
const resolveDirection = (): "ltr" | "rtl" => {
|
|
566
937
|
const menuEl = menuRef();
|
|
567
|
-
if (
|
|
938
|
+
if (
|
|
939
|
+
menuEl &&
|
|
940
|
+
typeof window !== "undefined" &&
|
|
941
|
+
typeof window.getComputedStyle === "function"
|
|
942
|
+
) {
|
|
568
943
|
const dir = window.getComputedStyle(menuEl).direction;
|
|
569
|
-
if (dir ===
|
|
944
|
+
if (dir === "rtl") return "rtl";
|
|
570
945
|
}
|
|
571
|
-
return typeof document !==
|
|
946
|
+
return typeof document !== "undefined" && document.dir === "rtl" ? "rtl" : "ltr";
|
|
572
947
|
};
|
|
573
|
-
const dropTargetDelegate =
|
|
574
|
-
??
|
|
575
|
-
??
|
|
948
|
+
const dropTargetDelegate =
|
|
949
|
+
hooks.dropTargetDelegate ??
|
|
950
|
+
parentCollectionRenderer?.dropTargetDelegate ??
|
|
951
|
+
(hooks.ListDropTargetDelegate
|
|
576
952
|
? new hooks.ListDropTargetDelegate(
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
953
|
+
() => state.collection(),
|
|
954
|
+
() => menuRef(),
|
|
955
|
+
{ layout: "stack", orientation: "vertical", direction: resolveDirection() },
|
|
956
|
+
)
|
|
581
957
|
: undefined);
|
|
582
958
|
if (!dropTargetDelegate) return undefined;
|
|
583
959
|
return hooks.useDroppableCollection(
|
|
@@ -593,28 +969,30 @@ export function Menu<T>(props: MenuProps<T>): JSX.Element {
|
|
|
593
969
|
},
|
|
594
970
|
},
|
|
595
971
|
activeDropState,
|
|
596
|
-
() => menuRef()
|
|
972
|
+
() => menuRef(),
|
|
597
973
|
);
|
|
598
974
|
});
|
|
599
975
|
const isRootDropTarget = createMemo(() => {
|
|
600
|
-
return Boolean(dropState()?.target?.type ===
|
|
976
|
+
return Boolean(dropState()?.target?.type === "root");
|
|
601
977
|
});
|
|
602
|
-
const dndRenderDropIndicator = createMemo(() =>
|
|
603
|
-
|
|
978
|
+
const dndRenderDropIndicator = createMemo(() =>
|
|
979
|
+
useRenderDropIndicator(stateProps.dragAndDropHooks, dropState()),
|
|
980
|
+
);
|
|
981
|
+
const dndDropIndicator = (index: number, position: "before" | "after" | "on") => {
|
|
604
982
|
const target = getDropTargetByIndex(index, position);
|
|
605
|
-
if (!target || target.type !==
|
|
983
|
+
if (!target || target.type !== "item") return undefined;
|
|
606
984
|
return dndRenderDropIndicator()?.(target);
|
|
607
985
|
};
|
|
608
986
|
const sectionedRenderEntries = createMemo(() => {
|
|
609
987
|
let globalIndex = 0;
|
|
610
|
-
return stateProps.items.map((entry) => {
|
|
988
|
+
return (stateProps.items ?? []).map((entry) => {
|
|
611
989
|
if (isCollectionSection(entry)) {
|
|
612
990
|
const sectionItems = entry.items.map((item) => ({
|
|
613
991
|
item,
|
|
614
992
|
index: globalIndex++,
|
|
615
993
|
}));
|
|
616
994
|
return {
|
|
617
|
-
type:
|
|
995
|
+
type: "section" as const,
|
|
618
996
|
section: entry,
|
|
619
997
|
items: sectionItems,
|
|
620
998
|
};
|
|
@@ -624,120 +1002,196 @@ export function Menu<T>(props: MenuProps<T>): JSX.Element {
|
|
|
624
1002
|
index: globalIndex++,
|
|
625
1003
|
};
|
|
626
1004
|
return {
|
|
627
|
-
type:
|
|
1005
|
+
type: "item" as const,
|
|
628
1006
|
item: indexedItem,
|
|
629
1007
|
};
|
|
630
1008
|
});
|
|
631
1009
|
});
|
|
1010
|
+
const renderDynamicItem = (item: T) =>
|
|
1011
|
+
typeof local.children === "function" ? local.children(item) : undefined;
|
|
1012
|
+
const resolveStaticChild = (child: unknown): JSX.Element | undefined => {
|
|
1013
|
+
return typeof child === "function"
|
|
1014
|
+
? (child as () => JSX.Element | undefined)()
|
|
1015
|
+
: (child as JSX.Element | undefined);
|
|
1016
|
+
};
|
|
1017
|
+
const renderStaticChildren = () => {
|
|
1018
|
+
const staticChildren = (local.staticChildren?.() ?? local.children) as unknown;
|
|
1019
|
+
if (Array.isArray(staticChildren)) {
|
|
1020
|
+
return staticChildren.map(resolveStaticChild);
|
|
1021
|
+
}
|
|
1022
|
+
return resolveStaticChild(staticChildren);
|
|
1023
|
+
};
|
|
632
1024
|
const collectionRenderer = createMemo<CollectionRendererContextValue<unknown>>(() => ({
|
|
633
1025
|
...parentCollectionRenderer,
|
|
634
|
-
renderItem: (item) =>
|
|
1026
|
+
renderItem: (item) => renderDynamicItem(item as T),
|
|
635
1027
|
renderDropIndicator: (index, position) =>
|
|
636
|
-
dndDropIndicator(index, position) ??
|
|
1028
|
+
dndDropIndicator(index, position) ??
|
|
1029
|
+
parentCollectionRenderer?.renderDropIndicator?.(index, position),
|
|
637
1030
|
}));
|
|
1031
|
+
const menuListChildren = () => (
|
|
1032
|
+
<SharedElementTransition>
|
|
1033
|
+
{state.collection().size === 0 && !usesStaticChildren() && local.renderEmptyState ? (
|
|
1034
|
+
<li role="presentation" data-empty-state>
|
|
1035
|
+
<div role="menuitem" style={{ display: "contents" }}>
|
|
1036
|
+
{local.renderEmptyState()}
|
|
1037
|
+
</div>
|
|
1038
|
+
</li>
|
|
1039
|
+
) : usesStaticChildren() ? (
|
|
1040
|
+
renderStaticChildren()
|
|
1041
|
+
) : hasSections() ? (
|
|
1042
|
+
<For each={sectionedRenderEntries()}>
|
|
1043
|
+
{(entry) =>
|
|
1044
|
+
entry.type === "section" ? (
|
|
1045
|
+
<li role="presentation" data-section-wrapper>
|
|
1046
|
+
<Section class="solidaria-Menu-section">
|
|
1047
|
+
{entry.section.title != null && (
|
|
1048
|
+
<Header class="solidaria-Menu-sectionHeader">{entry.section.title}</Header>
|
|
1049
|
+
)}
|
|
1050
|
+
<Group class="solidaria-Menu-sectionGroup">
|
|
1051
|
+
<ul role="group" aria-label={entry.section["aria-label"]}>
|
|
1052
|
+
<For each={entry.items}>
|
|
1053
|
+
{(indexedItem) => (
|
|
1054
|
+
<>
|
|
1055
|
+
{collectionRenderer().renderDropIndicator?.(
|
|
1056
|
+
indexedItem.index,
|
|
1057
|
+
"before",
|
|
1058
|
+
)}
|
|
1059
|
+
{collectionRenderer().renderDropIndicator?.(indexedItem.index, "on")}
|
|
1060
|
+
{renderDynamicItem(indexedItem.item)}
|
|
1061
|
+
{collectionRenderer().renderDropIndicator?.(indexedItem.index, "after")}
|
|
1062
|
+
</>
|
|
1063
|
+
)}
|
|
1064
|
+
</For>
|
|
1065
|
+
</ul>
|
|
1066
|
+
</Group>
|
|
1067
|
+
</Section>
|
|
1068
|
+
</li>
|
|
1069
|
+
) : (
|
|
1070
|
+
<>
|
|
1071
|
+
{collectionRenderer().renderDropIndicator?.(entry.item.index, "before")}
|
|
1072
|
+
{collectionRenderer().renderDropIndicator?.(entry.item.index, "on")}
|
|
1073
|
+
{renderDynamicItem(entry.item.item)}
|
|
1074
|
+
{collectionRenderer().renderDropIndicator?.(entry.item.index, "after")}
|
|
1075
|
+
</>
|
|
1076
|
+
)
|
|
1077
|
+
}
|
|
1078
|
+
</For>
|
|
1079
|
+
) : (
|
|
1080
|
+
<>
|
|
1081
|
+
{virtualRange()?.offsetTop ? (
|
|
1082
|
+
<li
|
|
1083
|
+
role="presentation"
|
|
1084
|
+
aria-hidden="true"
|
|
1085
|
+
style={{ height: `${virtualRange()!.offsetTop}px` }}
|
|
1086
|
+
data-virtualizer-spacer="top"
|
|
1087
|
+
/>
|
|
1088
|
+
) : null}
|
|
1089
|
+
<For each={visibleItems()}>
|
|
1090
|
+
{(item, index) => {
|
|
1091
|
+
const itemIndex = () => (virtualRange()?.start ?? 0) + index();
|
|
1092
|
+
const beforeIndicator = () =>
|
|
1093
|
+
collectionRenderer().renderDropIndicator?.(itemIndex(), "before");
|
|
1094
|
+
const onIndicator = () =>
|
|
1095
|
+
collectionRenderer().renderDropIndicator?.(itemIndex(), "on");
|
|
1096
|
+
const afterIndicator = () =>
|
|
1097
|
+
collectionRenderer().renderDropIndicator?.(itemIndex(), "after");
|
|
1098
|
+
return (
|
|
1099
|
+
<>
|
|
1100
|
+
{beforeIndicator()}
|
|
1101
|
+
{onIndicator()}
|
|
1102
|
+
{renderDynamicItem(item as T)}
|
|
1103
|
+
{afterIndicator()}
|
|
1104
|
+
</>
|
|
1105
|
+
);
|
|
1106
|
+
}}
|
|
1107
|
+
</For>
|
|
1108
|
+
{virtualRange()?.offsetBottom ? (
|
|
1109
|
+
<li
|
|
1110
|
+
role="presentation"
|
|
1111
|
+
aria-hidden="true"
|
|
1112
|
+
style={{ height: `${virtualRange()!.offsetBottom}px` }}
|
|
1113
|
+
data-virtualizer-spacer="bottom"
|
|
1114
|
+
/>
|
|
1115
|
+
) : null}
|
|
1116
|
+
</>
|
|
1117
|
+
)}
|
|
1118
|
+
</SharedElementTransition>
|
|
1119
|
+
);
|
|
1120
|
+
const menuListProps = () =>
|
|
1121
|
+
({
|
|
1122
|
+
ref: setResolvedMenuRef,
|
|
1123
|
+
...mergeProps(
|
|
1124
|
+
domProps(),
|
|
1125
|
+
cleanMenuProps(),
|
|
1126
|
+
cleanTriggerMenuProps(),
|
|
1127
|
+
cleanFocusProps(),
|
|
1128
|
+
(droppableCollection()?.collectionProps as Record<string, unknown> | undefined) ?? {},
|
|
1129
|
+
),
|
|
1130
|
+
class: renderProps.class(),
|
|
1131
|
+
style: renderProps.style(),
|
|
1132
|
+
slot: local.slot,
|
|
1133
|
+
"data-focused": state.isFocused() || undefined,
|
|
1134
|
+
"data-disabled": resolveDisabled() || undefined,
|
|
1135
|
+
"data-empty": state.collection().size === 0 || undefined,
|
|
1136
|
+
"data-drop-target": isRootDropTarget() || undefined,
|
|
1137
|
+
children: menuListChildren(),
|
|
1138
|
+
}) as JSX.HTMLAttributes<HTMLUListElement>;
|
|
638
1139
|
|
|
639
1140
|
// Only use FocusScope when inside a MenuTrigger (for popover behavior)
|
|
640
1141
|
// Standalone menus don't need focus restoration
|
|
641
1142
|
const menuContent = () => (
|
|
642
1143
|
<MenuContext.Provider
|
|
643
|
-
value={
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
1144
|
+
value={
|
|
1145
|
+
{
|
|
1146
|
+
state,
|
|
1147
|
+
isDisabled: resolveDisabled,
|
|
1148
|
+
dragAndDropHooks: stateProps.dragAndDropHooks,
|
|
1149
|
+
dragState: dragState(),
|
|
1150
|
+
dropState: dropState(),
|
|
1151
|
+
} as MenuContextValue<unknown>
|
|
1152
|
+
}
|
|
650
1153
|
>
|
|
651
1154
|
<MenuStateContext.Provider value={state}>
|
|
652
|
-
<
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
</>
|
|
693
|
-
)}
|
|
694
|
-
</For>
|
|
695
|
-
</ul>
|
|
696
|
-
</Group>
|
|
697
|
-
</Section>
|
|
698
|
-
</li>
|
|
699
|
-
)
|
|
700
|
-
: (
|
|
701
|
-
<>
|
|
702
|
-
{collectionRenderer().renderDropIndicator?.(entry.item.index, 'before')}
|
|
703
|
-
{collectionRenderer().renderDropIndicator?.(entry.item.index, 'on')}
|
|
704
|
-
{props.children?.(entry.item.item)}
|
|
705
|
-
{collectionRenderer().renderDropIndicator?.(entry.item.index, 'after')}
|
|
706
|
-
</>
|
|
707
|
-
)
|
|
708
|
-
}
|
|
709
|
-
</For>
|
|
710
|
-
)
|
|
711
|
-
: (
|
|
712
|
-
<>
|
|
713
|
-
{virtualRange()?.offsetTop
|
|
714
|
-
? <li role="presentation" aria-hidden="true" style={{ height: `${virtualRange()!.offsetTop}px` }} data-virtualizer-spacer="top" />
|
|
715
|
-
: null}
|
|
716
|
-
<For each={visibleItems()}>
|
|
717
|
-
{(item, index) => {
|
|
718
|
-
const itemIndex = () => (virtualRange()?.start ?? 0) + index();
|
|
719
|
-
const beforeIndicator = () => collectionRenderer().renderDropIndicator?.(itemIndex(), 'before');
|
|
720
|
-
const onIndicator = () => collectionRenderer().renderDropIndicator?.(itemIndex(), 'on');
|
|
721
|
-
const afterIndicator = () => collectionRenderer().renderDropIndicator?.(itemIndex(), 'after');
|
|
722
|
-
return (
|
|
723
|
-
<>
|
|
724
|
-
{beforeIndicator()}
|
|
725
|
-
{onIndicator()}
|
|
726
|
-
{props.children?.(item as T)}
|
|
727
|
-
{afterIndicator()}
|
|
728
|
-
</>
|
|
729
|
-
);
|
|
730
|
-
}}
|
|
731
|
-
</For>
|
|
732
|
-
{virtualRange()?.offsetBottom
|
|
733
|
-
? <li role="presentation" aria-hidden="true" style={{ height: `${virtualRange()!.offsetBottom}px` }} data-virtualizer-spacer="bottom" />
|
|
734
|
-
: null}
|
|
735
|
-
</>
|
|
736
|
-
)}
|
|
737
|
-
</SharedElementTransition>
|
|
738
|
-
</ul>
|
|
739
|
-
</>
|
|
740
|
-
</CollectionRendererContext.Provider>
|
|
1155
|
+
<MenuSectionSelectionRegistryContext.Provider value={sectionSelectionRegistry}>
|
|
1156
|
+
<StaticMenuCollectionContext.Provider
|
|
1157
|
+
value={usesStaticChildren() ? staticCollectionContext : null}
|
|
1158
|
+
>
|
|
1159
|
+
<MenuItemContext.Provider value={{ closeOnSelect: local.shouldCloseOnSelect }}>
|
|
1160
|
+
<CollectionRendererContext.Provider value={collectionRenderer()}>
|
|
1161
|
+
<>
|
|
1162
|
+
<Show when={ariaProps.label}>
|
|
1163
|
+
<span {...cleanLabelProps()}>{ariaProps.label as JSX.Element}</span>
|
|
1164
|
+
</Show>
|
|
1165
|
+
{local.render ? (
|
|
1166
|
+
local.render(menuListProps(), renderValues())
|
|
1167
|
+
) : (
|
|
1168
|
+
<ul
|
|
1169
|
+
ref={setResolvedMenuRef}
|
|
1170
|
+
{...mergeProps(
|
|
1171
|
+
domProps(),
|
|
1172
|
+
cleanMenuProps(),
|
|
1173
|
+
cleanTriggerMenuProps(),
|
|
1174
|
+
cleanFocusProps(),
|
|
1175
|
+
(droppableCollection()?.collectionProps as
|
|
1176
|
+
| Record<string, unknown>
|
|
1177
|
+
| undefined) ?? {},
|
|
1178
|
+
)}
|
|
1179
|
+
class={renderProps.class()}
|
|
1180
|
+
style={renderProps.style()}
|
|
1181
|
+
slot={local.slot}
|
|
1182
|
+
data-focused={state.isFocused() || undefined}
|
|
1183
|
+
data-disabled={resolveDisabled() || undefined}
|
|
1184
|
+
data-empty={state.collection().size === 0 || undefined}
|
|
1185
|
+
data-drop-target={isRootDropTarget() || undefined}
|
|
1186
|
+
>
|
|
1187
|
+
{menuListChildren()}
|
|
1188
|
+
</ul>
|
|
1189
|
+
)}
|
|
1190
|
+
</>
|
|
1191
|
+
</CollectionRendererContext.Provider>
|
|
1192
|
+
</MenuItemContext.Provider>
|
|
1193
|
+
</StaticMenuCollectionContext.Provider>
|
|
1194
|
+
</MenuSectionSelectionRegistryContext.Provider>
|
|
741
1195
|
</MenuStateContext.Provider>
|
|
742
1196
|
</MenuContext.Provider>
|
|
743
1197
|
);
|
|
@@ -758,81 +1212,198 @@ export function Menu<T>(props: MenuProps<T>): JSX.Element {
|
|
|
758
1212
|
*/
|
|
759
1213
|
export function MenuItem<T>(props: MenuItemProps<T>): JSX.Element {
|
|
760
1214
|
const [local, ariaProps] = splitProps(props, [
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
1215
|
+
"class",
|
|
1216
|
+
"style",
|
|
1217
|
+
"render",
|
|
1218
|
+
"slot",
|
|
1219
|
+
"id",
|
|
1220
|
+
"item",
|
|
1221
|
+
"textValue",
|
|
1222
|
+
"onAction",
|
|
1223
|
+
"href",
|
|
1224
|
+
"target",
|
|
1225
|
+
"rel",
|
|
1226
|
+
"download",
|
|
1227
|
+
"onHoverStart",
|
|
1228
|
+
"onHoverEnd",
|
|
1229
|
+
"onHoverChange",
|
|
1230
|
+
"ref",
|
|
768
1231
|
]);
|
|
769
1232
|
|
|
770
|
-
// Get state from context
|
|
771
1233
|
const context = useContext(MenuStateContext);
|
|
772
1234
|
if (!context) {
|
|
773
|
-
throw new Error(
|
|
1235
|
+
throw new Error("MenuItem must be used within a Menu");
|
|
774
1236
|
}
|
|
775
1237
|
const state = context as MenuState<T>;
|
|
776
1238
|
const menuContext = useContext(MenuContext) as MenuContextValue<T> | null;
|
|
1239
|
+
const itemContext = useContext(MenuItemContext);
|
|
1240
|
+
const staticCollection = useContext(StaticMenuCollectionContext);
|
|
1241
|
+
const sectionSelection = useContext(MenuSectionSelectionContext);
|
|
1242
|
+
const sectionSelectionRegistry = useContext(MenuSectionSelectionRegistryContext);
|
|
777
1243
|
const [ref, setRef] = createSignal<HTMLLIElement | null>(null);
|
|
1244
|
+
const contextProps = () => itemContext?.props?.() ?? {};
|
|
1245
|
+
const combinedOnAction = () => {
|
|
1246
|
+
local.onAction?.();
|
|
1247
|
+
itemContext?.onAction?.();
|
|
1248
|
+
};
|
|
1249
|
+
const activeSectionSelection = () =>
|
|
1250
|
+
sectionSelection && sectionSelection.selectionMode() !== "none" ? sectionSelection : null;
|
|
1251
|
+
let registeredStaticKey: Key | null = null;
|
|
1252
|
+
let registeredSectionSelectionKey: Key | null = null;
|
|
1253
|
+
let registeredSectionSelection: MenuSectionSelectionContextValue | null = null;
|
|
1254
|
+
|
|
1255
|
+
const unregisterSectionSelection = () => {
|
|
1256
|
+
if (registeredSectionSelectionKey != null && registeredSectionSelection) {
|
|
1257
|
+
sectionSelectionRegistry?.unregisterItem(
|
|
1258
|
+
registeredSectionSelectionKey,
|
|
1259
|
+
registeredSectionSelection,
|
|
1260
|
+
);
|
|
1261
|
+
registeredSectionSelectionKey = null;
|
|
1262
|
+
registeredSectionSelection = null;
|
|
1263
|
+
}
|
|
1264
|
+
};
|
|
1265
|
+
|
|
1266
|
+
createEffect(() => {
|
|
1267
|
+
if (!staticCollection) return;
|
|
1268
|
+
|
|
1269
|
+
if (registeredStaticKey != null && registeredStaticKey !== local.id) {
|
|
1270
|
+
staticCollection.unregisterItem(registeredStaticKey);
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
registeredStaticKey = local.id;
|
|
1274
|
+
staticCollection.registerItem({
|
|
1275
|
+
id: local.id,
|
|
1276
|
+
textValue: local.textValue ?? ariaProps["aria-label"],
|
|
1277
|
+
isDisabled:
|
|
1278
|
+
resolveBoolean(ariaProps.isDisabled) || (sectionSelection?.isDisabled(local.id) ?? false),
|
|
1279
|
+
});
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
onCleanup(() => {
|
|
1283
|
+
if (registeredStaticKey != null) {
|
|
1284
|
+
staticCollection?.unregisterItem(registeredStaticKey);
|
|
1285
|
+
}
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
createEffect(() => {
|
|
1289
|
+
const selection = activeSectionSelection();
|
|
1290
|
+
if (!sectionSelectionRegistry || !selection) {
|
|
1291
|
+
unregisterSectionSelection();
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
if (registeredSectionSelectionKey === local.id && registeredSectionSelection === selection) {
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
unregisterSectionSelection();
|
|
1300
|
+
registeredSectionSelectionKey = local.id;
|
|
1301
|
+
registeredSectionSelection = selection;
|
|
1302
|
+
sectionSelectionRegistry.registerItem(local.id, selection);
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
onCleanup(unregisterSectionSelection);
|
|
778
1306
|
|
|
779
|
-
// Create menu item aria props
|
|
780
1307
|
const itemAria = createMenuItem<T>(
|
|
781
1308
|
{
|
|
782
1309
|
key: local.id,
|
|
783
1310
|
get isDisabled() {
|
|
784
|
-
return Boolean(
|
|
1311
|
+
return Boolean(
|
|
1312
|
+
ariaProps.isDisabled ||
|
|
1313
|
+
sectionSelection?.isDisabled(local.id) ||
|
|
1314
|
+
menuContext?.isDisabled(),
|
|
1315
|
+
);
|
|
785
1316
|
},
|
|
786
|
-
get
|
|
787
|
-
return ariaProps[
|
|
1317
|
+
get "aria-label"() {
|
|
1318
|
+
return ariaProps["aria-label"] ?? local.textValue;
|
|
788
1319
|
},
|
|
789
1320
|
get onAction() {
|
|
790
|
-
return
|
|
1321
|
+
return combinedOnAction;
|
|
1322
|
+
},
|
|
1323
|
+
get closeOnSelect() {
|
|
1324
|
+
return (
|
|
1325
|
+
ariaProps.closeOnSelect ??
|
|
1326
|
+
sectionSelection?.shouldCloseOnSelect() ??
|
|
1327
|
+
itemContext?.closeOnSelect
|
|
1328
|
+
);
|
|
1329
|
+
},
|
|
1330
|
+
get href() {
|
|
1331
|
+
return local.href;
|
|
1332
|
+
},
|
|
1333
|
+
get target() {
|
|
1334
|
+
return local.target;
|
|
1335
|
+
},
|
|
1336
|
+
get rel() {
|
|
1337
|
+
return local.rel;
|
|
1338
|
+
},
|
|
1339
|
+
get download() {
|
|
1340
|
+
return local.download;
|
|
791
1341
|
},
|
|
792
1342
|
},
|
|
793
|
-
state
|
|
1343
|
+
state,
|
|
794
1344
|
);
|
|
795
1345
|
|
|
796
|
-
// Create hover
|
|
797
1346
|
const { isHovered, hoverProps } = createHover({
|
|
798
1347
|
get isDisabled() {
|
|
799
1348
|
return itemAria.isDisabled();
|
|
800
1349
|
},
|
|
1350
|
+
onHoverStart: local.onHoverStart,
|
|
1351
|
+
onHoverEnd: local.onHoverEnd,
|
|
1352
|
+
onHoverChange: local.onHoverChange,
|
|
801
1353
|
});
|
|
802
1354
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
1355
|
+
const renderValues = createMemo<MenuItemRenderProps>(() => {
|
|
1356
|
+
const selection = activeSectionSelection();
|
|
1357
|
+
return {
|
|
1358
|
+
isSelected: selection?.isSelected(local.id) ?? itemAria.isSelected(),
|
|
1359
|
+
selectionMode: selection?.selectionMode() ?? itemAria.selectionMode(),
|
|
1360
|
+
isFocused: itemAria.isFocused(),
|
|
1361
|
+
isFocusVisible: itemAria.isFocusVisible(),
|
|
1362
|
+
isPressed: itemAria.isPressed(),
|
|
1363
|
+
isHovered: isHovered(),
|
|
1364
|
+
isDisabled: itemAria.isDisabled(),
|
|
1365
|
+
hasSubmenu: Boolean(contextProps()["aria-haspopup"]),
|
|
1366
|
+
isOpen: contextProps()["aria-expanded"] === true,
|
|
1367
|
+
};
|
|
1368
|
+
});
|
|
812
1369
|
|
|
813
|
-
// Resolve render props
|
|
814
1370
|
const renderProps = useRenderProps(
|
|
815
1371
|
{
|
|
816
1372
|
children: props.children,
|
|
817
1373
|
class: local.class,
|
|
818
1374
|
style: local.style,
|
|
819
|
-
defaultClassName:
|
|
1375
|
+
defaultClassName: "solidaria-Menu-item",
|
|
820
1376
|
},
|
|
821
|
-
renderValues
|
|
1377
|
+
renderValues,
|
|
822
1378
|
);
|
|
823
1379
|
const hasPrimitiveLabel = () => {
|
|
824
|
-
return typeof props.children ===
|
|
1380
|
+
return typeof props.children === "string" || typeof props.children === "number";
|
|
825
1381
|
};
|
|
826
1382
|
|
|
827
|
-
// Remove ref from spread props
|
|
828
1383
|
const cleanItemProps = () => {
|
|
829
1384
|
const {
|
|
830
1385
|
ref: _ref1,
|
|
831
|
-
|
|
1386
|
+
"aria-describedby": _ariaDescribedby,
|
|
832
1387
|
...rest
|
|
833
1388
|
} = itemAria.menuItemProps as Record<string, unknown>;
|
|
834
|
-
if (!hasPrimitiveLabel() && rest[
|
|
835
|
-
delete rest[
|
|
1389
|
+
if (!hasPrimitiveLabel() && rest["aria-label"] == null) {
|
|
1390
|
+
delete rest["aria-labelledby"];
|
|
1391
|
+
}
|
|
1392
|
+
const selection = activeSectionSelection();
|
|
1393
|
+
const selectionMode = selection?.selectionMode();
|
|
1394
|
+
if (selectionMode) {
|
|
1395
|
+
rest.role =
|
|
1396
|
+
selectionMode === "single"
|
|
1397
|
+
? "menuitemradio"
|
|
1398
|
+
: selectionMode === "multiple"
|
|
1399
|
+
? "menuitemcheckbox"
|
|
1400
|
+
: "menuitem";
|
|
1401
|
+
if (selectionMode !== "none") {
|
|
1402
|
+
rest["aria-checked"] = selection?.isSelected(local.id) ?? false;
|
|
1403
|
+
} else {
|
|
1404
|
+
delete rest["aria-checked"];
|
|
1405
|
+
}
|
|
1406
|
+
rest["data-selected"] = selection?.isSelected(local.id) || undefined;
|
|
836
1407
|
}
|
|
837
1408
|
return rest;
|
|
838
1409
|
};
|
|
@@ -840,49 +1411,169 @@ export function MenuItem<T>(props: MenuItemProps<T>): JSX.Element {
|
|
|
840
1411
|
const { ref: _ref2, ...rest } = hoverProps as Record<string, unknown>;
|
|
841
1412
|
return rest;
|
|
842
1413
|
};
|
|
1414
|
+
const domProps = createMemo(() =>
|
|
1415
|
+
filterDOMProps(ariaProps as Record<string, unknown>, { global: true }),
|
|
1416
|
+
);
|
|
843
1417
|
const draggableItem = createMemo(() => {
|
|
844
|
-
if (!menuContext?.dragAndDropHooks?.useDraggableItem || !menuContext.dragState)
|
|
1418
|
+
if (!menuContext?.dragAndDropHooks?.useDraggableItem || !menuContext.dragState)
|
|
1419
|
+
return undefined;
|
|
845
1420
|
return menuContext.dragAndDropHooks.useDraggableItem(
|
|
846
1421
|
{
|
|
847
1422
|
key: local.id as string | number,
|
|
848
1423
|
},
|
|
849
|
-
menuContext.dragState as Parameters<NonNullable<DragAndDropHooks<T>[
|
|
1424
|
+
menuContext.dragState as Parameters<NonNullable<DragAndDropHooks<T>["useDraggableItem"]>>[1],
|
|
850
1425
|
);
|
|
851
1426
|
});
|
|
852
1427
|
const droppableItem = createMemo(() => {
|
|
853
|
-
if (!menuContext?.dragAndDropHooks?.useDroppableItem || !menuContext.dropState)
|
|
1428
|
+
if (!menuContext?.dragAndDropHooks?.useDroppableItem || !menuContext.dropState)
|
|
1429
|
+
return undefined;
|
|
854
1430
|
return menuContext.dragAndDropHooks.useDroppableItem(
|
|
855
1431
|
{
|
|
856
1432
|
key: local.id as string | number,
|
|
857
1433
|
},
|
|
858
|
-
menuContext.dropState as Parameters<NonNullable<DragAndDropHooks<T>[
|
|
859
|
-
() => ref()
|
|
1434
|
+
menuContext.dropState as Parameters<NonNullable<DragAndDropHooks<T>["useDroppableItem"]>>[1],
|
|
1435
|
+
() => ref(),
|
|
860
1436
|
);
|
|
861
1437
|
});
|
|
862
1438
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1439
|
+
const isLink = () => !!local.href;
|
|
1440
|
+
|
|
1441
|
+
const cleanItemPropsForLink = () => {
|
|
1442
|
+
const all = cleanItemProps();
|
|
1443
|
+
const { href: _href, target: _target, rel: _rel, download: _download, ...rest } = all;
|
|
1444
|
+
return rest;
|
|
1445
|
+
};
|
|
1446
|
+
|
|
1447
|
+
const linkDomProps = () => {
|
|
1448
|
+
const all = cleanItemProps();
|
|
1449
|
+
const result: Record<string, unknown> = {};
|
|
1450
|
+
if (all.href !== undefined) result.href = all.href;
|
|
1451
|
+
if (all.target !== undefined) result.target = all.target;
|
|
1452
|
+
if (all.rel !== undefined) result.rel = all.rel;
|
|
1453
|
+
if (all.download !== undefined) result.download = all.download;
|
|
1454
|
+
return result;
|
|
1455
|
+
};
|
|
1456
|
+
|
|
1457
|
+
const dataAttrs = () => {
|
|
1458
|
+
const selection = activeSectionSelection();
|
|
1459
|
+
return {
|
|
1460
|
+
"data-focused": itemAria.isFocused() || undefined,
|
|
1461
|
+
"data-focus-visible": itemAria.isFocusVisible() || undefined,
|
|
1462
|
+
"data-pressed": itemAria.isPressed() || undefined,
|
|
1463
|
+
"data-hovered": isHovered() || undefined,
|
|
1464
|
+
"data-disabled": itemAria.isDisabled() || undefined,
|
|
1465
|
+
"data-selected": (selection?.isSelected(local.id) ?? itemAria.isSelected()) || undefined,
|
|
1466
|
+
"data-has-submenu": Boolean(contextProps()["aria-haspopup"]) || undefined,
|
|
1467
|
+
"data-open": contextProps()["aria-expanded"] === true || undefined,
|
|
1468
|
+
"data-dragging": draggableItem()?.isDragging || undefined,
|
|
1469
|
+
"data-drop-target": droppableItem()?.isDropTarget || undefined,
|
|
1470
|
+
};
|
|
1471
|
+
};
|
|
1472
|
+
|
|
1473
|
+
const childContent = () =>
|
|
1474
|
+
hasPrimitiveLabel() ? (
|
|
1475
|
+
<span {...itemAria.labelProps}>{renderProps.renderChildren()}</span>
|
|
1476
|
+
) : (
|
|
1477
|
+
renderProps.renderChildren()
|
|
1478
|
+
);
|
|
1479
|
+
const setResolvedItemRef = (el: HTMLLIElement | null) => {
|
|
1480
|
+
setRef(el);
|
|
1481
|
+
itemContext?.setItemRef?.(el);
|
|
1482
|
+
if (el) assignRef(local.ref, el);
|
|
1483
|
+
};
|
|
1484
|
+
const menuItemProps = () =>
|
|
1485
|
+
({
|
|
1486
|
+
ref: setResolvedItemRef,
|
|
1487
|
+
...mergeProps(
|
|
867
1488
|
cleanItemProps(),
|
|
1489
|
+
contextProps() as Record<string, unknown>,
|
|
1490
|
+
domProps(),
|
|
868
1491
|
cleanHoverProps(),
|
|
869
1492
|
(draggableItem()?.dragProps as Record<string, unknown> | undefined) ?? {},
|
|
870
|
-
(droppableItem()?.dropProps as Record<string, unknown> | undefined) ?? {}
|
|
871
|
-
)
|
|
872
|
-
class
|
|
873
|
-
style
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
1493
|
+
(droppableItem()?.dropProps as Record<string, unknown> | undefined) ?? {},
|
|
1494
|
+
),
|
|
1495
|
+
class: renderProps.class(),
|
|
1496
|
+
style: renderProps.style(),
|
|
1497
|
+
...dataAttrs(),
|
|
1498
|
+
children: childContent(),
|
|
1499
|
+
}) as JSX.HTMLAttributes<HTMLLIElement>;
|
|
1500
|
+
const linkMenuItemProps = () =>
|
|
1501
|
+
({
|
|
1502
|
+
...mergeProps(
|
|
1503
|
+
cleanItemPropsForLink(),
|
|
1504
|
+
contextProps() as Record<string, unknown>,
|
|
1505
|
+
domProps(),
|
|
1506
|
+
cleanHoverProps(),
|
|
1507
|
+
linkDomProps(),
|
|
1508
|
+
(draggableItem()?.dragProps as Record<string, unknown> | undefined) ?? {},
|
|
1509
|
+
(droppableItem()?.dropProps as Record<string, unknown> | undefined) ?? {},
|
|
1510
|
+
),
|
|
1511
|
+
class: renderProps.class(),
|
|
1512
|
+
style: renderProps.style(),
|
|
1513
|
+
...dataAttrs(),
|
|
1514
|
+
children: childContent(),
|
|
1515
|
+
}) as JSX.HTMLAttributes<HTMLLIElement>;
|
|
1516
|
+
|
|
1517
|
+
if (local.render && !isLink()) {
|
|
1518
|
+
return local.render(menuItemProps(), renderValues());
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
return (
|
|
1522
|
+
<Show
|
|
1523
|
+
when={isLink()}
|
|
1524
|
+
fallback={
|
|
1525
|
+
<li
|
|
1526
|
+
ref={(el) => {
|
|
1527
|
+
setRef(el);
|
|
1528
|
+
itemContext?.setItemRef?.(el);
|
|
1529
|
+
assignRef(local.ref, el);
|
|
1530
|
+
}}
|
|
1531
|
+
{...mergeProps(
|
|
1532
|
+
cleanItemProps(),
|
|
1533
|
+
contextProps() as Record<string, unknown>,
|
|
1534
|
+
domProps(),
|
|
1535
|
+
cleanHoverProps(),
|
|
1536
|
+
(draggableItem()?.dragProps as Record<string, unknown> | undefined) ?? {},
|
|
1537
|
+
(droppableItem()?.dropProps as Record<string, unknown> | undefined) ?? {},
|
|
1538
|
+
)}
|
|
1539
|
+
class={renderProps.class()}
|
|
1540
|
+
style={renderProps.style()}
|
|
1541
|
+
{...dataAttrs()}
|
|
1542
|
+
>
|
|
1543
|
+
{childContent()}
|
|
1544
|
+
</li>
|
|
1545
|
+
}
|
|
881
1546
|
>
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1547
|
+
<li
|
|
1548
|
+
ref={(el) => {
|
|
1549
|
+
setRef(el);
|
|
1550
|
+
itemContext?.setItemRef?.(el);
|
|
1551
|
+
assignRef(local.ref, el);
|
|
1552
|
+
}}
|
|
1553
|
+
role="presentation"
|
|
1554
|
+
>
|
|
1555
|
+
{local.render ? (
|
|
1556
|
+
local.render(linkMenuItemProps(), renderValues())
|
|
1557
|
+
) : (
|
|
1558
|
+
<a
|
|
1559
|
+
{...mergeProps(
|
|
1560
|
+
cleanItemPropsForLink(),
|
|
1561
|
+
contextProps() as Record<string, unknown>,
|
|
1562
|
+
domProps(),
|
|
1563
|
+
cleanHoverProps(),
|
|
1564
|
+
linkDomProps(),
|
|
1565
|
+
(draggableItem()?.dragProps as Record<string, unknown> | undefined) ?? {},
|
|
1566
|
+
(droppableItem()?.dropProps as Record<string, unknown> | undefined) ?? {},
|
|
1567
|
+
)}
|
|
1568
|
+
class={renderProps.class()}
|
|
1569
|
+
style={renderProps.style()}
|
|
1570
|
+
{...dataAttrs()}
|
|
1571
|
+
>
|
|
1572
|
+
{childContent()}
|
|
1573
|
+
</a>
|
|
1574
|
+
)}
|
|
1575
|
+
</li>
|
|
1576
|
+
</Show>
|
|
886
1577
|
);
|
|
887
1578
|
}
|
|
888
1579
|
|
|
@@ -890,8 +1581,66 @@ export function MenuItem<T>(props: MenuItemProps<T>): JSX.Element {
|
|
|
890
1581
|
* Section primitive alias for Menu composition parity.
|
|
891
1582
|
*/
|
|
892
1583
|
export function MenuSection(props: MenuSectionProps): JSX.Element {
|
|
893
|
-
|
|
1584
|
+
const [selectionProps, sectionProps] = splitProps(props, [
|
|
1585
|
+
"selectionMode",
|
|
1586
|
+
"selectionBehavior",
|
|
1587
|
+
"disallowEmptySelection",
|
|
1588
|
+
"selectedKeys",
|
|
1589
|
+
"defaultSelectedKeys",
|
|
1590
|
+
"onSelectionChange",
|
|
1591
|
+
"disabledKeys",
|
|
1592
|
+
"disabledBehavior",
|
|
1593
|
+
"allowDuplicateSelectionEvents",
|
|
1594
|
+
"shouldCloseOnSelect",
|
|
1595
|
+
]);
|
|
1596
|
+
|
|
1597
|
+
const selectionState = createSelectionState({
|
|
1598
|
+
get selectionMode() {
|
|
1599
|
+
return selectionProps.selectionMode ?? "none";
|
|
1600
|
+
},
|
|
1601
|
+
get selectionBehavior() {
|
|
1602
|
+
return selectionProps.selectionBehavior;
|
|
1603
|
+
},
|
|
1604
|
+
get disallowEmptySelection() {
|
|
1605
|
+
return selectionProps.disallowEmptySelection;
|
|
1606
|
+
},
|
|
1607
|
+
get selectedKeys() {
|
|
1608
|
+
return selectionProps.selectionMode ? selectionProps.selectedKeys : undefined;
|
|
1609
|
+
},
|
|
1610
|
+
get defaultSelectedKeys() {
|
|
1611
|
+
return selectionProps.selectionMode ? selectionProps.defaultSelectedKeys : undefined;
|
|
1612
|
+
},
|
|
1613
|
+
get onSelectionChange() {
|
|
1614
|
+
return selectionProps.selectionMode ? selectionProps.onSelectionChange : undefined;
|
|
1615
|
+
},
|
|
1616
|
+
get disabledKeys() {
|
|
1617
|
+
return selectionProps.disabledKeys;
|
|
1618
|
+
},
|
|
1619
|
+
get disabledBehavior() {
|
|
1620
|
+
return selectionProps.disabledBehavior;
|
|
1621
|
+
},
|
|
1622
|
+
get allowDuplicateSelectionEvents() {
|
|
1623
|
+
return selectionProps.allowDuplicateSelectionEvents;
|
|
1624
|
+
},
|
|
1625
|
+
});
|
|
1626
|
+
|
|
1627
|
+
const sectionSelection: MenuSectionSelectionContextValue = {
|
|
1628
|
+
selectionMode: selectionState.selectionMode,
|
|
1629
|
+
isSelected: selectionState.isSelected,
|
|
1630
|
+
isDisabled: selectionState.isDisabled,
|
|
1631
|
+
select(key, event) {
|
|
1632
|
+
selectionState.select(key, event);
|
|
1633
|
+
},
|
|
1634
|
+
shouldCloseOnSelect() {
|
|
1635
|
+
return selectionProps.shouldCloseOnSelect;
|
|
1636
|
+
},
|
|
1637
|
+
};
|
|
1638
|
+
|
|
1639
|
+
return (
|
|
1640
|
+
<MenuSectionSelectionContext.Provider value={sectionSelection}>
|
|
1641
|
+
<Section {...sectionProps} />
|
|
1642
|
+
</MenuSectionSelectionContext.Provider>
|
|
1643
|
+
);
|
|
894
1644
|
}
|
|
895
1645
|
|
|
896
|
-
// Attach Item as a static property
|
|
897
1646
|
Menu.Item = MenuItem;
|