@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/ListBox.tsx
CHANGED
|
@@ -8,16 +8,21 @@
|
|
|
8
8
|
import {
|
|
9
9
|
type JSX,
|
|
10
10
|
createContext,
|
|
11
|
+
createEffect,
|
|
11
12
|
createMemo,
|
|
13
|
+
createSignal,
|
|
14
|
+
onCleanup,
|
|
12
15
|
splitProps,
|
|
13
16
|
useContext,
|
|
14
17
|
For,
|
|
18
|
+
Show,
|
|
15
19
|
} from 'solid-js';
|
|
16
20
|
import {
|
|
17
21
|
createListBox,
|
|
18
22
|
createOption,
|
|
19
23
|
createFocusRing,
|
|
20
24
|
createHover,
|
|
25
|
+
mergeProps,
|
|
21
26
|
type AriaListBoxProps,
|
|
22
27
|
type AriaOptionProps,
|
|
23
28
|
} from '@proyecto-viviana/solidaria';
|
|
@@ -25,6 +30,7 @@ import {
|
|
|
25
30
|
createListState,
|
|
26
31
|
type ListState,
|
|
27
32
|
type Key,
|
|
33
|
+
type DropTarget,
|
|
28
34
|
} from '@proyecto-viviana/solid-stately';
|
|
29
35
|
import {
|
|
30
36
|
type RenderChildren,
|
|
@@ -34,6 +40,31 @@ import {
|
|
|
34
40
|
useRenderProps,
|
|
35
41
|
filterDOMProps,
|
|
36
42
|
} from './utils';
|
|
43
|
+
import { SharedElementTransition } from './SharedElementTransition';
|
|
44
|
+
import {
|
|
45
|
+
SelectionIndicatorContext,
|
|
46
|
+
type SelectionIndicatorContextValue,
|
|
47
|
+
} from './SelectionIndicator';
|
|
48
|
+
import { useVirtualizerContext } from './Virtualizer';
|
|
49
|
+
import { type DragAndDropHooks } from './useDragAndDrop';
|
|
50
|
+
import {
|
|
51
|
+
getNormalizedDropTargetKey,
|
|
52
|
+
mergePersistedKeysIntoVirtualRange,
|
|
53
|
+
useDndPersistedKeys,
|
|
54
|
+
useRenderDropIndicator,
|
|
55
|
+
} from './DragAndDrop';
|
|
56
|
+
import {
|
|
57
|
+
CollectionRendererContext,
|
|
58
|
+
Section,
|
|
59
|
+
Header,
|
|
60
|
+
Group,
|
|
61
|
+
type CollectionEntry,
|
|
62
|
+
type CollectionRendererContextValue,
|
|
63
|
+
type SectionProps,
|
|
64
|
+
useCollectionRenderer,
|
|
65
|
+
isCollectionSection,
|
|
66
|
+
flattenCollectionEntries,
|
|
67
|
+
} from './Collection';
|
|
37
68
|
|
|
38
69
|
// ============================================
|
|
39
70
|
// TYPES
|
|
@@ -54,7 +85,7 @@ export interface ListBoxProps<T>
|
|
|
54
85
|
extends Omit<AriaListBoxProps, 'children'>,
|
|
55
86
|
SlotProps {
|
|
56
87
|
/** The items to render in the listbox. */
|
|
57
|
-
items: T[];
|
|
88
|
+
items: CollectionEntry<T>[];
|
|
58
89
|
/** Function to get the key from an item. */
|
|
59
90
|
getKey?: (item: T) => Key;
|
|
60
91
|
/** Function to get the text value from an item. */
|
|
@@ -79,6 +110,14 @@ export interface ListBoxProps<T>
|
|
|
79
110
|
style?: StyleOrFunction<ListBoxRenderProps>;
|
|
80
111
|
/** A function to render when the listbox is empty. */
|
|
81
112
|
renderEmptyState?: () => JSX.Element;
|
|
113
|
+
/** Whether there are more items to load. */
|
|
114
|
+
hasMore?: boolean;
|
|
115
|
+
/** Whether additional items are currently loading. */
|
|
116
|
+
isLoading?: boolean;
|
|
117
|
+
/** Called when the load more sentinel becomes visible. */
|
|
118
|
+
onLoadMore?: () => void | Promise<void>;
|
|
119
|
+
/** Drag and drop hooks from `useDragAndDrop`. */
|
|
120
|
+
dragAndDropHooks?: DragAndDropHooks<T>;
|
|
82
121
|
}
|
|
83
122
|
|
|
84
123
|
export interface ListBoxOptionRenderProps {
|
|
@@ -113,16 +152,36 @@ export interface ListBoxOptionProps<T>
|
|
|
113
152
|
textValue?: string;
|
|
114
153
|
}
|
|
115
154
|
|
|
155
|
+
export interface ListBoxLoadMoreItemProps extends SlotProps {
|
|
156
|
+
/** Called when the sentinel becomes visible. */
|
|
157
|
+
onLoadMore: () => void | Promise<void>;
|
|
158
|
+
/** Whether additional items are currently loading. */
|
|
159
|
+
isLoading?: boolean;
|
|
160
|
+
/** Content for the load more row. */
|
|
161
|
+
children?: JSX.Element;
|
|
162
|
+
/** The CSS className for the element. */
|
|
163
|
+
class?: ClassNameOrFunction<{ isLoading: boolean }>;
|
|
164
|
+
/** The inline style for the element. */
|
|
165
|
+
style?: StyleOrFunction<{ isLoading: boolean }>;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface ListBoxSectionProps extends SectionProps {}
|
|
169
|
+
|
|
116
170
|
// ============================================
|
|
117
171
|
// CONTEXT
|
|
118
172
|
// ============================================
|
|
119
173
|
|
|
120
174
|
interface ListBoxContextValue<T> {
|
|
121
175
|
state: ListState<T>;
|
|
176
|
+
isDisabled: () => boolean;
|
|
177
|
+
dragAndDropHooks?: DragAndDropHooks<unknown>;
|
|
178
|
+
dragState?: unknown;
|
|
179
|
+
dropState?: unknown;
|
|
122
180
|
}
|
|
123
181
|
|
|
124
182
|
export const ListBoxContext = createContext<ListBoxContextValue<unknown> | null>(null);
|
|
125
183
|
export const ListBoxStateContext = createContext<ListState<unknown> | null>(null);
|
|
184
|
+
export const ListStateContext = ListBoxStateContext;
|
|
126
185
|
|
|
127
186
|
// ============================================
|
|
128
187
|
// COMPONENTS
|
|
@@ -134,14 +193,20 @@ export const ListBoxStateContext = createContext<ListState<unknown> | null>(null
|
|
|
134
193
|
export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
|
|
135
194
|
const [local, stateProps, ariaProps] = splitProps(
|
|
136
195
|
props,
|
|
137
|
-
['children', 'class', 'style', 'slot', 'renderEmptyState'],
|
|
196
|
+
['children', 'class', 'style', 'slot', 'renderEmptyState', 'hasMore', 'isLoading', 'onLoadMore', 'dragAndDropHooks'],
|
|
138
197
|
['items', 'getKey', 'getTextValue', 'getDisabled', 'disabledKeys', 'selectionMode', 'selectedKeys', 'defaultSelectedKeys', 'onSelectionChange']
|
|
139
198
|
);
|
|
140
199
|
|
|
200
|
+
const flatItems = createMemo<T[]>(() => {
|
|
201
|
+
return flattenCollectionEntries(stateProps.items);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const hasSections = createMemo(() => stateProps.items.some((item) => isCollectionSection(item)));
|
|
205
|
+
|
|
141
206
|
// Create list state
|
|
142
207
|
const state = createListState<T>({
|
|
143
208
|
get items() {
|
|
144
|
-
return
|
|
209
|
+
return flatItems();
|
|
145
210
|
},
|
|
146
211
|
get getKey() {
|
|
147
212
|
return stateProps.getKey;
|
|
@@ -179,7 +244,7 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
|
|
|
179
244
|
};
|
|
180
245
|
|
|
181
246
|
// Create listbox aria props
|
|
182
|
-
const
|
|
247
|
+
const listBoxAria = createListBox(
|
|
183
248
|
{
|
|
184
249
|
...ariaProps,
|
|
185
250
|
get isDisabled() {
|
|
@@ -218,35 +283,303 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
|
|
|
218
283
|
|
|
219
284
|
// Remove ref from spread props
|
|
220
285
|
const cleanListBoxProps = () => {
|
|
221
|
-
const { ref: _ref1, ...rest } = listBoxProps as Record<string, unknown>;
|
|
286
|
+
const { ref: _ref1, ...rest } = listBoxAria.listBoxProps as Record<string, unknown>;
|
|
222
287
|
return rest;
|
|
223
288
|
};
|
|
224
289
|
const cleanFocusProps = () => {
|
|
225
290
|
const { ref: _ref2, ...rest } = focusProps as Record<string, unknown>;
|
|
226
291
|
return rest;
|
|
227
292
|
};
|
|
293
|
+
const cleanLabelProps = () => {
|
|
294
|
+
const { ref: _ref3, ...rest } = listBoxAria.labelProps as Record<string, unknown>;
|
|
295
|
+
return rest;
|
|
296
|
+
};
|
|
297
|
+
const [listRef, setListRef] = createSignal<HTMLElement | null>(null);
|
|
228
298
|
|
|
229
299
|
const isEmpty = () => stateProps.items.length === 0;
|
|
300
|
+
const parentCollectionRenderer = useCollectionRenderer<unknown>();
|
|
301
|
+
const getItemNodes = createMemo(() => Array.from(state.collection()).filter((node) => node.type === 'item'));
|
|
302
|
+
const getDropTargetByIndex = (index: number, position: 'before' | 'after' | 'on'): DropTarget | null => {
|
|
303
|
+
const node = getItemNodes()[index];
|
|
304
|
+
if (!node) return null;
|
|
305
|
+
return { type: 'item', key: node.key, dropPosition: position };
|
|
306
|
+
};
|
|
307
|
+
const hasDroppableDnd = createMemo(() => {
|
|
308
|
+
const hooks = local.dragAndDropHooks;
|
|
309
|
+
return Boolean(
|
|
310
|
+
hooks?.useDroppableCollectionState &&
|
|
311
|
+
hooks.useDroppableCollection &&
|
|
312
|
+
(hooks.dropTargetDelegate || parentCollectionRenderer?.dropTargetDelegate || hooks.ListDropTargetDelegate)
|
|
313
|
+
);
|
|
314
|
+
});
|
|
315
|
+
const dropState = createMemo(() => {
|
|
316
|
+
if (!hasDroppableDnd()) return undefined;
|
|
317
|
+
return local.dragAndDropHooks?.useDroppableCollectionState?.({});
|
|
318
|
+
});
|
|
319
|
+
const hasDraggableDnd = createMemo(() => {
|
|
320
|
+
const hooks = local.dragAndDropHooks;
|
|
321
|
+
return Boolean(hooks?.useDraggableCollectionState && hooks.useDraggableCollection);
|
|
322
|
+
});
|
|
323
|
+
const dragState = createMemo(() => {
|
|
324
|
+
if (!hasDraggableDnd()) return undefined;
|
|
325
|
+
return local.dragAndDropHooks?.useDraggableCollectionState?.({
|
|
326
|
+
items: flatItems(),
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
createEffect(() => {
|
|
330
|
+
if (!hasDraggableDnd()) return;
|
|
331
|
+
const hooks = local.dragAndDropHooks;
|
|
332
|
+
const activeDragState = dragState();
|
|
333
|
+
if (!hooks?.useDraggableCollection || !activeDragState) return;
|
|
334
|
+
hooks.useDraggableCollection({}, activeDragState, () => listRef());
|
|
335
|
+
});
|
|
336
|
+
const droppableCollection = createMemo(() => {
|
|
337
|
+
if (!hasDroppableDnd()) return undefined;
|
|
338
|
+
const hooks = local.dragAndDropHooks;
|
|
339
|
+
const activeDropState = dropState();
|
|
340
|
+
if (!hooks?.useDroppableCollection || !activeDropState) return undefined;
|
|
341
|
+
const resolveDirection = (): 'ltr' | 'rtl' => {
|
|
342
|
+
const el = listRef();
|
|
343
|
+
if (el && typeof window !== 'undefined' && typeof window.getComputedStyle === 'function') {
|
|
344
|
+
const dir = window.getComputedStyle(el).direction;
|
|
345
|
+
if (dir === 'rtl') return 'rtl';
|
|
346
|
+
}
|
|
347
|
+
return typeof document !== 'undefined' && document.dir === 'rtl' ? 'rtl' : 'ltr';
|
|
348
|
+
};
|
|
349
|
+
const dropTargetDelegate = hooks.dropTargetDelegate
|
|
350
|
+
?? parentCollectionRenderer?.dropTargetDelegate
|
|
351
|
+
?? (hooks.ListDropTargetDelegate
|
|
352
|
+
? new hooks.ListDropTargetDelegate(
|
|
353
|
+
() => state.collection(),
|
|
354
|
+
() => listRef(),
|
|
355
|
+
{ layout: 'stack', orientation: 'vertical', direction: resolveDirection() }
|
|
356
|
+
)
|
|
357
|
+
: undefined);
|
|
358
|
+
if (!dropTargetDelegate) return undefined;
|
|
359
|
+
return hooks.useDroppableCollection(
|
|
360
|
+
{
|
|
361
|
+
dropTargetDelegate,
|
|
362
|
+
keyboardDelegate: {
|
|
363
|
+
getFirstKey: () => state.collection().getFirstKey(),
|
|
364
|
+
getLastKey: () => state.collection().getLastKey(),
|
|
365
|
+
getKeyBelow: (key) => state.collection().getKeyAfter(key),
|
|
366
|
+
getKeyAbove: (key) => state.collection().getKeyBefore(key),
|
|
367
|
+
getKeyPageBelow: (key) => state.collection().getKeyAfter(key),
|
|
368
|
+
getKeyPageAbove: (key) => state.collection().getKeyBefore(key),
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
activeDropState,
|
|
372
|
+
() => listRef()
|
|
373
|
+
);
|
|
374
|
+
});
|
|
375
|
+
const isRootDropTarget = createMemo(() => {
|
|
376
|
+
return Boolean(dropState()?.target?.type === 'root');
|
|
377
|
+
});
|
|
378
|
+
const dndRenderDropIndicator = createMemo(() => useRenderDropIndicator(local.dragAndDropHooks, dropState()));
|
|
379
|
+
const dndDropIndicator = (index: number, position: 'before' | 'after' | 'on') => {
|
|
380
|
+
const target = getDropTargetByIndex(index, position);
|
|
381
|
+
if (!target || target.type !== 'item') return undefined;
|
|
382
|
+
return dndRenderDropIndicator()?.(target);
|
|
383
|
+
};
|
|
384
|
+
const virtualizer = useVirtualizerContext();
|
|
385
|
+
const persistedKeys = useDndPersistedKeys(
|
|
386
|
+
{ focusedKey: state.focusedKey },
|
|
387
|
+
local.dragAndDropHooks,
|
|
388
|
+
dropState(),
|
|
389
|
+
state.collection()
|
|
390
|
+
);
|
|
391
|
+
const virtualRange = createMemo(() => {
|
|
392
|
+
if (!virtualizer || !parentCollectionRenderer?.isVirtualized || hasSections()) return null;
|
|
393
|
+
const baseRange = virtualizer.getVisibleRange(stateProps.items.length);
|
|
394
|
+
const itemNodes = getItemNodes();
|
|
395
|
+
const persistedIndexes = Array.from(persistedKeys())
|
|
396
|
+
.map((key) => itemNodes.findIndex((node) => node.key === key))
|
|
397
|
+
.filter((index) => index >= 0);
|
|
398
|
+
const dropTarget = dropState()?.target;
|
|
399
|
+
const normalizedDropKey = getNormalizedDropTargetKey(dropTarget, state.collection());
|
|
400
|
+
const focusedKey = state.focusedKey();
|
|
401
|
+
const focusedIndex = focusedKey != null ? itemNodes.findIndex((node) => node.key === focusedKey) : -1;
|
|
402
|
+
const forceIncludeIndexes = [
|
|
403
|
+
dropTarget?.type === 'item' ? itemNodes.findIndex((node) => node.key === dropTarget.key) : -1,
|
|
404
|
+
normalizedDropKey != null ? itemNodes.findIndex((node) => node.key === normalizedDropKey) : -1,
|
|
405
|
+
dropTarget?.type === 'item' ? -1 : focusedIndex,
|
|
406
|
+
].filter((index) => index >= 0);
|
|
407
|
+
return mergePersistedKeysIntoVirtualRange(baseRange, persistedIndexes, stateProps.items.length, virtualizer, 80, {
|
|
408
|
+
forceIncludeIndexes,
|
|
409
|
+
forceIncludeMaxSpan: 320,
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
createEffect(() => {
|
|
413
|
+
if (!virtualizer || !parentCollectionRenderer?.isVirtualized) return;
|
|
414
|
+
const getItemNodes = () => Array.from(state.collection()).filter((node) => node.type === 'item');
|
|
415
|
+
virtualizer.setDropTargetItemCountResolver(() => getItemNodes().length);
|
|
416
|
+
virtualizer.setDropTargetIndexResolver((key) => {
|
|
417
|
+
const index = getItemNodes().findIndex((node) => node.key === key);
|
|
418
|
+
return index >= 0 ? index : null;
|
|
419
|
+
});
|
|
420
|
+
virtualizer.setDropTargetResolver((target) => {
|
|
421
|
+
const node = getItemNodes()[target.index];
|
|
422
|
+
if (!node) return target;
|
|
423
|
+
return {
|
|
424
|
+
...target,
|
|
425
|
+
key: typeof node.key === 'string' || typeof node.key === 'number' ? node.key : undefined,
|
|
426
|
+
};
|
|
427
|
+
});
|
|
428
|
+
onCleanup(() => {
|
|
429
|
+
virtualizer.setDropTargetIndexResolver(undefined);
|
|
430
|
+
virtualizer.setDropTargetItemCountResolver(undefined);
|
|
431
|
+
virtualizer.setDropTargetResolver(undefined);
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
const visibleItems = createMemo(() => {
|
|
435
|
+
const range = virtualRange();
|
|
436
|
+
if (!range) return stateProps.items;
|
|
437
|
+
return stateProps.items.slice(range.start, range.end);
|
|
438
|
+
});
|
|
439
|
+
const sectionedRenderEntries = createMemo(() => {
|
|
440
|
+
let globalIndex = 0;
|
|
441
|
+
return stateProps.items.map((entry) => {
|
|
442
|
+
if (isCollectionSection(entry)) {
|
|
443
|
+
const sectionItems = entry.items.map((item) => ({
|
|
444
|
+
item,
|
|
445
|
+
index: globalIndex++,
|
|
446
|
+
}));
|
|
447
|
+
return {
|
|
448
|
+
type: 'section' as const,
|
|
449
|
+
section: entry,
|
|
450
|
+
items: sectionItems,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
const indexedItem = {
|
|
454
|
+
item: entry as T,
|
|
455
|
+
index: globalIndex++,
|
|
456
|
+
};
|
|
457
|
+
return {
|
|
458
|
+
type: 'item' as const,
|
|
459
|
+
item: indexedItem,
|
|
460
|
+
};
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
const collectionRenderer = createMemo<CollectionRendererContextValue<unknown>>(() => ({
|
|
464
|
+
...parentCollectionRenderer,
|
|
465
|
+
renderItem: (item) => props.children(item as T),
|
|
466
|
+
renderDropIndicator: (index, position) =>
|
|
467
|
+
dndDropIndicator(index, position) ?? parentCollectionRenderer?.renderDropIndicator?.(index, position),
|
|
468
|
+
}));
|
|
230
469
|
|
|
231
470
|
return (
|
|
232
|
-
<ListBoxContext.Provider
|
|
471
|
+
<ListBoxContext.Provider
|
|
472
|
+
value={{
|
|
473
|
+
state,
|
|
474
|
+
isDisabled: resolveDisabled,
|
|
475
|
+
dragAndDropHooks: local.dragAndDropHooks as DragAndDropHooks<unknown> | undefined,
|
|
476
|
+
dragState: dragState(),
|
|
477
|
+
dropState: dropState(),
|
|
478
|
+
}}
|
|
479
|
+
>
|
|
233
480
|
<ListBoxStateContext.Provider value={state}>
|
|
234
|
-
<
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
481
|
+
<CollectionRendererContext.Provider value={collectionRenderer()}>
|
|
482
|
+
<>
|
|
483
|
+
<Show when={ariaProps.label}>
|
|
484
|
+
<span {...cleanLabelProps()}>{ariaProps.label as JSX.Element}</span>
|
|
485
|
+
</Show>
|
|
486
|
+
<ul
|
|
487
|
+
{...mergeProps(
|
|
488
|
+
domProps(),
|
|
489
|
+
cleanListBoxProps(),
|
|
490
|
+
cleanFocusProps(),
|
|
491
|
+
(droppableCollection()?.collectionProps as Record<string, unknown> | undefined) ?? {}
|
|
492
|
+
)}
|
|
493
|
+
ref={(el) => {
|
|
494
|
+
setListRef(el);
|
|
495
|
+
}}
|
|
496
|
+
class={renderProps.class()}
|
|
497
|
+
style={renderProps.style()}
|
|
498
|
+
data-focused={state.isFocused() || undefined}
|
|
499
|
+
data-focus-visible={isFocusVisible() || undefined}
|
|
500
|
+
data-disabled={resolveDisabled() || undefined}
|
|
501
|
+
data-empty={isEmpty() || undefined}
|
|
502
|
+
data-drop-target={isRootDropTarget() || undefined}
|
|
503
|
+
>
|
|
504
|
+
<SharedElementTransition>
|
|
505
|
+
{isEmpty() && local.renderEmptyState
|
|
506
|
+
? local.renderEmptyState()
|
|
507
|
+
: hasSections()
|
|
508
|
+
? (
|
|
509
|
+
<For each={sectionedRenderEntries()}>
|
|
510
|
+
{(entry) =>
|
|
511
|
+
entry.type === 'section'
|
|
512
|
+
? (
|
|
513
|
+
<li role="presentation" data-section-wrapper>
|
|
514
|
+
<Section class="solidaria-ListBox-section">
|
|
515
|
+
{entry.section.title != null && (
|
|
516
|
+
<Header class="solidaria-ListBox-sectionHeader">{entry.section.title}</Header>
|
|
517
|
+
)}
|
|
518
|
+
<Group class="solidaria-ListBox-sectionGroup">
|
|
519
|
+
<ul role="group" aria-label={entry.section['aria-label']}>
|
|
520
|
+
<For each={entry.items}>
|
|
521
|
+
{(indexedItem) => (
|
|
522
|
+
<>
|
|
523
|
+
{collectionRenderer().renderDropIndicator?.(indexedItem.index, 'before')}
|
|
524
|
+
{collectionRenderer().renderDropIndicator?.(indexedItem.index, 'on')}
|
|
525
|
+
{props.children(indexedItem.item)}
|
|
526
|
+
{collectionRenderer().renderDropIndicator?.(indexedItem.index, 'after')}
|
|
527
|
+
</>
|
|
528
|
+
)}
|
|
529
|
+
</For>
|
|
530
|
+
</ul>
|
|
531
|
+
</Group>
|
|
532
|
+
</Section>
|
|
533
|
+
</li>
|
|
534
|
+
)
|
|
535
|
+
: (
|
|
536
|
+
<>
|
|
537
|
+
{collectionRenderer().renderDropIndicator?.(entry.item.index, 'before')}
|
|
538
|
+
{collectionRenderer().renderDropIndicator?.(entry.item.index, 'on')}
|
|
539
|
+
{props.children(entry.item.item)}
|
|
540
|
+
{collectionRenderer().renderDropIndicator?.(entry.item.index, 'after')}
|
|
541
|
+
</>
|
|
542
|
+
)
|
|
543
|
+
}
|
|
544
|
+
</For>
|
|
545
|
+
)
|
|
546
|
+
: (
|
|
547
|
+
<>
|
|
548
|
+
{virtualRange()?.offsetTop
|
|
549
|
+
? <li role="presentation" aria-hidden="true" style={{ height: `${virtualRange()!.offsetTop}px` }} data-virtualizer-spacer="top" />
|
|
550
|
+
: null}
|
|
551
|
+
<For each={visibleItems()}>
|
|
552
|
+
{(item, index) => {
|
|
553
|
+
const itemIndex = () => (virtualRange()?.start ?? 0) + index();
|
|
554
|
+
const beforeIndicator = () => collectionRenderer().renderDropIndicator?.(itemIndex(), 'before');
|
|
555
|
+
const onIndicator = () => collectionRenderer().renderDropIndicator?.(itemIndex(), 'on');
|
|
556
|
+
const afterIndicator = () => collectionRenderer().renderDropIndicator?.(itemIndex(), 'after');
|
|
557
|
+
return (
|
|
558
|
+
<>
|
|
559
|
+
{beforeIndicator()}
|
|
560
|
+
{onIndicator()}
|
|
561
|
+
{props.children(item as T)}
|
|
562
|
+
{afterIndicator()}
|
|
563
|
+
</>
|
|
564
|
+
);
|
|
565
|
+
}}
|
|
566
|
+
</For>
|
|
567
|
+
{virtualRange()?.offsetBottom
|
|
568
|
+
? <li role="presentation" aria-hidden="true" style={{ height: `${virtualRange()!.offsetBottom}px` }} data-virtualizer-spacer="bottom" />
|
|
569
|
+
: null}
|
|
570
|
+
</>
|
|
571
|
+
)
|
|
572
|
+
}
|
|
573
|
+
</SharedElementTransition>
|
|
574
|
+
{local.hasMore && local.onLoadMore && (
|
|
575
|
+
<ListBoxLoadMoreItem
|
|
576
|
+
onLoadMore={local.onLoadMore}
|
|
577
|
+
isLoading={local.isLoading}
|
|
578
|
+
/>
|
|
579
|
+
)}
|
|
580
|
+
</ul>
|
|
581
|
+
</>
|
|
582
|
+
</CollectionRendererContext.Provider>
|
|
250
583
|
</ListBoxStateContext.Provider>
|
|
251
584
|
</ListBoxContext.Provider>
|
|
252
585
|
);
|
|
@@ -271,16 +604,21 @@ export function ListBoxOption<T>(props: ListBoxOptionProps<T>): JSX.Element {
|
|
|
271
604
|
throw new Error('ListBoxOption must be used within a ListBox');
|
|
272
605
|
}
|
|
273
606
|
const state = context as ListState<T>;
|
|
607
|
+
const listContext = useContext(ListBoxContext) as ListBoxContextValue<T> | null;
|
|
608
|
+
const [ref, setRef] = createSignal<HTMLLIElement | null>(null);
|
|
274
609
|
|
|
275
610
|
// Create option aria props
|
|
276
611
|
const optionAria = createOption<T>(
|
|
277
612
|
{
|
|
278
613
|
key: local.id,
|
|
279
614
|
get isDisabled() {
|
|
280
|
-
return ariaProps.isDisabled;
|
|
615
|
+
return Boolean(ariaProps.isDisabled || listContext?.isDisabled());
|
|
281
616
|
},
|
|
282
617
|
get 'aria-label'() {
|
|
283
|
-
return ariaProps['aria-label'];
|
|
618
|
+
return ariaProps['aria-label'] ?? local.textValue;
|
|
619
|
+
},
|
|
620
|
+
get shouldSelectOnPressUp() {
|
|
621
|
+
return ariaProps.shouldSelectOnPressUp;
|
|
284
622
|
},
|
|
285
623
|
},
|
|
286
624
|
state
|
|
@@ -313,10 +651,43 @@ export function ListBoxOption<T>(props: ListBoxOptionProps<T>): JSX.Element {
|
|
|
313
651
|
},
|
|
314
652
|
renderValues
|
|
315
653
|
);
|
|
654
|
+
const hasPrimitiveLabel = () => {
|
|
655
|
+
return typeof props.children === 'string' || typeof props.children === 'number';
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
const selectionIndicatorContext = createMemo<SelectionIndicatorContextValue>(() => ({
|
|
659
|
+
isSelected: optionAria.isSelected,
|
|
660
|
+
}));
|
|
661
|
+
const draggableItem = createMemo(() => {
|
|
662
|
+
if (!listContext?.dragAndDropHooks?.useDraggableItem || !listContext.dragState) return undefined;
|
|
663
|
+
return listContext.dragAndDropHooks.useDraggableItem(
|
|
664
|
+
{
|
|
665
|
+
key: local.id as string | number,
|
|
666
|
+
},
|
|
667
|
+
listContext.dragState as Parameters<NonNullable<DragAndDropHooks<T>['useDraggableItem']>>[1]
|
|
668
|
+
);
|
|
669
|
+
});
|
|
670
|
+
const droppableItem = createMemo(() => {
|
|
671
|
+
if (!listContext?.dragAndDropHooks?.useDroppableItem || !listContext.dropState) return undefined;
|
|
672
|
+
return listContext.dragAndDropHooks.useDroppableItem(
|
|
673
|
+
{
|
|
674
|
+
key: local.id as string | number,
|
|
675
|
+
},
|
|
676
|
+
listContext.dropState as Parameters<NonNullable<DragAndDropHooks<T>['useDroppableItem']>>[1],
|
|
677
|
+
() => ref()
|
|
678
|
+
);
|
|
679
|
+
});
|
|
316
680
|
|
|
317
681
|
// Remove ref from spread props
|
|
318
682
|
const cleanOptionProps = () => {
|
|
319
|
-
const {
|
|
683
|
+
const {
|
|
684
|
+
ref: _ref1,
|
|
685
|
+
'aria-describedby': _ariaDescribedby,
|
|
686
|
+
...rest
|
|
687
|
+
} = optionAria.optionProps as Record<string, unknown>;
|
|
688
|
+
if (!hasPrimitiveLabel() && rest['aria-label'] == null) {
|
|
689
|
+
delete rest['aria-labelledby'];
|
|
690
|
+
}
|
|
320
691
|
return rest;
|
|
321
692
|
};
|
|
322
693
|
const cleanHoverProps = () => {
|
|
@@ -324,23 +695,103 @@ export function ListBoxOption<T>(props: ListBoxOptionProps<T>): JSX.Element {
|
|
|
324
695
|
return rest;
|
|
325
696
|
};
|
|
326
697
|
|
|
698
|
+
return (
|
|
699
|
+
<SelectionIndicatorContext.Provider value={selectionIndicatorContext()}>
|
|
700
|
+
<li
|
|
701
|
+
ref={setRef}
|
|
702
|
+
{...mergeProps(
|
|
703
|
+
cleanOptionProps(),
|
|
704
|
+
cleanHoverProps(),
|
|
705
|
+
(draggableItem()?.dragProps as Record<string, unknown> | undefined) ?? {},
|
|
706
|
+
(droppableItem()?.dropProps as Record<string, unknown> | undefined) ?? {}
|
|
707
|
+
)}
|
|
708
|
+
class={renderProps.class()}
|
|
709
|
+
style={renderProps.style()}
|
|
710
|
+
data-selected={optionAria.isSelected() || undefined}
|
|
711
|
+
data-focused={optionAria.isFocused() || undefined}
|
|
712
|
+
data-focus-visible={optionAria.isFocusVisible() || undefined}
|
|
713
|
+
data-pressed={optionAria.isPressed() || undefined}
|
|
714
|
+
data-hovered={isHovered() || undefined}
|
|
715
|
+
data-disabled={optionAria.isDisabled() || undefined}
|
|
716
|
+
data-dragging={draggableItem()?.isDragging || undefined}
|
|
717
|
+
data-drop-target={droppableItem()?.isDropTarget || undefined}
|
|
718
|
+
>
|
|
719
|
+
{hasPrimitiveLabel()
|
|
720
|
+
? <span {...optionAria.labelProps}>{renderProps.renderChildren()}</span>
|
|
721
|
+
: renderProps.renderChildren()}
|
|
722
|
+
</li>
|
|
723
|
+
</SelectionIndicatorContext.Provider>
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Load more sentinel item for listbox collections.
|
|
729
|
+
*/
|
|
730
|
+
export function ListBoxLoadMoreItem(props: ListBoxLoadMoreItemProps): JSX.Element {
|
|
731
|
+
let ref: HTMLLIElement | undefined;
|
|
732
|
+
const [isPending, setIsPending] = createSignal(false);
|
|
733
|
+
|
|
734
|
+
const isLoading = () => !!props.isLoading || isPending();
|
|
735
|
+
|
|
736
|
+
const triggerLoadMore = async () => {
|
|
737
|
+
if (isLoading()) return;
|
|
738
|
+
setIsPending(true);
|
|
739
|
+
try {
|
|
740
|
+
await props.onLoadMore();
|
|
741
|
+
} finally {
|
|
742
|
+
setIsPending(false);
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
createEffect(() => {
|
|
747
|
+
if (!ref || typeof IntersectionObserver !== 'function') return;
|
|
748
|
+
|
|
749
|
+
const observer = new IntersectionObserver((entries) => {
|
|
750
|
+
const entry = entries[0];
|
|
751
|
+
if (entry?.isIntersecting) {
|
|
752
|
+
void triggerLoadMore();
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
observer.observe(ref);
|
|
757
|
+
return () => observer.disconnect();
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
const renderProps = useRenderProps(
|
|
761
|
+
{
|
|
762
|
+
children: props.children ?? (() => (isLoading() ? 'Loading more...' : 'Load more')),
|
|
763
|
+
class: props.class,
|
|
764
|
+
style: props.style,
|
|
765
|
+
defaultClassName: 'solidaria-ListBox-loadMore',
|
|
766
|
+
},
|
|
767
|
+
() => ({ isLoading: isLoading() })
|
|
768
|
+
);
|
|
769
|
+
|
|
327
770
|
return (
|
|
328
771
|
<li
|
|
329
|
-
{
|
|
330
|
-
|
|
772
|
+
ref={ref}
|
|
773
|
+
role="option"
|
|
774
|
+
aria-disabled={true}
|
|
775
|
+
tabIndex={0}
|
|
776
|
+
onFocus={() => {
|
|
777
|
+
void triggerLoadMore();
|
|
778
|
+
}}
|
|
331
779
|
class={renderProps.class()}
|
|
332
780
|
style={renderProps.style()}
|
|
333
|
-
data-
|
|
334
|
-
data-focused={optionAria.isFocused() || undefined}
|
|
335
|
-
data-focus-visible={optionAria.isFocusVisible() || undefined}
|
|
336
|
-
data-pressed={optionAria.isPressed() || undefined}
|
|
337
|
-
data-hovered={isHovered() || undefined}
|
|
338
|
-
data-disabled={optionAria.isDisabled() || undefined}
|
|
781
|
+
data-loading={isLoading() || undefined}
|
|
339
782
|
>
|
|
340
783
|
{renderProps.renderChildren()}
|
|
341
784
|
</li>
|
|
342
785
|
);
|
|
343
786
|
}
|
|
344
787
|
|
|
788
|
+
/**
|
|
789
|
+
* Section primitive alias for ListBox composition parity.
|
|
790
|
+
*/
|
|
791
|
+
export function ListBoxSection(props: ListBoxSectionProps): JSX.Element {
|
|
792
|
+
return <Section {...props} />;
|
|
793
|
+
}
|
|
794
|
+
|
|
345
795
|
// Attach Option as a static property
|
|
346
796
|
ListBox.Option = ListBoxOption;
|
|
797
|
+
ListBox.LoadMoreItem = ListBoxLoadMoreItem;
|