@proyecto-viviana/solidaria-components 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +39 -272
- package/dist/ActionBar.d.ts +79 -0
- package/dist/ActionBar.d.ts.map +1 -0
- package/dist/ActionGroup.d.ts +74 -0
- package/dist/ActionGroup.d.ts.map +1 -0
- package/dist/Alert.d.ts +70 -0
- package/dist/Alert.d.ts.map +1 -0
- package/dist/Autocomplete.d.ts +5 -5
- package/dist/Autocomplete.d.ts.map +1 -1
- package/dist/Breadcrumbs.d.ts +27 -8
- package/dist/Breadcrumbs.d.ts.map +1 -1
- package/dist/Button.d.ts +28 -5
- package/dist/Button.d.ts.map +1 -1
- package/dist/Calendar.d.ts +51 -7
- package/dist/Calendar.d.ts.map +1 -1
- package/dist/Checkbox.d.ts +33 -8
- package/dist/Checkbox.d.ts.map +1 -1
- package/dist/Collection.d.ts +130 -0
- package/dist/Collection.d.ts.map +1 -0
- package/dist/Color.d.ts +210 -9
- package/dist/Color.d.ts.map +1 -1
- package/dist/ColorEditor.d.ts +42 -0
- package/dist/ColorEditor.d.ts.map +1 -0
- package/dist/ComboBox.d.ts +146 -16
- package/dist/ComboBox.d.ts.map +1 -1
- package/dist/ContextualHelpTrigger.d.ts +40 -0
- package/dist/ContextualHelpTrigger.d.ts.map +1 -0
- package/dist/DateField.d.ts +35 -8
- package/dist/DateField.d.ts.map +1 -1
- package/dist/DatePicker.d.ts +101 -5
- package/dist/DatePicker.d.ts.map +1 -1
- package/dist/DateRangePickerContext.d.ts +30 -0
- package/dist/DateRangePickerContext.d.ts.map +1 -0
- package/dist/Dialog.d.ts +5 -5
- package/dist/Dialog.d.ts.map +1 -1
- package/dist/Disclosure.d.ts +25 -5
- package/dist/Disclosure.d.ts.map +1 -1
- package/dist/DragAndDrop.d.ts +80 -0
- package/dist/DragAndDrop.d.ts.map +1 -0
- package/dist/DragPreview.d.ts +14 -0
- package/dist/DragPreview.d.ts.map +1 -0
- package/dist/DropZone.d.ts +27 -0
- package/dist/DropZone.d.ts.map +1 -0
- package/dist/FieldError.d.ts +27 -0
- package/dist/FieldError.d.ts.map +1 -0
- package/dist/FileTrigger.d.ts +26 -0
- package/dist/FileTrigger.d.ts.map +1 -0
- package/dist/Focusable.d.ts +27 -0
- package/dist/Focusable.d.ts.map +1 -0
- package/dist/Form.d.ts +41 -0
- package/dist/Form.d.ts.map +1 -0
- package/dist/GridList.d.ts +69 -10
- package/dist/GridList.d.ts.map +1 -1
- package/dist/HiddenDateInput.d.ts +26 -0
- package/dist/HiddenDateInput.d.ts.map +1 -0
- package/dist/HiddenTimeInput.d.ts +25 -0
- package/dist/HiddenTimeInput.d.ts.map +1 -0
- package/dist/Icon.d.ts +57 -0
- package/dist/Icon.d.ts.map +1 -0
- package/dist/Keyboard.d.ts +13 -0
- package/dist/Keyboard.d.ts.map +1 -0
- package/dist/Landmark.d.ts +3 -3
- package/dist/Landmark.d.ts.map +1 -1
- package/dist/Link.d.ts +10 -4
- package/dist/Link.d.ts.map +1 -1
- package/dist/ListBox.d.ts +73 -11
- package/dist/ListBox.d.ts.map +1 -1
- package/dist/ListDropTargetDelegate.d.ts +38 -0
- package/dist/ListDropTargetDelegate.d.ts.map +1 -0
- package/dist/Menu.d.ts +79 -10
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Meter.d.ts +4 -4
- package/dist/Meter.d.ts.map +1 -1
- package/dist/Modal.d.ts +6 -4
- package/dist/Modal.d.ts.map +1 -1
- package/dist/NumberField.d.ts +10 -12
- package/dist/NumberField.d.ts.map +1 -1
- package/dist/Popover.d.ts +32 -7
- package/dist/Popover.d.ts.map +1 -1
- package/dist/Pressable.d.ts +27 -0
- package/dist/Pressable.d.ts.map +1 -0
- package/dist/ProgressBar.d.ts +6 -4
- package/dist/ProgressBar.d.ts.map +1 -1
- package/dist/RadioGroup.d.ts +43 -9
- package/dist/RadioGroup.d.ts.map +1 -1
- package/dist/RangeCalendar.d.ts +39 -7
- package/dist/RangeCalendar.d.ts.map +1 -1
- package/dist/RouterProvider.d.ts +75 -0
- package/dist/RouterProvider.d.ts.map +1 -0
- package/dist/SearchField.d.ts +23 -21
- package/dist/SearchField.d.ts.map +1 -1
- package/dist/Select.d.ts +48 -7
- package/dist/Select.d.ts.map +1 -1
- package/dist/SelectionIndicator.d.ts +30 -0
- package/dist/SelectionIndicator.d.ts.map +1 -0
- package/dist/Separator.d.ts +9 -3
- package/dist/Separator.d.ts.map +1 -1
- package/dist/SharedElementTransition.d.ts +41 -0
- package/dist/SharedElementTransition.d.ts.map +1 -0
- package/dist/Slider.d.ts +15 -8
- package/dist/Slider.d.ts.map +1 -1
- package/dist/StepList.d.ts +90 -0
- package/dist/StepList.d.ts.map +1 -0
- package/dist/Switch.d.ts +11 -5
- package/dist/Switch.d.ts.map +1 -1
- package/dist/Table.d.ts +222 -19
- package/dist/Table.d.ts.map +1 -1
- package/dist/Tabs.d.ts +47 -10
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/TagGroup.d.ts +22 -10
- package/dist/TagGroup.d.ts.map +1 -1
- package/dist/Text.d.ts +10 -0
- package/dist/Text.d.ts.map +1 -0
- package/dist/TextField.d.ts +19 -11
- package/dist/TextField.d.ts.map +1 -1
- package/dist/TimeField.d.ts +32 -7
- package/dist/TimeField.d.ts.map +1 -1
- package/dist/Toast.d.ts +29 -14
- package/dist/Toast.d.ts.map +1 -1
- package/dist/ToggleButton.d.ts +36 -0
- package/dist/ToggleButton.d.ts.map +1 -0
- package/dist/ToggleButtonGroup.d.ts +33 -0
- package/dist/ToggleButtonGroup.d.ts.map +1 -0
- package/dist/Toolbar.d.ts +7 -3
- package/dist/Toolbar.d.ts.map +1 -1
- package/dist/Tooltip.d.ts +58 -7
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/Tree.d.ts +102 -11
- package/dist/Tree.d.ts.map +1 -1
- package/dist/Virtualizer.d.ts +61 -0
- package/dist/Virtualizer.d.ts.map +1 -0
- package/dist/VirtualizerLayouts.d.ts +82 -0
- package/dist/VirtualizerLayouts.d.ts.map +1 -0
- package/dist/VisuallyHidden.d.ts +4 -2
- package/dist/VisuallyHidden.d.ts.map +1 -1
- package/dist/contexts.d.ts +6 -1
- package/dist/contexts.d.ts.map +1 -1
- package/dist/index.d.ts +73 -39
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23342 -10644
- package/dist/index.js.map +1 -7
- package/dist/index.jsx +18110 -0
- package/dist/index.jsx.map +1 -0
- package/dist/useDragAndDrop.d.ts +93 -0
- package/dist/useDragAndDrop.d.ts.map +1 -0
- package/dist/utils.d.ts +8 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/virtualizer/Layout.d.ts +79 -0
- package/dist/virtualizer/Layout.d.ts.map +1 -0
- package/package.json +33 -32
- package/src/ActionBar.tsx +251 -0
- package/src/ActionGroup.tsx +277 -0
- package/src/Alert.tsx +152 -0
- package/src/Autocomplete.tsx +39 -44
- package/src/Breadcrumbs.tsx +227 -72
- package/src/Button.tsx +315 -74
- package/src/Calendar.tsx +347 -141
- package/src/Checkbox.tsx +414 -123
- package/src/Collection.tsx +350 -0
- package/src/Color.tsx +1325 -284
- package/src/ColorEditor.tsx +213 -0
- package/src/ComboBox.tsx +644 -245
- package/src/ContextualHelpTrigger.tsx +195 -0
- package/src/DateField.tsx +274 -106
- package/src/DatePicker.tsx +892 -111
- package/src/DateRangePickerContext.tsx +44 -0
- package/src/Dialog.tsx +173 -104
- package/src/Disclosure.tsx +158 -105
- package/src/DragAndDrop.tsx +340 -0
- package/src/DragPreview.tsx +47 -0
- package/src/DropZone.tsx +233 -0
- package/src/FieldError.tsx +89 -0
- package/src/FileTrigger.tsx +83 -0
- package/src/Focusable.tsx +103 -0
- package/src/Form.tsx +140 -0
- package/src/GridList.tsx +542 -128
- package/src/HiddenDateInput.tsx +153 -0
- package/src/HiddenTimeInput.tsx +133 -0
- package/src/Icon.tsx +133 -0
- package/src/Keyboard.tsx +26 -0
- package/src/Landmark.tsx +37 -63
- package/src/Link.tsx +132 -69
- package/src/ListBox.tsx +656 -106
- package/src/ListDropTargetDelegate.ts +283 -0
- package/src/Menu.tsx +1234 -132
- package/src/Meter.tsx +44 -58
- package/src/Modal.tsx +262 -166
- package/src/NumberField.tsx +267 -151
- package/src/Popover.tsx +452 -343
- package/src/Pressable.tsx +108 -0
- package/src/ProgressBar.tsx +54 -59
- package/src/RadioGroup.tsx +533 -121
- package/src/RangeCalendar.tsx +249 -150
- package/src/RouterProvider.tsx +223 -0
- package/src/SearchField.tsx +460 -133
- package/src/Select.tsx +804 -233
- package/src/SelectionIndicator.tsx +108 -0
- package/src/Separator.tsx +47 -49
- package/src/SharedElementTransition.tsx +264 -0
- package/src/Slider.tsx +148 -98
- package/src/StepList.tsx +272 -0
- package/src/Switch.tsx +93 -46
- package/src/Table.tsx +1551 -225
- package/src/Tabs.tsx +377 -123
- package/src/TagGroup.tsx +233 -135
- package/src/Text.tsx +18 -0
- package/src/TextField.tsx +413 -86
- package/src/TimeField.tsx +232 -222
- package/src/Toast.tsx +306 -160
- package/src/ToggleButton.tsx +169 -0
- package/src/ToggleButtonGroup.tsx +141 -0
- package/src/Toolbar.tsx +61 -70
- package/src/Tooltip.tsx +473 -116
- package/src/Tree.tsx +1514 -175
- package/src/Virtualizer.tsx +730 -0
- package/src/VirtualizerLayouts.ts +280 -0
- package/src/VisuallyHidden.tsx +32 -38
- package/src/contexts.ts +29 -36
- package/src/index.ts +972 -620
- package/src/useDragAndDrop.ts +367 -0
- package/src/utils.tsx +69 -50
- package/src/virtualizer/Layout.ts +192 -0
- package/dist/index.ssr.js +0 -9785
- package/dist/index.ssr.js.map +0 -7
package/src/ListBox.tsx
CHANGED
|
@@ -8,24 +8,29 @@
|
|
|
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,
|
|
15
|
-
|
|
18
|
+
Show,
|
|
19
|
+
} from "solid-js";
|
|
16
20
|
import {
|
|
17
21
|
createListBox,
|
|
18
22
|
createOption,
|
|
19
23
|
createFocusRing,
|
|
20
|
-
|
|
24
|
+
mergeProps,
|
|
21
25
|
type AriaListBoxProps,
|
|
22
26
|
type AriaOptionProps,
|
|
23
|
-
} from
|
|
27
|
+
} from "@proyecto-viviana/solidaria";
|
|
24
28
|
import {
|
|
25
29
|
createListState,
|
|
26
30
|
type ListState,
|
|
27
31
|
type Key,
|
|
28
|
-
|
|
32
|
+
type DropTarget,
|
|
33
|
+
} from "@proyecto-viviana/solid-stately";
|
|
29
34
|
import {
|
|
30
35
|
type RenderChildren,
|
|
31
36
|
type ClassNameOrFunction,
|
|
@@ -33,11 +38,32 @@ import {
|
|
|
33
38
|
type SlotProps,
|
|
34
39
|
useRenderProps,
|
|
35
40
|
filterDOMProps,
|
|
36
|
-
} from
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
} from "./utils";
|
|
42
|
+
import { SharedElementTransition } from "./SharedElementTransition";
|
|
43
|
+
import {
|
|
44
|
+
SelectionIndicatorContext,
|
|
45
|
+
type SelectionIndicatorContextValue,
|
|
46
|
+
} from "./SelectionIndicator";
|
|
47
|
+
import { useVirtualizerContext } from "./Virtualizer";
|
|
48
|
+
import { type DragAndDropHooks } from "./useDragAndDrop";
|
|
49
|
+
import {
|
|
50
|
+
getNormalizedDropTargetKey,
|
|
51
|
+
mergePersistedKeysIntoVirtualRange,
|
|
52
|
+
useDndPersistedKeys,
|
|
53
|
+
useRenderDropIndicator,
|
|
54
|
+
} from "./DragAndDrop";
|
|
55
|
+
import {
|
|
56
|
+
CollectionRendererContext,
|
|
57
|
+
Section,
|
|
58
|
+
Header,
|
|
59
|
+
Group,
|
|
60
|
+
type CollectionEntry,
|
|
61
|
+
type CollectionRendererContextValue,
|
|
62
|
+
type SectionProps,
|
|
63
|
+
useCollectionRenderer,
|
|
64
|
+
isCollectionSection,
|
|
65
|
+
flattenCollectionEntries,
|
|
66
|
+
} from "./Collection";
|
|
41
67
|
|
|
42
68
|
export interface ListBoxRenderProps {
|
|
43
69
|
/** Whether the listbox has focus. */
|
|
@@ -50,11 +76,17 @@ export interface ListBoxRenderProps {
|
|
|
50
76
|
isEmpty: boolean;
|
|
51
77
|
}
|
|
52
78
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
79
|
+
type RefLike<T> = ((el: T) => void) | { current?: T | null } | undefined;
|
|
80
|
+
|
|
81
|
+
function assignRef<T>(ref: RefLike<T>, el: T): void {
|
|
82
|
+
if (!ref) return;
|
|
83
|
+
if (typeof ref === "function") ref(el);
|
|
84
|
+
else ref.current = el;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface ListBoxProps<T> extends Omit<AriaListBoxProps, "children">, SlotProps {
|
|
56
88
|
/** The items to render in the listbox. */
|
|
57
|
-
items: T[];
|
|
89
|
+
items: CollectionEntry<T>[];
|
|
58
90
|
/** Function to get the key from an item. */
|
|
59
91
|
getKey?: (item: T) => Key;
|
|
60
92
|
/** Function to get the text value from an item. */
|
|
@@ -62,15 +94,19 @@ export interface ListBoxProps<T>
|
|
|
62
94
|
/** Function to check if an item is disabled. */
|
|
63
95
|
getDisabled?: (item: T) => boolean;
|
|
64
96
|
/** The selection mode. */
|
|
65
|
-
selectionMode?:
|
|
97
|
+
selectionMode?: "none" | "single" | "multiple";
|
|
98
|
+
/** The selection behavior (toggle vs replace). */
|
|
99
|
+
selectionBehavior?: "toggle" | "replace";
|
|
100
|
+
/** Whether disabled items can still receive focus. */
|
|
101
|
+
disabledBehavior?: "selection" | "all";
|
|
66
102
|
/** Keys of disabled items. */
|
|
67
103
|
disabledKeys?: Iterable<Key>;
|
|
68
104
|
/** Currently selected keys (controlled). */
|
|
69
|
-
selectedKeys?:
|
|
105
|
+
selectedKeys?: "all" | Iterable<Key>;
|
|
70
106
|
/** Default selected keys (uncontrolled). */
|
|
71
|
-
defaultSelectedKeys?:
|
|
107
|
+
defaultSelectedKeys?: "all" | Iterable<Key>;
|
|
72
108
|
/** Handler called when selection changes. */
|
|
73
|
-
onSelectionChange?: (keys:
|
|
109
|
+
onSelectionChange?: (keys: "all" | Set<Key>) => void;
|
|
74
110
|
/** The children of the component. A function may be provided to render each item. */
|
|
75
111
|
children: (item: T) => JSX.Element;
|
|
76
112
|
/** The CSS className for the element. */
|
|
@@ -79,6 +115,22 @@ export interface ListBoxProps<T>
|
|
|
79
115
|
style?: StyleOrFunction<ListBoxRenderProps>;
|
|
80
116
|
/** A function to render when the listbox is empty. */
|
|
81
117
|
renderEmptyState?: () => JSX.Element;
|
|
118
|
+
/** Whether there are more items to load. */
|
|
119
|
+
hasMore?: boolean;
|
|
120
|
+
/** Whether additional items are currently loading. */
|
|
121
|
+
isLoading?: boolean;
|
|
122
|
+
/** Called when the load more sentinel becomes visible. */
|
|
123
|
+
onLoadMore?: () => void | Promise<void>;
|
|
124
|
+
/** Ref for the listbox element. */
|
|
125
|
+
ref?: RefLike<HTMLUListElement>;
|
|
126
|
+
/** Drag and drop hooks from `useDragAndDrop`. */
|
|
127
|
+
dragAndDropHooks?: DragAndDropHooks<T>;
|
|
128
|
+
/** Layout hint for styling parity. */
|
|
129
|
+
layout?: "stack" | "grid";
|
|
130
|
+
/** Orientation hint for styling parity. */
|
|
131
|
+
orientation?: "vertical" | "horizontal";
|
|
132
|
+
/** Slot definitions provided through ListBoxContext. */
|
|
133
|
+
slots?: Record<string, Partial<ListBoxProps<T>>>;
|
|
82
134
|
}
|
|
83
135
|
|
|
84
136
|
export interface ListBoxOptionRenderProps {
|
|
@@ -97,8 +149,7 @@ export interface ListBoxOptionRenderProps {
|
|
|
97
149
|
}
|
|
98
150
|
|
|
99
151
|
export interface ListBoxOptionProps<T>
|
|
100
|
-
extends Omit<AriaOptionProps,
|
|
101
|
-
SlotProps {
|
|
152
|
+
extends Omit<AriaOptionProps, "children" | "key">, SlotProps {
|
|
102
153
|
/** The unique key for the option. */
|
|
103
154
|
id: Key;
|
|
104
155
|
/** The item value. */
|
|
@@ -111,37 +162,90 @@ export interface ListBoxOptionProps<T>
|
|
|
111
162
|
style?: StyleOrFunction<ListBoxOptionRenderProps>;
|
|
112
163
|
/** The text value of the option (for typeahead). */
|
|
113
164
|
textValue?: string;
|
|
165
|
+
/** Ref for the option element. */
|
|
166
|
+
ref?: RefLike<HTMLLIElement>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface ListBoxLoadMoreItemProps extends SlotProps {
|
|
170
|
+
/** Called when the sentinel becomes visible. */
|
|
171
|
+
onLoadMore: () => void | Promise<void>;
|
|
172
|
+
/** Whether additional items are currently loading. */
|
|
173
|
+
isLoading?: boolean;
|
|
174
|
+
/** Scroll offset multiplier for early loading trigger (default: 1 = 100% of viewport height). */
|
|
175
|
+
scrollOffset?: number;
|
|
176
|
+
/** Content for the load more row. */
|
|
177
|
+
children?: JSX.Element;
|
|
178
|
+
/** The CSS className for the element. */
|
|
179
|
+
class?: ClassNameOrFunction<{ isLoading: boolean }>;
|
|
180
|
+
/** The inline style for the element. */
|
|
181
|
+
style?: StyleOrFunction<{ isLoading: boolean }>;
|
|
114
182
|
}
|
|
115
183
|
|
|
116
|
-
|
|
117
|
-
// CONTEXT
|
|
118
|
-
// ============================================
|
|
184
|
+
export interface ListBoxSectionProps extends SectionProps {}
|
|
119
185
|
|
|
120
186
|
interface ListBoxContextValue<T> {
|
|
121
187
|
state: ListState<T>;
|
|
188
|
+
isDisabled: () => boolean;
|
|
189
|
+
dragAndDropHooks?: DragAndDropHooks<unknown>;
|
|
190
|
+
dragState?: unknown;
|
|
191
|
+
dropState?: unknown;
|
|
192
|
+
slots?: Record<string, Partial<ListBoxProps<T>>>;
|
|
122
193
|
}
|
|
123
194
|
|
|
124
195
|
export const ListBoxContext = createContext<ListBoxContextValue<unknown> | null>(null);
|
|
125
196
|
export const ListBoxStateContext = createContext<ListState<unknown> | null>(null);
|
|
126
|
-
|
|
127
|
-
// ============================================
|
|
128
|
-
// COMPONENTS
|
|
129
|
-
// ============================================
|
|
197
|
+
export const ListStateContext = ListBoxStateContext;
|
|
130
198
|
|
|
131
199
|
/**
|
|
132
200
|
* A listbox displays a list of options and allows a user to select one or more of them.
|
|
133
201
|
*/
|
|
134
202
|
export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
|
|
203
|
+
const parentContext = useContext(ListBoxContext) as ListBoxContextValue<T> | null;
|
|
204
|
+
const contextSlotProps = parentContext?.slots?.[props.slot ?? "default"];
|
|
205
|
+
const mergedListBoxProps = contextSlotProps
|
|
206
|
+
? (mergeProps(contextSlotProps, props) as ListBoxProps<T>)
|
|
207
|
+
: props;
|
|
135
208
|
const [local, stateProps, ariaProps] = splitProps(
|
|
136
|
-
|
|
137
|
-
[
|
|
138
|
-
|
|
209
|
+
mergedListBoxProps,
|
|
210
|
+
[
|
|
211
|
+
"children",
|
|
212
|
+
"class",
|
|
213
|
+
"style",
|
|
214
|
+
"slot",
|
|
215
|
+
"renderEmptyState",
|
|
216
|
+
"hasMore",
|
|
217
|
+
"isLoading",
|
|
218
|
+
"onLoadMore",
|
|
219
|
+
"dragAndDropHooks",
|
|
220
|
+
"slots",
|
|
221
|
+
"ref",
|
|
222
|
+
],
|
|
223
|
+
[
|
|
224
|
+
"items",
|
|
225
|
+
"getKey",
|
|
226
|
+
"getTextValue",
|
|
227
|
+
"getDisabled",
|
|
228
|
+
"disabledKeys",
|
|
229
|
+
"disabledBehavior",
|
|
230
|
+
"selectionMode",
|
|
231
|
+
"selectionBehavior",
|
|
232
|
+
"selectedKeys",
|
|
233
|
+
"defaultSelectedKeys",
|
|
234
|
+
"onSelectionChange",
|
|
235
|
+
"layout",
|
|
236
|
+
"orientation",
|
|
237
|
+
],
|
|
139
238
|
);
|
|
140
239
|
|
|
141
|
-
|
|
240
|
+
const flatItems = createMemo<T[]>(() => {
|
|
241
|
+
return flattenCollectionEntries(stateProps.items);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const hasSections = createMemo(() => stateProps.items.some((item) => isCollectionSection(item)));
|
|
245
|
+
|
|
142
246
|
const state = createListState<T>({
|
|
143
247
|
get items() {
|
|
144
|
-
return
|
|
248
|
+
return flatItems();
|
|
145
249
|
},
|
|
146
250
|
get getKey() {
|
|
147
251
|
return stateProps.getKey;
|
|
@@ -158,6 +262,12 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
|
|
|
158
262
|
get selectionMode() {
|
|
159
263
|
return stateProps.selectionMode;
|
|
160
264
|
},
|
|
265
|
+
get selectionBehavior() {
|
|
266
|
+
return stateProps.selectionBehavior;
|
|
267
|
+
},
|
|
268
|
+
get disabledBehavior() {
|
|
269
|
+
return stateProps.disabledBehavior;
|
|
270
|
+
},
|
|
161
271
|
get selectedKeys() {
|
|
162
272
|
return stateProps.selectedKeys;
|
|
163
273
|
},
|
|
@@ -169,30 +279,26 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
|
|
|
169
279
|
},
|
|
170
280
|
});
|
|
171
281
|
|
|
172
|
-
// Helper to resolve isDisabled
|
|
173
282
|
const resolveDisabled = (): boolean => {
|
|
174
283
|
const disabled = ariaProps.isDisabled;
|
|
175
|
-
if (typeof disabled ===
|
|
284
|
+
if (typeof disabled === "function") {
|
|
176
285
|
return (disabled as () => boolean)();
|
|
177
286
|
}
|
|
178
287
|
return !!disabled;
|
|
179
288
|
};
|
|
180
289
|
|
|
181
|
-
|
|
182
|
-
const { listBoxProps } = createListBox(
|
|
290
|
+
const listBoxAria = createListBox(
|
|
183
291
|
{
|
|
184
292
|
...ariaProps,
|
|
185
293
|
get isDisabled() {
|
|
186
294
|
return resolveDisabled();
|
|
187
295
|
},
|
|
188
296
|
},
|
|
189
|
-
state
|
|
297
|
+
state,
|
|
190
298
|
);
|
|
191
299
|
|
|
192
|
-
// Create focus ring
|
|
193
300
|
const { isFocused, isFocusVisible, focusProps } = createFocusRing();
|
|
194
301
|
|
|
195
|
-
// Render props values
|
|
196
302
|
const renderValues = createMemo<ListBoxRenderProps>(() => ({
|
|
197
303
|
isFocused: state.isFocused() || isFocused(),
|
|
198
304
|
isFocusVisible: isFocusVisible(),
|
|
@@ -200,53 +306,366 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
|
|
|
200
306
|
isEmpty: state.collection().size === 0,
|
|
201
307
|
}));
|
|
202
308
|
|
|
203
|
-
// Resolve render props
|
|
204
309
|
const renderProps = useRenderProps(
|
|
205
310
|
{
|
|
206
311
|
class: local.class,
|
|
207
312
|
style: local.style,
|
|
208
|
-
defaultClassName:
|
|
313
|
+
defaultClassName: "solidaria-ListBox",
|
|
209
314
|
},
|
|
210
|
-
renderValues
|
|
315
|
+
renderValues,
|
|
211
316
|
);
|
|
212
317
|
|
|
213
|
-
// Filter DOM props
|
|
214
318
|
const domProps = createMemo(() => {
|
|
215
319
|
const filtered = filterDOMProps(ariaProps as Record<string, unknown>, { global: true });
|
|
216
320
|
return filtered;
|
|
217
321
|
});
|
|
218
322
|
|
|
219
|
-
// Remove ref from spread props
|
|
220
323
|
const cleanListBoxProps = () => {
|
|
221
|
-
const { ref: _ref1, ...rest } = listBoxProps as Record<string, unknown>;
|
|
324
|
+
const { ref: _ref1, ...rest } = listBoxAria.listBoxProps as Record<string, unknown>;
|
|
222
325
|
return rest;
|
|
223
326
|
};
|
|
224
327
|
const cleanFocusProps = () => {
|
|
225
328
|
const { ref: _ref2, ...rest } = focusProps as Record<string, unknown>;
|
|
226
329
|
return rest;
|
|
227
330
|
};
|
|
331
|
+
const cleanLabelProps = () => {
|
|
332
|
+
const { ref: _ref3, ...rest } = listBoxAria.labelProps as Record<string, unknown>;
|
|
333
|
+
return rest;
|
|
334
|
+
};
|
|
335
|
+
const [listRef, setListRef] = createSignal<HTMLElement | null>(null);
|
|
228
336
|
|
|
229
337
|
const isEmpty = () => stateProps.items.length === 0;
|
|
338
|
+
const parentCollectionRenderer = useCollectionRenderer<unknown>();
|
|
339
|
+
const getItemNodes = createMemo(() =>
|
|
340
|
+
Array.from(state.collection()).filter((node) => node.type === "item"),
|
|
341
|
+
);
|
|
342
|
+
const getDropTargetByIndex = (
|
|
343
|
+
index: number,
|
|
344
|
+
position: "before" | "after" | "on",
|
|
345
|
+
): DropTarget | null => {
|
|
346
|
+
const node = getItemNodes()[index];
|
|
347
|
+
if (!node) return null;
|
|
348
|
+
return { type: "item", key: node.key, dropPosition: position };
|
|
349
|
+
};
|
|
350
|
+
const hasDroppableDnd = createMemo(() => {
|
|
351
|
+
const hooks = local.dragAndDropHooks;
|
|
352
|
+
return Boolean(
|
|
353
|
+
hooks?.useDroppableCollectionState &&
|
|
354
|
+
hooks.useDroppableCollection &&
|
|
355
|
+
(hooks.dropTargetDelegate ||
|
|
356
|
+
parentCollectionRenderer?.dropTargetDelegate ||
|
|
357
|
+
hooks.ListDropTargetDelegate),
|
|
358
|
+
);
|
|
359
|
+
});
|
|
360
|
+
const dropState = createMemo(() => {
|
|
361
|
+
if (!hasDroppableDnd()) return undefined;
|
|
362
|
+
return local.dragAndDropHooks?.useDroppableCollectionState?.({});
|
|
363
|
+
});
|
|
364
|
+
const hasDraggableDnd = createMemo(() => {
|
|
365
|
+
const hooks = local.dragAndDropHooks;
|
|
366
|
+
return Boolean(hooks?.useDraggableCollectionState && hooks.useDraggableCollection);
|
|
367
|
+
});
|
|
368
|
+
const dragState = createMemo(() => {
|
|
369
|
+
if (!hasDraggableDnd()) return undefined;
|
|
370
|
+
return local.dragAndDropHooks?.useDraggableCollectionState?.({
|
|
371
|
+
items: flatItems(),
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
createEffect(() => {
|
|
375
|
+
if (!hasDraggableDnd()) return;
|
|
376
|
+
const hooks = local.dragAndDropHooks;
|
|
377
|
+
const activeDragState = dragState();
|
|
378
|
+
if (!hooks?.useDraggableCollection || !activeDragState) return;
|
|
379
|
+
hooks.useDraggableCollection({}, activeDragState, () => listRef());
|
|
380
|
+
});
|
|
381
|
+
const droppableCollection = createMemo(() => {
|
|
382
|
+
if (!hasDroppableDnd()) return undefined;
|
|
383
|
+
const hooks = local.dragAndDropHooks;
|
|
384
|
+
const activeDropState = dropState();
|
|
385
|
+
if (!hooks?.useDroppableCollection || !activeDropState) return undefined;
|
|
386
|
+
const resolveDirection = (): "ltr" | "rtl" => {
|
|
387
|
+
const el = listRef();
|
|
388
|
+
if (el && typeof window !== "undefined" && typeof window.getComputedStyle === "function") {
|
|
389
|
+
const dir = window.getComputedStyle(el).direction;
|
|
390
|
+
if (dir === "rtl") return "rtl";
|
|
391
|
+
}
|
|
392
|
+
return typeof document !== "undefined" && document.dir === "rtl" ? "rtl" : "ltr";
|
|
393
|
+
};
|
|
394
|
+
const dropTargetDelegate =
|
|
395
|
+
hooks.dropTargetDelegate ??
|
|
396
|
+
parentCollectionRenderer?.dropTargetDelegate ??
|
|
397
|
+
(hooks.ListDropTargetDelegate
|
|
398
|
+
? new hooks.ListDropTargetDelegate(
|
|
399
|
+
() => state.collection(),
|
|
400
|
+
() => listRef(),
|
|
401
|
+
{ layout: "stack", orientation: "vertical", direction: resolveDirection() },
|
|
402
|
+
)
|
|
403
|
+
: undefined);
|
|
404
|
+
if (!dropTargetDelegate) return undefined;
|
|
405
|
+
return hooks.useDroppableCollection(
|
|
406
|
+
{
|
|
407
|
+
dropTargetDelegate,
|
|
408
|
+
keyboardDelegate: {
|
|
409
|
+
getFirstKey: () => state.collection().getFirstKey(),
|
|
410
|
+
getLastKey: () => state.collection().getLastKey(),
|
|
411
|
+
getKeyBelow: (key) => state.collection().getKeyAfter(key),
|
|
412
|
+
getKeyAbove: (key) => state.collection().getKeyBefore(key),
|
|
413
|
+
getKeyPageBelow: (key) => state.collection().getKeyAfter(key),
|
|
414
|
+
getKeyPageAbove: (key) => state.collection().getKeyBefore(key),
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
activeDropState,
|
|
418
|
+
() => listRef(),
|
|
419
|
+
);
|
|
420
|
+
});
|
|
421
|
+
const isRootDropTarget = createMemo(() => {
|
|
422
|
+
return Boolean(dropState()?.target?.type === "root");
|
|
423
|
+
});
|
|
424
|
+
const dndRenderDropIndicator = createMemo(() =>
|
|
425
|
+
useRenderDropIndicator(local.dragAndDropHooks, dropState()),
|
|
426
|
+
);
|
|
427
|
+
const dndDropIndicator = (index: number, position: "before" | "after" | "on") => {
|
|
428
|
+
const target = getDropTargetByIndex(index, position);
|
|
429
|
+
if (!target || target.type !== "item") return undefined;
|
|
430
|
+
return dndRenderDropIndicator()?.(target);
|
|
431
|
+
};
|
|
432
|
+
const virtualizer = useVirtualizerContext();
|
|
433
|
+
const persistedKeys = useDndPersistedKeys(
|
|
434
|
+
{ focusedKey: state.focusedKey },
|
|
435
|
+
local.dragAndDropHooks,
|
|
436
|
+
dropState(),
|
|
437
|
+
state.collection(),
|
|
438
|
+
);
|
|
439
|
+
const virtualRange = createMemo(() => {
|
|
440
|
+
if (!virtualizer || !parentCollectionRenderer?.isVirtualized || hasSections()) return null;
|
|
441
|
+
const baseRange = virtualizer.getVisibleRange(stateProps.items.length);
|
|
442
|
+
const itemNodes = getItemNodes();
|
|
443
|
+
const persistedIndexes = Array.from(persistedKeys())
|
|
444
|
+
.map((key) => itemNodes.findIndex((node) => node.key === key))
|
|
445
|
+
.filter((index) => index >= 0);
|
|
446
|
+
const dropTarget = dropState()?.target;
|
|
447
|
+
const normalizedDropKey = getNormalizedDropTargetKey(dropTarget, state.collection());
|
|
448
|
+
const focusedKey = state.focusedKey();
|
|
449
|
+
const focusedIndex =
|
|
450
|
+
focusedKey != null ? itemNodes.findIndex((node) => node.key === focusedKey) : -1;
|
|
451
|
+
const forceIncludeIndexes = [
|
|
452
|
+
dropTarget?.type === "item" ? itemNodes.findIndex((node) => node.key === dropTarget.key) : -1,
|
|
453
|
+
normalizedDropKey != null
|
|
454
|
+
? itemNodes.findIndex((node) => node.key === normalizedDropKey)
|
|
455
|
+
: -1,
|
|
456
|
+
dropTarget?.type === "item" ? -1 : focusedIndex,
|
|
457
|
+
].filter((index) => index >= 0);
|
|
458
|
+
return mergePersistedKeysIntoVirtualRange(
|
|
459
|
+
baseRange,
|
|
460
|
+
persistedIndexes,
|
|
461
|
+
stateProps.items.length,
|
|
462
|
+
virtualizer,
|
|
463
|
+
80,
|
|
464
|
+
{
|
|
465
|
+
forceIncludeIndexes,
|
|
466
|
+
forceIncludeMaxSpan: 320,
|
|
467
|
+
},
|
|
468
|
+
);
|
|
469
|
+
});
|
|
470
|
+
createEffect(() => {
|
|
471
|
+
if (!virtualizer || !parentCollectionRenderer?.isVirtualized) return;
|
|
472
|
+
const getItemNodes = () =>
|
|
473
|
+
Array.from(state.collection()).filter((node) => node.type === "item");
|
|
474
|
+
virtualizer.setDropTargetItemCountResolver(() => getItemNodes().length);
|
|
475
|
+
virtualizer.setDropTargetIndexResolver((key) => {
|
|
476
|
+
const index = getItemNodes().findIndex((node) => node.key === key);
|
|
477
|
+
return index >= 0 ? index : null;
|
|
478
|
+
});
|
|
479
|
+
virtualizer.setDropTargetResolver((target) => {
|
|
480
|
+
const node = getItemNodes()[target.index];
|
|
481
|
+
if (!node) return target;
|
|
482
|
+
return {
|
|
483
|
+
...target,
|
|
484
|
+
key: typeof node.key === "string" || typeof node.key === "number" ? node.key : undefined,
|
|
485
|
+
};
|
|
486
|
+
});
|
|
487
|
+
onCleanup(() => {
|
|
488
|
+
virtualizer.setDropTargetIndexResolver(undefined);
|
|
489
|
+
virtualizer.setDropTargetItemCountResolver(undefined);
|
|
490
|
+
virtualizer.setDropTargetResolver(undefined);
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
const visibleItems = createMemo(() => {
|
|
494
|
+
const range = virtualRange();
|
|
495
|
+
if (!range) return stateProps.items;
|
|
496
|
+
return stateProps.items.slice(range.start, range.end);
|
|
497
|
+
});
|
|
498
|
+
const sectionedRenderEntries = createMemo(() => {
|
|
499
|
+
let globalIndex = 0;
|
|
500
|
+
return stateProps.items.map((entry) => {
|
|
501
|
+
if (isCollectionSection(entry)) {
|
|
502
|
+
const sectionItems = entry.items.map((item) => ({
|
|
503
|
+
item,
|
|
504
|
+
index: globalIndex++,
|
|
505
|
+
}));
|
|
506
|
+
return {
|
|
507
|
+
type: "section" as const,
|
|
508
|
+
section: entry,
|
|
509
|
+
items: sectionItems,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
const indexedItem = {
|
|
513
|
+
item: entry as T,
|
|
514
|
+
index: globalIndex++,
|
|
515
|
+
};
|
|
516
|
+
return {
|
|
517
|
+
type: "item" as const,
|
|
518
|
+
item: indexedItem,
|
|
519
|
+
};
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
const collectionRenderer = createMemo<CollectionRendererContextValue<unknown>>(() => ({
|
|
523
|
+
...parentCollectionRenderer,
|
|
524
|
+
renderItem: (item) => local.children(item as T),
|
|
525
|
+
renderDropIndicator: (index, position) =>
|
|
526
|
+
dndDropIndicator(index, position) ??
|
|
527
|
+
parentCollectionRenderer?.renderDropIndicator?.(index, position),
|
|
528
|
+
}));
|
|
230
529
|
|
|
231
530
|
return (
|
|
232
|
-
<ListBoxContext.Provider
|
|
531
|
+
<ListBoxContext.Provider
|
|
532
|
+
value={
|
|
533
|
+
{
|
|
534
|
+
state,
|
|
535
|
+
isDisabled: resolveDisabled,
|
|
536
|
+
dragAndDropHooks: local.dragAndDropHooks as DragAndDropHooks<unknown> | undefined,
|
|
537
|
+
dragState: dragState(),
|
|
538
|
+
dropState: dropState(),
|
|
539
|
+
slots: local.slots,
|
|
540
|
+
} as ListBoxContextValue<unknown>
|
|
541
|
+
}
|
|
542
|
+
>
|
|
233
543
|
<ListBoxStateContext.Provider value={state}>
|
|
234
|
-
<
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
544
|
+
<CollectionRendererContext.Provider value={collectionRenderer()}>
|
|
545
|
+
<>
|
|
546
|
+
<Show when={ariaProps.label}>
|
|
547
|
+
<span {...cleanLabelProps()}>{ariaProps.label as JSX.Element}</span>
|
|
548
|
+
</Show>
|
|
549
|
+
<ul
|
|
550
|
+
{...mergeProps(
|
|
551
|
+
domProps(),
|
|
552
|
+
cleanListBoxProps(),
|
|
553
|
+
cleanFocusProps(),
|
|
554
|
+
(droppableCollection()?.collectionProps as Record<string, unknown> | undefined) ??
|
|
555
|
+
{},
|
|
556
|
+
)}
|
|
557
|
+
ref={(el) => {
|
|
558
|
+
setListRef(el);
|
|
559
|
+
assignRef(local.ref, el);
|
|
560
|
+
}}
|
|
561
|
+
class={renderProps.class()}
|
|
562
|
+
style={renderProps.style()}
|
|
563
|
+
data-focused={state.isFocused() || undefined}
|
|
564
|
+
data-focus-visible={isFocusVisible() || undefined}
|
|
565
|
+
data-disabled={resolveDisabled() || undefined}
|
|
566
|
+
data-empty={isEmpty() || undefined}
|
|
567
|
+
data-layout={stateProps.layout}
|
|
568
|
+
data-orientation={stateProps.orientation}
|
|
569
|
+
data-drop-target={isRootDropTarget() || undefined}
|
|
570
|
+
slot={local.slot}
|
|
571
|
+
>
|
|
572
|
+
<SharedElementTransition>
|
|
573
|
+
{isEmpty() && local.renderEmptyState ? (
|
|
574
|
+
<li role="option" style={{ display: "contents" }} data-empty-state>
|
|
575
|
+
{local.renderEmptyState()}
|
|
576
|
+
</li>
|
|
577
|
+
) : hasSections() ? (
|
|
578
|
+
<For each={sectionedRenderEntries()}>
|
|
579
|
+
{(entry) =>
|
|
580
|
+
entry.type === "section" ? (
|
|
581
|
+
<li role="presentation" data-section-wrapper>
|
|
582
|
+
<Section class="solidaria-ListBox-section">
|
|
583
|
+
{entry.section.title != null && (
|
|
584
|
+
<Header class="solidaria-ListBox-sectionHeader">
|
|
585
|
+
{entry.section.title}
|
|
586
|
+
</Header>
|
|
587
|
+
)}
|
|
588
|
+
<Group class="solidaria-ListBox-sectionGroup">
|
|
589
|
+
<ul role="group" aria-label={entry.section["aria-label"]}>
|
|
590
|
+
<For each={entry.items}>
|
|
591
|
+
{(indexedItem) => (
|
|
592
|
+
<>
|
|
593
|
+
{collectionRenderer().renderDropIndicator?.(
|
|
594
|
+
indexedItem.index,
|
|
595
|
+
"before",
|
|
596
|
+
)}
|
|
597
|
+
{collectionRenderer().renderDropIndicator?.(
|
|
598
|
+
indexedItem.index,
|
|
599
|
+
"on",
|
|
600
|
+
)}
|
|
601
|
+
{local.children(indexedItem.item)}
|
|
602
|
+
{collectionRenderer().renderDropIndicator?.(
|
|
603
|
+
indexedItem.index,
|
|
604
|
+
"after",
|
|
605
|
+
)}
|
|
606
|
+
</>
|
|
607
|
+
)}
|
|
608
|
+
</For>
|
|
609
|
+
</ul>
|
|
610
|
+
</Group>
|
|
611
|
+
</Section>
|
|
612
|
+
</li>
|
|
613
|
+
) : (
|
|
614
|
+
<>
|
|
615
|
+
{collectionRenderer().renderDropIndicator?.(entry.item.index, "before")}
|
|
616
|
+
{collectionRenderer().renderDropIndicator?.(entry.item.index, "on")}
|
|
617
|
+
{local.children(entry.item.item)}
|
|
618
|
+
{collectionRenderer().renderDropIndicator?.(entry.item.index, "after")}
|
|
619
|
+
</>
|
|
620
|
+
)
|
|
621
|
+
}
|
|
622
|
+
</For>
|
|
623
|
+
) : (
|
|
624
|
+
<>
|
|
625
|
+
{virtualRange()?.offsetTop ? (
|
|
626
|
+
<li
|
|
627
|
+
role="presentation"
|
|
628
|
+
aria-hidden="true"
|
|
629
|
+
style={{ height: `${virtualRange()!.offsetTop}px` }}
|
|
630
|
+
data-virtualizer-spacer="top"
|
|
631
|
+
/>
|
|
632
|
+
) : null}
|
|
633
|
+
<For each={visibleItems()}>
|
|
634
|
+
{(item, index) => {
|
|
635
|
+
const itemIndex = () => (virtualRange()?.start ?? 0) + index();
|
|
636
|
+
const beforeIndicator = () =>
|
|
637
|
+
collectionRenderer().renderDropIndicator?.(itemIndex(), "before");
|
|
638
|
+
const onIndicator = () =>
|
|
639
|
+
collectionRenderer().renderDropIndicator?.(itemIndex(), "on");
|
|
640
|
+
const afterIndicator = () =>
|
|
641
|
+
collectionRenderer().renderDropIndicator?.(itemIndex(), "after");
|
|
642
|
+
return (
|
|
643
|
+
<>
|
|
644
|
+
{beforeIndicator()}
|
|
645
|
+
{onIndicator()}
|
|
646
|
+
{local.children(item as T)}
|
|
647
|
+
{afterIndicator()}
|
|
648
|
+
</>
|
|
649
|
+
);
|
|
650
|
+
}}
|
|
651
|
+
</For>
|
|
652
|
+
{virtualRange()?.offsetBottom ? (
|
|
653
|
+
<li
|
|
654
|
+
role="presentation"
|
|
655
|
+
aria-hidden="true"
|
|
656
|
+
style={{ height: `${virtualRange()!.offsetBottom}px` }}
|
|
657
|
+
data-virtualizer-spacer="bottom"
|
|
658
|
+
/>
|
|
659
|
+
) : null}
|
|
660
|
+
</>
|
|
661
|
+
)}
|
|
662
|
+
</SharedElementTransition>
|
|
663
|
+
{local.hasMore && local.onLoadMore && (
|
|
664
|
+
<ListBoxLoadMoreItem onLoadMore={local.onLoadMore} isLoading={local.isLoading} />
|
|
665
|
+
)}
|
|
666
|
+
</ul>
|
|
667
|
+
</>
|
|
668
|
+
</CollectionRendererContext.Provider>
|
|
250
669
|
</ListBoxStateContext.Provider>
|
|
251
670
|
</ListBoxContext.Provider>
|
|
252
671
|
);
|
|
@@ -257,90 +676,221 @@ export function ListBox<T>(props: ListBoxProps<T>): JSX.Element {
|
|
|
257
676
|
*/
|
|
258
677
|
export function ListBoxOption<T>(props: ListBoxOptionProps<T>): JSX.Element {
|
|
259
678
|
const [local, ariaProps] = splitProps(props, [
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
679
|
+
"class",
|
|
680
|
+
"style",
|
|
681
|
+
"slot",
|
|
682
|
+
"id",
|
|
683
|
+
"item",
|
|
684
|
+
"textValue",
|
|
685
|
+
"ref",
|
|
266
686
|
]);
|
|
267
687
|
|
|
268
|
-
// Get state from context
|
|
269
688
|
const context = useContext(ListBoxStateContext);
|
|
270
689
|
if (!context) {
|
|
271
|
-
throw new Error(
|
|
690
|
+
throw new Error("ListBoxOption must be used within a ListBox");
|
|
272
691
|
}
|
|
273
692
|
const state = context as ListState<T>;
|
|
693
|
+
const listContext = useContext(ListBoxContext) as ListBoxContextValue<T> | null;
|
|
694
|
+
const [ref, setRef] = createSignal<HTMLLIElement | null>(null);
|
|
274
695
|
|
|
275
|
-
// Create option aria props
|
|
276
696
|
const optionAria = createOption<T>(
|
|
277
697
|
{
|
|
278
698
|
key: local.id,
|
|
279
699
|
get isDisabled() {
|
|
280
|
-
return ariaProps.isDisabled;
|
|
700
|
+
return Boolean(ariaProps.isDisabled || listContext?.isDisabled());
|
|
701
|
+
},
|
|
702
|
+
get "aria-label"() {
|
|
703
|
+
return ariaProps["aria-label"] ?? local.textValue;
|
|
704
|
+
},
|
|
705
|
+
get shouldSelectOnPressUp() {
|
|
706
|
+
return ariaProps.shouldSelectOnPressUp;
|
|
281
707
|
},
|
|
282
|
-
get
|
|
283
|
-
return ariaProps
|
|
708
|
+
get shouldFocusOnHover() {
|
|
709
|
+
return ariaProps.shouldFocusOnHover;
|
|
710
|
+
},
|
|
711
|
+
get onHoverStart() {
|
|
712
|
+
return ariaProps.onHoverStart;
|
|
713
|
+
},
|
|
714
|
+
get onHoverEnd() {
|
|
715
|
+
return ariaProps.onHoverEnd;
|
|
716
|
+
},
|
|
717
|
+
get onHoverChange() {
|
|
718
|
+
return ariaProps.onHoverChange;
|
|
284
719
|
},
|
|
285
720
|
},
|
|
286
|
-
state
|
|
721
|
+
state,
|
|
287
722
|
);
|
|
288
723
|
|
|
289
|
-
// Create hover
|
|
290
|
-
const { isHovered, hoverProps } = createHover({
|
|
291
|
-
get isDisabled() {
|
|
292
|
-
return optionAria.isDisabled();
|
|
293
|
-
},
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
// Render props values
|
|
297
724
|
const renderValues = createMemo<ListBoxOptionRenderProps>(() => ({
|
|
298
725
|
isSelected: optionAria.isSelected(),
|
|
299
726
|
isFocused: optionAria.isFocused(),
|
|
300
727
|
isFocusVisible: optionAria.isFocusVisible(),
|
|
301
728
|
isPressed: optionAria.isPressed(),
|
|
302
|
-
isHovered: isHovered(),
|
|
729
|
+
isHovered: optionAria.isHovered(),
|
|
303
730
|
isDisabled: optionAria.isDisabled(),
|
|
304
731
|
}));
|
|
305
732
|
|
|
306
|
-
// Resolve render props
|
|
307
733
|
const renderProps = useRenderProps(
|
|
308
734
|
{
|
|
309
735
|
children: props.children,
|
|
310
736
|
class: local.class,
|
|
311
737
|
style: local.style,
|
|
312
|
-
defaultClassName:
|
|
738
|
+
defaultClassName: "solidaria-ListBox-option",
|
|
313
739
|
},
|
|
314
|
-
renderValues
|
|
740
|
+
renderValues,
|
|
315
741
|
);
|
|
742
|
+
const hasPrimitiveLabel = () => {
|
|
743
|
+
return typeof props.children === "string" || typeof props.children === "number";
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
const selectionIndicatorContext = createMemo<SelectionIndicatorContextValue>(() => ({
|
|
747
|
+
isSelected: optionAria.isSelected,
|
|
748
|
+
}));
|
|
749
|
+
const draggableItem = createMemo(() => {
|
|
750
|
+
if (!listContext?.dragAndDropHooks?.useDraggableItem || !listContext.dragState)
|
|
751
|
+
return undefined;
|
|
752
|
+
return listContext.dragAndDropHooks.useDraggableItem(
|
|
753
|
+
{
|
|
754
|
+
key: local.id as string | number,
|
|
755
|
+
},
|
|
756
|
+
listContext.dragState as Parameters<NonNullable<DragAndDropHooks<T>["useDraggableItem"]>>[1],
|
|
757
|
+
);
|
|
758
|
+
});
|
|
759
|
+
const droppableItem = createMemo(() => {
|
|
760
|
+
if (!listContext?.dragAndDropHooks?.useDroppableItem || !listContext.dropState)
|
|
761
|
+
return undefined;
|
|
762
|
+
return listContext.dragAndDropHooks.useDroppableItem(
|
|
763
|
+
{
|
|
764
|
+
key: local.id as string | number,
|
|
765
|
+
},
|
|
766
|
+
listContext.dropState as Parameters<NonNullable<DragAndDropHooks<T>["useDroppableItem"]>>[1],
|
|
767
|
+
() => ref(),
|
|
768
|
+
);
|
|
769
|
+
});
|
|
316
770
|
|
|
317
|
-
// Remove ref from spread props
|
|
318
771
|
const cleanOptionProps = () => {
|
|
319
|
-
const {
|
|
772
|
+
const {
|
|
773
|
+
ref: _ref1,
|
|
774
|
+
"aria-describedby": _ariaDescribedby,
|
|
775
|
+
...rest
|
|
776
|
+
} = optionAria.optionProps as Record<string, unknown>;
|
|
777
|
+
if (!hasPrimitiveLabel() && rest["aria-label"] == null) {
|
|
778
|
+
delete rest["aria-labelledby"];
|
|
779
|
+
}
|
|
320
780
|
return rest;
|
|
321
781
|
};
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
782
|
+
const domProps = () => filterDOMProps(ariaProps as Record<string, unknown>, { global: true });
|
|
783
|
+
|
|
784
|
+
return (
|
|
785
|
+
<SelectionIndicatorContext.Provider value={selectionIndicatorContext()}>
|
|
786
|
+
<li
|
|
787
|
+
ref={(el) => {
|
|
788
|
+
setRef(el);
|
|
789
|
+
assignRef(local.ref, el);
|
|
790
|
+
}}
|
|
791
|
+
{...mergeProps(
|
|
792
|
+
domProps(),
|
|
793
|
+
cleanOptionProps(),
|
|
794
|
+
(draggableItem()?.dragProps as Record<string, unknown> | undefined) ?? {},
|
|
795
|
+
(droppableItem()?.dropProps as Record<string, unknown> | undefined) ?? {},
|
|
796
|
+
)}
|
|
797
|
+
class={renderProps.class()}
|
|
798
|
+
style={renderProps.style()}
|
|
799
|
+
data-selected={optionAria.isSelected() || undefined}
|
|
800
|
+
data-focused={optionAria.isFocused() || undefined}
|
|
801
|
+
data-focus-visible={optionAria.isFocusVisible() || undefined}
|
|
802
|
+
data-pressed={optionAria.isPressed() || undefined}
|
|
803
|
+
data-hovered={optionAria.isHovered() || undefined}
|
|
804
|
+
data-disabled={optionAria.isDisabled() || undefined}
|
|
805
|
+
data-dragging={draggableItem()?.isDragging || undefined}
|
|
806
|
+
data-drop-target={droppableItem()?.isDropTarget || undefined}
|
|
807
|
+
slot={local.slot}
|
|
808
|
+
>
|
|
809
|
+
{hasPrimitiveLabel() ? (
|
|
810
|
+
<span {...optionAria.labelProps}>{renderProps.renderChildren()}</span>
|
|
811
|
+
) : (
|
|
812
|
+
renderProps.renderChildren()
|
|
813
|
+
)}
|
|
814
|
+
</li>
|
|
815
|
+
</SelectionIndicatorContext.Provider>
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Load more sentinel item for listbox collections.
|
|
821
|
+
*/
|
|
822
|
+
export function ListBoxLoadMoreItem(props: ListBoxLoadMoreItemProps): JSX.Element {
|
|
823
|
+
let sentinelRef: HTMLDivElement | undefined;
|
|
824
|
+
const [isPending, setIsPending] = createSignal(false);
|
|
825
|
+
|
|
826
|
+
const isLoading = () => !!props.isLoading || isPending();
|
|
827
|
+
|
|
828
|
+
const triggerLoadMore = async () => {
|
|
829
|
+
if (isLoading()) return;
|
|
830
|
+
setIsPending(true);
|
|
831
|
+
try {
|
|
832
|
+
await props.onLoadMore();
|
|
833
|
+
} finally {
|
|
834
|
+
setIsPending(false);
|
|
835
|
+
}
|
|
325
836
|
};
|
|
326
837
|
|
|
838
|
+
createEffect(() => {
|
|
839
|
+
if (!sentinelRef || typeof IntersectionObserver !== "function") return;
|
|
840
|
+
|
|
841
|
+
const offset = props.scrollOffset ?? 1;
|
|
842
|
+
const margin = `0px 0px ${100 * offset}% 0px`;
|
|
843
|
+
const observer = new IntersectionObserver(
|
|
844
|
+
(entries) => {
|
|
845
|
+
if (entries[0]?.isIntersecting) {
|
|
846
|
+
void triggerLoadMore();
|
|
847
|
+
}
|
|
848
|
+
},
|
|
849
|
+
{ rootMargin: margin },
|
|
850
|
+
);
|
|
851
|
+
|
|
852
|
+
observer.observe(sentinelRef);
|
|
853
|
+
return () => observer.disconnect();
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
const renderProps = useRenderProps(
|
|
857
|
+
{
|
|
858
|
+
children: props.children ?? (() => (isLoading() ? "Loading more..." : "Load more")),
|
|
859
|
+
class: props.class,
|
|
860
|
+
style: props.style,
|
|
861
|
+
defaultClassName: "solidaria-ListBox-loadMore",
|
|
862
|
+
},
|
|
863
|
+
() => ({ isLoading: isLoading() }),
|
|
864
|
+
);
|
|
865
|
+
|
|
327
866
|
return (
|
|
328
|
-
|
|
329
|
-
{
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
867
|
+
<>
|
|
868
|
+
<li style={{ position: "relative", width: 0, height: 0, overflow: "hidden" }} inert>
|
|
869
|
+
<div ref={sentinelRef} style={{ position: "absolute", height: "1px", width: "1px" }} />
|
|
870
|
+
</li>
|
|
871
|
+
<li
|
|
872
|
+
role="option"
|
|
873
|
+
aria-disabled={true}
|
|
874
|
+
tabIndex={0}
|
|
875
|
+
onFocus={() => {
|
|
876
|
+
void triggerLoadMore();
|
|
877
|
+
}}
|
|
878
|
+
class={renderProps.class()}
|
|
879
|
+
style={renderProps.style()}
|
|
880
|
+
data-loading={isLoading() || undefined}
|
|
881
|
+
>
|
|
882
|
+
{renderProps.renderChildren()}
|
|
883
|
+
</li>
|
|
884
|
+
</>
|
|
342
885
|
);
|
|
343
886
|
}
|
|
344
887
|
|
|
345
|
-
|
|
888
|
+
/**
|
|
889
|
+
* Section primitive alias for ListBox composition parity.
|
|
890
|
+
*/
|
|
891
|
+
export function ListBoxSection(props: ListBoxSectionProps): JSX.Element {
|
|
892
|
+
return <Section {...props} />;
|
|
893
|
+
}
|
|
894
|
+
|
|
346
895
|
ListBox.Option = ListBoxOption;
|
|
896
|
+
ListBox.LoadMoreItem = ListBoxLoadMoreItem;
|