@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/Select.tsx
CHANGED
|
@@ -24,10 +24,12 @@ import {
|
|
|
24
24
|
createInteractOutside,
|
|
25
25
|
FocusScope,
|
|
26
26
|
type AriaSelectProps,
|
|
27
|
+
type AriaListBoxProps,
|
|
27
28
|
type AriaOptionProps,
|
|
28
29
|
} from '@proyecto-viviana/solidaria';
|
|
29
30
|
import {
|
|
30
31
|
createSelectState,
|
|
32
|
+
type ListState,
|
|
31
33
|
type SelectState,
|
|
32
34
|
type Key,
|
|
33
35
|
type CollectionNode,
|
|
@@ -40,6 +42,10 @@ import {
|
|
|
40
42
|
useRenderProps,
|
|
41
43
|
filterDOMProps,
|
|
42
44
|
} from './utils';
|
|
45
|
+
import {
|
|
46
|
+
SelectionIndicatorContext,
|
|
47
|
+
type SelectionIndicatorContextValue,
|
|
48
|
+
} from './SelectionIndicator';
|
|
43
49
|
|
|
44
50
|
// ============================================
|
|
45
51
|
// TYPES
|
|
@@ -73,12 +79,20 @@ export interface SelectProps<T>
|
|
|
73
79
|
getDisabled?: (item: T) => boolean;
|
|
74
80
|
/** Keys of disabled items. */
|
|
75
81
|
disabledKeys?: Iterable<Key>;
|
|
82
|
+
/** Selection mode. */
|
|
83
|
+
selectionMode?: 'single' | 'multiple';
|
|
76
84
|
/** The currently selected key (controlled). */
|
|
77
85
|
selectedKey?: Key | null;
|
|
78
86
|
/** The default selected key (uncontrolled). */
|
|
79
87
|
defaultSelectedKey?: Key | null;
|
|
88
|
+
/** Currently selected keys (controlled, for multiple selection). */
|
|
89
|
+
selectedKeys?: 'all' | Iterable<Key>;
|
|
90
|
+
/** Default selected keys (uncontrolled, for multiple selection). */
|
|
91
|
+
defaultSelectedKeys?: 'all' | Iterable<Key>;
|
|
80
92
|
/** Handler called when selection changes. */
|
|
81
93
|
onSelectionChange?: (key: Key | null) => void;
|
|
94
|
+
/** Handler called when selected keys change. */
|
|
95
|
+
onSelectionChangeKeys?: (keys: 'all' | Set<Key>) => void;
|
|
82
96
|
/** Whether the select is open (controlled). */
|
|
83
97
|
isOpen?: boolean;
|
|
84
98
|
/** Whether the select is open by default (uncontrolled). */
|
|
@@ -195,10 +209,12 @@ interface SelectContextValue<T> {
|
|
|
195
209
|
state: SelectState<T>;
|
|
196
210
|
triggerProps: JSX.HTMLAttributes<HTMLElement>;
|
|
197
211
|
valueProps: JSX.HTMLAttributes<HTMLElement>;
|
|
212
|
+
labelProps: JSX.HTMLAttributes<HTMLElement>;
|
|
198
213
|
menuProps: JSX.HTMLAttributes<HTMLElement>;
|
|
199
214
|
isOpen: Accessor<boolean>;
|
|
200
215
|
isFocused: Accessor<boolean>;
|
|
201
216
|
isFocusVisible: Accessor<boolean>;
|
|
217
|
+
isDisabled: Accessor<boolean>;
|
|
202
218
|
placeholder?: string;
|
|
203
219
|
items: T[];
|
|
204
220
|
renderItem?: (item: T) => JSX.Element;
|
|
@@ -206,6 +222,7 @@ interface SelectContextValue<T> {
|
|
|
206
222
|
|
|
207
223
|
export const SelectContext = createContext<SelectContextValue<unknown> | null>(null);
|
|
208
224
|
export const SelectStateContext = createContext<SelectState<unknown> | null>(null);
|
|
225
|
+
export const SelectValueContext = SelectContext;
|
|
209
226
|
|
|
210
227
|
// ============================================
|
|
211
228
|
// COMPONENTS
|
|
@@ -218,9 +235,17 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
218
235
|
const [local, stateProps, ariaProps] = splitProps(
|
|
219
236
|
props,
|
|
220
237
|
['class', 'style', 'slot'],
|
|
221
|
-
['items', 'getKey', 'getTextValue', 'getDisabled', 'disabledKeys', 'selectedKey', 'defaultSelectedKey', 'onSelectionChange', 'isOpen', 'defaultOpen', 'onOpenChange', 'name']
|
|
238
|
+
['items', 'getKey', 'getTextValue', 'getDisabled', 'disabledKeys', 'selectionMode', 'selectedKey', 'defaultSelectedKey', 'selectedKeys', 'defaultSelectedKeys', 'onSelectionChange', 'onSelectionChangeKeys', 'isOpen', 'defaultOpen', 'onOpenChange', 'name']
|
|
222
239
|
);
|
|
223
240
|
|
|
241
|
+
const resolveDisabled = (): boolean => {
|
|
242
|
+
const disabled = ariaProps.isDisabled;
|
|
243
|
+
if (typeof disabled === 'function') {
|
|
244
|
+
return (disabled as () => boolean)();
|
|
245
|
+
}
|
|
246
|
+
return !!disabled;
|
|
247
|
+
};
|
|
248
|
+
|
|
224
249
|
// Create select state
|
|
225
250
|
const state = createSelectState<T>({
|
|
226
251
|
get items() {
|
|
@@ -238,15 +263,27 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
238
263
|
get disabledKeys() {
|
|
239
264
|
return stateProps.disabledKeys;
|
|
240
265
|
},
|
|
266
|
+
get selectionMode() {
|
|
267
|
+
return stateProps.selectionMode;
|
|
268
|
+
},
|
|
241
269
|
get selectedKey() {
|
|
242
270
|
return stateProps.selectedKey;
|
|
243
271
|
},
|
|
244
272
|
get defaultSelectedKey() {
|
|
245
273
|
return stateProps.defaultSelectedKey;
|
|
246
274
|
},
|
|
275
|
+
get selectedKeys() {
|
|
276
|
+
return stateProps.selectedKeys;
|
|
277
|
+
},
|
|
278
|
+
get defaultSelectedKeys() {
|
|
279
|
+
return stateProps.defaultSelectedKeys;
|
|
280
|
+
},
|
|
247
281
|
get onSelectionChange() {
|
|
248
282
|
return stateProps.onSelectionChange;
|
|
249
283
|
},
|
|
284
|
+
get onSelectionChangeKeys() {
|
|
285
|
+
return stateProps.onSelectionChangeKeys;
|
|
286
|
+
},
|
|
250
287
|
get isOpen() {
|
|
251
288
|
return stateProps.isOpen;
|
|
252
289
|
},
|
|
@@ -257,7 +294,7 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
257
294
|
return stateProps.onOpenChange;
|
|
258
295
|
},
|
|
259
296
|
get isDisabled() {
|
|
260
|
-
return
|
|
297
|
+
return resolveDisabled();
|
|
261
298
|
},
|
|
262
299
|
get isRequired() {
|
|
263
300
|
return ariaProps.isRequired;
|
|
@@ -265,7 +302,7 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
265
302
|
});
|
|
266
303
|
|
|
267
304
|
// Create select aria props
|
|
268
|
-
const { triggerProps, valueProps, menuProps, isFocused, isFocusVisible, isOpen } = createSelect<T>(
|
|
305
|
+
const { labelProps, triggerProps, valueProps, menuProps, isFocused, isFocusVisible, isOpen } = createSelect<T>(
|
|
269
306
|
ariaProps,
|
|
270
307
|
state
|
|
271
308
|
);
|
|
@@ -273,7 +310,7 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
273
310
|
// Create hover for wrapper
|
|
274
311
|
const { isHovered, hoverProps } = createHover({
|
|
275
312
|
get isDisabled() {
|
|
276
|
-
return
|
|
313
|
+
return resolveDisabled();
|
|
277
314
|
},
|
|
278
315
|
});
|
|
279
316
|
|
|
@@ -282,9 +319,11 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
282
319
|
isOpen: isOpen(),
|
|
283
320
|
isFocused: isFocused(),
|
|
284
321
|
isFocusVisible: isFocusVisible(),
|
|
285
|
-
isDisabled:
|
|
322
|
+
isDisabled: resolveDisabled(),
|
|
286
323
|
isRequired: !!ariaProps.isRequired,
|
|
287
|
-
isSelected: state.
|
|
324
|
+
isSelected: state.selectionMode() === 'multiple'
|
|
325
|
+
? state.selectedKeys() === 'all' || (state.selectedKeys() as Set<Key>).size > 0
|
|
326
|
+
: state.selectedKey() != null,
|
|
288
327
|
}));
|
|
289
328
|
|
|
290
329
|
// Resolve render props
|
|
@@ -308,13 +347,17 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
308
347
|
const { ref: _ref, ...rest } = hoverProps as Record<string, unknown>;
|
|
309
348
|
return rest;
|
|
310
349
|
};
|
|
350
|
+
const cleanLabelProps = () => {
|
|
351
|
+
const { ref: _ref, ...rest } = labelProps as Record<string, unknown>;
|
|
352
|
+
return rest;
|
|
353
|
+
};
|
|
311
354
|
|
|
312
355
|
// Create hidden select for form submission
|
|
313
356
|
const { containerProps, selectProps: hiddenSelectProps } = createHiddenSelect({
|
|
314
357
|
state,
|
|
315
358
|
name: stateProps.name,
|
|
316
359
|
get isDisabled() {
|
|
317
|
-
return
|
|
360
|
+
return resolveDisabled();
|
|
318
361
|
},
|
|
319
362
|
});
|
|
320
363
|
|
|
@@ -324,10 +367,12 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
324
367
|
state,
|
|
325
368
|
triggerProps,
|
|
326
369
|
valueProps,
|
|
370
|
+
labelProps,
|
|
327
371
|
menuProps,
|
|
328
372
|
isOpen,
|
|
329
373
|
isFocused,
|
|
330
374
|
isFocusVisible,
|
|
375
|
+
isDisabled: resolveDisabled,
|
|
331
376
|
placeholder: ariaProps.placeholder,
|
|
332
377
|
items: stateProps.items,
|
|
333
378
|
}}
|
|
@@ -341,7 +386,7 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
341
386
|
data-open={isOpen() || undefined}
|
|
342
387
|
data-focused={isFocused() || undefined}
|
|
343
388
|
data-focus-visible={isFocusVisible() || undefined}
|
|
344
|
-
data-disabled={
|
|
389
|
+
data-disabled={resolveDisabled() || undefined}
|
|
345
390
|
data-required={ariaProps.isRequired || undefined}
|
|
346
391
|
data-hovered={isHovered() || undefined}
|
|
347
392
|
>
|
|
@@ -351,10 +396,23 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
351
396
|
<option />
|
|
352
397
|
<For each={stateProps.items}>
|
|
353
398
|
{(item) => {
|
|
354
|
-
const
|
|
355
|
-
const
|
|
399
|
+
const itemRecord = isObjectRecord(item) ? item : null;
|
|
400
|
+
const fallbackKey = itemRecord != null
|
|
401
|
+
? toKey(itemRecord.key) ?? toKey(itemRecord.id)
|
|
402
|
+
: undefined;
|
|
403
|
+
const key = stateProps.getKey?.(item) ?? fallbackKey ?? String(item);
|
|
404
|
+
const fallbackTextValue = itemRecord != null
|
|
405
|
+
? toTextValue(itemRecord.textValue) ?? toTextValue(itemRecord.label)
|
|
406
|
+
: undefined;
|
|
407
|
+
const textValue = stateProps.getTextValue?.(item) ?? fallbackTextValue ?? String(item);
|
|
408
|
+
const selectedKeys = state.selectedKeys();
|
|
409
|
+
const isSelected = state.selectionMode() === 'multiple'
|
|
410
|
+
? selectedKeys === 'all'
|
|
411
|
+
? true
|
|
412
|
+
: (selectedKeys as Set<Key>).has(key)
|
|
413
|
+
: key === state.selectedKey();
|
|
356
414
|
return (
|
|
357
|
-
<option value={String(key)} selected={
|
|
415
|
+
<option value={String(key)} selected={isSelected}>
|
|
358
416
|
{textValue}
|
|
359
417
|
</option>
|
|
360
418
|
);
|
|
@@ -362,6 +420,9 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
362
420
|
</For>
|
|
363
421
|
</select>
|
|
364
422
|
</div>
|
|
423
|
+
<Show when={ariaProps.label}>
|
|
424
|
+
<span {...cleanLabelProps()}>{ariaProps.label as JSX.Element}</span>
|
|
425
|
+
</Show>
|
|
365
426
|
{props.children}
|
|
366
427
|
</div>
|
|
367
428
|
</SelectStateContext.Provider>
|
|
@@ -373,7 +434,7 @@ export function Select<T>(props: SelectProps<T>): JSX.Element {
|
|
|
373
434
|
* The trigger button for a select.
|
|
374
435
|
*/
|
|
375
436
|
export function SelectTrigger(props: SelectTriggerProps): JSX.Element {
|
|
376
|
-
const [local] = splitProps(props, ['class', 'style', 'slot']);
|
|
437
|
+
const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'children']);
|
|
377
438
|
|
|
378
439
|
// Get context
|
|
379
440
|
const context = useContext(SelectContext);
|
|
@@ -421,6 +482,7 @@ export function SelectTrigger(props: SelectTriggerProps): JSX.Element {
|
|
|
421
482
|
|
|
422
483
|
return (
|
|
423
484
|
<button
|
|
485
|
+
{...domProps}
|
|
424
486
|
{...cleanTriggerProps()}
|
|
425
487
|
{...cleanHoverProps()}
|
|
426
488
|
type="button"
|
|
@@ -446,7 +508,7 @@ function defaultSelectValueChildren<T>(values: SelectValueRenderProps<T>) {
|
|
|
446
508
|
* Displays the selected value in a select.
|
|
447
509
|
*/
|
|
448
510
|
export function SelectValue<T>(props: SelectValueProps<T>): JSX.Element {
|
|
449
|
-
const [local] = splitProps(props, ['class', 'style', 'slot', 'placeholder']);
|
|
511
|
+
const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'placeholder', 'children']);
|
|
450
512
|
|
|
451
513
|
// Get context
|
|
452
514
|
const context = useContext(SelectContext);
|
|
@@ -462,10 +524,14 @@ export function SelectValue<T>(props: SelectValueProps<T>): JSX.Element {
|
|
|
462
524
|
// Render props values
|
|
463
525
|
const renderValues = createMemo<SelectValueRenderProps<T>>(() => {
|
|
464
526
|
const selectedItem = state.selectedItem();
|
|
527
|
+
const selectedItems = state.selectedItems();
|
|
528
|
+
const selectedText = state.selectionMode() === 'multiple'
|
|
529
|
+
? selectedItems.map((item) => item.textValue).join(', ')
|
|
530
|
+
: selectedItem?.textValue ?? null;
|
|
465
531
|
return {
|
|
466
532
|
selectedItem,
|
|
467
|
-
selectedText
|
|
468
|
-
isSelected: selectedItem != null,
|
|
533
|
+
selectedText,
|
|
534
|
+
isSelected: state.selectionMode() === 'multiple' ? selectedItems.length > 0 : selectedItem != null,
|
|
469
535
|
placeholder: placeholder(),
|
|
470
536
|
};
|
|
471
537
|
});
|
|
@@ -483,6 +549,7 @@ export function SelectValue<T>(props: SelectValueProps<T>): JSX.Element {
|
|
|
483
549
|
|
|
484
550
|
return (
|
|
485
551
|
<span
|
|
552
|
+
{...domProps}
|
|
486
553
|
{...valueProps}
|
|
487
554
|
class={renderProps.class()}
|
|
488
555
|
style={renderProps.style()}
|
|
@@ -497,7 +564,7 @@ export function SelectValue<T>(props: SelectValueProps<T>): JSX.Element {
|
|
|
497
564
|
* The listbox popup for a select.
|
|
498
565
|
*/
|
|
499
566
|
export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
|
|
500
|
-
const [local] = splitProps(props, ['class', 'style', 'slot']);
|
|
567
|
+
const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'children']);
|
|
501
568
|
|
|
502
569
|
// Get context
|
|
503
570
|
const context = useContext(SelectContext);
|
|
@@ -525,29 +592,13 @@ export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
|
|
|
525
592
|
|
|
526
593
|
// Create listbox aria props - reuse select's internal list state via collection
|
|
527
594
|
const { listBoxProps } = createListBox(
|
|
528
|
-
{},
|
|
529
595
|
{
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
isFocused: state.isFocused,
|
|
534
|
-
setFocused: state.setFocused,
|
|
535
|
-
selectedKeys: () => {
|
|
536
|
-
const key = state.selectedKey();
|
|
537
|
-
return key != null ? new Set([key]) : new Set();
|
|
596
|
+
...(menuProps as unknown as AriaListBoxProps),
|
|
597
|
+
get isDisabled() {
|
|
598
|
+
return state.isDisabled;
|
|
538
599
|
},
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
selectionMode: () => 'single' as const,
|
|
542
|
-
disallowEmptySelection: () => true,
|
|
543
|
-
select: (key: Key) => state.setSelectedKey(key),
|
|
544
|
-
toggleSelection: (key: Key) => state.setSelectedKey(key),
|
|
545
|
-
replaceSelection: (key: Key) => state.setSelectedKey(key),
|
|
546
|
-
extendSelection: () => {},
|
|
547
|
-
selectAll: () => {},
|
|
548
|
-
clearSelection: () => state.setSelectedKey(null),
|
|
549
|
-
childFocusStrategy: () => null,
|
|
550
|
-
} as any
|
|
600
|
+
},
|
|
601
|
+
createSelectListStateAdapter(state)
|
|
551
602
|
);
|
|
552
603
|
|
|
553
604
|
// Render props values
|
|
@@ -582,6 +633,7 @@ export function SelectListBox<T>(props: SelectListBoxProps<T>): JSX.Element {
|
|
|
582
633
|
<FocusScope restoreFocus autoFocus>
|
|
583
634
|
<ul
|
|
584
635
|
ref={(el) => (listBoxRef = el)}
|
|
636
|
+
{...domProps}
|
|
585
637
|
{...cleanMenuProps()}
|
|
586
638
|
{...cleanListBoxProps()}
|
|
587
639
|
class={renderProps.class()}
|
|
@@ -626,49 +678,51 @@ export function SelectOption<T>(props: SelectOptionProps<T>): JSX.Element {
|
|
|
626
678
|
throw new Error('SelectOption must be used within a Select');
|
|
627
679
|
}
|
|
628
680
|
const state = context as SelectState<T>;
|
|
681
|
+
const selectContext = useContext(SelectContext) as SelectContextValue<T> | null;
|
|
629
682
|
|
|
630
683
|
// Create option aria props - adapt select state to list state interface
|
|
631
684
|
const optionAria = createOption<T>(
|
|
632
685
|
{
|
|
633
686
|
key: local.id,
|
|
634
687
|
get isDisabled() {
|
|
635
|
-
return ariaProps.isDisabled;
|
|
688
|
+
return Boolean(ariaProps.isDisabled || selectContext?.isDisabled());
|
|
636
689
|
},
|
|
637
690
|
get 'aria-label'() {
|
|
638
|
-
return ariaProps['aria-label'];
|
|
691
|
+
return ariaProps['aria-label'] ?? local.textValue;
|
|
639
692
|
},
|
|
640
693
|
},
|
|
641
694
|
{
|
|
642
|
-
|
|
643
|
-
focusedKey: state.focusedKey,
|
|
644
|
-
setFocusedKey: state.setFocusedKey,
|
|
645
|
-
isFocused: state.isFocused,
|
|
646
|
-
setFocused: state.setFocused,
|
|
647
|
-
selectedKeys: () => {
|
|
648
|
-
const key = state.selectedKey();
|
|
649
|
-
return key != null ? new Set([key]) : new Set();
|
|
650
|
-
},
|
|
651
|
-
isSelected: (key: Key) => state.selectedKey() === key,
|
|
652
|
-
isDisabled: state.isKeyDisabled,
|
|
653
|
-
selectionMode: () => 'single' as const,
|
|
654
|
-
disallowEmptySelection: () => true,
|
|
695
|
+
...createSelectListStateAdapter(state),
|
|
655
696
|
select: (key: Key) => {
|
|
697
|
+
if (state.selectionMode() === 'multiple') {
|
|
698
|
+
const keys = state.selectedKeys();
|
|
699
|
+
if (keys === 'all') return;
|
|
700
|
+
state.setSelectedKeys(new Set([...keys, key]));
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
656
703
|
state.setSelectedKey(key);
|
|
657
704
|
state.close();
|
|
658
705
|
},
|
|
659
706
|
toggleSelection: (key: Key) => {
|
|
707
|
+
if (state.selectionMode() === 'multiple') {
|
|
708
|
+
const keys = state.selectedKeys();
|
|
709
|
+
if (keys === 'all') return;
|
|
710
|
+
const next = new Set(keys);
|
|
711
|
+
if (next.has(key)) next.delete(key);
|
|
712
|
+
else next.add(key);
|
|
713
|
+
state.setSelectedKeys(next);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
660
716
|
state.setSelectedKey(key);
|
|
661
717
|
state.close();
|
|
662
718
|
},
|
|
663
719
|
replaceSelection: (key: Key) => {
|
|
664
720
|
state.setSelectedKey(key);
|
|
665
|
-
state.
|
|
721
|
+
if (state.selectionMode() !== 'multiple') {
|
|
722
|
+
state.close();
|
|
723
|
+
}
|
|
666
724
|
},
|
|
667
|
-
|
|
668
|
-
selectAll: () => {},
|
|
669
|
-
clearSelection: () => state.setSelectedKey(null),
|
|
670
|
-
childFocusStrategy: () => null,
|
|
671
|
-
} as any
|
|
725
|
+
}
|
|
672
726
|
);
|
|
673
727
|
|
|
674
728
|
// Create hover
|
|
@@ -698,10 +752,24 @@ export function SelectOption<T>(props: SelectOptionProps<T>): JSX.Element {
|
|
|
698
752
|
},
|
|
699
753
|
renderValues
|
|
700
754
|
);
|
|
755
|
+
const hasPrimitiveLabel = () => {
|
|
756
|
+
return typeof props.children === 'string' || typeof props.children === 'number';
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
const selectionIndicatorContext = createMemo<SelectionIndicatorContextValue>(() => ({
|
|
760
|
+
isSelected: optionAria.isSelected,
|
|
761
|
+
}));
|
|
701
762
|
|
|
702
763
|
// Remove ref from spread props
|
|
703
764
|
const cleanOptionProps = () => {
|
|
704
|
-
const {
|
|
765
|
+
const {
|
|
766
|
+
ref: _ref1,
|
|
767
|
+
'aria-describedby': _ariaDescribedby,
|
|
768
|
+
...rest
|
|
769
|
+
} = optionAria.optionProps as Record<string, unknown>;
|
|
770
|
+
if (!hasPrimitiveLabel() && rest['aria-label'] == null) {
|
|
771
|
+
delete rest['aria-labelledby'];
|
|
772
|
+
}
|
|
705
773
|
return rest;
|
|
706
774
|
};
|
|
707
775
|
const cleanHoverProps = () => {
|
|
@@ -710,23 +778,103 @@ export function SelectOption<T>(props: SelectOptionProps<T>): JSX.Element {
|
|
|
710
778
|
};
|
|
711
779
|
|
|
712
780
|
return (
|
|
713
|
-
<
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
781
|
+
<SelectionIndicatorContext.Provider value={selectionIndicatorContext()}>
|
|
782
|
+
<li
|
|
783
|
+
{...cleanOptionProps()}
|
|
784
|
+
{...cleanHoverProps()}
|
|
785
|
+
class={renderProps.class()}
|
|
786
|
+
style={renderProps.style()}
|
|
787
|
+
data-selected={optionAria.isSelected() || undefined}
|
|
788
|
+
data-focused={optionAria.isFocused() || undefined}
|
|
789
|
+
data-focus-visible={optionAria.isFocusVisible() || undefined}
|
|
790
|
+
data-pressed={optionAria.isPressed() || undefined}
|
|
791
|
+
data-hovered={isHovered() || undefined}
|
|
792
|
+
data-disabled={optionAria.isDisabled() || undefined}
|
|
793
|
+
>
|
|
794
|
+
{hasPrimitiveLabel()
|
|
795
|
+
? <span {...optionAria.labelProps}>{renderProps.renderChildren()}</span>
|
|
796
|
+
: renderProps.renderChildren()}
|
|
797
|
+
</li>
|
|
798
|
+
</SelectionIndicatorContext.Provider>
|
|
727
799
|
);
|
|
728
800
|
}
|
|
729
801
|
|
|
802
|
+
function isObjectRecord(value: unknown): value is Record<string, unknown> {
|
|
803
|
+
return typeof value === 'object' && value !== null;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function toKey(value: unknown): Key | undefined {
|
|
807
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
808
|
+
return value;
|
|
809
|
+
}
|
|
810
|
+
return undefined;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function toTextValue(value: unknown): string | undefined {
|
|
814
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
815
|
+
return String(value);
|
|
816
|
+
}
|
|
817
|
+
return undefined;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function createSelectListStateAdapter<T>(state: SelectState<T>): ListState<T> {
|
|
821
|
+
const selectedKeys = createMemo(() => {
|
|
822
|
+
const keys = state.selectedKeys();
|
|
823
|
+
return keys === 'all'
|
|
824
|
+
? new Set(Array.from(state.collection()).map((item) => item.key))
|
|
825
|
+
: keys;
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
const disabledKeys = createMemo(() => {
|
|
829
|
+
const keys = new Set<Key>();
|
|
830
|
+
for (const node of state.collection()) {
|
|
831
|
+
if (node.isDisabled) keys.add(node.key);
|
|
832
|
+
}
|
|
833
|
+
return keys;
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
return {
|
|
837
|
+
collection: state.collection,
|
|
838
|
+
isFocused: state.isFocused,
|
|
839
|
+
setFocused: state.setFocused,
|
|
840
|
+
focusedKey: state.focusedKey,
|
|
841
|
+
setFocusedKey: (key) => state.setFocusedKey(key ?? null),
|
|
842
|
+
childFocusStrategy: () => null,
|
|
843
|
+
selectionMode: () => state.selectionMode(),
|
|
844
|
+
selectionBehavior: () => 'replace',
|
|
845
|
+
disallowEmptySelection: () => true,
|
|
846
|
+
selectedKeys,
|
|
847
|
+
disabledKeys,
|
|
848
|
+
disabledBehavior: () => 'all',
|
|
849
|
+
isEmpty: () => selectedKeys().size === 0,
|
|
850
|
+
isSelectAll: () => state.selectedKeys() === 'all',
|
|
851
|
+
isSelected: (key) => selectedKeys().has(key),
|
|
852
|
+
isDisabled: state.isKeyDisabled,
|
|
853
|
+
setSelectionBehavior: () => {},
|
|
854
|
+
toggleSelection: (key) => {
|
|
855
|
+
if (state.selectionMode() !== 'multiple') {
|
|
856
|
+
state.setSelectedKey(key);
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
const keys = state.selectedKeys();
|
|
860
|
+
if (keys === 'all') return;
|
|
861
|
+
const next = new Set(keys);
|
|
862
|
+
if (next.has(key)) next.delete(key);
|
|
863
|
+
else next.add(key);
|
|
864
|
+
state.setSelectedKeys(next);
|
|
865
|
+
},
|
|
866
|
+
replaceSelection: (key) => state.setSelectedKey(key),
|
|
867
|
+
setSelectedKeys: (keys) => state.setSelectedKeys(keys),
|
|
868
|
+
selectAll: () => {},
|
|
869
|
+
clearSelection: () => state.selectionMode() === 'multiple' ? state.setSelectedKeys([]) : state.setSelectedKey(null),
|
|
870
|
+
toggleSelectAll: () => {},
|
|
871
|
+
extendSelection: (toKey) => state.setSelectedKey(toKey),
|
|
872
|
+
select: (key) => state.selectionMode() === 'multiple'
|
|
873
|
+
? state.setSelectedKeys([...(state.selectedKeys() === 'all' ? [] : state.selectedKeys() as Set<Key>), key])
|
|
874
|
+
: state.setSelectedKey(key),
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
|
|
730
878
|
// Attach sub-components
|
|
731
879
|
Select.Trigger = SelectTrigger;
|
|
732
880
|
Select.Value = SelectValue;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared SelectionIndicator primitive for selected collection items.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
type JSX,
|
|
7
|
+
createContext,
|
|
8
|
+
createMemo,
|
|
9
|
+
splitProps,
|
|
10
|
+
useContext,
|
|
11
|
+
Show,
|
|
12
|
+
} from 'solid-js';
|
|
13
|
+
import {
|
|
14
|
+
type RenderChildren,
|
|
15
|
+
type ClassNameOrFunction,
|
|
16
|
+
type StyleOrFunction,
|
|
17
|
+
type SlotProps,
|
|
18
|
+
useRenderProps,
|
|
19
|
+
} from './utils';
|
|
20
|
+
import { SharedElement, useHasSharedElementTransitionScope } from './SharedElementTransition';
|
|
21
|
+
|
|
22
|
+
export interface SelectionIndicatorContextValue {
|
|
23
|
+
isSelected: () => boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const SelectionIndicatorContext = createContext<SelectionIndicatorContextValue | null>(null);
|
|
27
|
+
|
|
28
|
+
export interface SelectionIndicatorRenderProps {
|
|
29
|
+
/** Whether the parent item is selected. */
|
|
30
|
+
isSelected: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface SelectionIndicatorProps extends SlotProps, Omit<JSX.HTMLAttributes<HTMLSpanElement>, 'class' | 'style' | 'children'> {
|
|
34
|
+
/** Optional controlled selected state override. */
|
|
35
|
+
isSelected?: boolean;
|
|
36
|
+
/** Whether to keep mounted when not selected. */
|
|
37
|
+
shouldForceMount?: boolean;
|
|
38
|
+
/** The children content. */
|
|
39
|
+
children?: RenderChildren<SelectionIndicatorRenderProps>;
|
|
40
|
+
/** The CSS className for the element. */
|
|
41
|
+
class?: ClassNameOrFunction<SelectionIndicatorRenderProps>;
|
|
42
|
+
/** The inline style for the element. */
|
|
43
|
+
style?: StyleOrFunction<SelectionIndicatorRenderProps>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* SelectionIndicator renders when its parent item is selected.
|
|
48
|
+
*/
|
|
49
|
+
export function SelectionIndicator(props: SelectionIndicatorProps): JSX.Element {
|
|
50
|
+
const [local, domProps] = splitProps(props, ['isSelected', 'shouldForceMount', 'children', 'class', 'style', 'slot']);
|
|
51
|
+
|
|
52
|
+
const context = useContext(SelectionIndicatorContext);
|
|
53
|
+
const hasSharedElementScope = useHasSharedElementTransitionScope();
|
|
54
|
+
const isSelected = () => local.isSelected ?? context?.isSelected() ?? false;
|
|
55
|
+
const isVisible = () => local.shouldForceMount || isSelected();
|
|
56
|
+
|
|
57
|
+
const renderValues = createMemo<SelectionIndicatorRenderProps>(() => ({
|
|
58
|
+
isSelected: isSelected(),
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
const renderProps = useRenderProps(
|
|
62
|
+
{
|
|
63
|
+
children: local.children,
|
|
64
|
+
class: local.class,
|
|
65
|
+
style: local.style,
|
|
66
|
+
defaultClassName: 'solidaria-SelectionIndicator',
|
|
67
|
+
},
|
|
68
|
+
renderValues
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const sharedElementProps = createMemo(() => {
|
|
72
|
+
const { ref: _ref, ...rest } = domProps as JSX.HTMLAttributes<HTMLSpanElement> & { ref?: unknown };
|
|
73
|
+
return rest;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (hasSharedElementScope) {
|
|
77
|
+
return (
|
|
78
|
+
<SharedElement
|
|
79
|
+
{...sharedElementProps()}
|
|
80
|
+
name="SelectionIndicator"
|
|
81
|
+
isVisible={isVisible()}
|
|
82
|
+
aria-hidden="true"
|
|
83
|
+
class={renderProps.class()}
|
|
84
|
+
style={renderProps.style()}
|
|
85
|
+
data-selected={isSelected() || undefined}
|
|
86
|
+
>
|
|
87
|
+
{renderProps.renderChildren()}
|
|
88
|
+
</SharedElement>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<Show when={isVisible()}>
|
|
94
|
+
<span
|
|
95
|
+
{...domProps}
|
|
96
|
+
aria-hidden="true"
|
|
97
|
+
class={renderProps.class()}
|
|
98
|
+
style={renderProps.style()}
|
|
99
|
+
data-selected={isSelected() || undefined}
|
|
100
|
+
>
|
|
101
|
+
{renderProps.renderChildren()}
|
|
102
|
+
</span>
|
|
103
|
+
</Show>
|
|
104
|
+
);
|
|
105
|
+
}
|