@proyecto-viviana/solidaria-components 0.2.5 → 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/LICENSE +21 -0
- package/README.md +39 -272
- package/dist/ActionBar.d.ts +79 -0
- package/dist/ActionBar.d.ts.map +1 -0
- package/dist/ActionGroup.d.ts +74 -0
- package/dist/ActionGroup.d.ts.map +1 -0
- package/dist/Alert.d.ts +70 -0
- package/dist/Alert.d.ts.map +1 -0
- package/dist/Autocomplete.d.ts +5 -5
- package/dist/Autocomplete.d.ts.map +1 -1
- package/dist/Breadcrumbs.d.ts +27 -8
- package/dist/Breadcrumbs.d.ts.map +1 -1
- package/dist/Button.d.ts +28 -5
- package/dist/Button.d.ts.map +1 -1
- package/dist/Calendar.d.ts +51 -7
- package/dist/Calendar.d.ts.map +1 -1
- package/dist/Checkbox.d.ts +33 -8
- package/dist/Checkbox.d.ts.map +1 -1
- package/dist/Collection.d.ts +130 -0
- package/dist/Collection.d.ts.map +1 -0
- package/dist/Color.d.ts +210 -9
- package/dist/Color.d.ts.map +1 -1
- package/dist/ColorEditor.d.ts +42 -0
- package/dist/ColorEditor.d.ts.map +1 -0
- package/dist/ComboBox.d.ts +146 -16
- package/dist/ComboBox.d.ts.map +1 -1
- package/dist/ContextualHelpTrigger.d.ts +40 -0
- package/dist/ContextualHelpTrigger.d.ts.map +1 -0
- package/dist/DateField.d.ts +35 -8
- package/dist/DateField.d.ts.map +1 -1
- package/dist/DatePicker.d.ts +101 -5
- 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 +25 -5
- package/dist/Disclosure.d.ts.map +1 -1
- package/dist/DragAndDrop.d.ts +80 -0
- package/dist/DragAndDrop.d.ts.map +1 -0
- package/dist/DragPreview.d.ts +14 -0
- package/dist/DragPreview.d.ts.map +1 -0
- package/dist/DropZone.d.ts +27 -0
- package/dist/DropZone.d.ts.map +1 -0
- package/dist/FieldError.d.ts +27 -0
- package/dist/FieldError.d.ts.map +1 -0
- package/dist/FileTrigger.d.ts +26 -0
- package/dist/FileTrigger.d.ts.map +1 -0
- package/dist/Focusable.d.ts +27 -0
- package/dist/Focusable.d.ts.map +1 -0
- package/dist/Form.d.ts +41 -0
- package/dist/Form.d.ts.map +1 -0
- package/dist/GridList.d.ts +69 -10
- 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 +57 -0
- package/dist/Icon.d.ts.map +1 -0
- package/dist/Keyboard.d.ts +13 -0
- package/dist/Keyboard.d.ts.map +1 -0
- 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 +73 -11
- package/dist/ListBox.d.ts.map +1 -1
- package/dist/ListDropTargetDelegate.d.ts +38 -0
- package/dist/ListDropTargetDelegate.d.ts.map +1 -0
- package/dist/Menu.d.ts +79 -10
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Meter.d.ts +4 -4
- package/dist/Meter.d.ts.map +1 -1
- package/dist/Modal.d.ts +6 -4
- package/dist/Modal.d.ts.map +1 -1
- package/dist/NumberField.d.ts +10 -12
- package/dist/NumberField.d.ts.map +1 -1
- package/dist/Popover.d.ts +32 -7
- package/dist/Popover.d.ts.map +1 -1
- package/dist/Pressable.d.ts +27 -0
- package/dist/Pressable.d.ts.map +1 -0
- package/dist/ProgressBar.d.ts +6 -4
- 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 +39 -7
- package/dist/RangeCalendar.d.ts.map +1 -1
- package/dist/RouterProvider.d.ts +75 -0
- package/dist/RouterProvider.d.ts.map +1 -0
- package/dist/SearchField.d.ts +23 -21
- package/dist/SearchField.d.ts.map +1 -1
- package/dist/Select.d.ts +48 -7
- package/dist/Select.d.ts.map +1 -1
- package/dist/SelectionIndicator.d.ts +30 -0
- package/dist/SelectionIndicator.d.ts.map +1 -0
- package/dist/Separator.d.ts +9 -3
- package/dist/Separator.d.ts.map +1 -1
- package/dist/SharedElementTransition.d.ts +41 -0
- package/dist/SharedElementTransition.d.ts.map +1 -0
- package/dist/Slider.d.ts +15 -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 +222 -19
- package/dist/Table.d.ts.map +1 -1
- package/dist/Tabs.d.ts +47 -10
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/TagGroup.d.ts +22 -10
- package/dist/TagGroup.d.ts.map +1 -1
- package/dist/Text.d.ts +10 -0
- package/dist/Text.d.ts.map +1 -0
- package/dist/TextField.d.ts +19 -11
- package/dist/TextField.d.ts.map +1 -1
- package/dist/TimeField.d.ts +32 -7
- 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 +36 -0
- package/dist/ToggleButton.d.ts.map +1 -0
- package/dist/ToggleButtonGroup.d.ts +33 -0
- package/dist/ToggleButtonGroup.d.ts.map +1 -0
- package/dist/Toolbar.d.ts +7 -3
- package/dist/Toolbar.d.ts.map +1 -1
- package/dist/Tooltip.d.ts +58 -7
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/Tree.d.ts +102 -11
- package/dist/Tree.d.ts.map +1 -1
- package/dist/Virtualizer.d.ts +61 -0
- package/dist/Virtualizer.d.ts.map +1 -0
- package/dist/VirtualizerLayouts.d.ts +82 -0
- package/dist/VirtualizerLayouts.d.ts.map +1 -0
- package/dist/VisuallyHidden.d.ts +4 -2
- package/dist/VisuallyHidden.d.ts.map +1 -1
- package/dist/contexts.d.ts +6 -1
- package/dist/contexts.d.ts.map +1 -1
- package/dist/index.d.ts +73 -39
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23342 -10644
- package/dist/index.js.map +1 -7
- package/dist/index.jsx +18110 -0
- package/dist/index.jsx.map +1 -0
- package/dist/useDragAndDrop.d.ts +93 -0
- package/dist/useDragAndDrop.d.ts.map +1 -0
- package/dist/utils.d.ts +8 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/virtualizer/Layout.d.ts +79 -0
- package/dist/virtualizer/Layout.d.ts.map +1 -0
- package/package.json +33 -32
- package/src/ActionBar.tsx +251 -0
- package/src/ActionGroup.tsx +277 -0
- package/src/Alert.tsx +152 -0
- package/src/Autocomplete.tsx +39 -44
- package/src/Breadcrumbs.tsx +227 -72
- package/src/Button.tsx +315 -74
- package/src/Calendar.tsx +347 -141
- package/src/Checkbox.tsx +414 -123
- package/src/Collection.tsx +350 -0
- package/src/Color.tsx +1325 -284
- package/src/ColorEditor.tsx +213 -0
- package/src/ComboBox.tsx +644 -245
- package/src/ContextualHelpTrigger.tsx +195 -0
- package/src/DateField.tsx +274 -106
- package/src/DatePicker.tsx +892 -111
- package/src/DateRangePickerContext.tsx +44 -0
- package/src/Dialog.tsx +173 -104
- package/src/Disclosure.tsx +158 -105
- package/src/DragAndDrop.tsx +340 -0
- package/src/DragPreview.tsx +47 -0
- package/src/DropZone.tsx +233 -0
- package/src/FieldError.tsx +89 -0
- package/src/FileTrigger.tsx +83 -0
- package/src/Focusable.tsx +103 -0
- package/src/Form.tsx +140 -0
- package/src/GridList.tsx +542 -128
- package/src/HiddenDateInput.tsx +153 -0
- package/src/HiddenTimeInput.tsx +133 -0
- package/src/Icon.tsx +133 -0
- package/src/Keyboard.tsx +26 -0
- package/src/Landmark.tsx +37 -63
- package/src/Link.tsx +132 -69
- package/src/ListBox.tsx +656 -106
- package/src/ListDropTargetDelegate.ts +283 -0
- package/src/Menu.tsx +1234 -132
- package/src/Meter.tsx +44 -58
- package/src/Modal.tsx +262 -166
- package/src/NumberField.tsx +267 -151
- package/src/Popover.tsx +452 -343
- package/src/Pressable.tsx +108 -0
- package/src/ProgressBar.tsx +54 -59
- package/src/RadioGroup.tsx +533 -121
- package/src/RangeCalendar.tsx +249 -150
- package/src/RouterProvider.tsx +223 -0
- package/src/SearchField.tsx +460 -133
- package/src/Select.tsx +804 -233
- package/src/SelectionIndicator.tsx +108 -0
- package/src/Separator.tsx +47 -49
- package/src/SharedElementTransition.tsx +264 -0
- package/src/Slider.tsx +148 -98
- package/src/StepList.tsx +272 -0
- package/src/Switch.tsx +93 -46
- package/src/Table.tsx +1551 -225
- package/src/Tabs.tsx +377 -123
- package/src/TagGroup.tsx +233 -135
- package/src/Text.tsx +18 -0
- package/src/TextField.tsx +413 -86
- package/src/TimeField.tsx +232 -222
- package/src/Toast.tsx +306 -160
- package/src/ToggleButton.tsx +169 -0
- package/src/ToggleButtonGroup.tsx +141 -0
- package/src/Toolbar.tsx +61 -70
- package/src/Tooltip.tsx +473 -116
- package/src/Tree.tsx +1514 -175
- package/src/Virtualizer.tsx +730 -0
- package/src/VirtualizerLayouts.ts +280 -0
- package/src/VisuallyHidden.tsx +32 -38
- package/src/contexts.ts +29 -36
- package/src/index.ts +972 -620
- package/src/useDragAndDrop.ts +367 -0
- package/src/utils.tsx +69 -50
- package/src/virtualizer/Layout.ts +192 -0
- package/dist/index.ssr.js +0 -9785
- package/dist/index.ssr.js.map +0 -7
package/src/ComboBox.tsx
CHANGED
|
@@ -10,28 +10,33 @@ import {
|
|
|
10
10
|
type Accessor,
|
|
11
11
|
createContext,
|
|
12
12
|
createMemo,
|
|
13
|
+
onCleanup,
|
|
13
14
|
splitProps,
|
|
14
15
|
useContext,
|
|
15
16
|
For,
|
|
16
17
|
Show,
|
|
17
|
-
} from
|
|
18
|
+
} from "solid-js";
|
|
18
19
|
import {
|
|
19
20
|
createComboBox,
|
|
20
21
|
createListBox,
|
|
21
22
|
createOption,
|
|
23
|
+
getComboBoxData,
|
|
22
24
|
createHover,
|
|
23
25
|
createInteractOutside,
|
|
26
|
+
mergeProps,
|
|
24
27
|
type AriaComboBoxProps,
|
|
28
|
+
type AriaListBoxProps,
|
|
25
29
|
type AriaOptionProps,
|
|
26
|
-
} from
|
|
30
|
+
} from "@proyecto-viviana/solidaria";
|
|
27
31
|
import {
|
|
28
32
|
createComboBoxState,
|
|
29
33
|
defaultContainsFilter,
|
|
30
34
|
type ComboBoxState,
|
|
35
|
+
type ListState,
|
|
31
36
|
type Key,
|
|
32
37
|
type FilterFn,
|
|
33
38
|
type MenuTriggerAction,
|
|
34
|
-
} from
|
|
39
|
+
} from "@proyecto-viviana/solid-stately";
|
|
35
40
|
import {
|
|
36
41
|
type RenderChildren,
|
|
37
42
|
type ClassNameOrFunction,
|
|
@@ -39,11 +44,22 @@ import {
|
|
|
39
44
|
type SlotProps,
|
|
40
45
|
useRenderProps,
|
|
41
46
|
filterDOMProps,
|
|
42
|
-
} from
|
|
47
|
+
} from "./utils";
|
|
48
|
+
import {
|
|
49
|
+
SelectionIndicatorContext,
|
|
50
|
+
type SelectionIndicatorContextValue,
|
|
51
|
+
} from "./SelectionIndicator";
|
|
52
|
+
|
|
53
|
+
type RefLike<T> = ((el: T) => void) | { current?: T | null } | undefined;
|
|
43
54
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
function assignRef<T>(ref: RefLike<T>, el: T): void {
|
|
56
|
+
if (!ref) return;
|
|
57
|
+
if (typeof ref === "function") {
|
|
58
|
+
ref(el);
|
|
59
|
+
} else {
|
|
60
|
+
ref.current = el;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
47
63
|
|
|
48
64
|
export interface ComboBoxRenderProps {
|
|
49
65
|
/** Whether the combobox listbox is open. */
|
|
@@ -56,17 +72,21 @@ export interface ComboBoxRenderProps {
|
|
|
56
72
|
isDisabled: boolean;
|
|
57
73
|
/** Whether the combobox is required. */
|
|
58
74
|
isRequired: boolean;
|
|
75
|
+
/** Whether the combobox is invalid. */
|
|
76
|
+
isInvalid: boolean;
|
|
77
|
+
/** Whether the combobox is read only. */
|
|
78
|
+
isReadOnly: boolean;
|
|
59
79
|
/** Whether a value is selected. */
|
|
60
80
|
isSelected: boolean;
|
|
61
81
|
/** The current input value. */
|
|
62
82
|
inputValue: string;
|
|
63
83
|
}
|
|
64
84
|
|
|
65
|
-
export interface ComboBoxProps<T>
|
|
66
|
-
extends Omit<AriaComboBoxProps, 'children'>,
|
|
67
|
-
SlotProps {
|
|
85
|
+
export interface ComboBoxProps<T> extends Omit<AriaComboBoxProps, "children">, SlotProps {
|
|
68
86
|
/** The items to render in the combobox. */
|
|
69
|
-
items
|
|
87
|
+
items?: T[];
|
|
88
|
+
/** The default items to render in the combobox when uncontrolled. */
|
|
89
|
+
defaultItems?: T[];
|
|
70
90
|
/** Function to get the key from an item. */
|
|
71
91
|
getKey?: (item: T) => Key;
|
|
72
92
|
/** Function to get the text value from an item. */
|
|
@@ -75,12 +95,20 @@ export interface ComboBoxProps<T>
|
|
|
75
95
|
getDisabled?: (item: T) => boolean;
|
|
76
96
|
/** Keys of disabled items. */
|
|
77
97
|
disabledKeys?: Iterable<Key>;
|
|
78
|
-
/** The
|
|
98
|
+
/** The selection mode for the combobox. */
|
|
99
|
+
selectionMode?: "single" | "multiple";
|
|
100
|
+
/** The currently selected key (controlled, single mode). */
|
|
79
101
|
selectedKey?: Key | null;
|
|
80
|
-
/** The default selected key (uncontrolled). */
|
|
102
|
+
/** The default selected key (uncontrolled, single mode). */
|
|
81
103
|
defaultSelectedKey?: Key | null;
|
|
82
|
-
/**
|
|
104
|
+
/** The currently selected keys (controlled, multiple mode). */
|
|
105
|
+
selectedKeys?: Iterable<Key>;
|
|
106
|
+
/** The default selected keys (uncontrolled, multiple mode). */
|
|
107
|
+
defaultSelectedKeys?: Iterable<Key>;
|
|
108
|
+
/** Handler called when selection changes (single mode). */
|
|
83
109
|
onSelectionChange?: (key: Key | null) => void;
|
|
110
|
+
/** Handler called when selection changes (multiple mode). */
|
|
111
|
+
onSelectionChangeMultiple?: (keys: Set<Key>) => void;
|
|
84
112
|
/** The current input value (controlled). */
|
|
85
113
|
inputValue?: string;
|
|
86
114
|
/** The default input value (uncontrolled). */
|
|
@@ -100,15 +128,29 @@ export interface ComboBoxProps<T>
|
|
|
100
128
|
/** Whether to allow an empty collection (show listbox even with no matches). */
|
|
101
129
|
allowsEmptyCollection?: boolean;
|
|
102
130
|
/** The trigger mechanism for the combobox menu. */
|
|
103
|
-
menuTrigger?:
|
|
131
|
+
menuTrigger?: "focus" | "input" | "manual";
|
|
104
132
|
/** The name of the combobox, used when submitting an HTML form. */
|
|
105
133
|
name?: string;
|
|
134
|
+
/**
|
|
135
|
+
* Controls what value is submitted in forms.
|
|
136
|
+
* - 'key': submit the selected key via hidden input (default)
|
|
137
|
+
* - 'text': submit the text input value
|
|
138
|
+
*
|
|
139
|
+
* When allowsCustomValue is true, formValue is forced to 'text'.
|
|
140
|
+
*/
|
|
141
|
+
formValue?: "key" | "text";
|
|
106
142
|
/** The children of the component (compound components: ComboBoxInput, ComboBoxButton, ComboBoxListBox). */
|
|
107
|
-
children:
|
|
143
|
+
children: RenderChildren<ComboBoxRenderProps>;
|
|
108
144
|
/** The CSS className for the element. */
|
|
109
145
|
class?: ClassNameOrFunction<ComboBoxRenderProps>;
|
|
110
146
|
/** The inline style for the element. */
|
|
111
147
|
style?: StyleOrFunction<ComboBoxRenderProps>;
|
|
148
|
+
/** Ref for the root combobox element. */
|
|
149
|
+
ref?: RefLike<HTMLDivElement>;
|
|
150
|
+
/** Internal alias for libraries that wrap ComboBox and need a root ref. */
|
|
151
|
+
rootRef?: RefLike<HTMLDivElement>;
|
|
152
|
+
/** Slot definitions provided through ComboBoxContext. */
|
|
153
|
+
slots?: Record<string, Partial<ComboBoxProps<T>>>;
|
|
112
154
|
}
|
|
113
155
|
|
|
114
156
|
export interface ComboBoxInputRenderProps {
|
|
@@ -135,6 +177,33 @@ export interface ComboBoxInputProps extends SlotProps {
|
|
|
135
177
|
style?: StyleOrFunction<ComboBoxInputRenderProps>;
|
|
136
178
|
}
|
|
137
179
|
|
|
180
|
+
export interface ComboBoxLabelProps extends SlotProps {
|
|
181
|
+
/** The children of the label element. */
|
|
182
|
+
children?: JSX.Element;
|
|
183
|
+
/** The CSS className for the element. */
|
|
184
|
+
class?: string;
|
|
185
|
+
/** The inline style for the element. */
|
|
186
|
+
style?: JSX.CSSProperties;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface ComboBoxDescriptionProps extends SlotProps {
|
|
190
|
+
/** The children of the description element. */
|
|
191
|
+
children?: JSX.Element;
|
|
192
|
+
/** The CSS className for the element. */
|
|
193
|
+
class?: string;
|
|
194
|
+
/** The inline style for the element. */
|
|
195
|
+
style?: JSX.CSSProperties;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export interface ComboBoxErrorMessageProps extends SlotProps {
|
|
199
|
+
/** The children of the error message element. */
|
|
200
|
+
children?: JSX.Element;
|
|
201
|
+
/** The CSS className for the element. */
|
|
202
|
+
class?: string;
|
|
203
|
+
/** The inline style for the element. */
|
|
204
|
+
style?: JSX.CSSProperties;
|
|
205
|
+
}
|
|
206
|
+
|
|
138
207
|
export interface ComboBoxButtonRenderProps {
|
|
139
208
|
/** Whether the combobox is open. */
|
|
140
209
|
isOpen: boolean;
|
|
@@ -148,6 +217,21 @@ export interface ComboBoxButtonRenderProps {
|
|
|
148
217
|
isDisabled: boolean;
|
|
149
218
|
}
|
|
150
219
|
|
|
220
|
+
export interface ComboBoxValueRenderProps {
|
|
221
|
+
textValue: string;
|
|
222
|
+
isPlaceholder: boolean;
|
|
223
|
+
selectedItems: unknown[];
|
|
224
|
+
selectedText: string;
|
|
225
|
+
state: ComboBoxState<unknown>;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export interface ComboBoxValueProps extends SlotProps {
|
|
229
|
+
children?: RenderChildren<ComboBoxValueRenderProps>;
|
|
230
|
+
class?: ClassNameOrFunction<ComboBoxValueRenderProps>;
|
|
231
|
+
style?: StyleOrFunction<ComboBoxValueRenderProps>;
|
|
232
|
+
placeholder?: JSX.Element;
|
|
233
|
+
}
|
|
234
|
+
|
|
151
235
|
export interface ComboBoxButtonProps extends SlotProps {
|
|
152
236
|
/** The children of the button. */
|
|
153
237
|
children?: RenderChildren<ComboBoxButtonRenderProps>;
|
|
@@ -187,8 +271,7 @@ export interface ComboBoxOptionRenderProps {
|
|
|
187
271
|
}
|
|
188
272
|
|
|
189
273
|
export interface ComboBoxOptionProps<T>
|
|
190
|
-
extends Omit<AriaOptionProps,
|
|
191
|
-
SlotProps {
|
|
274
|
+
extends Omit<AriaOptionProps, "children" | "key">, SlotProps {
|
|
192
275
|
/** The unique key for the option. */
|
|
193
276
|
id: Key;
|
|
194
277
|
/** The item value. */
|
|
@@ -201,18 +284,18 @@ export interface ComboBoxOptionProps<T>
|
|
|
201
284
|
style?: StyleOrFunction<ComboBoxOptionRenderProps>;
|
|
202
285
|
/** The text value of the option (for typeahead). */
|
|
203
286
|
textValue?: string;
|
|
287
|
+
/** Handler called when the option is activated. */
|
|
288
|
+
onAction?: () => void;
|
|
204
289
|
}
|
|
205
290
|
|
|
206
|
-
// ============================================
|
|
207
|
-
// CONTEXT
|
|
208
|
-
// ============================================
|
|
209
|
-
|
|
210
291
|
interface ComboBoxContextValue<T> {
|
|
211
292
|
state: ComboBoxState<T>;
|
|
212
|
-
inputProps: JSX.InputHTMLAttributes<HTMLInputElement>;
|
|
213
|
-
buttonProps: JSX.HTMLAttributes<HTMLElement>;
|
|
214
|
-
listBoxProps: JSX.HTMLAttributes<HTMLElement>;
|
|
215
|
-
labelProps: JSX.HTMLAttributes<HTMLElement>;
|
|
293
|
+
inputProps: () => JSX.InputHTMLAttributes<HTMLInputElement>;
|
|
294
|
+
buttonProps: () => JSX.HTMLAttributes<HTMLElement>;
|
|
295
|
+
listBoxProps: () => JSX.HTMLAttributes<HTMLElement>;
|
|
296
|
+
labelProps: () => JSX.HTMLAttributes<HTMLElement>;
|
|
297
|
+
descriptionProps: () => JSX.HTMLAttributes<HTMLElement>;
|
|
298
|
+
errorMessageProps: () => JSX.HTMLAttributes<HTMLElement>;
|
|
216
299
|
isOpen: Accessor<boolean>;
|
|
217
300
|
isFocused: Accessor<boolean>;
|
|
218
301
|
isFocusVisible: Accessor<boolean>;
|
|
@@ -221,54 +304,70 @@ interface ComboBoxContextValue<T> {
|
|
|
221
304
|
setInputRef: (el: HTMLInputElement | null) => void;
|
|
222
305
|
buttonRef: () => HTMLElement | null;
|
|
223
306
|
setButtonRef: (el: HTMLElement | null) => void;
|
|
307
|
+
triggerRef: () => HTMLElement | null;
|
|
308
|
+
setTriggerRef: (el: HTMLElement | null) => void;
|
|
309
|
+
listBoxRef: () => HTMLElement | null;
|
|
310
|
+
setListBoxRef: (el: HTMLElement | null) => void;
|
|
311
|
+
slots?: Record<string, Partial<ComboBoxProps<T>>>;
|
|
224
312
|
}
|
|
225
313
|
|
|
226
314
|
export const ComboBoxContext = createContext<ComboBoxContextValue<unknown> | null>(null);
|
|
227
315
|
export const ComboBoxStateContext = createContext<ComboBoxState<unknown> | null>(null);
|
|
228
|
-
|
|
229
|
-
// ============================================
|
|
230
|
-
// COMPONENTS
|
|
231
|
-
// ============================================
|
|
316
|
+
export const ComboBoxValueContext = ComboBoxContext;
|
|
232
317
|
|
|
233
318
|
/**
|
|
234
319
|
* A combobox combines a text input with a listbox, allowing users to filter a list of options.
|
|
235
320
|
*/
|
|
236
321
|
export function ComboBox<T>(props: ComboBoxProps<T>): JSX.Element {
|
|
322
|
+
const parentContext = useContext(ComboBoxContext) as ComboBoxContextValue<T> | null;
|
|
323
|
+
const contextSlotProps = parentContext?.slots?.[props.slot ?? "default"];
|
|
324
|
+
const mergedComboBoxProps = contextSlotProps
|
|
325
|
+
? (mergeProps(contextSlotProps, props) as ComboBoxProps<T>)
|
|
326
|
+
: props;
|
|
237
327
|
const [local, stateProps, ariaProps] = splitProps(
|
|
238
|
-
|
|
239
|
-
[
|
|
328
|
+
mergedComboBoxProps,
|
|
329
|
+
["class", "style", "slot", "children", "ref", "rootRef", "slots"],
|
|
240
330
|
[
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
331
|
+
"items",
|
|
332
|
+
"defaultItems",
|
|
333
|
+
"getKey",
|
|
334
|
+
"getTextValue",
|
|
335
|
+
"getDisabled",
|
|
336
|
+
"disabledKeys",
|
|
337
|
+
"selectionMode",
|
|
338
|
+
"selectedKey",
|
|
339
|
+
"defaultSelectedKey",
|
|
340
|
+
"selectedKeys",
|
|
341
|
+
"defaultSelectedKeys",
|
|
342
|
+
"onSelectionChange",
|
|
343
|
+
"onSelectionChangeMultiple",
|
|
344
|
+
"inputValue",
|
|
345
|
+
"defaultInputValue",
|
|
346
|
+
"onInputChange",
|
|
347
|
+
"isOpen",
|
|
348
|
+
"defaultOpen",
|
|
349
|
+
"onOpenChange",
|
|
350
|
+
"defaultFilter",
|
|
351
|
+
"allowsCustomValue",
|
|
352
|
+
"allowsEmptyCollection",
|
|
353
|
+
"menuTrigger",
|
|
354
|
+
"name",
|
|
355
|
+
"formValue",
|
|
356
|
+
],
|
|
261
357
|
);
|
|
262
358
|
|
|
263
|
-
// Refs
|
|
264
359
|
let inputRef: HTMLInputElement | null = null;
|
|
265
360
|
let buttonRef: HTMLElement | null = null;
|
|
361
|
+
let triggerRef: HTMLElement | null = null;
|
|
362
|
+
let listBoxRef: HTMLElement | null = null;
|
|
266
363
|
|
|
267
|
-
// Create combobox state
|
|
268
364
|
const state = createComboBoxState<T>({
|
|
269
365
|
get items() {
|
|
270
366
|
return stateProps.items;
|
|
271
367
|
},
|
|
368
|
+
get defaultItems() {
|
|
369
|
+
return stateProps.defaultItems;
|
|
370
|
+
},
|
|
272
371
|
get getKey() {
|
|
273
372
|
return stateProps.getKey;
|
|
274
373
|
},
|
|
@@ -281,15 +380,27 @@ export function ComboBox<T>(props: ComboBoxProps<T>): JSX.Element {
|
|
|
281
380
|
get disabledKeys() {
|
|
282
381
|
return stateProps.disabledKeys;
|
|
283
382
|
},
|
|
383
|
+
get selectionMode() {
|
|
384
|
+
return stateProps.selectionMode;
|
|
385
|
+
},
|
|
284
386
|
get selectedKey() {
|
|
285
387
|
return stateProps.selectedKey;
|
|
286
388
|
},
|
|
287
389
|
get defaultSelectedKey() {
|
|
288
390
|
return stateProps.defaultSelectedKey;
|
|
289
391
|
},
|
|
392
|
+
get selectedKeys() {
|
|
393
|
+
return stateProps.selectedKeys;
|
|
394
|
+
},
|
|
395
|
+
get defaultSelectedKeys() {
|
|
396
|
+
return stateProps.defaultSelectedKeys;
|
|
397
|
+
},
|
|
290
398
|
get onSelectionChange() {
|
|
291
399
|
return stateProps.onSelectionChange;
|
|
292
400
|
},
|
|
401
|
+
get onSelectionChangeMultiple() {
|
|
402
|
+
return stateProps.onSelectionChangeMultiple;
|
|
403
|
+
},
|
|
293
404
|
get inputValue() {
|
|
294
405
|
return stateProps.inputValue;
|
|
295
406
|
},
|
|
@@ -331,76 +442,121 @@ export function ComboBox<T>(props: ComboBoxProps<T>): JSX.Element {
|
|
|
331
442
|
},
|
|
332
443
|
});
|
|
333
444
|
|
|
334
|
-
|
|
445
|
+
const effectiveFormValue = createMemo<"key" | "text">(() => {
|
|
446
|
+
if (stateProps.allowsCustomValue) {
|
|
447
|
+
return "text";
|
|
448
|
+
}
|
|
449
|
+
return stateProps.formValue ?? "key";
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
const comboBoxAriaProps = createMemo(() => {
|
|
453
|
+
const cleanProps: Record<string, unknown> = {};
|
|
454
|
+
for (const key in ariaProps) {
|
|
455
|
+
if (!key.startsWith("data-")) {
|
|
456
|
+
cleanProps[key] = (ariaProps as Record<string, unknown>)[key];
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return cleanProps as AriaComboBoxProps;
|
|
460
|
+
});
|
|
461
|
+
|
|
335
462
|
const comboBoxAria = createComboBox<T>(
|
|
336
|
-
|
|
463
|
+
() => ({
|
|
464
|
+
...comboBoxAriaProps(),
|
|
465
|
+
get name() {
|
|
466
|
+
return effectiveFormValue() === "text" ? stateProps.name : undefined;
|
|
467
|
+
},
|
|
468
|
+
}),
|
|
337
469
|
state,
|
|
338
470
|
() => inputRef,
|
|
339
|
-
() => buttonRef
|
|
471
|
+
() => buttonRef,
|
|
472
|
+
() => listBoxRef,
|
|
340
473
|
);
|
|
341
474
|
|
|
342
|
-
// Create hover for wrapper
|
|
343
475
|
const { isHovered, hoverProps } = createHover({
|
|
344
476
|
get isDisabled() {
|
|
345
477
|
return ariaProps.isDisabled;
|
|
346
478
|
},
|
|
347
479
|
});
|
|
348
480
|
|
|
349
|
-
// Render props values
|
|
350
481
|
const renderValues = createMemo<ComboBoxRenderProps>(() => ({
|
|
351
482
|
isOpen: comboBoxAria.isOpen(),
|
|
352
483
|
isFocused: comboBoxAria.isFocused(),
|
|
353
484
|
isFocusVisible: comboBoxAria.isFocusVisible(),
|
|
354
485
|
isDisabled: !!ariaProps.isDisabled,
|
|
355
486
|
isRequired: !!ariaProps.isRequired,
|
|
487
|
+
isInvalid: !!ariaProps.isInvalid,
|
|
488
|
+
isReadOnly: !!ariaProps.isReadOnly,
|
|
356
489
|
isSelected: state.selectedKey() != null,
|
|
357
490
|
inputValue: state.inputValue(),
|
|
358
491
|
}));
|
|
359
492
|
|
|
360
|
-
// Resolve render props
|
|
361
493
|
const renderProps = useRenderProps(
|
|
362
494
|
{
|
|
363
495
|
class: local.class,
|
|
364
496
|
style: local.style,
|
|
365
|
-
defaultClassName:
|
|
497
|
+
defaultClassName: "solidaria-ComboBox",
|
|
366
498
|
},
|
|
367
|
-
renderValues
|
|
499
|
+
renderValues,
|
|
368
500
|
);
|
|
369
501
|
|
|
370
|
-
// Filter DOM props
|
|
371
502
|
const domProps = createMemo(() => {
|
|
372
503
|
const filtered = filterDOMProps(ariaProps as Record<string, unknown>, { global: true });
|
|
373
504
|
return filtered;
|
|
374
505
|
});
|
|
375
506
|
|
|
376
|
-
// Remove ref from hover props
|
|
377
507
|
const cleanHoverProps = () => {
|
|
378
508
|
const { ref: _ref, ...rest } = hoverProps as Record<string, unknown>;
|
|
379
509
|
return rest;
|
|
380
510
|
};
|
|
381
511
|
|
|
512
|
+
const ComboBoxChildren = () =>
|
|
513
|
+
typeof local.children === "function"
|
|
514
|
+
? (local.children as (values: ComboBoxRenderProps) => JSX.Element)(renderValues())
|
|
515
|
+
: local.children;
|
|
516
|
+
|
|
382
517
|
return (
|
|
383
518
|
<ComboBoxContext.Provider
|
|
384
|
-
value={
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
519
|
+
value={
|
|
520
|
+
{
|
|
521
|
+
state,
|
|
522
|
+
inputProps: () => comboBoxAria.inputProps,
|
|
523
|
+
buttonProps: () => comboBoxAria.buttonProps,
|
|
524
|
+
listBoxProps: () => comboBoxAria.listBoxProps,
|
|
525
|
+
labelProps: () => comboBoxAria.labelProps,
|
|
526
|
+
descriptionProps: () => comboBoxAria.descriptionProps,
|
|
527
|
+
errorMessageProps: () => comboBoxAria.errorMessageProps,
|
|
528
|
+
isOpen: comboBoxAria.isOpen,
|
|
529
|
+
isFocused: comboBoxAria.isFocused,
|
|
530
|
+
isFocusVisible: comboBoxAria.isFocusVisible,
|
|
531
|
+
items: stateProps.items ?? stateProps.defaultItems ?? [],
|
|
532
|
+
inputRef: () => inputRef,
|
|
533
|
+
setInputRef: (el) => {
|
|
534
|
+
inputRef = el;
|
|
535
|
+
},
|
|
536
|
+
buttonRef: () => buttonRef,
|
|
537
|
+
setButtonRef: (el) => {
|
|
538
|
+
buttonRef = el;
|
|
539
|
+
},
|
|
540
|
+
triggerRef: () => triggerRef,
|
|
541
|
+
setTriggerRef: (el) => {
|
|
542
|
+
triggerRef = el;
|
|
543
|
+
},
|
|
544
|
+
listBoxRef: () => listBoxRef,
|
|
545
|
+
setListBoxRef: (el) => {
|
|
546
|
+
listBoxRef = el;
|
|
547
|
+
},
|
|
548
|
+
slots: local.slots,
|
|
549
|
+
} as ComboBoxContextValue<unknown>
|
|
550
|
+
}
|
|
399
551
|
>
|
|
400
552
|
<ComboBoxStateContext.Provider value={state}>
|
|
401
553
|
<div
|
|
402
554
|
{...domProps()}
|
|
403
555
|
{...cleanHoverProps()}
|
|
556
|
+
ref={(el) => {
|
|
557
|
+
assignRef(local.ref, el);
|
|
558
|
+
assignRef(local.rootRef, el);
|
|
559
|
+
}}
|
|
404
560
|
class={renderProps.class()}
|
|
405
561
|
style={renderProps.style()}
|
|
406
562
|
data-open={comboBoxAria.isOpen() || undefined}
|
|
@@ -408,44 +564,114 @@ export function ComboBox<T>(props: ComboBoxProps<T>): JSX.Element {
|
|
|
408
564
|
data-focus-visible={comboBoxAria.isFocusVisible() || undefined}
|
|
409
565
|
data-disabled={ariaProps.isDisabled || undefined}
|
|
410
566
|
data-required={ariaProps.isRequired || undefined}
|
|
567
|
+
data-invalid={ariaProps.isInvalid || undefined}
|
|
568
|
+
data-readonly={ariaProps.isReadOnly || undefined}
|
|
411
569
|
data-hovered={isHovered() || undefined}
|
|
570
|
+
slot={local.slot}
|
|
412
571
|
>
|
|
413
|
-
{/* Hidden input for form submission */}
|
|
414
|
-
<Show when={stateProps.name}>
|
|
572
|
+
{/* Hidden input for key-based form submission parity */}
|
|
573
|
+
<Show when={stateProps.name && effectiveFormValue() === "key"}>
|
|
415
574
|
<input
|
|
416
575
|
type="hidden"
|
|
417
576
|
name={stateProps.name}
|
|
418
|
-
|
|
577
|
+
form={ariaProps.form}
|
|
578
|
+
value={state.selectedKey()?.toString() ?? ""}
|
|
419
579
|
/>
|
|
420
580
|
</Show>
|
|
421
|
-
|
|
581
|
+
<ComboBoxChildren />
|
|
422
582
|
</div>
|
|
423
583
|
</ComboBoxStateContext.Provider>
|
|
424
584
|
</ComboBoxContext.Provider>
|
|
425
585
|
);
|
|
426
586
|
}
|
|
427
587
|
|
|
588
|
+
/**
|
|
589
|
+
* Label element for a combobox.
|
|
590
|
+
*/
|
|
591
|
+
export function ComboBoxLabel(props: ComboBoxLabelProps): JSX.Element {
|
|
592
|
+
const [local, domProps] = splitProps(props, ["class", "style", "slot", "children"]);
|
|
593
|
+
|
|
594
|
+
const context = useContext(ComboBoxContext);
|
|
595
|
+
if (!context) {
|
|
596
|
+
throw new Error("ComboBoxLabel must be used within a ComboBox");
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const cleanLabelProps = () => {
|
|
600
|
+
const { ref: _ref, ...rest } = context.labelProps() as Record<string, unknown>;
|
|
601
|
+
return rest;
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
return (
|
|
605
|
+
<label {...domProps} {...cleanLabelProps()} class={local.class} style={local.style}>
|
|
606
|
+
{local.children}
|
|
607
|
+
</label>
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Description element for a combobox.
|
|
613
|
+
*/
|
|
614
|
+
export function ComboBoxDescription(props: ComboBoxDescriptionProps): JSX.Element {
|
|
615
|
+
const [local, domProps] = splitProps(props, ["class", "style", "slot", "children"]);
|
|
616
|
+
|
|
617
|
+
const context = useContext(ComboBoxContext);
|
|
618
|
+
if (!context) {
|
|
619
|
+
throw new Error("ComboBoxDescription must be used within a ComboBox");
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const cleanDescriptionProps = () => {
|
|
623
|
+
const { ref: _ref, ...rest } = context.descriptionProps() as Record<string, unknown>;
|
|
624
|
+
return rest;
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
return (
|
|
628
|
+
<div {...domProps} {...cleanDescriptionProps()} class={local.class} style={local.style}>
|
|
629
|
+
{local.children}
|
|
630
|
+
</div>
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Error message element for a combobox.
|
|
636
|
+
*/
|
|
637
|
+
export function ComboBoxErrorMessage(props: ComboBoxErrorMessageProps): JSX.Element {
|
|
638
|
+
const [local, domProps] = splitProps(props, ["class", "style", "slot", "children"]);
|
|
639
|
+
|
|
640
|
+
const context = useContext(ComboBoxContext);
|
|
641
|
+
if (!context) {
|
|
642
|
+
throw new Error("ComboBoxErrorMessage must be used within a ComboBox");
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const cleanErrorMessageProps = () => {
|
|
646
|
+
const { ref: _ref, ...rest } = context.errorMessageProps() as Record<string, unknown>;
|
|
647
|
+
return rest;
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
return (
|
|
651
|
+
<div {...domProps} {...cleanErrorMessageProps()} class={local.class} style={local.style}>
|
|
652
|
+
{local.children}
|
|
653
|
+
</div>
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
|
|
428
657
|
/**
|
|
429
658
|
* The text input for a combobox.
|
|
430
659
|
*/
|
|
431
660
|
export function ComboBoxInput(props: ComboBoxInputProps): JSX.Element {
|
|
432
|
-
const [local] = splitProps(props, [
|
|
661
|
+
const [local, domProps] = splitProps(props, ["class", "style", "slot", "children"]);
|
|
433
662
|
|
|
434
|
-
// Get context
|
|
435
663
|
const context = useContext(ComboBoxContext);
|
|
436
664
|
if (!context) {
|
|
437
|
-
throw new Error(
|
|
665
|
+
throw new Error("ComboBoxInput must be used within a ComboBox");
|
|
438
666
|
}
|
|
439
|
-
const {
|
|
667
|
+
const { isOpen, isFocused, isFocusVisible, state, setInputRef } = context;
|
|
440
668
|
|
|
441
|
-
// Create hover
|
|
442
669
|
const { isHovered, hoverProps } = createHover({
|
|
443
670
|
get isDisabled() {
|
|
444
671
|
return state.isDisabled;
|
|
445
672
|
},
|
|
446
673
|
});
|
|
447
674
|
|
|
448
|
-
// Render props values
|
|
449
675
|
const renderValues = createMemo<ComboBoxInputRenderProps>(() => ({
|
|
450
676
|
isOpen: isOpen(),
|
|
451
677
|
isFocused: isFocused(),
|
|
@@ -455,20 +681,18 @@ export function ComboBoxInput(props: ComboBoxInputProps): JSX.Element {
|
|
|
455
681
|
inputValue: state.inputValue(),
|
|
456
682
|
}));
|
|
457
683
|
|
|
458
|
-
// Resolve render props
|
|
459
684
|
const renderProps = useRenderProps(
|
|
460
685
|
{
|
|
461
|
-
children:
|
|
686
|
+
children: local.children,
|
|
462
687
|
class: local.class,
|
|
463
688
|
style: local.style,
|
|
464
|
-
defaultClassName:
|
|
689
|
+
defaultClassName: "solidaria-ComboBox-input",
|
|
465
690
|
},
|
|
466
|
-
renderValues
|
|
691
|
+
renderValues,
|
|
467
692
|
);
|
|
468
693
|
|
|
469
|
-
// Remove ref from spread props
|
|
470
694
|
const cleanInputProps = () => {
|
|
471
|
-
const { ref: _ref1, value: _value, ...rest } = inputProps as Record<string, unknown>;
|
|
695
|
+
const { ref: _ref1, value: _value, ...rest } = context.inputProps() as Record<string, unknown>;
|
|
472
696
|
return rest;
|
|
473
697
|
};
|
|
474
698
|
const cleanHoverProps = () => {
|
|
@@ -478,6 +702,7 @@ export function ComboBoxInput(props: ComboBoxInputProps): JSX.Element {
|
|
|
478
702
|
|
|
479
703
|
return (
|
|
480
704
|
<input
|
|
705
|
+
{...domProps}
|
|
481
706
|
ref={(el) => setInputRef(el)}
|
|
482
707
|
{...cleanInputProps()}
|
|
483
708
|
{...cleanHoverProps()}
|
|
@@ -493,52 +718,101 @@ export function ComboBoxInput(props: ComboBoxInputProps): JSX.Element {
|
|
|
493
718
|
);
|
|
494
719
|
}
|
|
495
720
|
|
|
721
|
+
export function ComboBoxValue(props: ComboBoxValueProps): JSX.Element {
|
|
722
|
+
const context = useContext(ComboBoxContext);
|
|
723
|
+
if (!context) {
|
|
724
|
+
throw new Error("ComboBoxValue must be used within a ComboBox");
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const state = context.state;
|
|
728
|
+
const isMulti = createMemo(() => state.selectionMode() === "multiple");
|
|
729
|
+
const selectedItem = createMemo(() => state.selectedItem());
|
|
730
|
+
const selectedItems = createMemo(() => {
|
|
731
|
+
if (isMulti()) {
|
|
732
|
+
return state.selectedItems().map((node) => node.value ?? null);
|
|
733
|
+
}
|
|
734
|
+
const item = selectedItem();
|
|
735
|
+
return item ? [item.value ?? null] : [];
|
|
736
|
+
});
|
|
737
|
+
const selectedText = createMemo(() => {
|
|
738
|
+
if (isMulti()) {
|
|
739
|
+
const items = state.selectedItems();
|
|
740
|
+
return items.map((n) => n.textValue).join(", ");
|
|
741
|
+
}
|
|
742
|
+
return selectedItem()?.textValue ?? "";
|
|
743
|
+
});
|
|
744
|
+
const textValue = createMemo(() => selectedText() || state.inputValue() || "");
|
|
745
|
+
const isPlaceholder = createMemo(() => textValue().length === 0);
|
|
746
|
+
|
|
747
|
+
const renderProps = useRenderProps(
|
|
748
|
+
{
|
|
749
|
+
children: props.children,
|
|
750
|
+
class: props.class,
|
|
751
|
+
style: props.style,
|
|
752
|
+
defaultClassName: "solidaria-ComboBox-value",
|
|
753
|
+
},
|
|
754
|
+
() => ({
|
|
755
|
+
textValue: textValue(),
|
|
756
|
+
isPlaceholder: isPlaceholder(),
|
|
757
|
+
selectedItems: selectedItems(),
|
|
758
|
+
selectedText: selectedText(),
|
|
759
|
+
state: state as ComboBoxState<unknown>,
|
|
760
|
+
}),
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
return (
|
|
764
|
+
<span
|
|
765
|
+
class={renderProps.class()}
|
|
766
|
+
style={renderProps.style()}
|
|
767
|
+
data-placeholder={isPlaceholder() || undefined}
|
|
768
|
+
>
|
|
769
|
+
{props.children
|
|
770
|
+
? renderProps.renderChildren()
|
|
771
|
+
: isPlaceholder()
|
|
772
|
+
? props.placeholder
|
|
773
|
+
: textValue()}
|
|
774
|
+
</span>
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
|
|
496
778
|
/**
|
|
497
779
|
* The trigger button for a combobox.
|
|
498
780
|
*/
|
|
499
781
|
export function ComboBoxButton(props: ComboBoxButtonProps): JSX.Element {
|
|
500
|
-
const [local] = splitProps(props, [
|
|
782
|
+
const [local, domProps] = splitProps(props, ["class", "style", "slot", "children"]);
|
|
501
783
|
|
|
502
|
-
// Get context
|
|
503
784
|
const context = useContext(ComboBoxContext);
|
|
504
785
|
if (!context) {
|
|
505
|
-
throw new Error(
|
|
786
|
+
throw new Error("ComboBoxButton must be used within a ComboBox");
|
|
506
787
|
}
|
|
507
|
-
const {
|
|
788
|
+
const { isOpen, isFocused, state, setButtonRef } = context;
|
|
508
789
|
|
|
509
|
-
// Create hover
|
|
510
790
|
const { isHovered, hoverProps } = createHover({
|
|
511
791
|
get isDisabled() {
|
|
512
792
|
return state.isDisabled;
|
|
513
793
|
},
|
|
514
794
|
});
|
|
515
795
|
|
|
516
|
-
// Track pressed state
|
|
517
|
-
let isPressed = false;
|
|
518
|
-
|
|
519
|
-
// Render props values
|
|
520
796
|
const renderValues = createMemo<ComboBoxButtonRenderProps>(() => ({
|
|
521
797
|
isOpen: isOpen(),
|
|
522
798
|
isFocused: isFocused(),
|
|
523
799
|
isHovered: isHovered(),
|
|
524
|
-
isPressed,
|
|
800
|
+
isPressed: isOpen(),
|
|
525
801
|
isDisabled: state.isDisabled,
|
|
526
802
|
}));
|
|
527
803
|
|
|
528
|
-
// Resolve render props
|
|
529
804
|
const renderProps = useRenderProps(
|
|
530
805
|
{
|
|
531
|
-
children:
|
|
806
|
+
children: local.children,
|
|
532
807
|
class: local.class,
|
|
533
808
|
style: local.style,
|
|
534
|
-
defaultClassName:
|
|
809
|
+
defaultClassName: "solidaria-ComboBox-button",
|
|
535
810
|
},
|
|
536
|
-
renderValues
|
|
811
|
+
renderValues,
|
|
537
812
|
);
|
|
538
813
|
|
|
539
|
-
// Remove ref from spread props
|
|
540
814
|
const cleanButtonProps = () => {
|
|
541
|
-
const { ref: _ref1, ...rest } = buttonProps as Record<string, unknown>;
|
|
815
|
+
const { ref: _ref1, ...rest } = context.buttonProps() as Record<string, unknown>;
|
|
542
816
|
return rest;
|
|
543
817
|
};
|
|
544
818
|
const cleanHoverProps = () => {
|
|
@@ -548,12 +822,14 @@ export function ComboBoxButton(props: ComboBoxButtonProps): JSX.Element {
|
|
|
548
822
|
|
|
549
823
|
return (
|
|
550
824
|
<button
|
|
825
|
+
{...domProps}
|
|
551
826
|
ref={(el) => setButtonRef(el)}
|
|
552
827
|
{...cleanButtonProps()}
|
|
553
828
|
{...cleanHoverProps()}
|
|
554
829
|
class={renderProps.class()}
|
|
555
830
|
style={renderProps.style()}
|
|
556
831
|
data-open={isOpen() || undefined}
|
|
832
|
+
data-pressed={isOpen() || undefined}
|
|
557
833
|
data-focused={isFocused() || undefined}
|
|
558
834
|
data-hovered={isHovered() || undefined}
|
|
559
835
|
data-disabled={state.isDisabled || undefined}
|
|
@@ -567,29 +843,31 @@ export function ComboBoxButton(props: ComboBoxButtonProps): JSX.Element {
|
|
|
567
843
|
* The listbox popup for a combobox.
|
|
568
844
|
*/
|
|
569
845
|
export function ComboBoxListBox<T>(props: ComboBoxListBoxProps<T>): JSX.Element {
|
|
570
|
-
const [local] = splitProps(props, [
|
|
846
|
+
const [local, domProps] = splitProps(props, ["class", "style", "slot", "children"]);
|
|
571
847
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
throw new Error('ComboBoxListBox must be used within a ComboBox');
|
|
848
|
+
const rawContext = useContext(ComboBoxContext);
|
|
849
|
+
if (!rawContext) {
|
|
850
|
+
throw new Error("ComboBoxListBox must be used within a ComboBox");
|
|
576
851
|
}
|
|
577
|
-
const
|
|
578
|
-
const state
|
|
852
|
+
const context = rawContext as ComboBoxContextValue<T>;
|
|
853
|
+
const { state: comboBoxState, isOpen, inputRef, buttonRef, setListBoxRef } = context;
|
|
854
|
+
const state = comboBoxState;
|
|
579
855
|
|
|
580
|
-
// Ref for the listbox element (for click outside detection)
|
|
581
856
|
let listBoxRef: HTMLUListElement | undefined;
|
|
582
857
|
|
|
583
|
-
// Handle click outside to close combobox
|
|
584
858
|
createInteractOutside({
|
|
585
859
|
ref: () => listBoxRef ?? null,
|
|
586
860
|
onInteractOutside: (e) => {
|
|
587
861
|
// Don't close if clicking the input or button
|
|
588
862
|
const target = e.target as HTMLElement;
|
|
589
863
|
const input = inputRef();
|
|
864
|
+
const button = buttonRef();
|
|
590
865
|
if (input?.contains(target)) {
|
|
591
866
|
return;
|
|
592
867
|
}
|
|
868
|
+
if (button?.contains(target)) {
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
593
871
|
if (isOpen()) {
|
|
594
872
|
state.close();
|
|
595
873
|
}
|
|
@@ -601,51 +879,25 @@ export function ComboBoxListBox<T>(props: ComboBoxListBoxProps<T>): JSX.Element
|
|
|
601
879
|
|
|
602
880
|
// Create listbox aria props using ComboBoxState's ListState-compatible interface
|
|
603
881
|
const { listBoxProps } = createListBox(
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
collection: state.collection,
|
|
607
|
-
focusedKey: state.focusedKey,
|
|
608
|
-
setFocusedKey: state.setFocusedKey,
|
|
609
|
-
isFocused: state.isFocused,
|
|
610
|
-
setFocused: state.setFocused,
|
|
611
|
-
// Use state's built-in methods
|
|
612
|
-
selectionMode: state.selectionMode,
|
|
613
|
-
select: state.select,
|
|
614
|
-
isSelected: state.isSelected,
|
|
615
|
-
isDisabled: state.isKeyDisabled,
|
|
616
|
-
// Additional ListState interface requirements
|
|
617
|
-
selectedKeys: () => {
|
|
618
|
-
const key = state.selectedKey();
|
|
619
|
-
return key != null ? new Set([key]) : new Set();
|
|
620
|
-
},
|
|
621
|
-
disallowEmptySelection: () => true,
|
|
622
|
-
toggleSelection: state.select,
|
|
623
|
-
replaceSelection: state.select,
|
|
624
|
-
extendSelection: () => {},
|
|
625
|
-
selectAll: () => {},
|
|
626
|
-
clearSelection: () => state.setSelectedKey(null),
|
|
627
|
-
childFocusStrategy: () => null,
|
|
628
|
-
} as any
|
|
882
|
+
context.listBoxProps as unknown as AriaListBoxProps,
|
|
883
|
+
createComboBoxListStateAdapter(state),
|
|
629
884
|
);
|
|
630
885
|
|
|
631
|
-
// Render props values
|
|
632
886
|
const renderValues = createMemo<ComboBoxListBoxRenderProps>(() => ({
|
|
633
887
|
isFocused: state.isFocused(),
|
|
634
888
|
}));
|
|
635
889
|
|
|
636
|
-
// Resolve render props
|
|
637
890
|
const renderProps = useRenderProps(
|
|
638
891
|
{
|
|
639
892
|
class: local.class,
|
|
640
893
|
style: local.style,
|
|
641
|
-
defaultClassName:
|
|
894
|
+
defaultClassName: "solidaria-ComboBox-listbox",
|
|
642
895
|
},
|
|
643
|
-
renderValues
|
|
896
|
+
renderValues,
|
|
644
897
|
);
|
|
645
898
|
|
|
646
|
-
// Remove ref from spread props
|
|
647
899
|
const cleanContextProps = () => {
|
|
648
|
-
const { ref: _ref1, ...rest } =
|
|
900
|
+
const { ref: _ref1, ...rest } = context.listBoxProps() as Record<string, unknown>;
|
|
649
901
|
return rest;
|
|
650
902
|
};
|
|
651
903
|
const cleanListBoxProps = () => {
|
|
@@ -654,46 +906,53 @@ export function ComboBoxListBox<T>(props: ComboBoxListBoxProps<T>): JSX.Element
|
|
|
654
906
|
};
|
|
655
907
|
|
|
656
908
|
const items = () => Array.from(state.collection());
|
|
909
|
+
const getNodeValue = (node: { key: Key; value?: T | null; index?: number }): T | null => {
|
|
910
|
+
if (node.value != null) {
|
|
911
|
+
return node.value;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
return (
|
|
915
|
+
context.items.find((item, index) => {
|
|
916
|
+
const candidate = item as { key?: Key; id?: Key };
|
|
917
|
+
const key = candidate.key ?? candidate.id ?? index;
|
|
918
|
+
return key === node.key;
|
|
919
|
+
}) ?? null
|
|
920
|
+
);
|
|
921
|
+
};
|
|
657
922
|
|
|
658
|
-
|
|
659
|
-
// This is critical - if we don't prevent default, the input loses focus
|
|
660
|
-
// and the blur handler closes the menu before the click can be processed
|
|
661
|
-
// We need to attach this in the ref callback to use capture phase
|
|
662
|
-
const setupMouseDownHandler = (el: HTMLUListElement) => {
|
|
923
|
+
const setListBoxElement = (el: HTMLUListElement) => {
|
|
663
924
|
listBoxRef = el;
|
|
664
|
-
|
|
665
|
-
const mouseHandler = (e: MouseEvent) => {
|
|
666
|
-
e.preventDefault();
|
|
667
|
-
};
|
|
668
|
-
const pointerHandler = (e: PointerEvent) => {
|
|
669
|
-
e.preventDefault();
|
|
670
|
-
};
|
|
671
|
-
el.addEventListener('mousedown', mouseHandler, true); // capture phase
|
|
672
|
-
el.addEventListener('pointerdown', pointerHandler, true); // capture phase
|
|
673
|
-
}
|
|
925
|
+
setListBoxRef(el);
|
|
674
926
|
};
|
|
675
927
|
|
|
928
|
+
onCleanup(() => {
|
|
929
|
+
setListBoxRef(null);
|
|
930
|
+
});
|
|
931
|
+
|
|
676
932
|
return (
|
|
677
933
|
<Show when={isOpen()}>
|
|
678
934
|
<ul
|
|
679
|
-
|
|
935
|
+
{...domProps}
|
|
936
|
+
ref={setListBoxElement}
|
|
680
937
|
{...cleanContextProps()}
|
|
681
938
|
{...cleanListBoxProps()}
|
|
682
939
|
class={renderProps.class()}
|
|
683
940
|
style={renderProps.style()}
|
|
684
941
|
data-focused={state.isFocused() || undefined}
|
|
685
942
|
>
|
|
686
|
-
<Show
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
}>
|
|
943
|
+
<Show
|
|
944
|
+
when={local.children}
|
|
945
|
+
fallback={
|
|
946
|
+
<For each={items()}>
|
|
947
|
+
{(node) => <ComboBoxOption id={node.key}>{node.textValue}</ComboBoxOption>}
|
|
948
|
+
</For>
|
|
949
|
+
}
|
|
950
|
+
>
|
|
695
951
|
<For each={items()}>
|
|
696
|
-
{(node) =>
|
|
952
|
+
{(node) => {
|
|
953
|
+
const value = getNodeValue(node);
|
|
954
|
+
return value != null ? (local.children as Function)!(value) : null;
|
|
955
|
+
}}
|
|
697
956
|
</For>
|
|
698
957
|
</Show>
|
|
699
958
|
</ul>
|
|
@@ -706,119 +965,259 @@ export function ComboBoxListBox<T>(props: ComboBoxListBoxProps<T>): JSX.Element
|
|
|
706
965
|
*/
|
|
707
966
|
export function ComboBoxOption<T>(props: ComboBoxOptionProps<T>): JSX.Element {
|
|
708
967
|
const [local, ariaProps] = splitProps(props, [
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
968
|
+
"class",
|
|
969
|
+
"style",
|
|
970
|
+
"slot",
|
|
971
|
+
"id",
|
|
972
|
+
"item",
|
|
973
|
+
"textValue",
|
|
974
|
+
"onAction",
|
|
715
975
|
]);
|
|
716
976
|
|
|
717
|
-
|
|
718
|
-
const
|
|
719
|
-
if (!
|
|
720
|
-
throw new Error(
|
|
977
|
+
const stateContext = useContext(ComboBoxStateContext);
|
|
978
|
+
const comboBoxContext = useContext(ComboBoxContext);
|
|
979
|
+
if (!stateContext) {
|
|
980
|
+
throw new Error("ComboBoxOption must be used within a ComboBox");
|
|
721
981
|
}
|
|
722
|
-
const state =
|
|
982
|
+
const state = stateContext as ComboBoxState<T>;
|
|
983
|
+
const optionId = () => {
|
|
984
|
+
const listBoxId = getComboBoxData(state as ComboBoxState<unknown>)?.listBoxId;
|
|
985
|
+
return listBoxId ? `${listBoxId}-option-${local.id}` : String(local.id);
|
|
986
|
+
};
|
|
723
987
|
|
|
724
988
|
// Create option aria props using ComboBoxState's ListState-compatible interface
|
|
725
989
|
const optionAria = createOption<T>(
|
|
726
990
|
{
|
|
727
991
|
key: local.id,
|
|
992
|
+
get optionId() {
|
|
993
|
+
return optionId();
|
|
994
|
+
},
|
|
728
995
|
get isDisabled() {
|
|
729
996
|
return ariaProps.isDisabled;
|
|
730
997
|
},
|
|
731
|
-
get
|
|
732
|
-
return ariaProps[
|
|
998
|
+
get "aria-label"() {
|
|
999
|
+
return ariaProps["aria-label"];
|
|
733
1000
|
},
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
collection: state.collection,
|
|
737
|
-
focusedKey: state.focusedKey,
|
|
738
|
-
setFocusedKey: state.setFocusedKey,
|
|
739
|
-
isFocused: state.isFocused,
|
|
740
|
-
setFocused: state.setFocused,
|
|
741
|
-
// Use state's built-in methods
|
|
742
|
-
selectionMode: state.selectionMode,
|
|
743
|
-
select: state.select,
|
|
744
|
-
isSelected: state.isSelected,
|
|
745
|
-
isDisabled: state.isKeyDisabled,
|
|
746
|
-
// Additional ListState interface requirements
|
|
747
|
-
selectedKeys: () => {
|
|
748
|
-
const key = state.selectedKey();
|
|
749
|
-
return key != null ? new Set([key]) : new Set();
|
|
1001
|
+
get onAction() {
|
|
1002
|
+
return local.onAction;
|
|
750
1003
|
},
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
1004
|
+
shouldSelectOnPressUp: true,
|
|
1005
|
+
shouldFocusOnHover: true,
|
|
1006
|
+
shouldUseVirtualFocus: true,
|
|
1007
|
+
allowsDifferentPressOrigin: true,
|
|
1008
|
+
get onHoverStart() {
|
|
1009
|
+
return ariaProps.onHoverStart;
|
|
1010
|
+
},
|
|
1011
|
+
get onHoverEnd() {
|
|
1012
|
+
return ariaProps.onHoverEnd;
|
|
1013
|
+
},
|
|
1014
|
+
get onHoverChange() {
|
|
1015
|
+
return ariaProps.onHoverChange;
|
|
1016
|
+
},
|
|
1017
|
+
},
|
|
1018
|
+
createComboBoxListStateAdapter(state),
|
|
759
1019
|
);
|
|
760
1020
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
return optionAria.isDisabled();
|
|
765
|
-
},
|
|
766
|
-
});
|
|
1021
|
+
const isOptionFocusVisible = () =>
|
|
1022
|
+
optionAria.isFocusVisible() ||
|
|
1023
|
+
(optionAria.isFocused() && (comboBoxContext?.isFocusVisible() ?? false));
|
|
767
1024
|
|
|
768
|
-
// Render props values
|
|
769
1025
|
const renderValues = createMemo<ComboBoxOptionRenderProps>(() => ({
|
|
770
1026
|
isSelected: optionAria.isSelected(),
|
|
771
1027
|
isFocused: optionAria.isFocused(),
|
|
772
|
-
isFocusVisible:
|
|
1028
|
+
isFocusVisible: isOptionFocusVisible(),
|
|
773
1029
|
isPressed: optionAria.isPressed(),
|
|
774
|
-
isHovered: isHovered(),
|
|
1030
|
+
isHovered: optionAria.isHovered(),
|
|
775
1031
|
isDisabled: optionAria.isDisabled(),
|
|
776
1032
|
}));
|
|
777
1033
|
|
|
778
|
-
// Resolve render props
|
|
779
1034
|
const renderProps = useRenderProps(
|
|
780
1035
|
{
|
|
781
1036
|
children: props.children,
|
|
782
1037
|
class: local.class,
|
|
783
1038
|
style: local.style,
|
|
784
|
-
defaultClassName:
|
|
1039
|
+
defaultClassName: "solidaria-ComboBox-option",
|
|
785
1040
|
},
|
|
786
|
-
renderValues
|
|
1041
|
+
renderValues,
|
|
787
1042
|
);
|
|
788
1043
|
|
|
789
|
-
|
|
1044
|
+
const selectionIndicatorContext = createMemo<SelectionIndicatorContextValue>(() => ({
|
|
1045
|
+
isSelected: optionAria.isSelected,
|
|
1046
|
+
}));
|
|
1047
|
+
|
|
790
1048
|
const cleanOptionProps = () => {
|
|
791
1049
|
const { ref: _ref1, ...rest } = optionAria.optionProps as Record<string, unknown>;
|
|
792
1050
|
return rest;
|
|
793
1051
|
};
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
1052
|
+
|
|
1053
|
+
return (
|
|
1054
|
+
<SelectionIndicatorContext.Provider value={selectionIndicatorContext()}>
|
|
1055
|
+
<li
|
|
1056
|
+
{...cleanOptionProps()}
|
|
1057
|
+
class={renderProps.class()}
|
|
1058
|
+
style={renderProps.style()}
|
|
1059
|
+
data-selected={optionAria.isSelected() || undefined}
|
|
1060
|
+
data-focused={optionAria.isFocused() || undefined}
|
|
1061
|
+
data-focus-visible={isOptionFocusVisible() || undefined}
|
|
1062
|
+
data-pressed={optionAria.isPressed() || undefined}
|
|
1063
|
+
data-hovered={optionAria.isHovered() || undefined}
|
|
1064
|
+
data-disabled={optionAria.isDisabled() || undefined}
|
|
1065
|
+
>
|
|
1066
|
+
{renderProps.renderChildren()}
|
|
1067
|
+
</li>
|
|
1068
|
+
</SelectionIndicatorContext.Provider>
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
export interface ComboBoxTagGroupProps {
|
|
1073
|
+
/** Render function for each selected item. */
|
|
1074
|
+
children: (item: { key: Key; label: string }) => JSX.Element;
|
|
1075
|
+
/** The CSS className for the container. */
|
|
1076
|
+
class?: string;
|
|
1077
|
+
/** The inline style for the container. */
|
|
1078
|
+
style?: JSX.CSSProperties;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* Renders selected items as tags in multi-select mode.
|
|
1083
|
+
*/
|
|
1084
|
+
export function ComboBoxTagGroup(props: ComboBoxTagGroupProps): JSX.Element {
|
|
1085
|
+
const context = useContext(ComboBoxContext);
|
|
1086
|
+
if (!context) {
|
|
1087
|
+
throw new Error("ComboBoxTagGroup must be used within a ComboBox");
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
const state = context.state;
|
|
1091
|
+
const items = createMemo(() =>
|
|
1092
|
+
state.selectedItems().map((node) => ({
|
|
1093
|
+
key: node.key,
|
|
1094
|
+
label: node.textValue,
|
|
1095
|
+
})),
|
|
1096
|
+
);
|
|
1097
|
+
|
|
1098
|
+
return (
|
|
1099
|
+
<Show when={items().length > 0}>
|
|
1100
|
+
<div
|
|
1101
|
+
class={props.class ?? "solidaria-ComboBox-tagGroup"}
|
|
1102
|
+
style={props.style}
|
|
1103
|
+
role="group"
|
|
1104
|
+
aria-label="Selected items"
|
|
1105
|
+
>
|
|
1106
|
+
<For each={items()}>{(item) => props.children(item)}</For>
|
|
1107
|
+
</div>
|
|
1108
|
+
</Show>
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
export interface ComboBoxTagProps {
|
|
1113
|
+
/** The item data. */
|
|
1114
|
+
item: { key: Key; label: string };
|
|
1115
|
+
/** Handler called when the tag remove button is clicked. */
|
|
1116
|
+
onRemove?: () => void;
|
|
1117
|
+
/** The children to render inside the tag. */
|
|
1118
|
+
children?: JSX.Element;
|
|
1119
|
+
/** The CSS className for the tag. */
|
|
1120
|
+
class?: string;
|
|
1121
|
+
/** The inline style for the tag. */
|
|
1122
|
+
style?: JSX.CSSProperties;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/**
|
|
1126
|
+
* A tag representing a selected item in a multi-select combobox.
|
|
1127
|
+
*/
|
|
1128
|
+
export function ComboBoxTag(props: ComboBoxTagProps): JSX.Element {
|
|
1129
|
+
const context = useContext(ComboBoxContext);
|
|
1130
|
+
if (!context) {
|
|
1131
|
+
throw new Error("ComboBoxTag must be used within a ComboBox");
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
const state = context.state;
|
|
1135
|
+
|
|
1136
|
+
const handleRemove = () => {
|
|
1137
|
+
if (props.onRemove) {
|
|
1138
|
+
props.onRemove();
|
|
1139
|
+
} else {
|
|
1140
|
+
state.removeSelectedKey(props.item.key);
|
|
1141
|
+
}
|
|
797
1142
|
};
|
|
798
1143
|
|
|
799
1144
|
return (
|
|
800
|
-
<
|
|
801
|
-
{
|
|
802
|
-
{
|
|
803
|
-
|
|
804
|
-
style={renderProps.style()}
|
|
805
|
-
data-selected={optionAria.isSelected() || undefined}
|
|
806
|
-
data-focused={optionAria.isFocused() || undefined}
|
|
807
|
-
data-focus-visible={optionAria.isFocusVisible() || undefined}
|
|
808
|
-
data-pressed={optionAria.isPressed() || undefined}
|
|
809
|
-
data-hovered={isHovered() || undefined}
|
|
810
|
-
data-disabled={optionAria.isDisabled() || undefined}
|
|
1145
|
+
<span
|
|
1146
|
+
class={props.class ?? "solidaria-ComboBox-tag"}
|
|
1147
|
+
style={props.style}
|
|
1148
|
+
data-key={String(props.item.key)}
|
|
811
1149
|
>
|
|
812
|
-
{
|
|
813
|
-
|
|
1150
|
+
{props.children ?? props.item.label}
|
|
1151
|
+
<button
|
|
1152
|
+
type="button"
|
|
1153
|
+
aria-label={`Remove ${props.item.label}`}
|
|
1154
|
+
onClick={handleRemove}
|
|
1155
|
+
class="solidaria-ComboBox-tag-remove"
|
|
1156
|
+
tabIndex={-1}
|
|
1157
|
+
>
|
|
1158
|
+
×
|
|
1159
|
+
</button>
|
|
1160
|
+
</span>
|
|
814
1161
|
);
|
|
815
1162
|
}
|
|
816
1163
|
|
|
817
|
-
// Attach sub-components
|
|
818
1164
|
ComboBox.Input = ComboBoxInput;
|
|
819
1165
|
ComboBox.Button = ComboBoxButton;
|
|
820
1166
|
ComboBox.ListBox = ComboBoxListBox;
|
|
821
1167
|
ComboBox.Option = ComboBoxOption;
|
|
1168
|
+
ComboBox.Label = ComboBoxLabel;
|
|
1169
|
+
ComboBox.Description = ComboBoxDescription;
|
|
1170
|
+
ComboBox.ErrorMessage = ComboBoxErrorMessage;
|
|
1171
|
+
ComboBox.TagGroup = ComboBoxTagGroup;
|
|
1172
|
+
ComboBox.Tag = ComboBoxTag;
|
|
822
1173
|
|
|
823
|
-
// Re-export filter function for convenience
|
|
824
1174
|
export { defaultContainsFilter };
|
|
1175
|
+
|
|
1176
|
+
function createComboBoxListStateAdapter<T>(state: ComboBoxState<T>): ListState<T> {
|
|
1177
|
+
const selectedKeys = createMemo(() => {
|
|
1178
|
+
if (state.selectionMode() === "multiple") {
|
|
1179
|
+
return state.selectedKeys();
|
|
1180
|
+
}
|
|
1181
|
+
const key = state.selectedKey();
|
|
1182
|
+
return key != null ? new Set<Key>([key]) : new Set<Key>();
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
const disabledKeys = createMemo(() => {
|
|
1186
|
+
const keys = new Set<Key>();
|
|
1187
|
+
for (const node of state.collection()) {
|
|
1188
|
+
if (node.isDisabled) keys.add(node.key);
|
|
1189
|
+
}
|
|
1190
|
+
return keys;
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
return {
|
|
1194
|
+
collection: state.collection,
|
|
1195
|
+
isFocused: state.isFocused,
|
|
1196
|
+
setFocused: state.setFocused,
|
|
1197
|
+
focusedKey: state.focusedKey,
|
|
1198
|
+
setFocusedKey: (key) => state.setFocusedKey(key ?? null),
|
|
1199
|
+
childFocusStrategy: () => null,
|
|
1200
|
+
selectionMode: state.selectionMode,
|
|
1201
|
+
selectionBehavior: () => "replace",
|
|
1202
|
+
disallowEmptySelection: () => true,
|
|
1203
|
+
selectedKeys,
|
|
1204
|
+
disabledKeys,
|
|
1205
|
+
disabledBehavior: () => "all",
|
|
1206
|
+
isEmpty: () => selectedKeys().size === 0,
|
|
1207
|
+
isSelectAll: () => false,
|
|
1208
|
+
isSelected: state.isSelected,
|
|
1209
|
+
isDisabled: state.isKeyDisabled,
|
|
1210
|
+
setSelectionBehavior: () => {},
|
|
1211
|
+
toggleSelection: (key) => state.select(key),
|
|
1212
|
+
replaceSelection: (key) => state.select(key),
|
|
1213
|
+
setSelectedKeys: (keys) => {
|
|
1214
|
+
const first = keys[Symbol.iterator]().next().value as Key | undefined;
|
|
1215
|
+
state.setSelectedKey(first ?? null);
|
|
1216
|
+
},
|
|
1217
|
+
selectAll: () => {},
|
|
1218
|
+
clearSelection: () => state.setSelectedKey(null),
|
|
1219
|
+
toggleSelectAll: () => {},
|
|
1220
|
+
extendSelection: (toKey) => state.select(toKey),
|
|
1221
|
+
select: (key) => state.select(key),
|
|
1222
|
+
};
|
|
1223
|
+
}
|