@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/GridList.tsx
CHANGED
|
@@ -11,27 +11,32 @@
|
|
|
11
11
|
import {
|
|
12
12
|
type JSX,
|
|
13
13
|
createContext,
|
|
14
|
+
createEffect,
|
|
14
15
|
createMemo,
|
|
15
16
|
createSignal,
|
|
17
|
+
onCleanup,
|
|
16
18
|
splitProps,
|
|
17
19
|
useContext,
|
|
18
20
|
For,
|
|
19
|
-
|
|
21
|
+
Show,
|
|
22
|
+
} from "solid-js";
|
|
20
23
|
import {
|
|
21
24
|
createGridList,
|
|
22
25
|
createGridListItem,
|
|
23
26
|
createGridListSelectionCheckbox,
|
|
24
27
|
createFocusRing,
|
|
25
28
|
createHover,
|
|
29
|
+
mergeProps,
|
|
26
30
|
type AriaGridListProps,
|
|
27
|
-
} from
|
|
31
|
+
} from "@proyecto-viviana/solidaria";
|
|
28
32
|
import {
|
|
29
33
|
createGridState,
|
|
30
34
|
type GridState,
|
|
31
35
|
type GridCollection,
|
|
32
36
|
type GridNode,
|
|
33
37
|
type Key,
|
|
34
|
-
|
|
38
|
+
type DropTarget,
|
|
39
|
+
} from "@proyecto-viviana/solid-stately";
|
|
35
40
|
import {
|
|
36
41
|
type RenderChildren,
|
|
37
42
|
type ClassNameOrFunction,
|
|
@@ -39,11 +44,34 @@ import {
|
|
|
39
44
|
type SlotProps,
|
|
40
45
|
useRenderProps,
|
|
41
46
|
filterDOMProps,
|
|
42
|
-
} from
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
} from "./utils";
|
|
48
|
+
import { SharedElementTransition } from "./SharedElementTransition";
|
|
49
|
+
import { type DragAndDropHooks } from "./useDragAndDrop";
|
|
50
|
+
import {
|
|
51
|
+
CollectionRendererContext,
|
|
52
|
+
type CollectionRendererContextValue,
|
|
53
|
+
Section,
|
|
54
|
+
type SectionProps,
|
|
55
|
+
useCollectionRenderer,
|
|
56
|
+
} from "./Collection";
|
|
57
|
+
import { useVirtualizerContext } from "./Virtualizer";
|
|
58
|
+
import {
|
|
59
|
+
getNormalizedDropTargetKey,
|
|
60
|
+
mergePersistedKeysIntoVirtualRange,
|
|
61
|
+
useDndPersistedKeys,
|
|
62
|
+
useRenderDropIndicator,
|
|
63
|
+
} from "./DragAndDrop";
|
|
64
|
+
|
|
65
|
+
type RefLike<T> = ((el: T) => void) | { current?: T | null } | undefined;
|
|
66
|
+
|
|
67
|
+
function assignRef<T>(ref: RefLike<T>, el: T): void {
|
|
68
|
+
if (!ref) return;
|
|
69
|
+
if (typeof ref === "function") {
|
|
70
|
+
ref(el);
|
|
71
|
+
} else {
|
|
72
|
+
ref.current = el;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
47
75
|
|
|
48
76
|
export interface GridListRenderProps {
|
|
49
77
|
/** Whether the grid list has focus. */
|
|
@@ -56,7 +84,8 @@ export interface GridListRenderProps {
|
|
|
56
84
|
isEmpty: boolean;
|
|
57
85
|
}
|
|
58
86
|
|
|
59
|
-
export interface GridListProps<T extends object>
|
|
87
|
+
export interface GridListProps<T extends object>
|
|
88
|
+
extends Omit<AriaGridListProps, "children">, SlotProps {
|
|
60
89
|
/** The items to render in the grid list. */
|
|
61
90
|
items: T[];
|
|
62
91
|
/** Function to get the key from an item. */
|
|
@@ -66,23 +95,35 @@ export interface GridListProps<T extends object> extends Omit<AriaGridListProps,
|
|
|
66
95
|
/** Function to check if an item is disabled. */
|
|
67
96
|
getDisabled?: (item: T) => boolean;
|
|
68
97
|
/** The selection mode. */
|
|
69
|
-
selectionMode?:
|
|
98
|
+
selectionMode?: "none" | "single" | "multiple";
|
|
99
|
+
/** How selection should behave when pressing an item. */
|
|
100
|
+
selectionBehavior?: "replace" | "toggle";
|
|
70
101
|
/** Keys of disabled items. */
|
|
71
102
|
disabledKeys?: Iterable<Key>;
|
|
72
103
|
/** Currently selected keys (controlled). */
|
|
73
|
-
selectedKeys?:
|
|
104
|
+
selectedKeys?: "all" | Iterable<Key>;
|
|
74
105
|
/** Default selected keys (uncontrolled). */
|
|
75
|
-
defaultSelectedKeys?:
|
|
106
|
+
defaultSelectedKeys?: "all" | Iterable<Key>;
|
|
76
107
|
/** Handler called when selection changes. */
|
|
77
|
-
onSelectionChange?: (keys:
|
|
108
|
+
onSelectionChange?: (keys: "all" | Set<Key>) => void;
|
|
78
109
|
/** The children of the component. A function may be provided to render each item. */
|
|
79
110
|
children: (item: T) => JSX.Element;
|
|
80
111
|
/** The CSS className for the element. */
|
|
81
112
|
class?: ClassNameOrFunction<GridListRenderProps>;
|
|
82
113
|
/** The inline style for the element. */
|
|
83
114
|
style?: StyleOrFunction<GridListRenderProps>;
|
|
115
|
+
/** Ref for the grid list root element. */
|
|
116
|
+
ref?: RefLike<HTMLDivElement>;
|
|
84
117
|
/** A function to render when the grid list is empty. */
|
|
85
118
|
renderEmptyState?: () => JSX.Element;
|
|
119
|
+
/** Whether there are more items to load. */
|
|
120
|
+
hasMore?: boolean;
|
|
121
|
+
/** Whether additional items are currently loading. */
|
|
122
|
+
isLoading?: boolean;
|
|
123
|
+
/** Called when the load more sentinel becomes visible. */
|
|
124
|
+
onLoadMore?: () => void | Promise<void>;
|
|
125
|
+
/** Drag and drop hooks from `useDragAndDrop`. */
|
|
126
|
+
dragAndDropHooks?: DragAndDropHooks<T>;
|
|
86
127
|
}
|
|
87
128
|
|
|
88
129
|
export interface GridListItemRenderProps {
|
|
@@ -98,9 +139,16 @@ export interface GridListItemRenderProps {
|
|
|
98
139
|
isHovered: boolean;
|
|
99
140
|
/** Whether the item is disabled. */
|
|
100
141
|
isDisabled: boolean;
|
|
142
|
+
/** The grid list selection mode. */
|
|
143
|
+
selectionMode: "none" | "single" | "multiple";
|
|
144
|
+
/** How selection behaves when pressing an item. */
|
|
145
|
+
selectionBehavior: "replace" | "toggle";
|
|
101
146
|
}
|
|
102
147
|
|
|
103
|
-
export interface GridListItemProps<T extends object>
|
|
148
|
+
export interface GridListItemProps<T extends object>
|
|
149
|
+
extends
|
|
150
|
+
SlotProps,
|
|
151
|
+
Omit<JSX.HTMLAttributes<HTMLDivElement>, "class" | "style" | "children" | "id" | "ref"> {
|
|
104
152
|
/** The unique key for the item. */
|
|
105
153
|
id: Key;
|
|
106
154
|
/** The item value. */
|
|
@@ -115,35 +163,53 @@ export interface GridListItemProps<T extends object> extends SlotProps {
|
|
|
115
163
|
textValue?: string;
|
|
116
164
|
/** Handler called when the item is activated. */
|
|
117
165
|
onAction?: () => void;
|
|
166
|
+
/** Ref for the rendered row element. */
|
|
167
|
+
ref?: RefLike<HTMLDivElement>;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface GridListLoadMoreItemProps extends SlotProps {
|
|
171
|
+
onLoadMore: () => void | Promise<void>;
|
|
172
|
+
isLoading?: boolean;
|
|
173
|
+
/** Scroll offset multiplier for early loading trigger (default: 1 = 100% of viewport height). */
|
|
174
|
+
scrollOffset?: number;
|
|
175
|
+
children?: JSX.Element;
|
|
176
|
+
class?: ClassNameOrFunction<{ isLoading: boolean }>;
|
|
177
|
+
style?: StyleOrFunction<{ isLoading: boolean }>;
|
|
118
178
|
}
|
|
119
179
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
180
|
+
export interface GridListSectionProps extends SectionProps {}
|
|
181
|
+
export interface GridListHeaderProps extends SlotProps {
|
|
182
|
+
children?: JSX.Element;
|
|
183
|
+
class?: string;
|
|
184
|
+
style?: JSX.CSSProperties;
|
|
185
|
+
}
|
|
123
186
|
|
|
124
187
|
interface GridListContextValue<T extends object> {
|
|
125
188
|
state: GridState<T, GridCollection<T>>;
|
|
126
189
|
collection: GridCollection<T>;
|
|
127
190
|
isDisabled: boolean;
|
|
191
|
+
selectionBehavior: "replace" | "toggle";
|
|
192
|
+
dragAndDropHooks?: DragAndDropHooks<T>;
|
|
193
|
+
dragState?: unknown;
|
|
194
|
+
dropState?: unknown;
|
|
128
195
|
}
|
|
129
196
|
|
|
130
197
|
export const GridListContext = createContext<GridListContextValue<object> | null>(null);
|
|
131
|
-
export const GridListStateContext = createContext<GridState<object, GridCollection<object>> | null>(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// ============================================
|
|
198
|
+
export const GridListStateContext = createContext<GridState<object, GridCollection<object>> | null>(
|
|
199
|
+
null,
|
|
200
|
+
);
|
|
201
|
+
export const GridListHeaderContext = createContext<null>(null);
|
|
136
202
|
|
|
137
203
|
function buildGridCollection<T extends object>(
|
|
138
204
|
items: T[],
|
|
139
205
|
getKey?: (item: T) => Key,
|
|
140
206
|
getTextValue?: (item: T) => string,
|
|
141
|
-
getDisabled?: (item: T) => boolean
|
|
207
|
+
getDisabled?: (item: T) => boolean,
|
|
142
208
|
): GridCollection<T> {
|
|
143
209
|
const nodes: GridNode<T>[] = items.map((item, index) => {
|
|
144
210
|
const key = getKey?.(item) ?? index;
|
|
145
211
|
return {
|
|
146
|
-
type:
|
|
212
|
+
type: "item" as const,
|
|
147
213
|
key,
|
|
148
214
|
value: item,
|
|
149
215
|
textValue: getTextValue?.(item) ?? String(key),
|
|
@@ -200,7 +266,7 @@ function buildGridCollection<T extends object>(
|
|
|
200
266
|
return [];
|
|
201
267
|
},
|
|
202
268
|
getTextValue(key: Key) {
|
|
203
|
-
return keyMap.get(key)?.textValue ??
|
|
269
|
+
return keyMap.get(key)?.textValue ?? "";
|
|
204
270
|
},
|
|
205
271
|
getCell(_rowKey: Key, _columnKey: Key) {
|
|
206
272
|
return null;
|
|
@@ -211,10 +277,6 @@ function buildGridCollection<T extends object>(
|
|
|
211
277
|
};
|
|
212
278
|
}
|
|
213
279
|
|
|
214
|
-
// ============================================
|
|
215
|
-
// COMPONENTS
|
|
216
|
-
// ============================================
|
|
217
|
-
|
|
218
280
|
/**
|
|
219
281
|
* A grid list displays a list of interactive items, with support for
|
|
220
282
|
* keyboard navigation, single or multiple selection, and row actions.
|
|
@@ -222,45 +284,52 @@ function buildGridCollection<T extends object>(
|
|
|
222
284
|
export function GridList<T extends object>(props: GridListProps<T>): JSX.Element {
|
|
223
285
|
const [local, stateProps, ariaProps] = splitProps(
|
|
224
286
|
props,
|
|
225
|
-
['children', 'class', 'style', 'slot', 'renderEmptyState'],
|
|
226
287
|
[
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
288
|
+
"children",
|
|
289
|
+
"class",
|
|
290
|
+
"style",
|
|
291
|
+
"ref",
|
|
292
|
+
"slot",
|
|
293
|
+
"renderEmptyState",
|
|
294
|
+
"hasMore",
|
|
295
|
+
"isLoading",
|
|
296
|
+
"onLoadMore",
|
|
297
|
+
"dragAndDropHooks",
|
|
298
|
+
],
|
|
299
|
+
[
|
|
300
|
+
"items",
|
|
301
|
+
"getKey",
|
|
302
|
+
"getTextValue",
|
|
303
|
+
"getDisabled",
|
|
304
|
+
"disabledKeys",
|
|
305
|
+
"selectionMode",
|
|
306
|
+
"selectedKeys",
|
|
307
|
+
"defaultSelectedKeys",
|
|
308
|
+
"onSelectionChange",
|
|
309
|
+
"selectionBehavior",
|
|
310
|
+
],
|
|
237
311
|
);
|
|
238
312
|
|
|
239
|
-
|
|
240
|
-
const [ref, setRef] = createSignal<HTMLUListElement | null>(null);
|
|
313
|
+
const [ref, setRef] = createSignal<HTMLDivElement | null>(null);
|
|
241
314
|
|
|
242
|
-
// Build collection
|
|
243
315
|
const collection = createMemo(() =>
|
|
244
316
|
buildGridCollection(
|
|
245
317
|
stateProps.items,
|
|
246
318
|
stateProps.getKey,
|
|
247
319
|
stateProps.getTextValue,
|
|
248
|
-
stateProps.getDisabled
|
|
249
|
-
)
|
|
320
|
+
stateProps.getDisabled,
|
|
321
|
+
),
|
|
250
322
|
);
|
|
251
323
|
|
|
252
|
-
// Get disabled keys from items + explicit disabledKeys
|
|
253
324
|
const allDisabledKeys = createMemo(() => {
|
|
254
325
|
const keys = new Set<Key>();
|
|
255
326
|
|
|
256
|
-
// Add explicitly disabled keys
|
|
257
327
|
if (stateProps.disabledKeys) {
|
|
258
328
|
for (const key of stateProps.disabledKeys) {
|
|
259
329
|
keys.add(key);
|
|
260
330
|
}
|
|
261
331
|
}
|
|
262
332
|
|
|
263
|
-
// Add keys from items marked as disabled
|
|
264
333
|
for (const node of collection().rows) {
|
|
265
334
|
if (node.isDisabled) {
|
|
266
335
|
keys.add(node.key);
|
|
@@ -270,35 +339,33 @@ export function GridList<T extends object>(props: GridListProps<T>): JSX.Element
|
|
|
270
339
|
return keys;
|
|
271
340
|
});
|
|
272
341
|
|
|
273
|
-
// Create grid state
|
|
274
342
|
const state = createGridState<T, GridCollection<T>>(() => ({
|
|
275
343
|
collection: collection(),
|
|
276
344
|
disabledKeys: allDisabledKeys(),
|
|
277
345
|
selectionMode: stateProps.selectionMode,
|
|
346
|
+
selectionBehavior: stateProps.selectionBehavior,
|
|
278
347
|
selectedKeys: stateProps.selectedKeys,
|
|
279
348
|
defaultSelectedKeys: stateProps.defaultSelectedKeys,
|
|
280
349
|
onSelectionChange: stateProps.onSelectionChange,
|
|
281
350
|
}));
|
|
282
351
|
|
|
283
|
-
// Create grid list aria props
|
|
284
352
|
const { gridProps } = createGridList<T, GridCollection<T>>(
|
|
285
353
|
() => ({
|
|
286
354
|
id: ariaProps.id,
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
355
|
+
"aria-label": ariaProps["aria-label"],
|
|
356
|
+
"aria-labelledby": ariaProps["aria-labelledby"],
|
|
357
|
+
"aria-describedby": ariaProps["aria-describedby"],
|
|
290
358
|
isVirtualized: ariaProps.isVirtualized,
|
|
291
359
|
onAction: ariaProps.onAction,
|
|
292
360
|
isDisabled: ariaProps.isDisabled,
|
|
361
|
+
selectionBehavior: stateProps.selectionBehavior,
|
|
293
362
|
}),
|
|
294
363
|
() => state,
|
|
295
|
-
ref
|
|
364
|
+
ref,
|
|
296
365
|
);
|
|
297
366
|
|
|
298
|
-
// Create focus ring
|
|
299
367
|
const { isFocused, isFocusVisible, focusProps } = createFocusRing();
|
|
300
368
|
|
|
301
|
-
// Render props values
|
|
302
369
|
const renderValues = createMemo<GridListRenderProps>(() => ({
|
|
303
370
|
isFocused: state.isFocused || isFocused(),
|
|
304
371
|
isFocusVisible: isFocusVisible(),
|
|
@@ -306,23 +373,20 @@ export function GridList<T extends object>(props: GridListProps<T>): JSX.Element
|
|
|
306
373
|
isEmpty: stateProps.items.length === 0,
|
|
307
374
|
}));
|
|
308
375
|
|
|
309
|
-
// Resolve render props
|
|
310
376
|
const renderProps = useRenderProps(
|
|
311
377
|
{
|
|
312
378
|
class: local.class,
|
|
313
379
|
style: local.style,
|
|
314
|
-
defaultClassName:
|
|
380
|
+
defaultClassName: "solidaria-GridList",
|
|
315
381
|
},
|
|
316
|
-
renderValues
|
|
382
|
+
renderValues,
|
|
317
383
|
);
|
|
318
384
|
|
|
319
|
-
// Filter DOM props
|
|
320
385
|
const domProps = createMemo(() => {
|
|
321
386
|
const filtered = filterDOMProps(ariaProps as Record<string, unknown>, { global: true });
|
|
322
387
|
return filtered;
|
|
323
388
|
});
|
|
324
389
|
|
|
325
|
-
// Remove ref from spread props
|
|
326
390
|
const cleanGridProps = () => {
|
|
327
391
|
const { ref: _ref1, ...rest } = gridProps as Record<string, unknown>;
|
|
328
392
|
return rest;
|
|
@@ -333,34 +397,264 @@ export function GridList<T extends object>(props: GridListProps<T>): JSX.Element
|
|
|
333
397
|
};
|
|
334
398
|
|
|
335
399
|
const isEmpty = () => stateProps.items.length === 0;
|
|
400
|
+
const virtualizer = useVirtualizerContext();
|
|
401
|
+
const parentCollectionRenderer = useCollectionRenderer<T>();
|
|
402
|
+
const getItemNodes = createMemo(() =>
|
|
403
|
+
Array.from(state.collection).filter((node) => node.type === "item"),
|
|
404
|
+
);
|
|
405
|
+
const getDropTargetByIndex = (
|
|
406
|
+
index: number,
|
|
407
|
+
position: "before" | "after" | "on",
|
|
408
|
+
): DropTarget | null => {
|
|
409
|
+
const node = getItemNodes()[index];
|
|
410
|
+
if (!node) return null;
|
|
411
|
+
return { type: "item", key: node.key, dropPosition: position };
|
|
412
|
+
};
|
|
413
|
+
const hasDroppableDnd = createMemo(() => {
|
|
414
|
+
const hooks = local.dragAndDropHooks;
|
|
415
|
+
return Boolean(
|
|
416
|
+
hooks?.useDroppableCollectionState &&
|
|
417
|
+
hooks.useDroppableCollection &&
|
|
418
|
+
(hooks.dropTargetDelegate ||
|
|
419
|
+
parentCollectionRenderer?.dropTargetDelegate ||
|
|
420
|
+
hooks.ListDropTargetDelegate),
|
|
421
|
+
);
|
|
422
|
+
});
|
|
423
|
+
const hasDraggableDnd = createMemo(() => {
|
|
424
|
+
const hooks = local.dragAndDropHooks;
|
|
425
|
+
return Boolean(hooks?.useDraggableCollectionState && hooks.useDraggableCollection);
|
|
426
|
+
});
|
|
427
|
+
const dragState = createMemo(() => {
|
|
428
|
+
if (!hasDraggableDnd()) return undefined;
|
|
429
|
+
return local.dragAndDropHooks?.useDraggableCollectionState?.({
|
|
430
|
+
items: stateProps.items,
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
const dropState = createMemo(() => {
|
|
434
|
+
if (!hasDroppableDnd()) return undefined;
|
|
435
|
+
return local.dragAndDropHooks?.useDroppableCollectionState?.({});
|
|
436
|
+
});
|
|
437
|
+
createEffect(() => {
|
|
438
|
+
if (!hasDraggableDnd()) return;
|
|
439
|
+
const hooks = local.dragAndDropHooks;
|
|
440
|
+
const activeDragState = dragState();
|
|
441
|
+
if (!hooks?.useDraggableCollection || !activeDragState) return;
|
|
442
|
+
hooks.useDraggableCollection({}, activeDragState, () => ref());
|
|
443
|
+
});
|
|
444
|
+
const droppableCollection = createMemo(() => {
|
|
445
|
+
if (!hasDroppableDnd()) return undefined;
|
|
446
|
+
const hooks = local.dragAndDropHooks;
|
|
447
|
+
const activeDropState = dropState();
|
|
448
|
+
if (!hooks?.useDroppableCollection || !activeDropState) return undefined;
|
|
449
|
+
const resolveDirection = (): "ltr" | "rtl" => {
|
|
450
|
+
const el = ref();
|
|
451
|
+
if (el && typeof window !== "undefined" && typeof window.getComputedStyle === "function") {
|
|
452
|
+
const dir = window.getComputedStyle(el).direction;
|
|
453
|
+
if (dir === "rtl") return "rtl";
|
|
454
|
+
}
|
|
455
|
+
return typeof document !== "undefined" && document.dir === "rtl" ? "rtl" : "ltr";
|
|
456
|
+
};
|
|
457
|
+
const dropTargetDelegate =
|
|
458
|
+
hooks.dropTargetDelegate ??
|
|
459
|
+
parentCollectionRenderer?.dropTargetDelegate ??
|
|
460
|
+
(hooks.ListDropTargetDelegate
|
|
461
|
+
? new hooks.ListDropTargetDelegate(
|
|
462
|
+
() => state.collection,
|
|
463
|
+
() => ref(),
|
|
464
|
+
{ layout: "grid", orientation: "vertical", direction: resolveDirection() },
|
|
465
|
+
)
|
|
466
|
+
: undefined);
|
|
467
|
+
if (!dropTargetDelegate) return undefined;
|
|
468
|
+
return hooks.useDroppableCollection(
|
|
469
|
+
{
|
|
470
|
+
dropTargetDelegate,
|
|
471
|
+
keyboardDelegate: {
|
|
472
|
+
getFirstKey: () => state.collection.getFirstKey?.() ?? null,
|
|
473
|
+
getLastKey: () => state.collection.getLastKey?.() ?? null,
|
|
474
|
+
getKeyBelow: (key) => state.collection.getKeyAfter?.(key) ?? null,
|
|
475
|
+
getKeyAbove: (key) => state.collection.getKeyBefore?.(key) ?? null,
|
|
476
|
+
getKeyLeftOf: (key) =>
|
|
477
|
+
resolveDirection() === "rtl"
|
|
478
|
+
? (state.collection.getKeyAfter?.(key) ?? null)
|
|
479
|
+
: (state.collection.getKeyBefore?.(key) ?? null),
|
|
480
|
+
getKeyRightOf: (key) =>
|
|
481
|
+
resolveDirection() === "rtl"
|
|
482
|
+
? (state.collection.getKeyBefore?.(key) ?? null)
|
|
483
|
+
: (state.collection.getKeyAfter?.(key) ?? null),
|
|
484
|
+
getKeyPageBelow: (key) => state.collection.getKeyAfter?.(key) ?? null,
|
|
485
|
+
getKeyPageAbove: (key) => state.collection.getKeyBefore?.(key) ?? null,
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
activeDropState,
|
|
489
|
+
() => ref(),
|
|
490
|
+
);
|
|
491
|
+
});
|
|
492
|
+
const isRootDropTarget = createMemo(() => {
|
|
493
|
+
return Boolean(dropState()?.target?.type === "root");
|
|
494
|
+
});
|
|
495
|
+
const dndRenderDropIndicator = createMemo(() =>
|
|
496
|
+
useRenderDropIndicator(local.dragAndDropHooks, dropState()),
|
|
497
|
+
);
|
|
498
|
+
const dndDropIndicator = (index: number, position: "before" | "after" | "on") => {
|
|
499
|
+
const target = getDropTargetByIndex(index, position);
|
|
500
|
+
if (!target || target.type !== "item") return undefined;
|
|
501
|
+
return dndRenderDropIndicator()?.(target);
|
|
502
|
+
};
|
|
503
|
+
const persistedKeys = useDndPersistedKeys(
|
|
504
|
+
{ focusedKey: () => state.focusedKey },
|
|
505
|
+
local.dragAndDropHooks,
|
|
506
|
+
dropState(),
|
|
507
|
+
state.collection,
|
|
508
|
+
);
|
|
509
|
+
const virtualRange = createMemo(() => {
|
|
510
|
+
if (!virtualizer || !parentCollectionRenderer?.isVirtualized) return null;
|
|
511
|
+
const baseRange = virtualizer.getVisibleRange(stateProps.items.length);
|
|
512
|
+
const itemNodes = getItemNodes();
|
|
513
|
+
const persistedIndexes = Array.from(persistedKeys())
|
|
514
|
+
.map((key) => itemNodes.findIndex((node) => node.key === key))
|
|
515
|
+
.filter((index) => index >= 0);
|
|
516
|
+
const dropTarget = dropState()?.target;
|
|
517
|
+
const normalizedDropKey = getNormalizedDropTargetKey(dropTarget, state.collection);
|
|
518
|
+
const focusedKey = state.focusedKey;
|
|
519
|
+
const focusedIndex =
|
|
520
|
+
focusedKey != null ? itemNodes.findIndex((node) => node.key === focusedKey) : -1;
|
|
521
|
+
const forceIncludeIndexes = [
|
|
522
|
+
dropTarget?.type === "item" ? itemNodes.findIndex((node) => node.key === dropTarget.key) : -1,
|
|
523
|
+
normalizedDropKey != null
|
|
524
|
+
? itemNodes.findIndex((node) => node.key === normalizedDropKey)
|
|
525
|
+
: -1,
|
|
526
|
+
dropTarget?.type === "item" ? -1 : focusedIndex,
|
|
527
|
+
].filter((index) => index >= 0);
|
|
528
|
+
return mergePersistedKeysIntoVirtualRange(
|
|
529
|
+
baseRange,
|
|
530
|
+
persistedIndexes,
|
|
531
|
+
stateProps.items.length,
|
|
532
|
+
virtualizer,
|
|
533
|
+
80,
|
|
534
|
+
{
|
|
535
|
+
forceIncludeIndexes,
|
|
536
|
+
forceIncludeMaxSpan: 320,
|
|
537
|
+
},
|
|
538
|
+
);
|
|
539
|
+
});
|
|
540
|
+
createEffect(() => {
|
|
541
|
+
if (!virtualizer || !parentCollectionRenderer?.isVirtualized) return;
|
|
542
|
+
virtualizer.setDropTargetItemCountResolver(() => state.collection.size);
|
|
543
|
+
virtualizer.setDropTargetIndexResolver((key) => {
|
|
544
|
+
const entries = Array.from(state.collection);
|
|
545
|
+
const index = entries.findIndex((node) => node.key === key);
|
|
546
|
+
return index >= 0 ? index : null;
|
|
547
|
+
});
|
|
548
|
+
virtualizer.setDropTargetResolver((target) => {
|
|
549
|
+
const node = Array.from(state.collection)[target.index];
|
|
550
|
+
if (!node) return target;
|
|
551
|
+
return {
|
|
552
|
+
...target,
|
|
553
|
+
key: typeof node.key === "string" || typeof node.key === "number" ? node.key : undefined,
|
|
554
|
+
};
|
|
555
|
+
});
|
|
556
|
+
onCleanup(() => {
|
|
557
|
+
virtualizer.setDropTargetIndexResolver(undefined);
|
|
558
|
+
virtualizer.setDropTargetItemCountResolver(undefined);
|
|
559
|
+
virtualizer.setDropTargetResolver(undefined);
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
const visibleItems = createMemo(() => {
|
|
563
|
+
const range = virtualRange();
|
|
564
|
+
if (!range) return stateProps.items;
|
|
565
|
+
return stateProps.items.slice(range.start, range.end);
|
|
566
|
+
});
|
|
336
567
|
|
|
337
568
|
const contextValue = createMemo<GridListContextValue<T>>(() => ({
|
|
338
569
|
state,
|
|
339
570
|
collection: collection(),
|
|
340
571
|
isDisabled: ariaProps.isDisabled ?? false,
|
|
572
|
+
selectionBehavior: stateProps.selectionBehavior ?? "replace",
|
|
573
|
+
dragAndDropHooks: local.dragAndDropHooks,
|
|
574
|
+
dragState: dragState(),
|
|
575
|
+
dropState: dropState(),
|
|
576
|
+
}));
|
|
577
|
+
const collectionRenderer = createMemo<CollectionRendererContextValue<unknown>>(() => ({
|
|
578
|
+
...parentCollectionRenderer,
|
|
579
|
+
renderItem: (item) => props.children(item as T),
|
|
580
|
+
renderDropIndicator: (index: number, position: "before" | "after" | "on") =>
|
|
581
|
+
dndDropIndicator(index, position) ??
|
|
582
|
+
parentCollectionRenderer?.renderDropIndicator?.(index, position),
|
|
341
583
|
}));
|
|
342
584
|
|
|
343
585
|
return (
|
|
344
|
-
<GridListContext.Provider value={contextValue() as GridListContextValue<object>}>
|
|
345
|
-
<GridListStateContext.Provider
|
|
346
|
-
<
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
586
|
+
<GridListContext.Provider value={contextValue() as unknown as GridListContextValue<object>}>
|
|
587
|
+
<GridListStateContext.Provider
|
|
588
|
+
value={state as unknown as GridState<object, GridCollection<object>>}
|
|
589
|
+
>
|
|
590
|
+
<CollectionRendererContext.Provider value={collectionRenderer()}>
|
|
591
|
+
<div
|
|
592
|
+
ref={(element) => {
|
|
593
|
+
setRef(element);
|
|
594
|
+
assignRef(local.ref, element);
|
|
595
|
+
}}
|
|
596
|
+
{...mergeProps(
|
|
597
|
+
domProps(),
|
|
598
|
+
cleanGridProps(),
|
|
599
|
+
cleanFocusProps(),
|
|
600
|
+
(droppableCollection()?.collectionProps as Record<string, unknown> | undefined) ?? {},
|
|
601
|
+
)}
|
|
602
|
+
class={renderProps.class()}
|
|
603
|
+
style={renderProps.style()}
|
|
604
|
+
data-focused={state.isFocused || undefined}
|
|
605
|
+
data-focus-visible={isFocusVisible() || undefined}
|
|
606
|
+
data-disabled={ariaProps.isDisabled || undefined}
|
|
607
|
+
data-empty={isEmpty() || undefined}
|
|
608
|
+
data-drop-target={isRootDropTarget() || undefined}
|
|
609
|
+
>
|
|
610
|
+
<SharedElementTransition>
|
|
611
|
+
{isEmpty() && local.renderEmptyState ? (
|
|
612
|
+
local.renderEmptyState()
|
|
613
|
+
) : (
|
|
614
|
+
<>
|
|
615
|
+
{virtualRange()?.offsetTop ? (
|
|
616
|
+
<div
|
|
617
|
+
role="presentation"
|
|
618
|
+
aria-hidden="true"
|
|
619
|
+
style={{ height: `${virtualRange()!.offsetTop}px` }}
|
|
620
|
+
data-virtualizer-spacer="top"
|
|
621
|
+
/>
|
|
622
|
+
) : null}
|
|
623
|
+
<For each={visibleItems()}>
|
|
624
|
+
{(item, index) => {
|
|
625
|
+
const itemIndex = () => (virtualRange()?.start ?? 0) + index();
|
|
626
|
+
const beforeIndicator = () =>
|
|
627
|
+
collectionRenderer().renderDropIndicator?.(itemIndex(), "before");
|
|
628
|
+
const onIndicator = () =>
|
|
629
|
+
collectionRenderer().renderDropIndicator?.(itemIndex(), "on");
|
|
630
|
+
const afterIndicator = () =>
|
|
631
|
+
collectionRenderer().renderDropIndicator?.(itemIndex(), "after");
|
|
632
|
+
return (
|
|
633
|
+
<>
|
|
634
|
+
{beforeIndicator()}
|
|
635
|
+
{onIndicator()}
|
|
636
|
+
{props.children(item)}
|
|
637
|
+
{afterIndicator()}
|
|
638
|
+
</>
|
|
639
|
+
);
|
|
640
|
+
}}
|
|
641
|
+
</For>
|
|
642
|
+
{virtualRange()?.offsetBottom ? (
|
|
643
|
+
<div
|
|
644
|
+
role="presentation"
|
|
645
|
+
aria-hidden="true"
|
|
646
|
+
style={{ height: `${virtualRange()!.offsetBottom}px` }}
|
|
647
|
+
data-virtualizer-spacer="bottom"
|
|
648
|
+
/>
|
|
649
|
+
) : null}
|
|
650
|
+
</>
|
|
651
|
+
)}
|
|
652
|
+
</SharedElementTransition>
|
|
653
|
+
{local.hasMore && local.onLoadMore && (
|
|
654
|
+
<GridListLoadMoreItem onLoadMore={local.onLoadMore} isLoading={local.isLoading} />
|
|
655
|
+
)}
|
|
656
|
+
</div>
|
|
657
|
+
</CollectionRendererContext.Provider>
|
|
364
658
|
</GridListStateContext.Provider>
|
|
365
659
|
</GridListContext.Provider>
|
|
366
660
|
);
|
|
@@ -370,33 +664,32 @@ export function GridList<T extends object>(props: GridListProps<T>): JSX.Element
|
|
|
370
664
|
* An item in a grid list.
|
|
371
665
|
*/
|
|
372
666
|
export function GridListItem<T extends object>(props: GridListItemProps<T>): JSX.Element {
|
|
373
|
-
const [local] = splitProps(props, [
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
667
|
+
const [local, domProps] = splitProps(props, [
|
|
668
|
+
"class",
|
|
669
|
+
"style",
|
|
670
|
+
"slot",
|
|
671
|
+
"id",
|
|
672
|
+
"item",
|
|
673
|
+
"textValue",
|
|
674
|
+
"onAction",
|
|
675
|
+
"children",
|
|
676
|
+
"ref",
|
|
381
677
|
]);
|
|
382
678
|
|
|
383
|
-
// Get state from context
|
|
384
679
|
const context = useContext(GridListStateContext);
|
|
385
680
|
if (!context) {
|
|
386
|
-
throw new Error(
|
|
681
|
+
throw new Error("GridListItem must be used within a GridList");
|
|
387
682
|
}
|
|
388
683
|
const state = context as GridState<T, GridCollection<T>>;
|
|
684
|
+
const listContext = useContext(GridListContext) as GridListContextValue<T> | null;
|
|
389
685
|
|
|
390
|
-
|
|
391
|
-
const [ref, setRef] = createSignal<HTMLLIElement | null>(null);
|
|
686
|
+
const [ref, setRef] = createSignal<HTMLDivElement | null>(null);
|
|
392
687
|
|
|
393
|
-
// Find or create the item node
|
|
394
688
|
const itemNode = createMemo(() => {
|
|
395
689
|
const node = state.collection.getItem(local.id);
|
|
396
690
|
if (!node) {
|
|
397
|
-
// Create a simple node for the item
|
|
398
691
|
return {
|
|
399
|
-
type:
|
|
692
|
+
type: "item" as const,
|
|
400
693
|
key: local.id,
|
|
401
694
|
value: local.item ?? null,
|
|
402
695
|
textValue: local.textValue ?? String(local.id),
|
|
@@ -409,53 +702,73 @@ export function GridListItem<T extends object>(props: GridListItemProps<T>): JSX
|
|
|
409
702
|
return node as GridNode<T>;
|
|
410
703
|
});
|
|
411
704
|
|
|
412
|
-
|
|
413
|
-
const { rowProps, gridCellProps, isSelected, isDisabled, isPressed } = createGridListItem<T, GridCollection<T>>(
|
|
705
|
+
const itemAria = createGridListItem<T, GridCollection<T>>(
|
|
414
706
|
() => ({
|
|
415
707
|
node: itemNode(),
|
|
416
708
|
onAction: local.onAction,
|
|
709
|
+
selectionBehavior: listContext?.selectionBehavior ?? "replace",
|
|
417
710
|
}),
|
|
418
711
|
() => state,
|
|
419
|
-
ref
|
|
712
|
+
ref,
|
|
420
713
|
);
|
|
714
|
+
const isSelected = () => itemAria.isSelected;
|
|
715
|
+
const isDisabled = () => itemAria.isDisabled;
|
|
716
|
+
const isPressed = () => itemAria.isPressed;
|
|
421
717
|
|
|
422
|
-
// Create hover
|
|
423
718
|
const { isHovered, hoverProps } = createHover({
|
|
424
719
|
get isDisabled() {
|
|
425
|
-
return isDisabled;
|
|
720
|
+
return isDisabled();
|
|
426
721
|
},
|
|
427
722
|
});
|
|
428
723
|
|
|
429
|
-
// Create focus ring
|
|
430
724
|
const { isFocusVisible, focusProps } = createFocusRing();
|
|
431
725
|
|
|
432
|
-
// Check if focused
|
|
433
726
|
const isFocused = createMemo(() => state.focusedKey === local.id);
|
|
727
|
+
const draggableItem = createMemo(() => {
|
|
728
|
+
if (!listContext?.dragAndDropHooks?.useDraggableItem || !listContext.dragState)
|
|
729
|
+
return undefined;
|
|
730
|
+
return listContext.dragAndDropHooks.useDraggableItem(
|
|
731
|
+
{
|
|
732
|
+
key: local.id as string | number,
|
|
733
|
+
},
|
|
734
|
+
listContext.dragState as Parameters<NonNullable<DragAndDropHooks<T>["useDraggableItem"]>>[1],
|
|
735
|
+
);
|
|
736
|
+
});
|
|
737
|
+
const droppableItem = createMemo(() => {
|
|
738
|
+
if (!listContext?.dragAndDropHooks?.useDroppableItem || !listContext.dropState)
|
|
739
|
+
return undefined;
|
|
740
|
+
return listContext.dragAndDropHooks.useDroppableItem(
|
|
741
|
+
{
|
|
742
|
+
key: local.id as string | number,
|
|
743
|
+
},
|
|
744
|
+
listContext.dropState as Parameters<NonNullable<DragAndDropHooks<T>["useDroppableItem"]>>[1],
|
|
745
|
+
() => ref(),
|
|
746
|
+
);
|
|
747
|
+
});
|
|
434
748
|
|
|
435
|
-
// Render props values
|
|
436
749
|
const renderValues = createMemo<GridListItemRenderProps>(() => ({
|
|
437
|
-
isSelected,
|
|
750
|
+
isSelected: isSelected(),
|
|
438
751
|
isFocused: isFocused(),
|
|
439
752
|
isFocusVisible: isFocusVisible() && isFocused(),
|
|
440
|
-
isPressed,
|
|
753
|
+
isPressed: isPressed(),
|
|
441
754
|
isHovered: isHovered(),
|
|
442
|
-
isDisabled,
|
|
755
|
+
isDisabled: isDisabled(),
|
|
756
|
+
selectionMode: state.selectionMode,
|
|
757
|
+
selectionBehavior: listContext?.selectionBehavior ?? "replace",
|
|
443
758
|
}));
|
|
444
759
|
|
|
445
|
-
// Resolve render props
|
|
446
760
|
const renderProps = useRenderProps(
|
|
447
761
|
{
|
|
448
762
|
children: props.children,
|
|
449
763
|
class: local.class,
|
|
450
764
|
style: local.style,
|
|
451
|
-
defaultClassName:
|
|
765
|
+
defaultClassName: "solidaria-GridList-item",
|
|
452
766
|
},
|
|
453
|
-
renderValues
|
|
767
|
+
renderValues,
|
|
454
768
|
);
|
|
455
769
|
|
|
456
|
-
// Remove ref from spread props
|
|
457
770
|
const cleanRowProps = () => {
|
|
458
|
-
const { ref: _ref1, ...rest } = rowProps as Record<string, unknown>;
|
|
771
|
+
const { ref: _ref1, ...rest } = itemAria.rowProps as Record<string, unknown>;
|
|
459
772
|
return rest;
|
|
460
773
|
};
|
|
461
774
|
const cleanHoverProps = () => {
|
|
@@ -468,44 +781,145 @@ export function GridListItem<T extends object>(props: GridListItemProps<T>): JSX
|
|
|
468
781
|
};
|
|
469
782
|
|
|
470
783
|
return (
|
|
471
|
-
<
|
|
472
|
-
ref={
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
784
|
+
<div
|
|
785
|
+
ref={(element) => {
|
|
786
|
+
setRef(element);
|
|
787
|
+
assignRef(local.ref, element);
|
|
788
|
+
}}
|
|
789
|
+
{...domProps}
|
|
790
|
+
{...mergeProps(
|
|
791
|
+
cleanRowProps(),
|
|
792
|
+
cleanHoverProps(),
|
|
793
|
+
cleanFocusProps(),
|
|
794
|
+
(draggableItem()?.dragProps as Record<string, unknown> | undefined) ?? {},
|
|
795
|
+
(droppableItem()?.dropProps as Record<string, unknown> | undefined) ?? {},
|
|
796
|
+
)}
|
|
476
797
|
class={renderProps.class()}
|
|
477
798
|
style={renderProps.style()}
|
|
478
|
-
data-selected={isSelected || undefined}
|
|
799
|
+
data-selected={isSelected() || undefined}
|
|
479
800
|
data-focused={isFocused() || undefined}
|
|
480
801
|
data-focus-visible={(isFocusVisible() && isFocused()) || undefined}
|
|
481
|
-
data-pressed={isPressed || undefined}
|
|
802
|
+
data-pressed={isPressed() || undefined}
|
|
482
803
|
data-hovered={isHovered() || undefined}
|
|
483
|
-
data-disabled={isDisabled || undefined}
|
|
804
|
+
data-disabled={isDisabled() || undefined}
|
|
805
|
+
data-dragging={draggableItem()?.isDragging || undefined}
|
|
806
|
+
data-drop-target={droppableItem()?.isDropTarget || undefined}
|
|
484
807
|
>
|
|
485
|
-
<div {...gridCellProps}>{renderProps.renderChildren()}</div>
|
|
486
|
-
</
|
|
808
|
+
<div {...itemAria.gridCellProps}>{renderProps.renderChildren()}</div>
|
|
809
|
+
</div>
|
|
487
810
|
);
|
|
488
811
|
}
|
|
489
812
|
|
|
490
813
|
/**
|
|
491
814
|
* A checkbox for item selection in a grid list.
|
|
492
815
|
*/
|
|
493
|
-
export function GridListSelectionCheckbox(props: {
|
|
816
|
+
export function GridListSelectionCheckbox(props: {
|
|
817
|
+
itemKey: Key;
|
|
818
|
+
class?: string;
|
|
819
|
+
style?: JSX.CSSProperties;
|
|
820
|
+
excludeFromTabOrder?: boolean;
|
|
821
|
+
"aria-label"?: string;
|
|
822
|
+
}): JSX.Element {
|
|
494
823
|
const context = useContext(GridListStateContext);
|
|
495
824
|
if (!context) {
|
|
496
|
-
throw new Error(
|
|
825
|
+
throw new Error("GridListSelectionCheckbox must be used within a GridList");
|
|
497
826
|
}
|
|
498
827
|
|
|
499
828
|
const state = context as GridState<object, GridCollection<object>>;
|
|
500
829
|
|
|
501
|
-
const
|
|
830
|
+
const checkboxAria = createGridListSelectionCheckbox<object, GridCollection<object>>(
|
|
502
831
|
() => ({ key: props.itemKey }),
|
|
503
|
-
() => state
|
|
832
|
+
() => state,
|
|
833
|
+
);
|
|
834
|
+
|
|
835
|
+
return (
|
|
836
|
+
<input
|
|
837
|
+
{...checkboxAria.checkboxProps}
|
|
838
|
+
class={props.class}
|
|
839
|
+
style={props.style}
|
|
840
|
+
tabIndex={props.excludeFromTabOrder ? -1 : undefined}
|
|
841
|
+
aria-label={props["aria-label"] ?? "Select"}
|
|
842
|
+
/>
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
export function GridListLoadMoreItem(props: GridListLoadMoreItemProps): JSX.Element {
|
|
847
|
+
let sentinelRef: HTMLDivElement | undefined;
|
|
848
|
+
const [isPending, setIsPending] = createSignal(false);
|
|
849
|
+
const isLoading = () => !!props.isLoading || isPending();
|
|
850
|
+
|
|
851
|
+
const triggerLoadMore = async () => {
|
|
852
|
+
if (isLoading()) return;
|
|
853
|
+
setIsPending(true);
|
|
854
|
+
try {
|
|
855
|
+
await props.onLoadMore();
|
|
856
|
+
} finally {
|
|
857
|
+
setIsPending(false);
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
createEffect(() => {
|
|
862
|
+
if (!sentinelRef || typeof IntersectionObserver !== "function") return;
|
|
863
|
+
const offset = props.scrollOffset ?? 1;
|
|
864
|
+
const margin = `0px 0px ${100 * offset}% 0px`;
|
|
865
|
+
const observer = new IntersectionObserver(
|
|
866
|
+
(entries) => {
|
|
867
|
+
if (entries[0]?.isIntersecting) {
|
|
868
|
+
void triggerLoadMore();
|
|
869
|
+
}
|
|
870
|
+
},
|
|
871
|
+
{ rootMargin: margin },
|
|
872
|
+
);
|
|
873
|
+
observer.observe(sentinelRef);
|
|
874
|
+
return () => observer.disconnect();
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
const renderProps = useRenderProps(
|
|
878
|
+
{
|
|
879
|
+
children: props.children ?? (() => (isLoading() ? "Loading more..." : "Load more")),
|
|
880
|
+
class: props.class,
|
|
881
|
+
style: props.style,
|
|
882
|
+
defaultClassName: "solidaria-GridList-loadMore",
|
|
883
|
+
},
|
|
884
|
+
() => ({ isLoading: isLoading() }),
|
|
885
|
+
);
|
|
886
|
+
|
|
887
|
+
return (
|
|
888
|
+
<>
|
|
889
|
+
<div style={{ position: "relative", width: 0, height: 0, overflow: "hidden" }} inert>
|
|
890
|
+
<div ref={sentinelRef} style={{ position: "absolute", height: "1px", width: "1px" }} />
|
|
891
|
+
</div>
|
|
892
|
+
<div
|
|
893
|
+
role="row"
|
|
894
|
+
tabIndex={0}
|
|
895
|
+
onFocus={() => {
|
|
896
|
+
void triggerLoadMore();
|
|
897
|
+
}}
|
|
898
|
+
class={renderProps.class()}
|
|
899
|
+
style={renderProps.style()}
|
|
900
|
+
data-loading={isLoading() || undefined}
|
|
901
|
+
>
|
|
902
|
+
{renderProps.renderChildren()}
|
|
903
|
+
</div>
|
|
904
|
+
</>
|
|
504
905
|
);
|
|
906
|
+
}
|
|
505
907
|
|
|
506
|
-
|
|
908
|
+
export function GridListHeader(props: GridListHeaderProps): JSX.Element {
|
|
909
|
+
return (
|
|
910
|
+
<div class={props.class ?? "solidaria-GridListHeader"} style={props.style}>
|
|
911
|
+
{props.children}
|
|
912
|
+
</div>
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* Section primitive alias for GridList composition parity.
|
|
918
|
+
*/
|
|
919
|
+
export function GridListSection(props: GridListSectionProps): JSX.Element {
|
|
920
|
+
return <Section {...props} />;
|
|
507
921
|
}
|
|
508
922
|
|
|
509
|
-
// Attach Item and SelectionCheckbox as static properties
|
|
510
923
|
GridList.Item = GridListItem;
|
|
511
924
|
GridList.SelectionCheckbox = GridListSelectionCheckbox;
|
|
925
|
+
GridList.LoadMoreItem = GridListLoadMoreItem;
|