@proyecto-viviana/solidaria-components 0.1.3 → 0.2.2
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/dist/Color.d.ts +6 -2
- package/dist/Color.d.ts.map +1 -1
- package/dist/ComboBox.d.ts +3 -3
- package/dist/ComboBox.d.ts.map +1 -1
- package/dist/GridList.d.ts +2 -2
- package/dist/GridList.d.ts.map +1 -1
- package/dist/ListBox.d.ts +5 -5
- package/dist/ListBox.d.ts.map +1 -1
- package/dist/Menu.d.ts +3 -3
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Select.d.ts +3 -3
- package/dist/Select.d.ts.map +1 -1
- package/dist/Table.d.ts +2 -2
- package/dist/Table.d.ts.map +1 -1
- package/dist/Tabs.d.ts +1 -1
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/index.js +15 -15
- package/dist/index.js.map +2 -2
- package/dist/index.jsx +9056 -0
- package/dist/index.jsx.map +7 -0
- package/dist/index.ssr.js +15 -15
- package/dist/index.ssr.js.map +2 -2
- package/package.json +8 -10
- package/src/Autocomplete.tsx +0 -174
- package/src/Breadcrumbs.tsx +0 -264
- package/src/Button.tsx +0 -238
- package/src/Calendar.tsx +0 -471
- package/src/Checkbox.tsx +0 -387
- package/src/Color.tsx +0 -1370
- package/src/ComboBox.tsx +0 -824
- package/src/DateField.tsx +0 -337
- package/src/DatePicker.tsx +0 -367
- package/src/Dialog.tsx +0 -262
- package/src/Disclosure.tsx +0 -439
- package/src/GridList.tsx +0 -511
- package/src/Landmark.tsx +0 -203
- package/src/Link.tsx +0 -201
- package/src/ListBox.tsx +0 -346
- package/src/Menu.tsx +0 -544
- package/src/Meter.tsx +0 -157
- package/src/Modal.tsx +0 -433
- package/src/NumberField.tsx +0 -542
- package/src/Popover.tsx +0 -540
- package/src/ProgressBar.tsx +0 -162
- package/src/RadioGroup.tsx +0 -356
- package/src/RangeCalendar.tsx +0 -462
- package/src/SearchField.tsx +0 -479
- package/src/Select.tsx +0 -734
- package/src/Separator.tsx +0 -130
- package/src/Slider.tsx +0 -500
- package/src/Switch.tsx +0 -213
- package/src/Table.tsx +0 -857
- package/src/Tabs.tsx +0 -552
- package/src/TagGroup.tsx +0 -421
- package/src/TextField.tsx +0 -271
- package/src/TimeField.tsx +0 -455
- package/src/Toast.tsx +0 -503
- package/src/Toolbar.tsx +0 -160
- package/src/Tooltip.tsx +0 -423
- package/src/Tree.tsx +0 -551
- package/src/VisuallyHidden.tsx +0 -60
- package/src/contexts.ts +0 -74
- package/src/index.ts +0 -620
- package/src/utils.tsx +0 -329
package/src/ComboBox.tsx
DELETED
|
@@ -1,824 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ComboBox component for solidaria-components
|
|
3
|
-
*
|
|
4
|
-
* A pre-wired headless combobox that combines state + aria hooks.
|
|
5
|
-
* Port of react-aria-components/src/ComboBox.tsx
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
type JSX,
|
|
10
|
-
type Accessor,
|
|
11
|
-
createContext,
|
|
12
|
-
createMemo,
|
|
13
|
-
splitProps,
|
|
14
|
-
useContext,
|
|
15
|
-
For,
|
|
16
|
-
Show,
|
|
17
|
-
} from 'solid-js';
|
|
18
|
-
import {
|
|
19
|
-
createComboBox,
|
|
20
|
-
createListBox,
|
|
21
|
-
createOption,
|
|
22
|
-
createHover,
|
|
23
|
-
createInteractOutside,
|
|
24
|
-
type AriaComboBoxProps,
|
|
25
|
-
type AriaOptionProps,
|
|
26
|
-
} from '@proyecto-viviana/solidaria';
|
|
27
|
-
import {
|
|
28
|
-
createComboBoxState,
|
|
29
|
-
defaultContainsFilter,
|
|
30
|
-
type ComboBoxState,
|
|
31
|
-
type Key,
|
|
32
|
-
type FilterFn,
|
|
33
|
-
type MenuTriggerAction,
|
|
34
|
-
} from '@proyecto-viviana/solid-stately';
|
|
35
|
-
import {
|
|
36
|
-
type RenderChildren,
|
|
37
|
-
type ClassNameOrFunction,
|
|
38
|
-
type StyleOrFunction,
|
|
39
|
-
type SlotProps,
|
|
40
|
-
useRenderProps,
|
|
41
|
-
filterDOMProps,
|
|
42
|
-
} from './utils';
|
|
43
|
-
|
|
44
|
-
// ============================================
|
|
45
|
-
// TYPES
|
|
46
|
-
// ============================================
|
|
47
|
-
|
|
48
|
-
export interface ComboBoxRenderProps {
|
|
49
|
-
/** Whether the combobox listbox is open. */
|
|
50
|
-
isOpen: boolean;
|
|
51
|
-
/** Whether the combobox input is focused. */
|
|
52
|
-
isFocused: boolean;
|
|
53
|
-
/** Whether the combobox input has keyboard focus. */
|
|
54
|
-
isFocusVisible: boolean;
|
|
55
|
-
/** Whether the combobox is disabled. */
|
|
56
|
-
isDisabled: boolean;
|
|
57
|
-
/** Whether the combobox is required. */
|
|
58
|
-
isRequired: boolean;
|
|
59
|
-
/** Whether a value is selected. */
|
|
60
|
-
isSelected: boolean;
|
|
61
|
-
/** The current input value. */
|
|
62
|
-
inputValue: string;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface ComboBoxProps<T>
|
|
66
|
-
extends Omit<AriaComboBoxProps, 'children'>,
|
|
67
|
-
SlotProps {
|
|
68
|
-
/** The items to render in the combobox. */
|
|
69
|
-
items: T[];
|
|
70
|
-
/** Function to get the key from an item. */
|
|
71
|
-
getKey?: (item: T) => Key;
|
|
72
|
-
/** Function to get the text value from an item. */
|
|
73
|
-
getTextValue?: (item: T) => string;
|
|
74
|
-
/** Function to check if an item is disabled. */
|
|
75
|
-
getDisabled?: (item: T) => boolean;
|
|
76
|
-
/** Keys of disabled items. */
|
|
77
|
-
disabledKeys?: Iterable<Key>;
|
|
78
|
-
/** The currently selected key (controlled). */
|
|
79
|
-
selectedKey?: Key | null;
|
|
80
|
-
/** The default selected key (uncontrolled). */
|
|
81
|
-
defaultSelectedKey?: Key | null;
|
|
82
|
-
/** Handler called when selection changes. */
|
|
83
|
-
onSelectionChange?: (key: Key | null) => void;
|
|
84
|
-
/** The current input value (controlled). */
|
|
85
|
-
inputValue?: string;
|
|
86
|
-
/** The default input value (uncontrolled). */
|
|
87
|
-
defaultInputValue?: string;
|
|
88
|
-
/** Handler called when input value changes. */
|
|
89
|
-
onInputChange?: (value: string) => void;
|
|
90
|
-
/** Whether the combobox is open (controlled). */
|
|
91
|
-
isOpen?: boolean;
|
|
92
|
-
/** Whether the combobox is open by default (uncontrolled). */
|
|
93
|
-
defaultOpen?: boolean;
|
|
94
|
-
/** Handler called when the open state changes. */
|
|
95
|
-
onOpenChange?: (isOpen: boolean, trigger?: MenuTriggerAction) => void;
|
|
96
|
-
/** The filter function to use for filtering items. */
|
|
97
|
-
defaultFilter?: FilterFn;
|
|
98
|
-
/** Whether to allow custom values that don't match any item. */
|
|
99
|
-
allowsCustomValue?: boolean;
|
|
100
|
-
/** Whether to allow an empty collection (show listbox even with no matches). */
|
|
101
|
-
allowsEmptyCollection?: boolean;
|
|
102
|
-
/** The trigger mechanism for the combobox menu. */
|
|
103
|
-
menuTrigger?: 'focus' | 'input' | 'manual';
|
|
104
|
-
/** The name of the combobox, used when submitting an HTML form. */
|
|
105
|
-
name?: string;
|
|
106
|
-
/** The children of the component (compound components: ComboBoxInput, ComboBoxButton, ComboBoxListBox). */
|
|
107
|
-
children: JSX.Element;
|
|
108
|
-
/** The CSS className for the element. */
|
|
109
|
-
class?: ClassNameOrFunction<ComboBoxRenderProps>;
|
|
110
|
-
/** The inline style for the element. */
|
|
111
|
-
style?: StyleOrFunction<ComboBoxRenderProps>;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export interface ComboBoxInputRenderProps {
|
|
115
|
-
/** Whether the combobox is open. */
|
|
116
|
-
isOpen: boolean;
|
|
117
|
-
/** Whether the input is focused. */
|
|
118
|
-
isFocused: boolean;
|
|
119
|
-
/** Whether the input has keyboard focus. */
|
|
120
|
-
isFocusVisible: boolean;
|
|
121
|
-
/** Whether the input is hovered. */
|
|
122
|
-
isHovered: boolean;
|
|
123
|
-
/** Whether the input is disabled. */
|
|
124
|
-
isDisabled: boolean;
|
|
125
|
-
/** The current input value. */
|
|
126
|
-
inputValue: string;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export interface ComboBoxInputProps extends SlotProps {
|
|
130
|
-
/** The children of the input. */
|
|
131
|
-
children?: RenderChildren<ComboBoxInputRenderProps>;
|
|
132
|
-
/** The CSS className for the element. */
|
|
133
|
-
class?: ClassNameOrFunction<ComboBoxInputRenderProps>;
|
|
134
|
-
/** The inline style for the element. */
|
|
135
|
-
style?: StyleOrFunction<ComboBoxInputRenderProps>;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export interface ComboBoxButtonRenderProps {
|
|
139
|
-
/** Whether the combobox is open. */
|
|
140
|
-
isOpen: boolean;
|
|
141
|
-
/** Whether the button is focused. */
|
|
142
|
-
isFocused: boolean;
|
|
143
|
-
/** Whether the button is hovered. */
|
|
144
|
-
isHovered: boolean;
|
|
145
|
-
/** Whether the button is pressed. */
|
|
146
|
-
isPressed: boolean;
|
|
147
|
-
/** Whether the button is disabled. */
|
|
148
|
-
isDisabled: boolean;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export interface ComboBoxButtonProps extends SlotProps {
|
|
152
|
-
/** The children of the button. */
|
|
153
|
-
children?: RenderChildren<ComboBoxButtonRenderProps>;
|
|
154
|
-
/** The CSS className for the element. */
|
|
155
|
-
class?: ClassNameOrFunction<ComboBoxButtonRenderProps>;
|
|
156
|
-
/** The inline style for the element. */
|
|
157
|
-
style?: StyleOrFunction<ComboBoxButtonRenderProps>;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export interface ComboBoxListBoxRenderProps {
|
|
161
|
-
/** Whether the listbox is focused. */
|
|
162
|
-
isFocused: boolean;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export interface ComboBoxListBoxProps<T> extends SlotProps {
|
|
166
|
-
/** The children of the listbox. A function may be provided to render each item. */
|
|
167
|
-
children?: (item: T) => JSX.Element;
|
|
168
|
-
/** The CSS className for the element. */
|
|
169
|
-
class?: ClassNameOrFunction<ComboBoxListBoxRenderProps>;
|
|
170
|
-
/** The inline style for the element. */
|
|
171
|
-
style?: StyleOrFunction<ComboBoxListBoxRenderProps>;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export interface ComboBoxOptionRenderProps {
|
|
175
|
-
/** Whether the option is selected. */
|
|
176
|
-
isSelected: boolean;
|
|
177
|
-
/** Whether the option is focused. */
|
|
178
|
-
isFocused: boolean;
|
|
179
|
-
/** Whether the option has keyboard focus. */
|
|
180
|
-
isFocusVisible: boolean;
|
|
181
|
-
/** Whether the option is pressed. */
|
|
182
|
-
isPressed: boolean;
|
|
183
|
-
/** Whether the option is hovered. */
|
|
184
|
-
isHovered: boolean;
|
|
185
|
-
/** Whether the option is disabled. */
|
|
186
|
-
isDisabled: boolean;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export interface ComboBoxOptionProps<T>
|
|
190
|
-
extends Omit<AriaOptionProps, 'children' | 'key'>,
|
|
191
|
-
SlotProps {
|
|
192
|
-
/** The unique key for the option. */
|
|
193
|
-
id: Key;
|
|
194
|
-
/** The item value. */
|
|
195
|
-
item?: T;
|
|
196
|
-
/** The children of the option. A function may be provided to receive render props. */
|
|
197
|
-
children?: RenderChildren<ComboBoxOptionRenderProps>;
|
|
198
|
-
/** The CSS className for the element. */
|
|
199
|
-
class?: ClassNameOrFunction<ComboBoxOptionRenderProps>;
|
|
200
|
-
/** The inline style for the element. */
|
|
201
|
-
style?: StyleOrFunction<ComboBoxOptionRenderProps>;
|
|
202
|
-
/** The text value of the option (for typeahead). */
|
|
203
|
-
textValue?: string;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// ============================================
|
|
207
|
-
// CONTEXT
|
|
208
|
-
// ============================================
|
|
209
|
-
|
|
210
|
-
interface ComboBoxContextValue<T> {
|
|
211
|
-
state: ComboBoxState<T>;
|
|
212
|
-
inputProps: JSX.InputHTMLAttributes<HTMLInputElement>;
|
|
213
|
-
buttonProps: JSX.HTMLAttributes<HTMLElement>;
|
|
214
|
-
listBoxProps: JSX.HTMLAttributes<HTMLElement>;
|
|
215
|
-
labelProps: JSX.HTMLAttributes<HTMLElement>;
|
|
216
|
-
isOpen: Accessor<boolean>;
|
|
217
|
-
isFocused: Accessor<boolean>;
|
|
218
|
-
isFocusVisible: Accessor<boolean>;
|
|
219
|
-
items: T[];
|
|
220
|
-
inputRef: () => HTMLInputElement | null;
|
|
221
|
-
setInputRef: (el: HTMLInputElement | null) => void;
|
|
222
|
-
buttonRef: () => HTMLElement | null;
|
|
223
|
-
setButtonRef: (el: HTMLElement | null) => void;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
export const ComboBoxContext = createContext<ComboBoxContextValue<unknown> | null>(null);
|
|
227
|
-
export const ComboBoxStateContext = createContext<ComboBoxState<unknown> | null>(null);
|
|
228
|
-
|
|
229
|
-
// ============================================
|
|
230
|
-
// COMPONENTS
|
|
231
|
-
// ============================================
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* A combobox combines a text input with a listbox, allowing users to filter a list of options.
|
|
235
|
-
*/
|
|
236
|
-
export function ComboBox<T>(props: ComboBoxProps<T>): JSX.Element {
|
|
237
|
-
const [local, stateProps, ariaProps] = splitProps(
|
|
238
|
-
props,
|
|
239
|
-
['class', 'style', 'slot'],
|
|
240
|
-
[
|
|
241
|
-
'items',
|
|
242
|
-
'getKey',
|
|
243
|
-
'getTextValue',
|
|
244
|
-
'getDisabled',
|
|
245
|
-
'disabledKeys',
|
|
246
|
-
'selectedKey',
|
|
247
|
-
'defaultSelectedKey',
|
|
248
|
-
'onSelectionChange',
|
|
249
|
-
'inputValue',
|
|
250
|
-
'defaultInputValue',
|
|
251
|
-
'onInputChange',
|
|
252
|
-
'isOpen',
|
|
253
|
-
'defaultOpen',
|
|
254
|
-
'onOpenChange',
|
|
255
|
-
'defaultFilter',
|
|
256
|
-
'allowsCustomValue',
|
|
257
|
-
'allowsEmptyCollection',
|
|
258
|
-
'menuTrigger',
|
|
259
|
-
'name',
|
|
260
|
-
]
|
|
261
|
-
);
|
|
262
|
-
|
|
263
|
-
// Refs
|
|
264
|
-
let inputRef: HTMLInputElement | null = null;
|
|
265
|
-
let buttonRef: HTMLElement | null = null;
|
|
266
|
-
|
|
267
|
-
// Create combobox state
|
|
268
|
-
const state = createComboBoxState<T>({
|
|
269
|
-
get items() {
|
|
270
|
-
return stateProps.items;
|
|
271
|
-
},
|
|
272
|
-
get getKey() {
|
|
273
|
-
return stateProps.getKey;
|
|
274
|
-
},
|
|
275
|
-
get getTextValue() {
|
|
276
|
-
return stateProps.getTextValue;
|
|
277
|
-
},
|
|
278
|
-
get getDisabled() {
|
|
279
|
-
return stateProps.getDisabled;
|
|
280
|
-
},
|
|
281
|
-
get disabledKeys() {
|
|
282
|
-
return stateProps.disabledKeys;
|
|
283
|
-
},
|
|
284
|
-
get selectedKey() {
|
|
285
|
-
return stateProps.selectedKey;
|
|
286
|
-
},
|
|
287
|
-
get defaultSelectedKey() {
|
|
288
|
-
return stateProps.defaultSelectedKey;
|
|
289
|
-
},
|
|
290
|
-
get onSelectionChange() {
|
|
291
|
-
return stateProps.onSelectionChange;
|
|
292
|
-
},
|
|
293
|
-
get inputValue() {
|
|
294
|
-
return stateProps.inputValue;
|
|
295
|
-
},
|
|
296
|
-
get defaultInputValue() {
|
|
297
|
-
return stateProps.defaultInputValue;
|
|
298
|
-
},
|
|
299
|
-
get onInputChange() {
|
|
300
|
-
return stateProps.onInputChange;
|
|
301
|
-
},
|
|
302
|
-
get isOpen() {
|
|
303
|
-
return stateProps.isOpen;
|
|
304
|
-
},
|
|
305
|
-
get defaultOpen() {
|
|
306
|
-
return stateProps.defaultOpen;
|
|
307
|
-
},
|
|
308
|
-
get onOpenChange() {
|
|
309
|
-
return stateProps.onOpenChange;
|
|
310
|
-
},
|
|
311
|
-
get defaultFilter() {
|
|
312
|
-
return stateProps.defaultFilter;
|
|
313
|
-
},
|
|
314
|
-
get allowsCustomValue() {
|
|
315
|
-
return stateProps.allowsCustomValue;
|
|
316
|
-
},
|
|
317
|
-
get allowsEmptyCollection() {
|
|
318
|
-
return stateProps.allowsEmptyCollection;
|
|
319
|
-
},
|
|
320
|
-
get menuTrigger() {
|
|
321
|
-
return stateProps.menuTrigger;
|
|
322
|
-
},
|
|
323
|
-
get isDisabled() {
|
|
324
|
-
return ariaProps.isDisabled;
|
|
325
|
-
},
|
|
326
|
-
get isReadOnly() {
|
|
327
|
-
return ariaProps.isReadOnly;
|
|
328
|
-
},
|
|
329
|
-
get isRequired() {
|
|
330
|
-
return ariaProps.isRequired;
|
|
331
|
-
},
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
// Create combobox aria props
|
|
335
|
-
const comboBoxAria = createComboBox<T>(
|
|
336
|
-
ariaProps,
|
|
337
|
-
state,
|
|
338
|
-
() => inputRef,
|
|
339
|
-
() => buttonRef
|
|
340
|
-
);
|
|
341
|
-
|
|
342
|
-
// Create hover for wrapper
|
|
343
|
-
const { isHovered, hoverProps } = createHover({
|
|
344
|
-
get isDisabled() {
|
|
345
|
-
return ariaProps.isDisabled;
|
|
346
|
-
},
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
// Render props values
|
|
350
|
-
const renderValues = createMemo<ComboBoxRenderProps>(() => ({
|
|
351
|
-
isOpen: comboBoxAria.isOpen(),
|
|
352
|
-
isFocused: comboBoxAria.isFocused(),
|
|
353
|
-
isFocusVisible: comboBoxAria.isFocusVisible(),
|
|
354
|
-
isDisabled: !!ariaProps.isDisabled,
|
|
355
|
-
isRequired: !!ariaProps.isRequired,
|
|
356
|
-
isSelected: state.selectedKey() != null,
|
|
357
|
-
inputValue: state.inputValue(),
|
|
358
|
-
}));
|
|
359
|
-
|
|
360
|
-
// Resolve render props
|
|
361
|
-
const renderProps = useRenderProps(
|
|
362
|
-
{
|
|
363
|
-
class: local.class,
|
|
364
|
-
style: local.style,
|
|
365
|
-
defaultClassName: 'solidaria-ComboBox',
|
|
366
|
-
},
|
|
367
|
-
renderValues
|
|
368
|
-
);
|
|
369
|
-
|
|
370
|
-
// Filter DOM props
|
|
371
|
-
const domProps = createMemo(() => {
|
|
372
|
-
const filtered = filterDOMProps(ariaProps as Record<string, unknown>, { global: true });
|
|
373
|
-
return filtered;
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
// Remove ref from hover props
|
|
377
|
-
const cleanHoverProps = () => {
|
|
378
|
-
const { ref: _ref, ...rest } = hoverProps as Record<string, unknown>;
|
|
379
|
-
return rest;
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
return (
|
|
383
|
-
<ComboBoxContext.Provider
|
|
384
|
-
value={{
|
|
385
|
-
state,
|
|
386
|
-
inputProps: comboBoxAria.inputProps,
|
|
387
|
-
buttonProps: comboBoxAria.buttonProps,
|
|
388
|
-
listBoxProps: comboBoxAria.listBoxProps,
|
|
389
|
-
labelProps: comboBoxAria.labelProps,
|
|
390
|
-
isOpen: comboBoxAria.isOpen,
|
|
391
|
-
isFocused: comboBoxAria.isFocused,
|
|
392
|
-
isFocusVisible: comboBoxAria.isFocusVisible,
|
|
393
|
-
items: stateProps.items,
|
|
394
|
-
inputRef: () => inputRef,
|
|
395
|
-
setInputRef: (el) => { inputRef = el; },
|
|
396
|
-
buttonRef: () => buttonRef,
|
|
397
|
-
setButtonRef: (el) => { buttonRef = el; },
|
|
398
|
-
}}
|
|
399
|
-
>
|
|
400
|
-
<ComboBoxStateContext.Provider value={state}>
|
|
401
|
-
<div
|
|
402
|
-
{...domProps()}
|
|
403
|
-
{...cleanHoverProps()}
|
|
404
|
-
class={renderProps.class()}
|
|
405
|
-
style={renderProps.style()}
|
|
406
|
-
data-open={comboBoxAria.isOpen() || undefined}
|
|
407
|
-
data-focused={comboBoxAria.isFocused() || undefined}
|
|
408
|
-
data-focus-visible={comboBoxAria.isFocusVisible() || undefined}
|
|
409
|
-
data-disabled={ariaProps.isDisabled || undefined}
|
|
410
|
-
data-required={ariaProps.isRequired || undefined}
|
|
411
|
-
data-hovered={isHovered() || undefined}
|
|
412
|
-
>
|
|
413
|
-
{/* Hidden input for form submission */}
|
|
414
|
-
<Show when={stateProps.name}>
|
|
415
|
-
<input
|
|
416
|
-
type="hidden"
|
|
417
|
-
name={stateProps.name}
|
|
418
|
-
value={state.selectedKey()?.toString() ?? ''}
|
|
419
|
-
/>
|
|
420
|
-
</Show>
|
|
421
|
-
{props.children}
|
|
422
|
-
</div>
|
|
423
|
-
</ComboBoxStateContext.Provider>
|
|
424
|
-
</ComboBoxContext.Provider>
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* The text input for a combobox.
|
|
430
|
-
*/
|
|
431
|
-
export function ComboBoxInput(props: ComboBoxInputProps): JSX.Element {
|
|
432
|
-
const [local] = splitProps(props, ['class', 'style', 'slot']);
|
|
433
|
-
|
|
434
|
-
// Get context
|
|
435
|
-
const context = useContext(ComboBoxContext);
|
|
436
|
-
if (!context) {
|
|
437
|
-
throw new Error('ComboBoxInput must be used within a ComboBox');
|
|
438
|
-
}
|
|
439
|
-
const { inputProps, isOpen, isFocused, isFocusVisible, state, setInputRef } = context;
|
|
440
|
-
|
|
441
|
-
// Create hover
|
|
442
|
-
const { isHovered, hoverProps } = createHover({
|
|
443
|
-
get isDisabled() {
|
|
444
|
-
return state.isDisabled;
|
|
445
|
-
},
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
// Render props values
|
|
449
|
-
const renderValues = createMemo<ComboBoxInputRenderProps>(() => ({
|
|
450
|
-
isOpen: isOpen(),
|
|
451
|
-
isFocused: isFocused(),
|
|
452
|
-
isFocusVisible: isFocusVisible(),
|
|
453
|
-
isHovered: isHovered(),
|
|
454
|
-
isDisabled: state.isDisabled,
|
|
455
|
-
inputValue: state.inputValue(),
|
|
456
|
-
}));
|
|
457
|
-
|
|
458
|
-
// Resolve render props
|
|
459
|
-
const renderProps = useRenderProps(
|
|
460
|
-
{
|
|
461
|
-
children: props.children,
|
|
462
|
-
class: local.class,
|
|
463
|
-
style: local.style,
|
|
464
|
-
defaultClassName: 'solidaria-ComboBox-input',
|
|
465
|
-
},
|
|
466
|
-
renderValues
|
|
467
|
-
);
|
|
468
|
-
|
|
469
|
-
// Remove ref from spread props
|
|
470
|
-
const cleanInputProps = () => {
|
|
471
|
-
const { ref: _ref1, value: _value, ...rest } = inputProps as Record<string, unknown>;
|
|
472
|
-
return rest;
|
|
473
|
-
};
|
|
474
|
-
const cleanHoverProps = () => {
|
|
475
|
-
const { ref: _ref2, ...rest } = hoverProps as Record<string, unknown>;
|
|
476
|
-
return rest;
|
|
477
|
-
};
|
|
478
|
-
|
|
479
|
-
return (
|
|
480
|
-
<input
|
|
481
|
-
ref={(el) => setInputRef(el)}
|
|
482
|
-
{...cleanInputProps()}
|
|
483
|
-
{...cleanHoverProps()}
|
|
484
|
-
value={state.inputValue()}
|
|
485
|
-
class={renderProps.class()}
|
|
486
|
-
style={renderProps.style()}
|
|
487
|
-
data-open={isOpen() || undefined}
|
|
488
|
-
data-focused={isFocused() || undefined}
|
|
489
|
-
data-focus-visible={isFocusVisible() || undefined}
|
|
490
|
-
data-hovered={isHovered() || undefined}
|
|
491
|
-
data-disabled={state.isDisabled || undefined}
|
|
492
|
-
/>
|
|
493
|
-
);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
/**
|
|
497
|
-
* The trigger button for a combobox.
|
|
498
|
-
*/
|
|
499
|
-
export function ComboBoxButton(props: ComboBoxButtonProps): JSX.Element {
|
|
500
|
-
const [local] = splitProps(props, ['class', 'style', 'slot']);
|
|
501
|
-
|
|
502
|
-
// Get context
|
|
503
|
-
const context = useContext(ComboBoxContext);
|
|
504
|
-
if (!context) {
|
|
505
|
-
throw new Error('ComboBoxButton must be used within a ComboBox');
|
|
506
|
-
}
|
|
507
|
-
const { buttonProps, isOpen, isFocused, state, setButtonRef } = context;
|
|
508
|
-
|
|
509
|
-
// Create hover
|
|
510
|
-
const { isHovered, hoverProps } = createHover({
|
|
511
|
-
get isDisabled() {
|
|
512
|
-
return state.isDisabled;
|
|
513
|
-
},
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
// Track pressed state
|
|
517
|
-
let isPressed = false;
|
|
518
|
-
|
|
519
|
-
// Render props values
|
|
520
|
-
const renderValues = createMemo<ComboBoxButtonRenderProps>(() => ({
|
|
521
|
-
isOpen: isOpen(),
|
|
522
|
-
isFocused: isFocused(),
|
|
523
|
-
isHovered: isHovered(),
|
|
524
|
-
isPressed,
|
|
525
|
-
isDisabled: state.isDisabled,
|
|
526
|
-
}));
|
|
527
|
-
|
|
528
|
-
// Resolve render props
|
|
529
|
-
const renderProps = useRenderProps(
|
|
530
|
-
{
|
|
531
|
-
children: props.children,
|
|
532
|
-
class: local.class,
|
|
533
|
-
style: local.style,
|
|
534
|
-
defaultClassName: 'solidaria-ComboBox-button',
|
|
535
|
-
},
|
|
536
|
-
renderValues
|
|
537
|
-
);
|
|
538
|
-
|
|
539
|
-
// Remove ref from spread props
|
|
540
|
-
const cleanButtonProps = () => {
|
|
541
|
-
const { ref: _ref1, ...rest } = buttonProps as Record<string, unknown>;
|
|
542
|
-
return rest;
|
|
543
|
-
};
|
|
544
|
-
const cleanHoverProps = () => {
|
|
545
|
-
const { ref: _ref2, ...rest } = hoverProps as Record<string, unknown>;
|
|
546
|
-
return rest;
|
|
547
|
-
};
|
|
548
|
-
|
|
549
|
-
return (
|
|
550
|
-
<button
|
|
551
|
-
ref={(el) => setButtonRef(el)}
|
|
552
|
-
{...cleanButtonProps()}
|
|
553
|
-
{...cleanHoverProps()}
|
|
554
|
-
class={renderProps.class()}
|
|
555
|
-
style={renderProps.style()}
|
|
556
|
-
data-open={isOpen() || undefined}
|
|
557
|
-
data-focused={isFocused() || undefined}
|
|
558
|
-
data-hovered={isHovered() || undefined}
|
|
559
|
-
data-disabled={state.isDisabled || undefined}
|
|
560
|
-
>
|
|
561
|
-
{renderProps.renderChildren()}
|
|
562
|
-
</button>
|
|
563
|
-
);
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
/**
|
|
567
|
-
* The listbox popup for a combobox.
|
|
568
|
-
*/
|
|
569
|
-
export function ComboBoxListBox<T>(props: ComboBoxListBoxProps<T>): JSX.Element {
|
|
570
|
-
const [local] = splitProps(props, ['class', 'style', 'slot']);
|
|
571
|
-
|
|
572
|
-
// Get context
|
|
573
|
-
const context = useContext(ComboBoxContext);
|
|
574
|
-
if (!context) {
|
|
575
|
-
throw new Error('ComboBoxListBox must be used within a ComboBox');
|
|
576
|
-
}
|
|
577
|
-
const { listBoxProps: contextListBoxProps, state: comboBoxState, isOpen, inputRef } = context;
|
|
578
|
-
const state = comboBoxState as ComboBoxState<T>;
|
|
579
|
-
|
|
580
|
-
// Ref for the listbox element (for click outside detection)
|
|
581
|
-
let listBoxRef: HTMLUListElement | undefined;
|
|
582
|
-
|
|
583
|
-
// Handle click outside to close combobox
|
|
584
|
-
createInteractOutside({
|
|
585
|
-
ref: () => listBoxRef ?? null,
|
|
586
|
-
onInteractOutside: (e) => {
|
|
587
|
-
// Don't close if clicking the input or button
|
|
588
|
-
const target = e.target as HTMLElement;
|
|
589
|
-
const input = inputRef();
|
|
590
|
-
if (input?.contains(target)) {
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
if (isOpen()) {
|
|
594
|
-
state.close();
|
|
595
|
-
}
|
|
596
|
-
},
|
|
597
|
-
get isDisabled() {
|
|
598
|
-
return !isOpen();
|
|
599
|
-
},
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
// Create listbox aria props using ComboBoxState's ListState-compatible interface
|
|
603
|
-
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
|
|
629
|
-
);
|
|
630
|
-
|
|
631
|
-
// Render props values
|
|
632
|
-
const renderValues = createMemo<ComboBoxListBoxRenderProps>(() => ({
|
|
633
|
-
isFocused: state.isFocused(),
|
|
634
|
-
}));
|
|
635
|
-
|
|
636
|
-
// Resolve render props
|
|
637
|
-
const renderProps = useRenderProps(
|
|
638
|
-
{
|
|
639
|
-
class: local.class,
|
|
640
|
-
style: local.style,
|
|
641
|
-
defaultClassName: 'solidaria-ComboBox-listbox',
|
|
642
|
-
},
|
|
643
|
-
renderValues
|
|
644
|
-
);
|
|
645
|
-
|
|
646
|
-
// Remove ref from spread props
|
|
647
|
-
const cleanContextProps = () => {
|
|
648
|
-
const { ref: _ref1, ...rest } = contextListBoxProps as Record<string, unknown>;
|
|
649
|
-
return rest;
|
|
650
|
-
};
|
|
651
|
-
const cleanListBoxProps = () => {
|
|
652
|
-
const { ref: _ref2, ...rest } = listBoxProps as Record<string, unknown>;
|
|
653
|
-
return rest;
|
|
654
|
-
};
|
|
655
|
-
|
|
656
|
-
const items = () => Array.from(state.collection());
|
|
657
|
-
|
|
658
|
-
// Prevent focus from being lost when clicking in the listbox
|
|
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) => {
|
|
663
|
-
listBoxRef = el;
|
|
664
|
-
if (el) {
|
|
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
|
-
}
|
|
674
|
-
};
|
|
675
|
-
|
|
676
|
-
return (
|
|
677
|
-
<Show when={isOpen()}>
|
|
678
|
-
<ul
|
|
679
|
-
ref={setupMouseDownHandler}
|
|
680
|
-
{...cleanContextProps()}
|
|
681
|
-
{...cleanListBoxProps()}
|
|
682
|
-
class={renderProps.class()}
|
|
683
|
-
style={renderProps.style()}
|
|
684
|
-
data-focused={state.isFocused() || undefined}
|
|
685
|
-
>
|
|
686
|
-
<Show when={props.children} fallback={
|
|
687
|
-
<For each={items()}>
|
|
688
|
-
{(node) => (
|
|
689
|
-
<ComboBoxOption id={node.key}>
|
|
690
|
-
{node.textValue}
|
|
691
|
-
</ComboBoxOption>
|
|
692
|
-
)}
|
|
693
|
-
</For>
|
|
694
|
-
}>
|
|
695
|
-
<For each={items()}>
|
|
696
|
-
{(node) => node.value != null ? props.children!(node.value) : null}
|
|
697
|
-
</For>
|
|
698
|
-
</Show>
|
|
699
|
-
</ul>
|
|
700
|
-
</Show>
|
|
701
|
-
);
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
/**
|
|
705
|
-
* An option in a combobox listbox.
|
|
706
|
-
*/
|
|
707
|
-
export function ComboBoxOption<T>(props: ComboBoxOptionProps<T>): JSX.Element {
|
|
708
|
-
const [local, ariaProps] = splitProps(props, [
|
|
709
|
-
'class',
|
|
710
|
-
'style',
|
|
711
|
-
'slot',
|
|
712
|
-
'id',
|
|
713
|
-
'item',
|
|
714
|
-
'textValue',
|
|
715
|
-
]);
|
|
716
|
-
|
|
717
|
-
// Get state from context
|
|
718
|
-
const context = useContext(ComboBoxStateContext);
|
|
719
|
-
if (!context) {
|
|
720
|
-
throw new Error('ComboBoxOption must be used within a ComboBox');
|
|
721
|
-
}
|
|
722
|
-
const state = context as ComboBoxState<T>;
|
|
723
|
-
|
|
724
|
-
// Create option aria props using ComboBoxState's ListState-compatible interface
|
|
725
|
-
const optionAria = createOption<T>(
|
|
726
|
-
{
|
|
727
|
-
key: local.id,
|
|
728
|
-
get isDisabled() {
|
|
729
|
-
return ariaProps.isDisabled;
|
|
730
|
-
},
|
|
731
|
-
get 'aria-label'() {
|
|
732
|
-
return ariaProps['aria-label'];
|
|
733
|
-
},
|
|
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();
|
|
750
|
-
},
|
|
751
|
-
disallowEmptySelection: () => true,
|
|
752
|
-
toggleSelection: state.select,
|
|
753
|
-
replaceSelection: state.select,
|
|
754
|
-
extendSelection: () => {},
|
|
755
|
-
selectAll: () => {},
|
|
756
|
-
clearSelection: () => state.setSelectedKey(null),
|
|
757
|
-
childFocusStrategy: () => null,
|
|
758
|
-
} as any
|
|
759
|
-
);
|
|
760
|
-
|
|
761
|
-
// Create hover
|
|
762
|
-
const { isHovered, hoverProps } = createHover({
|
|
763
|
-
get isDisabled() {
|
|
764
|
-
return optionAria.isDisabled();
|
|
765
|
-
},
|
|
766
|
-
});
|
|
767
|
-
|
|
768
|
-
// Render props values
|
|
769
|
-
const renderValues = createMemo<ComboBoxOptionRenderProps>(() => ({
|
|
770
|
-
isSelected: optionAria.isSelected(),
|
|
771
|
-
isFocused: optionAria.isFocused(),
|
|
772
|
-
isFocusVisible: optionAria.isFocusVisible(),
|
|
773
|
-
isPressed: optionAria.isPressed(),
|
|
774
|
-
isHovered: isHovered(),
|
|
775
|
-
isDisabled: optionAria.isDisabled(),
|
|
776
|
-
}));
|
|
777
|
-
|
|
778
|
-
// Resolve render props
|
|
779
|
-
const renderProps = useRenderProps(
|
|
780
|
-
{
|
|
781
|
-
children: props.children,
|
|
782
|
-
class: local.class,
|
|
783
|
-
style: local.style,
|
|
784
|
-
defaultClassName: 'solidaria-ComboBox-option',
|
|
785
|
-
},
|
|
786
|
-
renderValues
|
|
787
|
-
);
|
|
788
|
-
|
|
789
|
-
// Remove ref from spread props
|
|
790
|
-
const cleanOptionProps = () => {
|
|
791
|
-
const { ref: _ref1, ...rest } = optionAria.optionProps as Record<string, unknown>;
|
|
792
|
-
return rest;
|
|
793
|
-
};
|
|
794
|
-
const cleanHoverProps = () => {
|
|
795
|
-
const { ref: _ref2, ...rest } = hoverProps as Record<string, unknown>;
|
|
796
|
-
return rest;
|
|
797
|
-
};
|
|
798
|
-
|
|
799
|
-
return (
|
|
800
|
-
<li
|
|
801
|
-
{...cleanOptionProps()}
|
|
802
|
-
{...cleanHoverProps()}
|
|
803
|
-
class={renderProps.class()}
|
|
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}
|
|
811
|
-
>
|
|
812
|
-
{renderProps.renderChildren()}
|
|
813
|
-
</li>
|
|
814
|
-
);
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
// Attach sub-components
|
|
818
|
-
ComboBox.Input = ComboBoxInput;
|
|
819
|
-
ComboBox.Button = ComboBoxButton;
|
|
820
|
-
ComboBox.ListBox = ComboBoxListBox;
|
|
821
|
-
ComboBox.Option = ComboBoxOption;
|
|
822
|
-
|
|
823
|
-
// Re-export filter function for convenience
|
|
824
|
-
export { defaultContainsFilter };
|