@proyecto-viviana/solidaria-components 0.2.5 → 0.2.9
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/dist/ActionBar.d.ts +71 -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/Breadcrumbs.d.ts +10 -2
- package/dist/Breadcrumbs.d.ts.map +1 -1
- package/dist/Button.d.ts +4 -0
- package/dist/Button.d.ts.map +1 -1
- package/dist/Calendar.d.ts +13 -0
- package/dist/Calendar.d.ts.map +1 -1
- package/dist/Checkbox.d.ts +2 -2
- package/dist/Checkbox.d.ts.map +1 -1
- package/dist/Collection.d.ts +125 -0
- package/dist/Collection.d.ts.map +1 -0
- package/dist/Color.d.ts +114 -2
- 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 +64 -0
- 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 +27 -2
- package/dist/DateField.d.ts.map +1 -1
- package/dist/DatePicker.d.ts +67 -2
- package/dist/DatePicker.d.ts.map +1 -1
- package/dist/Dialog.d.ts.map +1 -1
- package/dist/Disclosure.d.ts +2 -0
- 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 +23 -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 +27 -0
- package/dist/Form.d.ts.map +1 -0
- package/dist/GridList.d.ts +40 -1
- package/dist/GridList.d.ts.map +1 -1
- 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/Link.d.ts.map +1 -1
- package/dist/ListBox.d.ts +43 -1
- 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 +20 -2
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Meter.d.ts +2 -2
- package/dist/Meter.d.ts.map +1 -1
- package/dist/Modal.d.ts +2 -0
- package/dist/Modal.d.ts.map +1 -1
- package/dist/NumberField.d.ts +2 -0
- package/dist/NumberField.d.ts.map +1 -1
- package/dist/Popover.d.ts +4 -2
- 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 +2 -2
- package/dist/ProgressBar.d.ts.map +1 -1
- package/dist/RadioGroup.d.ts.map +1 -1
- package/dist/RangeCalendar.d.ts +5 -0
- 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 +2 -3
- package/dist/SearchField.d.ts.map +1 -1
- package/dist/Select.d.ts +11 -0
- 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/SharedElementTransition.d.ts +39 -0
- package/dist/SharedElementTransition.d.ts.map +1 -0
- package/dist/Slider.d.ts +6 -3
- package/dist/Slider.d.ts.map +1 -1
- package/dist/Table.d.ts +39 -0
- package/dist/Table.d.ts.map +1 -1
- package/dist/Tabs.d.ts +4 -3
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/TagGroup.d.ts +12 -2
- 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 +4 -0
- package/dist/TextField.d.ts.map +1 -1
- package/dist/TimeField.d.ts +26 -1
- package/dist/TimeField.d.ts.map +1 -1
- package/dist/Toast.d.ts.map +1 -1
- package/dist/ToggleButton.d.ts +30 -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.map +1 -1
- package/dist/Tooltip.d.ts +9 -0
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/Tree.d.ts +44 -2
- 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 +3 -1
- package/dist/VisuallyHidden.d.ts.map +1 -1
- package/dist/contexts.d.ts +1 -0
- package/dist/contexts.d.ts.map +1 -1
- package/dist/index.d.ts +57 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13961 -5946
- package/dist/index.js.map +1 -7
- package/dist/index.ssr.js +9612 -2401
- package/dist/index.ssr.js.map +1 -7
- package/dist/useDragAndDrop.d.ts +93 -0
- package/dist/useDragAndDrop.d.ts.map +1 -0
- package/dist/utils.d.ts +7 -1
- 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 +8 -6
- package/src/ActionBar.tsx +248 -0
- package/src/ActionGroup.tsx +285 -0
- package/src/Alert.tsx +177 -0
- package/src/Autocomplete.tsx +1 -1
- package/src/Breadcrumbs.tsx +103 -17
- package/src/Button.tsx +65 -21
- package/src/Calendar.tsx +179 -53
- package/src/Checkbox.tsx +1 -2
- package/src/Collection.tsx +341 -0
- package/src/Color.tsx +652 -34
- package/src/ColorEditor.tsx +231 -0
- package/src/ComboBox.tsx +315 -81
- package/src/ContextualHelpTrigger.tsx +183 -0
- package/src/DateField.tsx +93 -19
- package/src/DatePicker.tsx +495 -25
- package/src/Dialog.tsx +40 -9
- package/src/Disclosure.tsx +33 -27
- package/src/DragAndDrop.tsx +334 -0
- package/src/DragPreview.tsx +45 -0
- package/src/DropZone.tsx +213 -0
- package/src/FieldError.tsx +67 -0
- package/src/FileTrigger.tsx +83 -0
- package/src/Focusable.tsx +106 -0
- package/src/Form.tsx +85 -0
- package/src/GridList.tsx +379 -41
- package/src/Icon.tsx +154 -0
- package/src/Keyboard.tsx +26 -0
- package/src/Link.tsx +14 -1
- package/src/ListBox.tsx +484 -33
- package/src/ListDropTargetDelegate.ts +282 -0
- package/src/Menu.tsx +388 -35
- package/src/Meter.tsx +7 -3
- package/src/Modal.tsx +32 -4
- package/src/NumberField.tsx +163 -43
- package/src/Popover.tsx +136 -180
- package/src/Pressable.tsx +108 -0
- package/src/ProgressBar.tsx +7 -3
- package/src/RadioGroup.tsx +35 -25
- package/src/RangeCalendar.tsx +100 -68
- package/src/RouterProvider.tsx +240 -0
- package/src/SearchField.tsx +142 -34
- package/src/Select.tsx +221 -73
- package/src/SelectionIndicator.tsx +105 -0
- package/src/SharedElementTransition.tsx +258 -0
- package/src/Slider.tsx +16 -6
- package/src/Table.tsx +417 -57
- package/src/Tabs.tsx +68 -35
- package/src/TagGroup.tsx +121 -36
- package/src/Text.tsx +18 -0
- package/src/TextField.tsx +25 -8
- package/src/TimeField.tsx +101 -151
- package/src/Toast.tsx +108 -14
- package/src/ToggleButton.tsx +159 -0
- package/src/ToggleButtonGroup.tsx +136 -0
- package/src/Toolbar.tsx +14 -8
- package/src/Tooltip.tsx +108 -19
- package/src/Tree.tsx +1143 -87
- package/src/Virtualizer.tsx +702 -0
- package/src/VirtualizerLayouts.ts +265 -0
- package/src/VisuallyHidden.tsx +15 -21
- package/src/contexts.ts +1 -0
- package/src/index.ts +1057 -620
- package/src/useDragAndDrop.ts +351 -0
- package/src/utils.tsx +37 -3
- package/src/virtualizer/Layout.ts +200 -0
package/src/ComboBox.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type Accessor,
|
|
11
11
|
createContext,
|
|
12
12
|
createMemo,
|
|
13
|
+
onCleanup,
|
|
13
14
|
splitProps,
|
|
14
15
|
useContext,
|
|
15
16
|
For,
|
|
@@ -22,12 +23,14 @@ import {
|
|
|
22
23
|
createHover,
|
|
23
24
|
createInteractOutside,
|
|
24
25
|
type AriaComboBoxProps,
|
|
26
|
+
type AriaListBoxProps,
|
|
25
27
|
type AriaOptionProps,
|
|
26
28
|
} from '@proyecto-viviana/solidaria';
|
|
27
29
|
import {
|
|
28
30
|
createComboBoxState,
|
|
29
31
|
defaultContainsFilter,
|
|
30
32
|
type ComboBoxState,
|
|
33
|
+
type ListState,
|
|
31
34
|
type Key,
|
|
32
35
|
type FilterFn,
|
|
33
36
|
type MenuTriggerAction,
|
|
@@ -40,6 +43,10 @@ import {
|
|
|
40
43
|
useRenderProps,
|
|
41
44
|
filterDOMProps,
|
|
42
45
|
} from './utils';
|
|
46
|
+
import {
|
|
47
|
+
SelectionIndicatorContext,
|
|
48
|
+
type SelectionIndicatorContextValue,
|
|
49
|
+
} from './SelectionIndicator';
|
|
43
50
|
|
|
44
51
|
// ============================================
|
|
45
52
|
// TYPES
|
|
@@ -56,6 +63,8 @@ export interface ComboBoxRenderProps {
|
|
|
56
63
|
isDisabled: boolean;
|
|
57
64
|
/** Whether the combobox is required. */
|
|
58
65
|
isRequired: boolean;
|
|
66
|
+
/** Whether the combobox is invalid. */
|
|
67
|
+
isInvalid: boolean;
|
|
59
68
|
/** Whether a value is selected. */
|
|
60
69
|
isSelected: boolean;
|
|
61
70
|
/** The current input value. */
|
|
@@ -103,6 +112,14 @@ export interface ComboBoxProps<T>
|
|
|
103
112
|
menuTrigger?: 'focus' | 'input' | 'manual';
|
|
104
113
|
/** The name of the combobox, used when submitting an HTML form. */
|
|
105
114
|
name?: string;
|
|
115
|
+
/**
|
|
116
|
+
* Controls what value is submitted in forms.
|
|
117
|
+
* - 'key': submit the selected key via hidden input (default)
|
|
118
|
+
* - 'text': submit the text input value
|
|
119
|
+
*
|
|
120
|
+
* When allowsCustomValue is true, formValue is forced to 'text'.
|
|
121
|
+
*/
|
|
122
|
+
formValue?: 'key' | 'text';
|
|
106
123
|
/** The children of the component (compound components: ComboBoxInput, ComboBoxButton, ComboBoxListBox). */
|
|
107
124
|
children: JSX.Element;
|
|
108
125
|
/** The CSS className for the element. */
|
|
@@ -135,6 +152,33 @@ export interface ComboBoxInputProps extends SlotProps {
|
|
|
135
152
|
style?: StyleOrFunction<ComboBoxInputRenderProps>;
|
|
136
153
|
}
|
|
137
154
|
|
|
155
|
+
export interface ComboBoxLabelProps extends SlotProps {
|
|
156
|
+
/** The children of the label element. */
|
|
157
|
+
children?: JSX.Element;
|
|
158
|
+
/** The CSS className for the element. */
|
|
159
|
+
class?: string;
|
|
160
|
+
/** The inline style for the element. */
|
|
161
|
+
style?: JSX.CSSProperties;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface ComboBoxDescriptionProps extends SlotProps {
|
|
165
|
+
/** The children of the description element. */
|
|
166
|
+
children?: JSX.Element;
|
|
167
|
+
/** The CSS className for the element. */
|
|
168
|
+
class?: string;
|
|
169
|
+
/** The inline style for the element. */
|
|
170
|
+
style?: JSX.CSSProperties;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface ComboBoxErrorMessageProps extends SlotProps {
|
|
174
|
+
/** The children of the error message element. */
|
|
175
|
+
children?: JSX.Element;
|
|
176
|
+
/** The CSS className for the element. */
|
|
177
|
+
class?: string;
|
|
178
|
+
/** The inline style for the element. */
|
|
179
|
+
style?: JSX.CSSProperties;
|
|
180
|
+
}
|
|
181
|
+
|
|
138
182
|
export interface ComboBoxButtonRenderProps {
|
|
139
183
|
/** Whether the combobox is open. */
|
|
140
184
|
isOpen: boolean;
|
|
@@ -148,6 +192,17 @@ export interface ComboBoxButtonRenderProps {
|
|
|
148
192
|
isDisabled: boolean;
|
|
149
193
|
}
|
|
150
194
|
|
|
195
|
+
export interface ComboBoxValueRenderProps {
|
|
196
|
+
textValue: string;
|
|
197
|
+
isPlaceholder: boolean;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export interface ComboBoxValueProps extends SlotProps {
|
|
201
|
+
children?: RenderChildren<ComboBoxValueRenderProps>;
|
|
202
|
+
class?: ClassNameOrFunction<ComboBoxValueRenderProps>;
|
|
203
|
+
style?: StyleOrFunction<ComboBoxValueRenderProps>;
|
|
204
|
+
}
|
|
205
|
+
|
|
151
206
|
export interface ComboBoxButtonProps extends SlotProps {
|
|
152
207
|
/** The children of the button. */
|
|
153
208
|
children?: RenderChildren<ComboBoxButtonRenderProps>;
|
|
@@ -213,6 +268,8 @@ interface ComboBoxContextValue<T> {
|
|
|
213
268
|
buttonProps: JSX.HTMLAttributes<HTMLElement>;
|
|
214
269
|
listBoxProps: JSX.HTMLAttributes<HTMLElement>;
|
|
215
270
|
labelProps: JSX.HTMLAttributes<HTMLElement>;
|
|
271
|
+
descriptionProps: JSX.HTMLAttributes<HTMLElement>;
|
|
272
|
+
errorMessageProps: JSX.HTMLAttributes<HTMLElement>;
|
|
216
273
|
isOpen: Accessor<boolean>;
|
|
217
274
|
isFocused: Accessor<boolean>;
|
|
218
275
|
isFocusVisible: Accessor<boolean>;
|
|
@@ -221,10 +278,13 @@ interface ComboBoxContextValue<T> {
|
|
|
221
278
|
setInputRef: (el: HTMLInputElement | null) => void;
|
|
222
279
|
buttonRef: () => HTMLElement | null;
|
|
223
280
|
setButtonRef: (el: HTMLElement | null) => void;
|
|
281
|
+
listBoxRef: () => HTMLElement | null;
|
|
282
|
+
setListBoxRef: (el: HTMLElement | null) => void;
|
|
224
283
|
}
|
|
225
284
|
|
|
226
285
|
export const ComboBoxContext = createContext<ComboBoxContextValue<unknown> | null>(null);
|
|
227
286
|
export const ComboBoxStateContext = createContext<ComboBoxState<unknown> | null>(null);
|
|
287
|
+
export const ComboBoxValueContext = ComboBoxContext;
|
|
228
288
|
|
|
229
289
|
// ============================================
|
|
230
290
|
// COMPONENTS
|
|
@@ -236,7 +296,7 @@ export const ComboBoxStateContext = createContext<ComboBoxState<unknown> | null>
|
|
|
236
296
|
export function ComboBox<T>(props: ComboBoxProps<T>): JSX.Element {
|
|
237
297
|
const [local, stateProps, ariaProps] = splitProps(
|
|
238
298
|
props,
|
|
239
|
-
['class', 'style', 'slot'],
|
|
299
|
+
['class', 'style', 'slot', 'children'],
|
|
240
300
|
[
|
|
241
301
|
'items',
|
|
242
302
|
'getKey',
|
|
@@ -257,12 +317,14 @@ export function ComboBox<T>(props: ComboBoxProps<T>): JSX.Element {
|
|
|
257
317
|
'allowsEmptyCollection',
|
|
258
318
|
'menuTrigger',
|
|
259
319
|
'name',
|
|
320
|
+
'formValue',
|
|
260
321
|
]
|
|
261
322
|
);
|
|
262
323
|
|
|
263
324
|
// Refs
|
|
264
325
|
let inputRef: HTMLInputElement | null = null;
|
|
265
326
|
let buttonRef: HTMLElement | null = null;
|
|
327
|
+
let listBoxRef: HTMLElement | null = null;
|
|
266
328
|
|
|
267
329
|
// Create combobox state
|
|
268
330
|
const state = createComboBoxState<T>({
|
|
@@ -331,12 +393,25 @@ export function ComboBox<T>(props: ComboBoxProps<T>): JSX.Element {
|
|
|
331
393
|
},
|
|
332
394
|
});
|
|
333
395
|
|
|
396
|
+
const effectiveFormValue = createMemo<'key' | 'text'>(() => {
|
|
397
|
+
if (stateProps.allowsCustomValue) {
|
|
398
|
+
return 'text';
|
|
399
|
+
}
|
|
400
|
+
return stateProps.formValue ?? 'key';
|
|
401
|
+
});
|
|
402
|
+
|
|
334
403
|
// Create combobox aria props
|
|
335
404
|
const comboBoxAria = createComboBox<T>(
|
|
336
|
-
|
|
405
|
+
{
|
|
406
|
+
...ariaProps,
|
|
407
|
+
get name() {
|
|
408
|
+
return effectiveFormValue() === 'text' ? stateProps.name : undefined;
|
|
409
|
+
},
|
|
410
|
+
},
|
|
337
411
|
state,
|
|
338
412
|
() => inputRef,
|
|
339
|
-
() => buttonRef
|
|
413
|
+
() => buttonRef,
|
|
414
|
+
() => listBoxRef
|
|
340
415
|
);
|
|
341
416
|
|
|
342
417
|
// Create hover for wrapper
|
|
@@ -353,6 +428,7 @@ export function ComboBox<T>(props: ComboBoxProps<T>): JSX.Element {
|
|
|
353
428
|
isFocusVisible: comboBoxAria.isFocusVisible(),
|
|
354
429
|
isDisabled: !!ariaProps.isDisabled,
|
|
355
430
|
isRequired: !!ariaProps.isRequired,
|
|
431
|
+
isInvalid: !!ariaProps.isInvalid,
|
|
356
432
|
isSelected: state.selectedKey() != null,
|
|
357
433
|
inputValue: state.inputValue(),
|
|
358
434
|
}));
|
|
@@ -387,6 +463,8 @@ export function ComboBox<T>(props: ComboBoxProps<T>): JSX.Element {
|
|
|
387
463
|
buttonProps: comboBoxAria.buttonProps,
|
|
388
464
|
listBoxProps: comboBoxAria.listBoxProps,
|
|
389
465
|
labelProps: comboBoxAria.labelProps,
|
|
466
|
+
descriptionProps: comboBoxAria.descriptionProps,
|
|
467
|
+
errorMessageProps: comboBoxAria.errorMessageProps,
|
|
390
468
|
isOpen: comboBoxAria.isOpen,
|
|
391
469
|
isFocused: comboBoxAria.isFocused,
|
|
392
470
|
isFocusVisible: comboBoxAria.isFocusVisible,
|
|
@@ -395,6 +473,8 @@ export function ComboBox<T>(props: ComboBoxProps<T>): JSX.Element {
|
|
|
395
473
|
setInputRef: (el) => { inputRef = el; },
|
|
396
474
|
buttonRef: () => buttonRef,
|
|
397
475
|
setButtonRef: (el) => { buttonRef = el; },
|
|
476
|
+
listBoxRef: () => listBoxRef,
|
|
477
|
+
setListBoxRef: (el) => { listBoxRef = el; },
|
|
398
478
|
}}
|
|
399
479
|
>
|
|
400
480
|
<ComboBoxStateContext.Provider value={state}>
|
|
@@ -408,28 +488,113 @@ export function ComboBox<T>(props: ComboBoxProps<T>): JSX.Element {
|
|
|
408
488
|
data-focus-visible={comboBoxAria.isFocusVisible() || undefined}
|
|
409
489
|
data-disabled={ariaProps.isDisabled || undefined}
|
|
410
490
|
data-required={ariaProps.isRequired || undefined}
|
|
491
|
+
data-invalid={ariaProps.isInvalid || undefined}
|
|
411
492
|
data-hovered={isHovered() || undefined}
|
|
412
493
|
>
|
|
413
|
-
{/* Hidden input for form submission */}
|
|
414
|
-
<Show when={stateProps.name}>
|
|
494
|
+
{/* Hidden input for key-based form submission parity */}
|
|
495
|
+
<Show when={stateProps.name && effectiveFormValue() === 'key'}>
|
|
415
496
|
<input
|
|
416
497
|
type="hidden"
|
|
417
498
|
name={stateProps.name}
|
|
418
499
|
value={state.selectedKey()?.toString() ?? ''}
|
|
419
500
|
/>
|
|
420
501
|
</Show>
|
|
421
|
-
{
|
|
502
|
+
{local.children}
|
|
422
503
|
</div>
|
|
423
504
|
</ComboBoxStateContext.Provider>
|
|
424
505
|
</ComboBoxContext.Provider>
|
|
425
506
|
);
|
|
426
507
|
}
|
|
427
508
|
|
|
509
|
+
/**
|
|
510
|
+
* Label element for a combobox.
|
|
511
|
+
*/
|
|
512
|
+
export function ComboBoxLabel(props: ComboBoxLabelProps): JSX.Element {
|
|
513
|
+
const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'children']);
|
|
514
|
+
|
|
515
|
+
const context = useContext(ComboBoxContext);
|
|
516
|
+
if (!context) {
|
|
517
|
+
throw new Error('ComboBoxLabel must be used within a ComboBox');
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const cleanLabelProps = () => {
|
|
521
|
+
const { ref: _ref, ...rest } = context.labelProps as Record<string, unknown>;
|
|
522
|
+
return rest;
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
return (
|
|
526
|
+
<label
|
|
527
|
+
{...domProps}
|
|
528
|
+
{...cleanLabelProps()}
|
|
529
|
+
class={local.class}
|
|
530
|
+
style={local.style}
|
|
531
|
+
>
|
|
532
|
+
{local.children}
|
|
533
|
+
</label>
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Description element for a combobox.
|
|
539
|
+
*/
|
|
540
|
+
export function ComboBoxDescription(props: ComboBoxDescriptionProps): JSX.Element {
|
|
541
|
+
const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'children']);
|
|
542
|
+
|
|
543
|
+
const context = useContext(ComboBoxContext);
|
|
544
|
+
if (!context) {
|
|
545
|
+
throw new Error('ComboBoxDescription must be used within a ComboBox');
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const cleanDescriptionProps = () => {
|
|
549
|
+
const { ref: _ref, ...rest } = context.descriptionProps as Record<string, unknown>;
|
|
550
|
+
return rest;
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
return (
|
|
554
|
+
<div
|
|
555
|
+
{...domProps}
|
|
556
|
+
{...cleanDescriptionProps()}
|
|
557
|
+
class={local.class}
|
|
558
|
+
style={local.style}
|
|
559
|
+
>
|
|
560
|
+
{local.children}
|
|
561
|
+
</div>
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Error message element for a combobox.
|
|
567
|
+
*/
|
|
568
|
+
export function ComboBoxErrorMessage(props: ComboBoxErrorMessageProps): JSX.Element {
|
|
569
|
+
const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'children']);
|
|
570
|
+
|
|
571
|
+
const context = useContext(ComboBoxContext);
|
|
572
|
+
if (!context) {
|
|
573
|
+
throw new Error('ComboBoxErrorMessage must be used within a ComboBox');
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const cleanErrorMessageProps = () => {
|
|
577
|
+
const { ref: _ref, ...rest } = context.errorMessageProps as Record<string, unknown>;
|
|
578
|
+
return rest;
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
return (
|
|
582
|
+
<div
|
|
583
|
+
{...domProps}
|
|
584
|
+
{...cleanErrorMessageProps()}
|
|
585
|
+
class={local.class}
|
|
586
|
+
style={local.style}
|
|
587
|
+
>
|
|
588
|
+
{local.children}
|
|
589
|
+
</div>
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
|
|
428
593
|
/**
|
|
429
594
|
* The text input for a combobox.
|
|
430
595
|
*/
|
|
431
596
|
export function ComboBoxInput(props: ComboBoxInputProps): JSX.Element {
|
|
432
|
-
const [local] = splitProps(props, ['class', 'style', 'slot']);
|
|
597
|
+
const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'children']);
|
|
433
598
|
|
|
434
599
|
// Get context
|
|
435
600
|
const context = useContext(ComboBoxContext);
|
|
@@ -458,7 +623,7 @@ export function ComboBoxInput(props: ComboBoxInputProps): JSX.Element {
|
|
|
458
623
|
// Resolve render props
|
|
459
624
|
const renderProps = useRenderProps(
|
|
460
625
|
{
|
|
461
|
-
children:
|
|
626
|
+
children: local.children,
|
|
462
627
|
class: local.class,
|
|
463
628
|
style: local.style,
|
|
464
629
|
defaultClassName: 'solidaria-ComboBox-input',
|
|
@@ -478,6 +643,7 @@ export function ComboBoxInput(props: ComboBoxInputProps): JSX.Element {
|
|
|
478
643
|
|
|
479
644
|
return (
|
|
480
645
|
<input
|
|
646
|
+
{...domProps}
|
|
481
647
|
ref={(el) => setInputRef(el)}
|
|
482
648
|
{...cleanInputProps()}
|
|
483
649
|
{...cleanHoverProps()}
|
|
@@ -493,11 +659,45 @@ export function ComboBoxInput(props: ComboBoxInputProps): JSX.Element {
|
|
|
493
659
|
);
|
|
494
660
|
}
|
|
495
661
|
|
|
662
|
+
export function ComboBoxValue(props: ComboBoxValueProps): JSX.Element {
|
|
663
|
+
const context = useContext(ComboBoxContext);
|
|
664
|
+
if (!context) {
|
|
665
|
+
throw new Error('ComboBoxValue must be used within a ComboBox');
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const state = context.state;
|
|
669
|
+
const textValue = createMemo(() => state.inputValue() ?? '');
|
|
670
|
+
const isPlaceholder = createMemo(() => textValue().length === 0);
|
|
671
|
+
|
|
672
|
+
const renderProps = useRenderProps(
|
|
673
|
+
{
|
|
674
|
+
children: props.children,
|
|
675
|
+
class: props.class,
|
|
676
|
+
style: props.style,
|
|
677
|
+
defaultClassName: 'solidaria-ComboBox-value',
|
|
678
|
+
},
|
|
679
|
+
() => ({
|
|
680
|
+
textValue: textValue(),
|
|
681
|
+
isPlaceholder: isPlaceholder(),
|
|
682
|
+
})
|
|
683
|
+
);
|
|
684
|
+
|
|
685
|
+
return (
|
|
686
|
+
<span
|
|
687
|
+
class={renderProps.class()}
|
|
688
|
+
style={renderProps.style()}
|
|
689
|
+
data-placeholder={isPlaceholder() || undefined}
|
|
690
|
+
>
|
|
691
|
+
{props.children ? renderProps.renderChildren() : textValue()}
|
|
692
|
+
</span>
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
|
|
496
696
|
/**
|
|
497
697
|
* The trigger button for a combobox.
|
|
498
698
|
*/
|
|
499
699
|
export function ComboBoxButton(props: ComboBoxButtonProps): JSX.Element {
|
|
500
|
-
const [local] = splitProps(props, ['class', 'style', 'slot']);
|
|
700
|
+
const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'children']);
|
|
501
701
|
|
|
502
702
|
// Get context
|
|
503
703
|
const context = useContext(ComboBoxContext);
|
|
@@ -513,22 +713,19 @@ export function ComboBoxButton(props: ComboBoxButtonProps): JSX.Element {
|
|
|
513
713
|
},
|
|
514
714
|
});
|
|
515
715
|
|
|
516
|
-
// Track pressed state
|
|
517
|
-
let isPressed = false;
|
|
518
|
-
|
|
519
716
|
// Render props values
|
|
520
717
|
const renderValues = createMemo<ComboBoxButtonRenderProps>(() => ({
|
|
521
718
|
isOpen: isOpen(),
|
|
522
719
|
isFocused: isFocused(),
|
|
523
720
|
isHovered: isHovered(),
|
|
524
|
-
isPressed,
|
|
721
|
+
isPressed: isOpen(),
|
|
525
722
|
isDisabled: state.isDisabled,
|
|
526
723
|
}));
|
|
527
724
|
|
|
528
725
|
// Resolve render props
|
|
529
726
|
const renderProps = useRenderProps(
|
|
530
727
|
{
|
|
531
|
-
children:
|
|
728
|
+
children: local.children,
|
|
532
729
|
class: local.class,
|
|
533
730
|
style: local.style,
|
|
534
731
|
defaultClassName: 'solidaria-ComboBox-button',
|
|
@@ -548,6 +745,7 @@ export function ComboBoxButton(props: ComboBoxButtonProps): JSX.Element {
|
|
|
548
745
|
|
|
549
746
|
return (
|
|
550
747
|
<button
|
|
748
|
+
{...domProps}
|
|
551
749
|
ref={(el) => setButtonRef(el)}
|
|
552
750
|
{...cleanButtonProps()}
|
|
553
751
|
{...cleanHoverProps()}
|
|
@@ -567,14 +765,21 @@ export function ComboBoxButton(props: ComboBoxButtonProps): JSX.Element {
|
|
|
567
765
|
* The listbox popup for a combobox.
|
|
568
766
|
*/
|
|
569
767
|
export function ComboBoxListBox<T>(props: ComboBoxListBoxProps<T>): JSX.Element {
|
|
570
|
-
const [local] = splitProps(props, ['class', 'style', 'slot']);
|
|
768
|
+
const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'children']);
|
|
571
769
|
|
|
572
770
|
// Get context
|
|
573
771
|
const context = useContext(ComboBoxContext);
|
|
574
772
|
if (!context) {
|
|
575
773
|
throw new Error('ComboBoxListBox must be used within a ComboBox');
|
|
576
774
|
}
|
|
577
|
-
const {
|
|
775
|
+
const {
|
|
776
|
+
listBoxProps: contextListBoxProps,
|
|
777
|
+
state: comboBoxState,
|
|
778
|
+
isOpen,
|
|
779
|
+
inputRef,
|
|
780
|
+
buttonRef,
|
|
781
|
+
setListBoxRef,
|
|
782
|
+
} = context;
|
|
578
783
|
const state = comboBoxState as ComboBoxState<T>;
|
|
579
784
|
|
|
580
785
|
// Ref for the listbox element (for click outside detection)
|
|
@@ -587,9 +792,13 @@ export function ComboBoxListBox<T>(props: ComboBoxListBoxProps<T>): JSX.Element
|
|
|
587
792
|
// Don't close if clicking the input or button
|
|
588
793
|
const target = e.target as HTMLElement;
|
|
589
794
|
const input = inputRef();
|
|
795
|
+
const button = buttonRef();
|
|
590
796
|
if (input?.contains(target)) {
|
|
591
797
|
return;
|
|
592
798
|
}
|
|
799
|
+
if (button?.contains(target)) {
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
593
802
|
if (isOpen()) {
|
|
594
803
|
state.close();
|
|
595
804
|
}
|
|
@@ -601,31 +810,8 @@ export function ComboBoxListBox<T>(props: ComboBoxListBoxProps<T>): JSX.Element
|
|
|
601
810
|
|
|
602
811
|
// Create listbox aria props using ComboBoxState's ListState-compatible interface
|
|
603
812
|
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
|
|
813
|
+
contextListBoxProps as unknown as AriaListBoxProps,
|
|
814
|
+
createComboBoxListStateAdapter(state)
|
|
629
815
|
);
|
|
630
816
|
|
|
631
817
|
// Render props values
|
|
@@ -659,8 +845,14 @@ export function ComboBoxListBox<T>(props: ComboBoxListBoxProps<T>): JSX.Element
|
|
|
659
845
|
// This is critical - if we don't prevent default, the input loses focus
|
|
660
846
|
// and the blur handler closes the menu before the click can be processed
|
|
661
847
|
// We need to attach this in the ref callback to use capture phase
|
|
848
|
+
let cleanupFocusGuard: (() => void) | undefined;
|
|
849
|
+
|
|
662
850
|
const setupMouseDownHandler = (el: HTMLUListElement) => {
|
|
851
|
+
cleanupFocusGuard?.();
|
|
852
|
+
cleanupFocusGuard = undefined;
|
|
853
|
+
|
|
663
854
|
listBoxRef = el;
|
|
855
|
+
setListBoxRef(el);
|
|
664
856
|
if (el) {
|
|
665
857
|
const mouseHandler = (e: MouseEvent) => {
|
|
666
858
|
e.preventDefault();
|
|
@@ -670,12 +862,22 @@ export function ComboBoxListBox<T>(props: ComboBoxListBoxProps<T>): JSX.Element
|
|
|
670
862
|
};
|
|
671
863
|
el.addEventListener('mousedown', mouseHandler, true); // capture phase
|
|
672
864
|
el.addEventListener('pointerdown', pointerHandler, true); // capture phase
|
|
865
|
+
cleanupFocusGuard = () => {
|
|
866
|
+
el.removeEventListener('mousedown', mouseHandler, true);
|
|
867
|
+
el.removeEventListener('pointerdown', pointerHandler, true);
|
|
868
|
+
};
|
|
673
869
|
}
|
|
674
870
|
};
|
|
675
871
|
|
|
872
|
+
onCleanup(() => {
|
|
873
|
+
cleanupFocusGuard?.();
|
|
874
|
+
setListBoxRef(null);
|
|
875
|
+
});
|
|
876
|
+
|
|
676
877
|
return (
|
|
677
878
|
<Show when={isOpen()}>
|
|
678
879
|
<ul
|
|
880
|
+
{...domProps}
|
|
679
881
|
ref={setupMouseDownHandler}
|
|
680
882
|
{...cleanContextProps()}
|
|
681
883
|
{...cleanListBoxProps()}
|
|
@@ -683,7 +885,7 @@ export function ComboBoxListBox<T>(props: ComboBoxListBoxProps<T>): JSX.Element
|
|
|
683
885
|
style={renderProps.style()}
|
|
684
886
|
data-focused={state.isFocused() || undefined}
|
|
685
887
|
>
|
|
686
|
-
<Show when={
|
|
888
|
+
<Show when={local.children} fallback={
|
|
687
889
|
<For each={items()}>
|
|
688
890
|
{(node) => (
|
|
689
891
|
<ComboBoxOption id={node.key}>
|
|
@@ -693,7 +895,7 @@ export function ComboBoxListBox<T>(props: ComboBoxListBoxProps<T>): JSX.Element
|
|
|
693
895
|
</For>
|
|
694
896
|
}>
|
|
695
897
|
<For each={items()}>
|
|
696
|
-
{(node) => node.value != null ?
|
|
898
|
+
{(node) => node.value != null ? (local.children as Function)!(node.value) : null}
|
|
697
899
|
</For>
|
|
698
900
|
</Show>
|
|
699
901
|
</ul>
|
|
@@ -732,30 +934,7 @@ export function ComboBoxOption<T>(props: ComboBoxOptionProps<T>): JSX.Element {
|
|
|
732
934
|
return ariaProps['aria-label'];
|
|
733
935
|
},
|
|
734
936
|
},
|
|
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
|
|
937
|
+
createComboBoxListStateAdapter(state)
|
|
759
938
|
);
|
|
760
939
|
|
|
761
940
|
// Create hover
|
|
@@ -786,6 +965,10 @@ export function ComboBoxOption<T>(props: ComboBoxOptionProps<T>): JSX.Element {
|
|
|
786
965
|
renderValues
|
|
787
966
|
);
|
|
788
967
|
|
|
968
|
+
const selectionIndicatorContext = createMemo<SelectionIndicatorContextValue>(() => ({
|
|
969
|
+
isSelected: optionAria.isSelected,
|
|
970
|
+
}));
|
|
971
|
+
|
|
789
972
|
// Remove ref from spread props
|
|
790
973
|
const cleanOptionProps = () => {
|
|
791
974
|
const { ref: _ref1, ...rest } = optionAria.optionProps as Record<string, unknown>;
|
|
@@ -797,20 +980,22 @@ export function ComboBoxOption<T>(props: ComboBoxOptionProps<T>): JSX.Element {
|
|
|
797
980
|
};
|
|
798
981
|
|
|
799
982
|
return (
|
|
800
|
-
<
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
983
|
+
<SelectionIndicatorContext.Provider value={selectionIndicatorContext()}>
|
|
984
|
+
<li
|
|
985
|
+
{...cleanOptionProps()}
|
|
986
|
+
{...cleanHoverProps()}
|
|
987
|
+
class={renderProps.class()}
|
|
988
|
+
style={renderProps.style()}
|
|
989
|
+
data-selected={optionAria.isSelected() || undefined}
|
|
990
|
+
data-focused={optionAria.isFocused() || undefined}
|
|
991
|
+
data-focus-visible={optionAria.isFocusVisible() || undefined}
|
|
992
|
+
data-pressed={optionAria.isPressed() || undefined}
|
|
993
|
+
data-hovered={isHovered() || undefined}
|
|
994
|
+
data-disabled={optionAria.isDisabled() || undefined}
|
|
995
|
+
>
|
|
996
|
+
{renderProps.renderChildren()}
|
|
997
|
+
</li>
|
|
998
|
+
</SelectionIndicatorContext.Provider>
|
|
814
999
|
);
|
|
815
1000
|
}
|
|
816
1001
|
|
|
@@ -819,6 +1004,55 @@ ComboBox.Input = ComboBoxInput;
|
|
|
819
1004
|
ComboBox.Button = ComboBoxButton;
|
|
820
1005
|
ComboBox.ListBox = ComboBoxListBox;
|
|
821
1006
|
ComboBox.Option = ComboBoxOption;
|
|
1007
|
+
ComboBox.Label = ComboBoxLabel;
|
|
1008
|
+
ComboBox.Description = ComboBoxDescription;
|
|
1009
|
+
ComboBox.ErrorMessage = ComboBoxErrorMessage;
|
|
822
1010
|
|
|
823
1011
|
// Re-export filter function for convenience
|
|
824
1012
|
export { defaultContainsFilter };
|
|
1013
|
+
|
|
1014
|
+
function createComboBoxListStateAdapter<T>(state: ComboBoxState<T>): ListState<T> {
|
|
1015
|
+
const selectedKeys = createMemo(() => {
|
|
1016
|
+
const key = state.selectedKey();
|
|
1017
|
+
return key != null ? new Set<Key>([key]) : new Set<Key>();
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
const disabledKeys = createMemo(() => {
|
|
1021
|
+
const keys = new Set<Key>();
|
|
1022
|
+
for (const node of state.collection()) {
|
|
1023
|
+
if (node.isDisabled) keys.add(node.key);
|
|
1024
|
+
}
|
|
1025
|
+
return keys;
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
return {
|
|
1029
|
+
collection: state.collection,
|
|
1030
|
+
isFocused: state.isFocused,
|
|
1031
|
+
setFocused: state.setFocused,
|
|
1032
|
+
focusedKey: state.focusedKey,
|
|
1033
|
+
setFocusedKey: (key) => state.setFocusedKey(key ?? null),
|
|
1034
|
+
childFocusStrategy: () => null,
|
|
1035
|
+
selectionMode: state.selectionMode,
|
|
1036
|
+
selectionBehavior: () => 'replace',
|
|
1037
|
+
disallowEmptySelection: () => true,
|
|
1038
|
+
selectedKeys,
|
|
1039
|
+
disabledKeys,
|
|
1040
|
+
disabledBehavior: () => 'all',
|
|
1041
|
+
isEmpty: () => selectedKeys().size === 0,
|
|
1042
|
+
isSelectAll: () => false,
|
|
1043
|
+
isSelected: state.isSelected,
|
|
1044
|
+
isDisabled: state.isKeyDisabled,
|
|
1045
|
+
setSelectionBehavior: () => {},
|
|
1046
|
+
toggleSelection: (key) => state.select(key),
|
|
1047
|
+
replaceSelection: (key) => state.select(key),
|
|
1048
|
+
setSelectedKeys: (keys) => {
|
|
1049
|
+
const first = keys[Symbol.iterator]().next().value as Key | undefined;
|
|
1050
|
+
state.setSelectedKey(first ?? null);
|
|
1051
|
+
},
|
|
1052
|
+
selectAll: () => {},
|
|
1053
|
+
clearSelection: () => state.setSelectedKey(null),
|
|
1054
|
+
toggleSelectAll: () => {},
|
|
1055
|
+
extendSelection: (toKey) => state.select(toKey),
|
|
1056
|
+
select: (key) => state.select(key),
|
|
1057
|
+
};
|
|
1058
|
+
}
|