@proyecto-viviana/solidaria-components 0.2.9 → 0.3.0
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 +23247 -18564
- package/dist/index.js.map +1 -1
- package/dist/index.jsx +18110 -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 +243 -175
- package/src/NumberField.tsx +139 -143
- package/src/Popover.tsx +386 -233
- 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 +209 -157
- 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 +40 -55
- package/src/virtualizer/Layout.ts +14 -22
- package/dist/index.ssr.js +0 -16996
- package/dist/index.ssr.js.map +0 -1
package/src/Select.tsx
CHANGED
|
@@ -9,12 +9,16 @@ import {
|
|
|
9
9
|
type JSX,
|
|
10
10
|
type Accessor,
|
|
11
11
|
createContext,
|
|
12
|
+
createEffect,
|
|
12
13
|
createMemo,
|
|
14
|
+
createSignal,
|
|
15
|
+
createUniqueId,
|
|
13
16
|
splitProps,
|
|
14
17
|
useContext,
|
|
15
18
|
For,
|
|
16
19
|
Show,
|
|
17
|
-
|
|
20
|
+
untrack,
|
|
21
|
+
} from "solid-js";
|
|
18
22
|
import {
|
|
19
23
|
createSelect,
|
|
20
24
|
createHiddenSelect,
|
|
@@ -23,17 +27,22 @@ import {
|
|
|
23
27
|
createHover,
|
|
24
28
|
createInteractOutside,
|
|
25
29
|
FocusScope,
|
|
30
|
+
focusSafely,
|
|
31
|
+
mergeProps,
|
|
26
32
|
type AriaSelectProps,
|
|
27
33
|
type AriaListBoxProps,
|
|
28
34
|
type AriaOptionProps,
|
|
29
|
-
} from
|
|
35
|
+
} from "@proyecto-viviana/solidaria";
|
|
30
36
|
import {
|
|
31
37
|
createSelectState,
|
|
32
38
|
type ListState,
|
|
33
39
|
type SelectState,
|
|
34
40
|
type Key,
|
|
35
41
|
type CollectionNode,
|
|
36
|
-
|
|
42
|
+
DEFAULT_VALIDATION_RESULT,
|
|
43
|
+
type ValidationResult,
|
|
44
|
+
} from "@proyecto-viviana/solid-stately";
|
|
45
|
+
import { FieldErrorContext, type FieldErrorContextValue } from "./FieldError";
|
|
37
46
|
import {
|
|
38
47
|
type RenderChildren,
|
|
39
48
|
type ClassNameOrFunction,
|
|
@@ -41,15 +50,43 @@ import {
|
|
|
41
50
|
type SlotProps,
|
|
42
51
|
useRenderProps,
|
|
43
52
|
filterDOMProps,
|
|
44
|
-
} from
|
|
53
|
+
} from "./utils";
|
|
45
54
|
import {
|
|
46
55
|
SelectionIndicatorContext,
|
|
47
56
|
type SelectionIndicatorContextValue,
|
|
48
|
-
} from
|
|
57
|
+
} from "./SelectionIndicator";
|
|
58
|
+
import { ListBoxLoadMoreItem } from "./ListBox";
|
|
49
59
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
type RefLike<T> = ((el: T) => void) | { current?: T | null } | undefined;
|
|
61
|
+
|
|
62
|
+
function assignRef<T>(ref: RefLike<T>, el: T): void {
|
|
63
|
+
if (!ref) return;
|
|
64
|
+
if (typeof ref === "function") {
|
|
65
|
+
ref(el);
|
|
66
|
+
} else {
|
|
67
|
+
ref.current = el;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getNativeSelectValidation(select: HTMLSelectElement): ValidationResult {
|
|
72
|
+
return {
|
|
73
|
+
isInvalid: !select.validity.valid,
|
|
74
|
+
validationDetails: {
|
|
75
|
+
badInput: select.validity.badInput,
|
|
76
|
+
customError: select.validity.customError,
|
|
77
|
+
patternMismatch: select.validity.patternMismatch,
|
|
78
|
+
rangeOverflow: select.validity.rangeOverflow,
|
|
79
|
+
rangeUnderflow: select.validity.rangeUnderflow,
|
|
80
|
+
stepMismatch: select.validity.stepMismatch,
|
|
81
|
+
tooLong: select.validity.tooLong,
|
|
82
|
+
tooShort: select.validity.tooShort,
|
|
83
|
+
typeMismatch: select.validity.typeMismatch,
|
|
84
|
+
valueMissing: select.validity.valueMissing,
|
|
85
|
+
valid: select.validity.valid,
|
|
86
|
+
},
|
|
87
|
+
validationErrors: select.validationMessage ? [select.validationMessage] : [],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
53
90
|
|
|
54
91
|
export interface SelectRenderProps {
|
|
55
92
|
/** Whether the select is open. */
|
|
@@ -66,9 +103,7 @@ export interface SelectRenderProps {
|
|
|
66
103
|
isSelected: boolean;
|
|
67
104
|
}
|
|
68
105
|
|
|
69
|
-
export interface SelectProps<T>
|
|
70
|
-
extends Omit<AriaSelectProps, 'children'>,
|
|
71
|
-
SlotProps {
|
|
106
|
+
export interface SelectProps<T> extends Omit<AriaSelectProps, "children">, SlotProps {
|
|
72
107
|
/** The items to render in the select. */
|
|
73
108
|
items: T[];
|
|
74
109
|
/** Function to get the key from an item. */
|
|
@@ -80,19 +115,19 @@ export interface SelectProps<T>
|
|
|
80
115
|
/** Keys of disabled items. */
|
|
81
116
|
disabledKeys?: Iterable<Key>;
|
|
82
117
|
/** Selection mode. */
|
|
83
|
-
selectionMode?:
|
|
118
|
+
selectionMode?: "single" | "multiple";
|
|
84
119
|
/** The currently selected key (controlled). */
|
|
85
120
|
selectedKey?: Key | null;
|
|
86
121
|
/** The default selected key (uncontrolled). */
|
|
87
122
|
defaultSelectedKey?: Key | null;
|
|
88
123
|
/** Currently selected keys (controlled, for multiple selection). */
|
|
89
|
-
selectedKeys?:
|
|
124
|
+
selectedKeys?: "all" | Iterable<Key>;
|
|
90
125
|
/** Default selected keys (uncontrolled, for multiple selection). */
|
|
91
|
-
defaultSelectedKeys?:
|
|
126
|
+
defaultSelectedKeys?: "all" | Iterable<Key>;
|
|
92
127
|
/** Handler called when selection changes. */
|
|
93
128
|
onSelectionChange?: (key: Key | null) => void;
|
|
94
129
|
/** Handler called when selected keys change. */
|
|
95
|
-
onSelectionChangeKeys?: (keys:
|
|
130
|
+
onSelectionChangeKeys?: (keys: "all" | Set<Key>) => void;
|
|
96
131
|
/** Whether the select is open (controlled). */
|
|
97
132
|
isOpen?: boolean;
|
|
98
133
|
/** Whether the select is open by default (uncontrolled). */
|
|
@@ -104,16 +139,25 @@ export interface SelectProps<T>
|
|
|
104
139
|
/** The name of the select, used when submitting an HTML form. */
|
|
105
140
|
name?: string;
|
|
106
141
|
/** The children of the component (compound components: SelectTrigger, SelectListBox). */
|
|
107
|
-
children:
|
|
142
|
+
children: RenderChildren<SelectRenderProps>;
|
|
108
143
|
/** The CSS className for the element. */
|
|
109
144
|
class?: ClassNameOrFunction<SelectRenderProps>;
|
|
110
145
|
/** The inline style for the element. */
|
|
111
146
|
style?: StyleOrFunction<SelectRenderProps>;
|
|
147
|
+
/** Custom renderer for the outer select element. */
|
|
148
|
+
render?: (
|
|
149
|
+
props: JSX.HTMLAttributes<HTMLDivElement>,
|
|
150
|
+
renderProps: SelectRenderProps,
|
|
151
|
+
) => JSX.Element;
|
|
152
|
+
/** Ref for the outer select element. */
|
|
153
|
+
ref?: RefLike<HTMLDivElement>;
|
|
112
154
|
}
|
|
113
155
|
|
|
114
156
|
export interface SelectValueRenderProps<T> {
|
|
115
157
|
/** The selected item. */
|
|
116
158
|
selectedItem: CollectionNode<T> | null;
|
|
159
|
+
/** The selected items. */
|
|
160
|
+
selectedItems: CollectionNode<T>[];
|
|
117
161
|
/** The text value of the selected item. */
|
|
118
162
|
selectedText: string | null;
|
|
119
163
|
/** Whether a value is selected. */
|
|
@@ -163,6 +207,18 @@ export interface SelectListBoxRenderProps {
|
|
|
163
207
|
export interface SelectListBoxProps<T> extends SlotProps {
|
|
164
208
|
/** The children of the listbox. A function may be provided to render each item. */
|
|
165
209
|
children?: (item: T) => JSX.Element;
|
|
210
|
+
/** Content to display when the listbox has no items. */
|
|
211
|
+
renderEmptyState?: () => JSX.Element;
|
|
212
|
+
/** Called when the load more sentinel becomes visible. */
|
|
213
|
+
onLoadMore?: () => void | Promise<void>;
|
|
214
|
+
/** Whether additional items are currently loading. */
|
|
215
|
+
isLoading?: boolean;
|
|
216
|
+
/** Content to display in the load more sentinel row. */
|
|
217
|
+
renderLoadMore?: () => JSX.Element | undefined;
|
|
218
|
+
/** CSS class for the load more sentinel row. */
|
|
219
|
+
loadMoreClass?: ClassNameOrFunction<{ isLoading: boolean }>;
|
|
220
|
+
/** Whether the listbox is rendered inside an overlay popover. */
|
|
221
|
+
isInPopover?: boolean;
|
|
166
222
|
/** The CSS className for the element. */
|
|
167
223
|
class?: ClassNameOrFunction<SelectListBoxRenderProps>;
|
|
168
224
|
/** The inline style for the element. */
|
|
@@ -184,9 +240,7 @@ export interface SelectOptionRenderProps {
|
|
|
184
240
|
isDisabled: boolean;
|
|
185
241
|
}
|
|
186
242
|
|
|
187
|
-
export interface SelectOptionProps<T>
|
|
188
|
-
extends Omit<AriaOptionProps, 'children' | 'key'>,
|
|
189
|
-
SlotProps {
|
|
243
|
+
export interface SelectOptionProps<T> extends Omit<AriaOptionProps, "children" | "key">, SlotProps {
|
|
190
244
|
/** The unique key for the option. */
|
|
191
245
|
id: Key;
|
|
192
246
|
/** The item value. */
|
|
@@ -201,16 +255,17 @@ export interface SelectOptionProps<T>
|
|
|
201
255
|
textValue?: string;
|
|
202
256
|
}
|
|
203
257
|
|
|
204
|
-
// ============================================
|
|
205
|
-
// CONTEXT
|
|
206
|
-
// ============================================
|
|
207
|
-
|
|
208
258
|
interface SelectContextValue<T> {
|
|
209
259
|
state: SelectState<T>;
|
|
260
|
+
rootRef: Accessor<HTMLElement | null>;
|
|
261
|
+
triggerRef: Accessor<HTMLElement | null>;
|
|
262
|
+
setTriggerRef: (el: HTMLElement | null) => void;
|
|
210
263
|
triggerProps: JSX.HTMLAttributes<HTMLElement>;
|
|
211
264
|
valueProps: JSX.HTMLAttributes<HTMLElement>;
|
|
212
265
|
labelProps: JSX.HTMLAttributes<HTMLElement>;
|
|
213
266
|
menuProps: JSX.HTMLAttributes<HTMLElement>;
|
|
267
|
+
errorMessageProps?: JSX.HTMLAttributes<HTMLElement>;
|
|
268
|
+
validation?: ValidationResult;
|
|
214
269
|
isOpen: Accessor<boolean>;
|
|
215
270
|
isFocused: Accessor<boolean>;
|
|
216
271
|
isFocusVisible: Accessor<boolean>;
|
|
@@ -218,35 +273,67 @@ interface SelectContextValue<T> {
|
|
|
218
273
|
placeholder?: string;
|
|
219
274
|
items: T[];
|
|
220
275
|
renderItem?: (item: T) => JSX.Element;
|
|
276
|
+
slots?: Record<string, Partial<SelectProps<T>>>;
|
|
277
|
+
autoFocus?: boolean;
|
|
221
278
|
}
|
|
222
279
|
|
|
223
280
|
export const SelectContext = createContext<SelectContextValue<unknown> | null>(null);
|
|
224
281
|
export const SelectStateContext = createContext<SelectState<unknown> | null>(null);
|
|
225
282
|
export const SelectValueContext = SelectContext;
|
|
226
283
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
284
|
+
const selectRootLabelProps = new Set([
|
|
285
|
+
"aria-label",
|
|
286
|
+
"aria-labelledby",
|
|
287
|
+
"aria-describedby",
|
|
288
|
+
"aria-details",
|
|
289
|
+
]);
|
|
230
290
|
|
|
231
291
|
/**
|
|
232
292
|
* A select displays a collapsible list of options and allows a user to select one of them.
|
|
233
293
|
*/
|
|
234
294
|
export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
295
|
+
const parentContext = useContext(SelectContext) as SelectContextValue<T> | null;
|
|
296
|
+
const contextSlotProps = parentContext?.slots?.[props.slot ?? "default"] as
|
|
297
|
+
| Partial<SelectProps<T>>
|
|
298
|
+
| undefined;
|
|
299
|
+
const mergedSelectProps = (
|
|
300
|
+
contextSlotProps ? mergeProps(contextSlotProps, props) : props
|
|
301
|
+
) as SelectProps<T>;
|
|
235
302
|
const [local, stateProps, ariaProps] = splitProps(
|
|
236
|
-
|
|
237
|
-
[
|
|
238
|
-
[
|
|
303
|
+
mergedSelectProps,
|
|
304
|
+
["class", "style", "render", "ref", "slot", "children"],
|
|
305
|
+
[
|
|
306
|
+
"items",
|
|
307
|
+
"getKey",
|
|
308
|
+
"getTextValue",
|
|
309
|
+
"getDisabled",
|
|
310
|
+
"disabledKeys",
|
|
311
|
+
"selectionMode",
|
|
312
|
+
"selectedKey",
|
|
313
|
+
"defaultSelectedKey",
|
|
314
|
+
"selectedKeys",
|
|
315
|
+
"defaultSelectedKeys",
|
|
316
|
+
"onSelectionChange",
|
|
317
|
+
"onSelectionChangeKeys",
|
|
318
|
+
"isOpen",
|
|
319
|
+
"defaultOpen",
|
|
320
|
+
"onOpenChange",
|
|
321
|
+
"name",
|
|
322
|
+
],
|
|
239
323
|
);
|
|
324
|
+
let rootRef: HTMLDivElement | undefined;
|
|
325
|
+
const [selectValidation, setSelectValidation] =
|
|
326
|
+
createSignal<ValidationResult>(DEFAULT_VALIDATION_RESULT);
|
|
327
|
+
const errorMessageId = createUniqueId();
|
|
240
328
|
|
|
241
329
|
const resolveDisabled = (): boolean => {
|
|
242
330
|
const disabled = ariaProps.isDisabled;
|
|
243
|
-
if (typeof disabled ===
|
|
331
|
+
if (typeof disabled === "function") {
|
|
244
332
|
return (disabled as () => boolean)();
|
|
245
333
|
}
|
|
246
334
|
return !!disabled;
|
|
247
335
|
};
|
|
248
336
|
|
|
249
|
-
// Create select state
|
|
250
337
|
const state = createSelectState<T>({
|
|
251
338
|
get items() {
|
|
252
339
|
return stateProps.items;
|
|
@@ -301,48 +388,74 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
301
388
|
},
|
|
302
389
|
});
|
|
303
390
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
ariaProps,
|
|
307
|
-
|
|
308
|
-
|
|
391
|
+
const selectAriaProps = createMemo(() => {
|
|
392
|
+
const clean: Record<string, unknown> = {};
|
|
393
|
+
for (const key in ariaProps as Record<string, unknown>) {
|
|
394
|
+
if (!key.startsWith("data-")) {
|
|
395
|
+
clean[key] = (ariaProps as Record<string, unknown>)[key];
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return clean as typeof ariaProps;
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
const { labelProps, triggerProps, valueProps, menuProps, isFocused, isFocusVisible, isOpen } =
|
|
402
|
+
createSelect<T>(selectAriaProps, state);
|
|
309
403
|
|
|
310
|
-
// Create hover for wrapper
|
|
311
404
|
const { isHovered, hoverProps } = createHover({
|
|
312
405
|
get isDisabled() {
|
|
313
406
|
return resolveDisabled();
|
|
314
407
|
},
|
|
315
408
|
});
|
|
316
409
|
|
|
317
|
-
// Render props values
|
|
318
410
|
const renderValues = createMemo<SelectRenderProps>(() => ({
|
|
319
411
|
isOpen: isOpen(),
|
|
320
412
|
isFocused: isFocused(),
|
|
321
413
|
isFocusVisible: isFocusVisible(),
|
|
322
414
|
isDisabled: resolveDisabled(),
|
|
323
415
|
isRequired: !!ariaProps.isRequired,
|
|
324
|
-
isSelected:
|
|
325
|
-
|
|
326
|
-
|
|
416
|
+
isSelected:
|
|
417
|
+
state.selectionMode() === "multiple"
|
|
418
|
+
? state.selectedKeys() === "all" || (state.selectedKeys() as Set<Key>).size > 0
|
|
419
|
+
: state.selectedKey() != null,
|
|
327
420
|
}));
|
|
421
|
+
const childRenderValues: SelectRenderProps = {
|
|
422
|
+
get isOpen() {
|
|
423
|
+
return isOpen();
|
|
424
|
+
},
|
|
425
|
+
get isFocused() {
|
|
426
|
+
return isFocused();
|
|
427
|
+
},
|
|
428
|
+
get isFocusVisible() {
|
|
429
|
+
return isFocusVisible();
|
|
430
|
+
},
|
|
431
|
+
get isDisabled() {
|
|
432
|
+
return resolveDisabled();
|
|
433
|
+
},
|
|
434
|
+
get isRequired() {
|
|
435
|
+
return !!ariaProps.isRequired;
|
|
436
|
+
},
|
|
437
|
+
get isSelected() {
|
|
438
|
+
return hasSelection();
|
|
439
|
+
},
|
|
440
|
+
};
|
|
328
441
|
|
|
329
|
-
// Resolve render props
|
|
330
442
|
const renderProps = useRenderProps(
|
|
331
443
|
{
|
|
332
444
|
class: local.class,
|
|
333
445
|
style: local.style,
|
|
334
|
-
defaultClassName:
|
|
446
|
+
defaultClassName: "solidaria-Select",
|
|
335
447
|
},
|
|
336
|
-
renderValues
|
|
448
|
+
renderValues,
|
|
337
449
|
);
|
|
338
450
|
|
|
339
|
-
// Filter DOM props
|
|
340
451
|
const domProps = createMemo(() => {
|
|
341
452
|
const filtered = filterDOMProps(ariaProps as Record<string, unknown>, { global: true });
|
|
453
|
+
for (const key of selectRootLabelProps) {
|
|
454
|
+
delete filtered[key];
|
|
455
|
+
}
|
|
342
456
|
return filtered;
|
|
343
457
|
});
|
|
344
458
|
|
|
345
|
-
// Remove ref from hover props
|
|
346
459
|
const cleanHoverProps = () => {
|
|
347
460
|
const { ref: _ref, ...rest } = hoverProps as Record<string, unknown>;
|
|
348
461
|
return rest;
|
|
@@ -351,80 +464,264 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
351
464
|
const { ref: _ref, ...rest } = labelProps as Record<string, unknown>;
|
|
352
465
|
return rest;
|
|
353
466
|
};
|
|
467
|
+
const setRootRef = (el: HTMLDivElement) => {
|
|
468
|
+
rootRef = el;
|
|
469
|
+
assignRef(local.ref, el);
|
|
470
|
+
};
|
|
471
|
+
const validation = createMemo<ValidationResult>(() => {
|
|
472
|
+
const current = selectValidation();
|
|
473
|
+
if (current.isInvalid || !ariaProps.isInvalid) {
|
|
474
|
+
return current;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return {
|
|
478
|
+
...DEFAULT_VALIDATION_RESULT,
|
|
479
|
+
isInvalid: true,
|
|
480
|
+
};
|
|
481
|
+
});
|
|
482
|
+
const isInvalid = createMemo(() => validation().isInvalid);
|
|
483
|
+
const triggerDescribedBy = () => {
|
|
484
|
+
const ids = [
|
|
485
|
+
(triggerProps as { "aria-describedby"?: string })["aria-describedby"],
|
|
486
|
+
isInvalid() ? errorMessageId : undefined,
|
|
487
|
+
]
|
|
488
|
+
.filter(Boolean)
|
|
489
|
+
.join(" ")
|
|
490
|
+
.split(" ")
|
|
491
|
+
.filter(Boolean);
|
|
492
|
+
return ids.length ? Array.from(new Set(ids)).join(" ") : undefined;
|
|
493
|
+
};
|
|
494
|
+
const triggerPropsWithValidation = () =>
|
|
495
|
+
({
|
|
496
|
+
...triggerProps,
|
|
497
|
+
"aria-describedby": triggerDescribedBy(),
|
|
498
|
+
}) as JSX.HTMLAttributes<HTMLElement>;
|
|
499
|
+
const fieldErrorContext: FieldErrorContextValue = {
|
|
500
|
+
get validation() {
|
|
501
|
+
return validation();
|
|
502
|
+
},
|
|
503
|
+
get errorMessageProps() {
|
|
504
|
+
return { id: errorMessageId };
|
|
505
|
+
},
|
|
506
|
+
};
|
|
507
|
+
const focusTrigger = () => {
|
|
508
|
+
triggerRef?.focus();
|
|
509
|
+
};
|
|
510
|
+
const hasSelection = () =>
|
|
511
|
+
state.selectionMode() === "multiple"
|
|
512
|
+
? state.selectedKeys() === "all" || (state.selectedKeys() as Set<Key>).size > 0
|
|
513
|
+
: state.selectedKey() != null;
|
|
514
|
+
const hasNativeValidation = () => (ariaProps.validationBehavior ?? "native") === "native";
|
|
515
|
+
const getSelectValidation = (select: HTMLSelectElement): ValidationResult => {
|
|
516
|
+
if (ariaProps.isRequired && !hasSelection()) {
|
|
517
|
+
return {
|
|
518
|
+
isInvalid: true,
|
|
519
|
+
validationDetails: {
|
|
520
|
+
badInput: false,
|
|
521
|
+
customError: false,
|
|
522
|
+
patternMismatch: false,
|
|
523
|
+
rangeOverflow: false,
|
|
524
|
+
rangeUnderflow: false,
|
|
525
|
+
stepMismatch: false,
|
|
526
|
+
tooLong: false,
|
|
527
|
+
tooShort: false,
|
|
528
|
+
typeMismatch: false,
|
|
529
|
+
valueMissing: true,
|
|
530
|
+
valid: false,
|
|
531
|
+
},
|
|
532
|
+
validationErrors: [select.validationMessage || "Constraints not satisfied"],
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
return getNativeSelectValidation(select);
|
|
536
|
+
};
|
|
354
537
|
|
|
355
|
-
// Create hidden select for form submission
|
|
356
538
|
const { containerProps, selectProps: hiddenSelectProps } = createHiddenSelect({
|
|
357
539
|
state,
|
|
358
540
|
name: stateProps.name,
|
|
541
|
+
form: ariaProps.form,
|
|
542
|
+
isRequired: ariaProps.isRequired,
|
|
543
|
+
validationBehavior: ariaProps.validationBehavior ?? "native",
|
|
359
544
|
get isDisabled() {
|
|
360
545
|
return resolveDisabled();
|
|
361
546
|
},
|
|
362
547
|
});
|
|
548
|
+
const handleHiddenSelectInvalid: JSX.EventHandler<HTMLSelectElement, Event> = (event) => {
|
|
549
|
+
setSelectValidation(getSelectValidation(event.currentTarget));
|
|
550
|
+
focusTrigger();
|
|
551
|
+
event.preventDefault();
|
|
552
|
+
};
|
|
553
|
+
const handleHiddenSelectChange: JSX.EventHandler<HTMLSelectElement, Event> = (event) => {
|
|
554
|
+
(hiddenSelectProps as { onChange?: JSX.EventHandler<HTMLSelectElement, Event> }).onChange?.(
|
|
555
|
+
event,
|
|
556
|
+
);
|
|
557
|
+
setSelectValidation(
|
|
558
|
+
hasSelection() && event.currentTarget.validity.valid
|
|
559
|
+
? DEFAULT_VALIDATION_RESULT
|
|
560
|
+
: getSelectValidation(event.currentTarget),
|
|
561
|
+
);
|
|
562
|
+
};
|
|
563
|
+
createEffect(() => {
|
|
564
|
+
if (hasSelection() && selectValidation().isInvalid) {
|
|
565
|
+
setSelectValidation(DEFAULT_VALIDATION_RESULT);
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
let triggerRef: HTMLElement | null = null;
|
|
569
|
+
const setTriggerRef = (el: HTMLElement | null) => {
|
|
570
|
+
triggerRef = el;
|
|
571
|
+
};
|
|
363
572
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
<select {...hiddenSelectProps}>
|
|
396
|
-
<option />
|
|
397
|
-
<For each={stateProps.items}>
|
|
398
|
-
{(item) => {
|
|
399
|
-
const itemRecord = isObjectRecord(item) ? item : null;
|
|
400
|
-
const fallbackKey = itemRecord != null
|
|
401
|
-
? toKey(itemRecord.key) ?? toKey(itemRecord.id)
|
|
573
|
+
const RootChildren = () => {
|
|
574
|
+
const selectChildren = untrack(() =>
|
|
575
|
+
typeof local.children === "function"
|
|
576
|
+
? (local.children as (values: SelectRenderProps) => JSX.Element)(childRenderValues)
|
|
577
|
+
: local.children,
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
return (
|
|
581
|
+
<>
|
|
582
|
+
<div {...containerProps}>
|
|
583
|
+
<select
|
|
584
|
+
{...hiddenSelectProps}
|
|
585
|
+
name={hasSelection() ? undefined : stateProps.name}
|
|
586
|
+
required={
|
|
587
|
+
(hasNativeValidation() && ariaProps.isRequired && !hasSelection()) || undefined
|
|
588
|
+
}
|
|
589
|
+
onInvalid={handleHiddenSelectInvalid}
|
|
590
|
+
onChange={handleHiddenSelectChange}
|
|
591
|
+
>
|
|
592
|
+
<Show when={state.selectionMode() !== "multiple"}>
|
|
593
|
+
<option selected={state.selectedKey() == null} />
|
|
594
|
+
</Show>
|
|
595
|
+
<For each={stateProps.items}>
|
|
596
|
+
{(item) => {
|
|
597
|
+
const itemRecord = isObjectRecord(item) ? item : null;
|
|
598
|
+
const fallbackKey =
|
|
599
|
+
itemRecord != null ? (toKey(itemRecord.key) ?? toKey(itemRecord.id)) : undefined;
|
|
600
|
+
const key = stateProps.getKey?.(item) ?? fallbackKey ?? String(item);
|
|
601
|
+
const fallbackTextValue =
|
|
602
|
+
itemRecord != null
|
|
603
|
+
? (toTextValue(itemRecord.textValue) ?? toTextValue(itemRecord.label))
|
|
402
604
|
: undefined;
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const isSelected = state.selectionMode() === 'multiple'
|
|
410
|
-
? selectedKeys === 'all'
|
|
605
|
+
const textValue =
|
|
606
|
+
stateProps.getTextValue?.(item) ?? fallbackTextValue ?? String(item);
|
|
607
|
+
const selectedKeys = state.selectedKeys();
|
|
608
|
+
const isSelected =
|
|
609
|
+
state.selectionMode() === "multiple"
|
|
610
|
+
? selectedKeys === "all"
|
|
411
611
|
? true
|
|
412
612
|
: (selectedKeys as Set<Key>).has(key)
|
|
413
613
|
: key === state.selectedKey();
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
614
|
+
return (
|
|
615
|
+
<option value={String(key)} selected={isSelected}>
|
|
616
|
+
{textValue}
|
|
617
|
+
</option>
|
|
618
|
+
);
|
|
619
|
+
}}
|
|
620
|
+
</For>
|
|
621
|
+
</select>
|
|
622
|
+
<Show when={state.selectionMode() === "multiple" && stateProps.name}>
|
|
623
|
+
<For
|
|
624
|
+
each={
|
|
625
|
+
state.selectedKeys() === "all"
|
|
626
|
+
? Array.from(state.collection()).map((item) => item.key)
|
|
627
|
+
: Array.from(state.selectedKeys() as Set<Key>)
|
|
628
|
+
}
|
|
629
|
+
>
|
|
630
|
+
{(key) => (
|
|
631
|
+
<input
|
|
632
|
+
type="hidden"
|
|
633
|
+
name={stateProps.name}
|
|
634
|
+
form={ariaProps.form}
|
|
635
|
+
value={String(key)}
|
|
636
|
+
disabled={resolveDisabled()}
|
|
637
|
+
/>
|
|
638
|
+
)}
|
|
639
|
+
</For>
|
|
640
|
+
</Show>
|
|
641
|
+
<Show
|
|
642
|
+
when={
|
|
643
|
+
state.selectionMode() !== "multiple" && stateProps.name && state.selectedKey() != null
|
|
644
|
+
}
|
|
645
|
+
>
|
|
646
|
+
<input
|
|
647
|
+
type="hidden"
|
|
648
|
+
name={stateProps.name}
|
|
649
|
+
form={ariaProps.form}
|
|
650
|
+
value={String(state.selectedKey())}
|
|
651
|
+
disabled={resolveDisabled()}
|
|
652
|
+
/>
|
|
425
653
|
</Show>
|
|
426
|
-
{props.children}
|
|
427
654
|
</div>
|
|
655
|
+
<Show when={ariaProps.label}>
|
|
656
|
+
<span {...cleanLabelProps()}>{ariaProps.label as JSX.Element}</span>
|
|
657
|
+
</Show>
|
|
658
|
+
{selectChildren}
|
|
659
|
+
</>
|
|
660
|
+
);
|
|
661
|
+
};
|
|
662
|
+
const baseRootProps = () =>
|
|
663
|
+
({
|
|
664
|
+
...domProps(),
|
|
665
|
+
...cleanHoverProps(),
|
|
666
|
+
ref: setRootRef,
|
|
667
|
+
class: renderProps.class(),
|
|
668
|
+
style: renderProps.style(),
|
|
669
|
+
slot: local.slot,
|
|
670
|
+
"data-open": isOpen() || undefined,
|
|
671
|
+
"data-disabled": resolveDisabled() || undefined,
|
|
672
|
+
"data-required": ariaProps.isRequired || undefined,
|
|
673
|
+
"data-invalid": isInvalid() || undefined,
|
|
674
|
+
"data-hovered": isHovered() || undefined,
|
|
675
|
+
}) as JSX.HTMLAttributes<HTMLDivElement>;
|
|
676
|
+
const RootContent = () => {
|
|
677
|
+
const renderedRootChildren = <RootChildren />;
|
|
678
|
+
const rootProps = () =>
|
|
679
|
+
({
|
|
680
|
+
...baseRootProps(),
|
|
681
|
+
children: renderedRootChildren,
|
|
682
|
+
}) as JSX.HTMLAttributes<HTMLDivElement>;
|
|
683
|
+
|
|
684
|
+
return local.render ? (
|
|
685
|
+
local.render(rootProps(), renderValues())
|
|
686
|
+
) : (
|
|
687
|
+
<div {...baseRootProps()}>{renderedRootChildren}</div>
|
|
688
|
+
);
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
return (
|
|
692
|
+
<SelectContext.Provider
|
|
693
|
+
value={
|
|
694
|
+
{
|
|
695
|
+
state,
|
|
696
|
+
rootRef: () => rootRef ?? null,
|
|
697
|
+
triggerRef: () => triggerRef,
|
|
698
|
+
setTriggerRef,
|
|
699
|
+
get triggerProps() {
|
|
700
|
+
return triggerPropsWithValidation();
|
|
701
|
+
},
|
|
702
|
+
valueProps,
|
|
703
|
+
labelProps,
|
|
704
|
+
menuProps,
|
|
705
|
+
get errorMessageProps() {
|
|
706
|
+
return { id: errorMessageId };
|
|
707
|
+
},
|
|
708
|
+
get validation() {
|
|
709
|
+
return validation();
|
|
710
|
+
},
|
|
711
|
+
isOpen,
|
|
712
|
+
isFocused,
|
|
713
|
+
isFocusVisible,
|
|
714
|
+
isDisabled: resolveDisabled,
|
|
715
|
+
placeholder: ariaProps.placeholder,
|
|
716
|
+
items: stateProps.items,
|
|
717
|
+
autoFocus: !!ariaProps.autoFocus,
|
|
718
|
+
} as SelectContextValue<unknown>
|
|
719
|
+
}
|
|
720
|
+
>
|
|
721
|
+
<SelectStateContext.Provider value={state}>
|
|
722
|
+
<FieldErrorContext.Provider value={fieldErrorContext}>
|
|
723
|
+
<RootContent />
|
|
724
|
+
</FieldErrorContext.Provider>
|
|
428
725
|
</SelectStateContext.Provider>
|
|
429
726
|
</SelectContext.Provider>
|
|
430
727
|
);
|
|
@@ -434,23 +731,31 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
434
731
|
* The trigger button for a select.
|
|
435
732
|
*/
|
|
436
733
|
export function SelectTrigger(props: SelectTriggerProps): JSX.Element {
|
|
437
|
-
const [local, domProps] = splitProps(props, [
|
|
734
|
+
const [local, domProps] = splitProps(props, ["class", "style", "slot", "children"]);
|
|
438
735
|
|
|
439
|
-
// Get context
|
|
440
736
|
const context = useContext(SelectContext);
|
|
441
737
|
if (!context) {
|
|
442
|
-
throw new Error(
|
|
738
|
+
throw new Error("SelectTrigger must be used within a Select");
|
|
443
739
|
}
|
|
444
|
-
const {
|
|
740
|
+
const { isOpen, isFocused, isFocusVisible, state } = context;
|
|
741
|
+
let triggerRef: HTMLButtonElement | undefined;
|
|
742
|
+
const setTriggerRef = (el: HTMLButtonElement) => {
|
|
743
|
+
triggerRef = el;
|
|
744
|
+
context.setTriggerRef(el);
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
createEffect(() => {
|
|
748
|
+
if (context.autoFocus) {
|
|
749
|
+
triggerRef?.focus();
|
|
750
|
+
}
|
|
751
|
+
});
|
|
445
752
|
|
|
446
|
-
// Create hover
|
|
447
753
|
const { isHovered, hoverProps } = createHover({
|
|
448
754
|
get isDisabled() {
|
|
449
755
|
return state.isDisabled;
|
|
450
756
|
},
|
|
451
757
|
});
|
|
452
758
|
|
|
453
|
-
// Render props values
|
|
454
759
|
const renderValues = createMemo<SelectTriggerRenderProps>(() => ({
|
|
455
760
|
isOpen: isOpen(),
|
|
456
761
|
isFocused: isFocused(),
|
|
@@ -459,33 +764,43 @@ export function SelectTrigger(props: SelectTriggerProps): JSX.Element {
|
|
|
459
764
|
isDisabled: state.isDisabled,
|
|
460
765
|
}));
|
|
461
766
|
|
|
462
|
-
// Resolve render props
|
|
463
767
|
const renderProps = useRenderProps(
|
|
464
768
|
{
|
|
465
769
|
children: props.children,
|
|
466
770
|
class: local.class,
|
|
467
771
|
style: local.style,
|
|
468
|
-
defaultClassName:
|
|
772
|
+
defaultClassName: "solidaria-Select-trigger",
|
|
469
773
|
},
|
|
470
|
-
renderValues
|
|
774
|
+
renderValues,
|
|
471
775
|
);
|
|
472
776
|
|
|
473
|
-
// Remove ref from spread props
|
|
474
777
|
const cleanTriggerProps = () => {
|
|
475
|
-
const { ref: _ref1, ...rest } = triggerProps as Record<string, unknown>;
|
|
778
|
+
const { ref: _ref1, ...rest } = context.triggerProps as Record<string, unknown>;
|
|
476
779
|
return rest;
|
|
477
780
|
};
|
|
478
781
|
const cleanHoverProps = () => {
|
|
479
782
|
const { ref: _ref2, ...rest } = hoverProps as Record<string, unknown>;
|
|
480
783
|
return rest;
|
|
481
784
|
};
|
|
482
|
-
|
|
785
|
+
const triggerAriaProps = () => context.triggerProps as Record<string, unknown>;
|
|
786
|
+
const menuAriaProps = () => context.menuProps as Record<string, unknown>;
|
|
483
787
|
return (
|
|
484
788
|
<button
|
|
789
|
+
ref={setTriggerRef}
|
|
485
790
|
{...domProps}
|
|
486
791
|
{...cleanTriggerProps()}
|
|
487
792
|
{...cleanHoverProps()}
|
|
488
793
|
type="button"
|
|
794
|
+
id={triggerAriaProps().id as string | undefined}
|
|
795
|
+
tabIndex={state.isDisabled ? undefined : 0}
|
|
796
|
+
aria-label={triggerAriaProps()["aria-label"] as string | undefined}
|
|
797
|
+
aria-labelledby={triggerAriaProps()["aria-labelledby"] as string | undefined}
|
|
798
|
+
aria-haspopup="listbox"
|
|
799
|
+
aria-expanded={isOpen()}
|
|
800
|
+
aria-controls={isOpen() ? (menuAriaProps().id as string | undefined) : undefined}
|
|
801
|
+
aria-disabled={state.isDisabled || undefined}
|
|
802
|
+
aria-required={triggerAriaProps()["aria-required"] as boolean | undefined}
|
|
803
|
+
aria-describedby={triggerAriaProps()["aria-describedby"] as string | undefined}
|
|
489
804
|
class={renderProps.class()}
|
|
490
805
|
style={renderProps.style()}
|
|
491
806
|
data-open={isOpen() || undefined}
|
|
@@ -501,50 +816,67 @@ export function SelectTrigger(props: SelectTriggerProps): JSX.Element {
|
|
|
501
816
|
|
|
502
817
|
// Default children function for SelectValue - defined at module level for SSR stability
|
|
503
818
|
function defaultSelectValueChildren<T>(values: SelectValueRenderProps<T>) {
|
|
504
|
-
return values.selectedText ?? values.placeholder ??
|
|
819
|
+
return values.selectedText ?? values.placeholder ?? "";
|
|
505
820
|
}
|
|
506
821
|
|
|
507
822
|
/**
|
|
508
823
|
* Displays the selected value in a select.
|
|
509
824
|
*/
|
|
510
825
|
export function SelectValue<T>(props: SelectValueProps<T>): JSX.Element {
|
|
511
|
-
const [local, domProps] = splitProps(props, [
|
|
826
|
+
const [local, domProps] = splitProps(props, [
|
|
827
|
+
"class",
|
|
828
|
+
"style",
|
|
829
|
+
"slot",
|
|
830
|
+
"placeholder",
|
|
831
|
+
"children",
|
|
832
|
+
]);
|
|
512
833
|
|
|
513
|
-
// Get context
|
|
514
834
|
const context = useContext(SelectContext);
|
|
515
835
|
if (!context) {
|
|
516
|
-
throw new Error(
|
|
836
|
+
throw new Error("SelectValue must be used within a Select");
|
|
517
837
|
}
|
|
518
838
|
const { valueProps, placeholder: contextPlaceholder } = context;
|
|
519
839
|
const state = context.state as SelectState<T>;
|
|
520
840
|
|
|
521
|
-
// Use local placeholder if provided, otherwise fall back to context
|
|
522
841
|
const placeholder = () => local.placeholder ?? contextPlaceholder;
|
|
523
842
|
|
|
524
|
-
// Render props values
|
|
525
843
|
const renderValues = createMemo<SelectValueRenderProps<T>>(() => {
|
|
526
|
-
const
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
844
|
+
const collection = state.collection();
|
|
845
|
+
const selectedItem =
|
|
846
|
+
state.selectedKey() == null ? null : collection.getItem(state.selectedKey() as Key);
|
|
847
|
+
const selectedKeys = state.selectedKeys();
|
|
848
|
+
const selectedItems =
|
|
849
|
+
selectedKeys === "all"
|
|
850
|
+
? Array.from(collection)
|
|
851
|
+
: Array.from(selectedKeys as Set<Key>)
|
|
852
|
+
.map((key) => collection.getItem(key))
|
|
853
|
+
.filter((item): item is CollectionNode<T> => item != null);
|
|
854
|
+
const selectedText =
|
|
855
|
+
state.selectionMode() === "multiple"
|
|
856
|
+
? selectedItems.length > 0
|
|
857
|
+
? new Intl.ListFormat(undefined, { style: "long", type: "conjunction" }).format(
|
|
858
|
+
selectedItems.map((item) => item.textValue),
|
|
859
|
+
)
|
|
860
|
+
: null
|
|
861
|
+
: (selectedItem?.textValue ?? null);
|
|
531
862
|
return {
|
|
532
863
|
selectedItem,
|
|
864
|
+
selectedItems,
|
|
533
865
|
selectedText,
|
|
534
|
-
isSelected:
|
|
866
|
+
isSelected:
|
|
867
|
+
state.selectionMode() === "multiple" ? selectedItems.length > 0 : selectedItem != null,
|
|
535
868
|
placeholder: placeholder(),
|
|
536
869
|
};
|
|
537
870
|
});
|
|
538
871
|
|
|
539
|
-
// Resolve render props
|
|
540
872
|
const renderProps = useRenderProps(
|
|
541
873
|
{
|
|
542
874
|
children: props.children ?? defaultSelectValueChildren,
|
|
543
875
|
class: local.class,
|
|
544
876
|
style: local.style,
|
|
545
|
-
defaultClassName:
|
|
877
|
+
defaultClassName: "solidaria-Select-value",
|
|
546
878
|
},
|
|
547
|
-
renderValues
|
|
879
|
+
renderValues,
|
|
548
880
|
);
|
|
549
881
|
|
|
550
882
|
return (
|
|
@@ -555,7 +887,9 @@ export function SelectValue<T>(props: SelectValueProps<T>): JSX.Element {
|
|
|
555
887
|
style={renderProps.style()}
|
|
556
888
|
data-placeholder={!renderValues().isSelected || undefined}
|
|
557
889
|
>
|
|
558
|
-
{
|
|
890
|
+
{props.children == null
|
|
891
|
+
? (renderValues().selectedText ?? renderValues().placeholder ?? "")
|
|
892
|
+
: renderProps.renderChildren()}
|
|
559
893
|
</span>
|
|
560
894
|
);
|
|
561
895
|
}
|
|
@@ -564,59 +898,79 @@ export function SelectValue<T>(props: SelectValueProps<T>): JSX.Element {
|
|
|
564
898
|
* The listbox popup for a select.
|
|
565
899
|
*/
|
|
566
900
|
export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
|
|
567
|
-
const [local, domProps] = splitProps(props, [
|
|
901
|
+
const [local, domProps] = splitProps(props, [
|
|
902
|
+
"class",
|
|
903
|
+
"style",
|
|
904
|
+
"slot",
|
|
905
|
+
"children",
|
|
906
|
+
"renderEmptyState",
|
|
907
|
+
"onLoadMore",
|
|
908
|
+
"isLoading",
|
|
909
|
+
"renderLoadMore",
|
|
910
|
+
"loadMoreClass",
|
|
911
|
+
"isInPopover",
|
|
912
|
+
]);
|
|
568
913
|
|
|
569
|
-
// Get context
|
|
570
914
|
const context = useContext(SelectContext);
|
|
571
915
|
if (!context) {
|
|
572
|
-
throw new Error(
|
|
916
|
+
throw new Error("SelectListBox must be used within a Select");
|
|
573
917
|
}
|
|
574
|
-
const { menuProps, state: selectState, isOpen } = context;
|
|
918
|
+
const { menuProps, rootRef, state: selectState, isOpen } = context;
|
|
575
919
|
const state = selectState as SelectState<T>;
|
|
576
920
|
|
|
577
|
-
|
|
921
|
+
createEffect(() => {
|
|
922
|
+
if (!isOpen()) {
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
if (state.focusedKey() != null) {
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
const selectedKey = state.selectedKey();
|
|
929
|
+
if (selectedKey != null && !state.collection().getItem(selectedKey)?.isDisabled) {
|
|
930
|
+
state.setFocusedKey(selectedKey);
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
|
|
578
934
|
let listBoxRef: HTMLUListElement | undefined;
|
|
579
935
|
|
|
580
|
-
// Handle click outside to close select
|
|
581
936
|
createInteractOutside({
|
|
582
|
-
ref: () => listBoxRef ?? null,
|
|
937
|
+
ref: () => rootRef() ?? listBoxRef ?? null,
|
|
583
938
|
onInteractOutside: () => {
|
|
584
939
|
if (isOpen()) {
|
|
585
940
|
state.close();
|
|
586
941
|
}
|
|
587
942
|
},
|
|
588
943
|
get isDisabled() {
|
|
589
|
-
return !isOpen();
|
|
944
|
+
return !isOpen() || local.isInPopover === true;
|
|
590
945
|
},
|
|
591
946
|
});
|
|
592
947
|
|
|
593
|
-
// Create listbox aria props - reuse select's internal list state via collection
|
|
594
948
|
const { listBoxProps } = createListBox(
|
|
595
949
|
{
|
|
596
950
|
...(menuProps as unknown as AriaListBoxProps),
|
|
951
|
+
shouldSelectOnPressUp: true,
|
|
952
|
+
shouldFocusOnHover: true,
|
|
953
|
+
shouldSelectOnFocus: local.isInPopover === true ? false : undefined,
|
|
597
954
|
get isDisabled() {
|
|
598
955
|
return state.isDisabled;
|
|
599
956
|
},
|
|
600
957
|
},
|
|
601
|
-
createSelectListStateAdapter(state)
|
|
958
|
+
createSelectListStateAdapter(state),
|
|
602
959
|
);
|
|
603
960
|
|
|
604
|
-
// Render props values
|
|
605
961
|
const renderValues = createMemo<SelectListBoxRenderProps>(() => ({
|
|
606
962
|
isFocused: state.isFocused(),
|
|
607
963
|
}));
|
|
608
964
|
|
|
609
|
-
// Resolve render props
|
|
610
965
|
const renderProps = useRenderProps(
|
|
611
966
|
{
|
|
612
967
|
class: local.class,
|
|
613
968
|
style: local.style,
|
|
614
|
-
defaultClassName:
|
|
969
|
+
defaultClassName: "solidaria-Select-listbox",
|
|
615
970
|
},
|
|
616
|
-
renderValues
|
|
971
|
+
renderValues,
|
|
617
972
|
);
|
|
618
973
|
|
|
619
|
-
// Remove ref from spread props
|
|
620
974
|
const cleanMenuProps = () => {
|
|
621
975
|
const { ref: _ref1, ...rest } = menuProps as Record<string, unknown>;
|
|
622
976
|
return rest;
|
|
@@ -627,34 +981,74 @@ export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
|
|
|
627
981
|
};
|
|
628
982
|
|
|
629
983
|
const items = () => Array.from(state.collection());
|
|
984
|
+
createEffect(() => {
|
|
985
|
+
if (!isOpen()) return;
|
|
986
|
+
const focusedKey = state.focusedKey();
|
|
987
|
+
if (focusedKey == null) return;
|
|
988
|
+
|
|
989
|
+
queueMicrotask(() => {
|
|
990
|
+
const option = Array.from(
|
|
991
|
+
listBoxRef?.querySelectorAll<HTMLElement>("[role='option']") ?? [],
|
|
992
|
+
).find((element) => element.id === String(focusedKey));
|
|
993
|
+
if (option && document.activeElement !== option) {
|
|
994
|
+
focusSafely(option);
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
});
|
|
630
998
|
|
|
631
|
-
|
|
632
|
-
<
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
</For>
|
|
651
|
-
}>
|
|
999
|
+
const listBox = () => (
|
|
1000
|
+
<ul
|
|
1001
|
+
ref={(el) => (listBoxRef = el)}
|
|
1002
|
+
{...domProps}
|
|
1003
|
+
{...cleanMenuProps()}
|
|
1004
|
+
{...cleanListBoxProps()}
|
|
1005
|
+
class={renderProps.class()}
|
|
1006
|
+
style={renderProps.style()}
|
|
1007
|
+
data-focused={state.isFocused() || undefined}
|
|
1008
|
+
data-empty={state.collection().size === 0 || undefined}
|
|
1009
|
+
>
|
|
1010
|
+
{state.collection().size === 0 && local.renderEmptyState ? (
|
|
1011
|
+
<li role="option" style={{ display: "contents" }} data-empty-state>
|
|
1012
|
+
{local.renderEmptyState()}
|
|
1013
|
+
</li>
|
|
1014
|
+
) : (
|
|
1015
|
+
<Show
|
|
1016
|
+
when={local.children}
|
|
1017
|
+
fallback={
|
|
652
1018
|
<For each={items()}>
|
|
653
|
-
{(node) => node.
|
|
1019
|
+
{(node) => <SelectOption id={node.key}>{node.textValue}</SelectOption>}
|
|
654
1020
|
</For>
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
1021
|
+
}
|
|
1022
|
+
>
|
|
1023
|
+
<For each={items()}>
|
|
1024
|
+
{(node) => (node.value != null ? local.children!(node.value) : null)}
|
|
1025
|
+
</For>
|
|
1026
|
+
</Show>
|
|
1027
|
+
)}
|
|
1028
|
+
<Show when={local.onLoadMore}>
|
|
1029
|
+
<ListBoxLoadMoreItem
|
|
1030
|
+
onLoadMore={local.onLoadMore!}
|
|
1031
|
+
isLoading={local.isLoading}
|
|
1032
|
+
class={local.loadMoreClass}
|
|
1033
|
+
>
|
|
1034
|
+
{local.renderLoadMore?.()}
|
|
1035
|
+
</ListBoxLoadMoreItem>
|
|
1036
|
+
</Show>
|
|
1037
|
+
</ul>
|
|
1038
|
+
);
|
|
1039
|
+
|
|
1040
|
+
return (
|
|
1041
|
+
<Show when={isOpen()}>
|
|
1042
|
+
<Show
|
|
1043
|
+
when={local.isInPopover}
|
|
1044
|
+
fallback={
|
|
1045
|
+
<FocusScope restoreFocus autoFocus>
|
|
1046
|
+
{listBox()}
|
|
1047
|
+
</FocusScope>
|
|
1048
|
+
}
|
|
1049
|
+
>
|
|
1050
|
+
{listBox()}
|
|
1051
|
+
</Show>
|
|
658
1052
|
</Show>
|
|
659
1053
|
);
|
|
660
1054
|
}
|
|
@@ -664,39 +1058,48 @@ export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
|
|
|
664
1058
|
*/
|
|
665
1059
|
export function SelectOption<T>(props: SelectOptionProps<T>): JSX.Element {
|
|
666
1060
|
const [local, ariaProps] = splitProps(props, [
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
1061
|
+
"class",
|
|
1062
|
+
"style",
|
|
1063
|
+
"slot",
|
|
1064
|
+
"id",
|
|
1065
|
+
"item",
|
|
1066
|
+
"textValue",
|
|
673
1067
|
]);
|
|
674
1068
|
|
|
675
|
-
// Get state from context
|
|
676
1069
|
const context = useContext(SelectStateContext);
|
|
677
1070
|
if (!context) {
|
|
678
|
-
throw new Error(
|
|
1071
|
+
throw new Error("SelectOption must be used within a Select");
|
|
679
1072
|
}
|
|
680
1073
|
const state = context as SelectState<T>;
|
|
681
1074
|
const selectContext = useContext(SelectContext) as SelectContextValue<T> | null;
|
|
682
1075
|
|
|
683
|
-
// Create option aria props - adapt select state to list state interface
|
|
684
1076
|
const optionAria = createOption<T>(
|
|
685
1077
|
{
|
|
686
1078
|
key: local.id,
|
|
687
1079
|
get isDisabled() {
|
|
688
1080
|
return Boolean(ariaProps.isDisabled || selectContext?.isDisabled());
|
|
689
1081
|
},
|
|
690
|
-
get
|
|
691
|
-
return ariaProps[
|
|
1082
|
+
get "aria-label"() {
|
|
1083
|
+
return ariaProps["aria-label"] ?? local.textValue;
|
|
1084
|
+
},
|
|
1085
|
+
shouldSelectOnPressUp: true,
|
|
1086
|
+
shouldFocusOnHover: true,
|
|
1087
|
+
get onHoverStart() {
|
|
1088
|
+
return ariaProps.onHoverStart;
|
|
1089
|
+
},
|
|
1090
|
+
get onHoverEnd() {
|
|
1091
|
+
return ariaProps.onHoverEnd;
|
|
1092
|
+
},
|
|
1093
|
+
get onHoverChange() {
|
|
1094
|
+
return ariaProps.onHoverChange;
|
|
692
1095
|
},
|
|
693
1096
|
},
|
|
694
1097
|
{
|
|
695
1098
|
...createSelectListStateAdapter(state),
|
|
696
1099
|
select: (key: Key) => {
|
|
697
|
-
if (state.selectionMode() ===
|
|
1100
|
+
if (state.selectionMode() === "multiple") {
|
|
698
1101
|
const keys = state.selectedKeys();
|
|
699
|
-
if (keys ===
|
|
1102
|
+
if (keys === "all") return;
|
|
700
1103
|
state.setSelectedKeys(new Set([...keys, key]));
|
|
701
1104
|
return;
|
|
702
1105
|
}
|
|
@@ -704,9 +1107,9 @@ export function SelectOption<T>(props: SelectOptionProps<T>): JSX.Element {
|
|
|
704
1107
|
state.close();
|
|
705
1108
|
},
|
|
706
1109
|
toggleSelection: (key: Key) => {
|
|
707
|
-
if (state.selectionMode() ===
|
|
1110
|
+
if (state.selectionMode() === "multiple") {
|
|
708
1111
|
const keys = state.selectedKeys();
|
|
709
|
-
if (keys ===
|
|
1112
|
+
if (keys === "all") return;
|
|
710
1113
|
const next = new Set(keys);
|
|
711
1114
|
if (next.has(key)) next.delete(key);
|
|
712
1115
|
else next.add(key);
|
|
@@ -718,100 +1121,118 @@ export function SelectOption<T>(props: SelectOptionProps<T>): JSX.Element {
|
|
|
718
1121
|
},
|
|
719
1122
|
replaceSelection: (key: Key) => {
|
|
720
1123
|
state.setSelectedKey(key);
|
|
721
|
-
if (state.selectionMode() !==
|
|
1124
|
+
if (state.selectionMode() !== "multiple") {
|
|
722
1125
|
state.close();
|
|
723
1126
|
}
|
|
724
1127
|
},
|
|
725
|
-
}
|
|
726
|
-
);
|
|
727
|
-
|
|
728
|
-
// Create hover
|
|
729
|
-
const { isHovered, hoverProps } = createHover({
|
|
730
|
-
get isDisabled() {
|
|
731
|
-
return optionAria.isDisabled();
|
|
732
1128
|
},
|
|
733
|
-
|
|
1129
|
+
);
|
|
1130
|
+
const isOptionFocusVisible = () =>
|
|
1131
|
+
optionAria.isFocused() && (selectContext?.isFocusVisible() ?? optionAria.isFocusVisible());
|
|
734
1132
|
|
|
735
|
-
// Render props values
|
|
736
1133
|
const renderValues = createMemo<SelectOptionRenderProps>(() => ({
|
|
737
1134
|
isSelected: optionAria.isSelected(),
|
|
738
1135
|
isFocused: optionAria.isFocused(),
|
|
739
|
-
isFocusVisible:
|
|
1136
|
+
isFocusVisible: isOptionFocusVisible(),
|
|
740
1137
|
isPressed: optionAria.isPressed(),
|
|
741
|
-
isHovered: isHovered(),
|
|
1138
|
+
isHovered: optionAria.isHovered(),
|
|
742
1139
|
isDisabled: optionAria.isDisabled(),
|
|
743
1140
|
}));
|
|
744
1141
|
|
|
745
|
-
// Resolve render props
|
|
746
1142
|
const renderProps = useRenderProps(
|
|
747
1143
|
{
|
|
748
1144
|
children: props.children,
|
|
749
1145
|
class: local.class,
|
|
750
1146
|
style: local.style,
|
|
751
|
-
defaultClassName:
|
|
1147
|
+
defaultClassName: "solidaria-Select-option",
|
|
752
1148
|
},
|
|
753
|
-
renderValues
|
|
1149
|
+
renderValues,
|
|
754
1150
|
);
|
|
755
1151
|
const hasPrimitiveLabel = () => {
|
|
756
|
-
return typeof props.children ===
|
|
1152
|
+
return typeof props.children === "string" || typeof props.children === "number";
|
|
757
1153
|
};
|
|
758
1154
|
|
|
759
1155
|
const selectionIndicatorContext = createMemo<SelectionIndicatorContextValue>(() => ({
|
|
760
1156
|
isSelected: optionAria.isSelected,
|
|
761
1157
|
}));
|
|
762
1158
|
|
|
763
|
-
// Remove ref from spread props
|
|
764
1159
|
const cleanOptionProps = () => {
|
|
765
1160
|
const {
|
|
766
1161
|
ref: _ref1,
|
|
767
|
-
|
|
1162
|
+
"aria-describedby": _ariaDescribedby,
|
|
768
1163
|
...rest
|
|
769
1164
|
} = optionAria.optionProps as Record<string, unknown>;
|
|
770
|
-
if (!hasPrimitiveLabel() && rest[
|
|
771
|
-
delete rest[
|
|
1165
|
+
if (!hasPrimitiveLabel() && rest["aria-label"] == null) {
|
|
1166
|
+
delete rest["aria-labelledby"];
|
|
772
1167
|
}
|
|
1168
|
+
const onClick = rest.onClick as ((event: MouseEvent) => void) | undefined;
|
|
1169
|
+
rest.onClick = ((event: MouseEvent) => {
|
|
1170
|
+
const wasSelected = optionAria.isSelected();
|
|
1171
|
+
onClick?.(event);
|
|
1172
|
+
if (typeof PointerEvent === "undefined") {
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
queueMicrotask(() => {
|
|
1176
|
+
if (state.selectionMode() !== "multiple" || optionAria.isSelected() === wasSelected) {
|
|
1177
|
+
selectOption();
|
|
1178
|
+
}
|
|
1179
|
+
});
|
|
1180
|
+
}) as JSX.EventHandler<HTMLLIElement, MouseEvent>;
|
|
773
1181
|
return rest;
|
|
774
1182
|
};
|
|
775
|
-
const
|
|
776
|
-
|
|
777
|
-
|
|
1183
|
+
const selectOption = () => {
|
|
1184
|
+
if (optionAria.isDisabled()) {
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
if (state.selectionMode() === "multiple") {
|
|
1188
|
+
const keys = state.selectedKeys();
|
|
1189
|
+
if (keys === "all") return;
|
|
1190
|
+
const next = new Set(keys);
|
|
1191
|
+
if (next.has(local.id)) next.delete(local.id);
|
|
1192
|
+
else next.add(local.id);
|
|
1193
|
+
state.setSelectedKeys(next);
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
state.setSelectedKey(local.id);
|
|
1197
|
+
state.close();
|
|
778
1198
|
};
|
|
779
1199
|
|
|
780
1200
|
return (
|
|
781
1201
|
<SelectionIndicatorContext.Provider value={selectionIndicatorContext()}>
|
|
782
1202
|
<li
|
|
783
1203
|
{...cleanOptionProps()}
|
|
784
|
-
{...cleanHoverProps()}
|
|
785
1204
|
class={renderProps.class()}
|
|
786
1205
|
style={renderProps.style()}
|
|
787
1206
|
data-selected={optionAria.isSelected() || undefined}
|
|
788
1207
|
data-focused={optionAria.isFocused() || undefined}
|
|
789
|
-
data-focus-visible={
|
|
1208
|
+
data-focus-visible={isOptionFocusVisible() || undefined}
|
|
790
1209
|
data-pressed={optionAria.isPressed() || undefined}
|
|
791
|
-
data-hovered={isHovered() || undefined}
|
|
1210
|
+
data-hovered={optionAria.isHovered() || undefined}
|
|
792
1211
|
data-disabled={optionAria.isDisabled() || undefined}
|
|
793
1212
|
>
|
|
794
|
-
{hasPrimitiveLabel()
|
|
795
|
-
|
|
796
|
-
|
|
1213
|
+
{hasPrimitiveLabel() ? (
|
|
1214
|
+
<span {...optionAria.labelProps}>{renderProps.renderChildren()}</span>
|
|
1215
|
+
) : (
|
|
1216
|
+
renderProps.renderChildren()
|
|
1217
|
+
)}
|
|
797
1218
|
</li>
|
|
798
1219
|
</SelectionIndicatorContext.Provider>
|
|
799
1220
|
);
|
|
800
1221
|
}
|
|
801
1222
|
|
|
802
1223
|
function isObjectRecord(value: unknown): value is Record<string, unknown> {
|
|
803
|
-
return typeof value ===
|
|
1224
|
+
return typeof value === "object" && value !== null;
|
|
804
1225
|
}
|
|
805
1226
|
|
|
806
1227
|
function toKey(value: unknown): Key | undefined {
|
|
807
|
-
if (typeof value ===
|
|
1228
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
808
1229
|
return value;
|
|
809
1230
|
}
|
|
810
1231
|
return undefined;
|
|
811
1232
|
}
|
|
812
1233
|
|
|
813
1234
|
function toTextValue(value: unknown): string | undefined {
|
|
814
|
-
if (typeof value ===
|
|
1235
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
815
1236
|
return String(value);
|
|
816
1237
|
}
|
|
817
1238
|
return undefined;
|
|
@@ -820,9 +1241,7 @@ function toTextValue(value: unknown): string | undefined {
|
|
|
820
1241
|
function createSelectListStateAdapter<T>(state: SelectState<T>): ListState<T> {
|
|
821
1242
|
const selectedKeys = createMemo(() => {
|
|
822
1243
|
const keys = state.selectedKeys();
|
|
823
|
-
return keys ===
|
|
824
|
-
? new Set(Array.from(state.collection()).map((item) => item.key))
|
|
825
|
-
: keys;
|
|
1244
|
+
return keys === "all" ? new Set(Array.from(state.collection()).map((item) => item.key)) : keys;
|
|
826
1245
|
});
|
|
827
1246
|
|
|
828
1247
|
const disabledKeys = createMemo(() => {
|
|
@@ -841,23 +1260,23 @@ function createSelectListStateAdapter<T>(state: SelectState<T>): ListState<T> {
|
|
|
841
1260
|
setFocusedKey: (key) => state.setFocusedKey(key ?? null),
|
|
842
1261
|
childFocusStrategy: () => null,
|
|
843
1262
|
selectionMode: () => state.selectionMode(),
|
|
844
|
-
selectionBehavior: () =>
|
|
1263
|
+
selectionBehavior: () => "replace",
|
|
845
1264
|
disallowEmptySelection: () => true,
|
|
846
1265
|
selectedKeys,
|
|
847
1266
|
disabledKeys,
|
|
848
|
-
disabledBehavior: () =>
|
|
1267
|
+
disabledBehavior: () => "all",
|
|
849
1268
|
isEmpty: () => selectedKeys().size === 0,
|
|
850
|
-
isSelectAll: () => state.selectedKeys() ===
|
|
1269
|
+
isSelectAll: () => state.selectedKeys() === "all",
|
|
851
1270
|
isSelected: (key) => selectedKeys().has(key),
|
|
852
1271
|
isDisabled: state.isKeyDisabled,
|
|
853
1272
|
setSelectionBehavior: () => {},
|
|
854
1273
|
toggleSelection: (key) => {
|
|
855
|
-
if (state.selectionMode() !==
|
|
1274
|
+
if (state.selectionMode() !== "multiple") {
|
|
856
1275
|
state.setSelectedKey(key);
|
|
857
1276
|
return;
|
|
858
1277
|
}
|
|
859
1278
|
const keys = state.selectedKeys();
|
|
860
|
-
if (keys ===
|
|
1279
|
+
if (keys === "all") return;
|
|
861
1280
|
const next = new Set(keys);
|
|
862
1281
|
if (next.has(key)) next.delete(key);
|
|
863
1282
|
else next.add(key);
|
|
@@ -866,16 +1285,20 @@ function createSelectListStateAdapter<T>(state: SelectState<T>): ListState<T> {
|
|
|
866
1285
|
replaceSelection: (key) => state.setSelectedKey(key),
|
|
867
1286
|
setSelectedKeys: (keys) => state.setSelectedKeys(keys),
|
|
868
1287
|
selectAll: () => {},
|
|
869
|
-
clearSelection: () =>
|
|
1288
|
+
clearSelection: () =>
|
|
1289
|
+
state.selectionMode() === "multiple" ? state.setSelectedKeys([]) : state.setSelectedKey(null),
|
|
870
1290
|
toggleSelectAll: () => {},
|
|
871
1291
|
extendSelection: (toKey) => state.setSelectedKey(toKey),
|
|
872
|
-
select: (key) =>
|
|
873
|
-
|
|
874
|
-
|
|
1292
|
+
select: (key) =>
|
|
1293
|
+
state.selectionMode() === "multiple"
|
|
1294
|
+
? state.setSelectedKeys([
|
|
1295
|
+
...(state.selectedKeys() === "all" ? [] : (state.selectedKeys() as Set<Key>)),
|
|
1296
|
+
key,
|
|
1297
|
+
])
|
|
1298
|
+
: state.setSelectedKey(key),
|
|
875
1299
|
};
|
|
876
1300
|
}
|
|
877
1301
|
|
|
878
|
-
// Attach sub-components
|
|
879
1302
|
Select.Trigger = SelectTrigger;
|
|
880
1303
|
Select.Value = SelectValue;
|
|
881
1304
|
Select.ListBox = SelectListBox;
|