@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.
Files changed (225) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -272
  3. package/dist/ActionBar.d.ts +79 -0
  4. package/dist/ActionBar.d.ts.map +1 -0
  5. package/dist/ActionGroup.d.ts +74 -0
  6. package/dist/ActionGroup.d.ts.map +1 -0
  7. package/dist/Alert.d.ts +70 -0
  8. package/dist/Alert.d.ts.map +1 -0
  9. package/dist/Autocomplete.d.ts +5 -5
  10. package/dist/Autocomplete.d.ts.map +1 -1
  11. package/dist/Breadcrumbs.d.ts +27 -8
  12. package/dist/Breadcrumbs.d.ts.map +1 -1
  13. package/dist/Button.d.ts +28 -5
  14. package/dist/Button.d.ts.map +1 -1
  15. package/dist/Calendar.d.ts +51 -7
  16. package/dist/Calendar.d.ts.map +1 -1
  17. package/dist/Checkbox.d.ts +33 -8
  18. package/dist/Checkbox.d.ts.map +1 -1
  19. package/dist/Collection.d.ts +130 -0
  20. package/dist/Collection.d.ts.map +1 -0
  21. package/dist/Color.d.ts +210 -9
  22. package/dist/Color.d.ts.map +1 -1
  23. package/dist/ColorEditor.d.ts +42 -0
  24. package/dist/ColorEditor.d.ts.map +1 -0
  25. package/dist/ComboBox.d.ts +146 -16
  26. package/dist/ComboBox.d.ts.map +1 -1
  27. package/dist/ContextualHelpTrigger.d.ts +40 -0
  28. package/dist/ContextualHelpTrigger.d.ts.map +1 -0
  29. package/dist/DateField.d.ts +35 -8
  30. package/dist/DateField.d.ts.map +1 -1
  31. package/dist/DatePicker.d.ts +101 -5
  32. package/dist/DatePicker.d.ts.map +1 -1
  33. package/dist/DateRangePickerContext.d.ts +30 -0
  34. package/dist/DateRangePickerContext.d.ts.map +1 -0
  35. package/dist/Dialog.d.ts +5 -5
  36. package/dist/Dialog.d.ts.map +1 -1
  37. package/dist/Disclosure.d.ts +25 -5
  38. package/dist/Disclosure.d.ts.map +1 -1
  39. package/dist/DragAndDrop.d.ts +80 -0
  40. package/dist/DragAndDrop.d.ts.map +1 -0
  41. package/dist/DragPreview.d.ts +14 -0
  42. package/dist/DragPreview.d.ts.map +1 -0
  43. package/dist/DropZone.d.ts +27 -0
  44. package/dist/DropZone.d.ts.map +1 -0
  45. package/dist/FieldError.d.ts +27 -0
  46. package/dist/FieldError.d.ts.map +1 -0
  47. package/dist/FileTrigger.d.ts +26 -0
  48. package/dist/FileTrigger.d.ts.map +1 -0
  49. package/dist/Focusable.d.ts +27 -0
  50. package/dist/Focusable.d.ts.map +1 -0
  51. package/dist/Form.d.ts +41 -0
  52. package/dist/Form.d.ts.map +1 -0
  53. package/dist/GridList.d.ts +69 -10
  54. package/dist/GridList.d.ts.map +1 -1
  55. package/dist/HiddenDateInput.d.ts +26 -0
  56. package/dist/HiddenDateInput.d.ts.map +1 -0
  57. package/dist/HiddenTimeInput.d.ts +25 -0
  58. package/dist/HiddenTimeInput.d.ts.map +1 -0
  59. package/dist/Icon.d.ts +57 -0
  60. package/dist/Icon.d.ts.map +1 -0
  61. package/dist/Keyboard.d.ts +13 -0
  62. package/dist/Keyboard.d.ts.map +1 -0
  63. package/dist/Landmark.d.ts +3 -3
  64. package/dist/Landmark.d.ts.map +1 -1
  65. package/dist/Link.d.ts +10 -4
  66. package/dist/Link.d.ts.map +1 -1
  67. package/dist/ListBox.d.ts +73 -11
  68. package/dist/ListBox.d.ts.map +1 -1
  69. package/dist/ListDropTargetDelegate.d.ts +38 -0
  70. package/dist/ListDropTargetDelegate.d.ts.map +1 -0
  71. package/dist/Menu.d.ts +79 -10
  72. package/dist/Menu.d.ts.map +1 -1
  73. package/dist/Meter.d.ts +4 -4
  74. package/dist/Meter.d.ts.map +1 -1
  75. package/dist/Modal.d.ts +6 -4
  76. package/dist/Modal.d.ts.map +1 -1
  77. package/dist/NumberField.d.ts +10 -12
  78. package/dist/NumberField.d.ts.map +1 -1
  79. package/dist/Popover.d.ts +32 -7
  80. package/dist/Popover.d.ts.map +1 -1
  81. package/dist/Pressable.d.ts +27 -0
  82. package/dist/Pressable.d.ts.map +1 -0
  83. package/dist/ProgressBar.d.ts +6 -4
  84. package/dist/ProgressBar.d.ts.map +1 -1
  85. package/dist/RadioGroup.d.ts +43 -9
  86. package/dist/RadioGroup.d.ts.map +1 -1
  87. package/dist/RangeCalendar.d.ts +39 -7
  88. package/dist/RangeCalendar.d.ts.map +1 -1
  89. package/dist/RouterProvider.d.ts +75 -0
  90. package/dist/RouterProvider.d.ts.map +1 -0
  91. package/dist/SearchField.d.ts +23 -21
  92. package/dist/SearchField.d.ts.map +1 -1
  93. package/dist/Select.d.ts +48 -7
  94. package/dist/Select.d.ts.map +1 -1
  95. package/dist/SelectionIndicator.d.ts +30 -0
  96. package/dist/SelectionIndicator.d.ts.map +1 -0
  97. package/dist/Separator.d.ts +9 -3
  98. package/dist/Separator.d.ts.map +1 -1
  99. package/dist/SharedElementTransition.d.ts +41 -0
  100. package/dist/SharedElementTransition.d.ts.map +1 -0
  101. package/dist/Slider.d.ts +15 -8
  102. package/dist/Slider.d.ts.map +1 -1
  103. package/dist/StepList.d.ts +90 -0
  104. package/dist/StepList.d.ts.map +1 -0
  105. package/dist/Switch.d.ts +11 -5
  106. package/dist/Switch.d.ts.map +1 -1
  107. package/dist/Table.d.ts +222 -19
  108. package/dist/Table.d.ts.map +1 -1
  109. package/dist/Tabs.d.ts +47 -10
  110. package/dist/Tabs.d.ts.map +1 -1
  111. package/dist/TagGroup.d.ts +22 -10
  112. package/dist/TagGroup.d.ts.map +1 -1
  113. package/dist/Text.d.ts +10 -0
  114. package/dist/Text.d.ts.map +1 -0
  115. package/dist/TextField.d.ts +19 -11
  116. package/dist/TextField.d.ts.map +1 -1
  117. package/dist/TimeField.d.ts +32 -7
  118. package/dist/TimeField.d.ts.map +1 -1
  119. package/dist/Toast.d.ts +29 -14
  120. package/dist/Toast.d.ts.map +1 -1
  121. package/dist/ToggleButton.d.ts +36 -0
  122. package/dist/ToggleButton.d.ts.map +1 -0
  123. package/dist/ToggleButtonGroup.d.ts +33 -0
  124. package/dist/ToggleButtonGroup.d.ts.map +1 -0
  125. package/dist/Toolbar.d.ts +7 -3
  126. package/dist/Toolbar.d.ts.map +1 -1
  127. package/dist/Tooltip.d.ts +58 -7
  128. package/dist/Tooltip.d.ts.map +1 -1
  129. package/dist/Tree.d.ts +102 -11
  130. package/dist/Tree.d.ts.map +1 -1
  131. package/dist/Virtualizer.d.ts +61 -0
  132. package/dist/Virtualizer.d.ts.map +1 -0
  133. package/dist/VirtualizerLayouts.d.ts +82 -0
  134. package/dist/VirtualizerLayouts.d.ts.map +1 -0
  135. package/dist/VisuallyHidden.d.ts +4 -2
  136. package/dist/VisuallyHidden.d.ts.map +1 -1
  137. package/dist/contexts.d.ts +6 -1
  138. package/dist/contexts.d.ts.map +1 -1
  139. package/dist/index.d.ts +73 -39
  140. package/dist/index.d.ts.map +1 -1
  141. package/dist/index.js +23342 -10644
  142. package/dist/index.js.map +1 -7
  143. package/dist/index.jsx +18110 -0
  144. package/dist/index.jsx.map +1 -0
  145. package/dist/useDragAndDrop.d.ts +93 -0
  146. package/dist/useDragAndDrop.d.ts.map +1 -0
  147. package/dist/utils.d.ts +8 -2
  148. package/dist/utils.d.ts.map +1 -1
  149. package/dist/virtualizer/Layout.d.ts +79 -0
  150. package/dist/virtualizer/Layout.d.ts.map +1 -0
  151. package/package.json +33 -32
  152. package/src/ActionBar.tsx +251 -0
  153. package/src/ActionGroup.tsx +277 -0
  154. package/src/Alert.tsx +152 -0
  155. package/src/Autocomplete.tsx +39 -44
  156. package/src/Breadcrumbs.tsx +227 -72
  157. package/src/Button.tsx +315 -74
  158. package/src/Calendar.tsx +347 -141
  159. package/src/Checkbox.tsx +414 -123
  160. package/src/Collection.tsx +350 -0
  161. package/src/Color.tsx +1325 -284
  162. package/src/ColorEditor.tsx +213 -0
  163. package/src/ComboBox.tsx +644 -245
  164. package/src/ContextualHelpTrigger.tsx +195 -0
  165. package/src/DateField.tsx +274 -106
  166. package/src/DatePicker.tsx +892 -111
  167. package/src/DateRangePickerContext.tsx +44 -0
  168. package/src/Dialog.tsx +173 -104
  169. package/src/Disclosure.tsx +158 -105
  170. package/src/DragAndDrop.tsx +340 -0
  171. package/src/DragPreview.tsx +47 -0
  172. package/src/DropZone.tsx +233 -0
  173. package/src/FieldError.tsx +89 -0
  174. package/src/FileTrigger.tsx +83 -0
  175. package/src/Focusable.tsx +103 -0
  176. package/src/Form.tsx +140 -0
  177. package/src/GridList.tsx +542 -128
  178. package/src/HiddenDateInput.tsx +153 -0
  179. package/src/HiddenTimeInput.tsx +133 -0
  180. package/src/Icon.tsx +133 -0
  181. package/src/Keyboard.tsx +26 -0
  182. package/src/Landmark.tsx +37 -63
  183. package/src/Link.tsx +132 -69
  184. package/src/ListBox.tsx +656 -106
  185. package/src/ListDropTargetDelegate.ts +283 -0
  186. package/src/Menu.tsx +1234 -132
  187. package/src/Meter.tsx +44 -58
  188. package/src/Modal.tsx +262 -166
  189. package/src/NumberField.tsx +267 -151
  190. package/src/Popover.tsx +452 -343
  191. package/src/Pressable.tsx +108 -0
  192. package/src/ProgressBar.tsx +54 -59
  193. package/src/RadioGroup.tsx +533 -121
  194. package/src/RangeCalendar.tsx +249 -150
  195. package/src/RouterProvider.tsx +223 -0
  196. package/src/SearchField.tsx +460 -133
  197. package/src/Select.tsx +804 -233
  198. package/src/SelectionIndicator.tsx +108 -0
  199. package/src/Separator.tsx +47 -49
  200. package/src/SharedElementTransition.tsx +264 -0
  201. package/src/Slider.tsx +148 -98
  202. package/src/StepList.tsx +272 -0
  203. package/src/Switch.tsx +93 -46
  204. package/src/Table.tsx +1551 -225
  205. package/src/Tabs.tsx +377 -123
  206. package/src/TagGroup.tsx +233 -135
  207. package/src/Text.tsx +18 -0
  208. package/src/TextField.tsx +413 -86
  209. package/src/TimeField.tsx +232 -222
  210. package/src/Toast.tsx +306 -160
  211. package/src/ToggleButton.tsx +169 -0
  212. package/src/ToggleButtonGroup.tsx +141 -0
  213. package/src/Toolbar.tsx +61 -70
  214. package/src/Tooltip.tsx +473 -116
  215. package/src/Tree.tsx +1514 -175
  216. package/src/Virtualizer.tsx +730 -0
  217. package/src/VirtualizerLayouts.ts +280 -0
  218. package/src/VisuallyHidden.tsx +32 -38
  219. package/src/contexts.ts +29 -36
  220. package/src/index.ts +972 -620
  221. package/src/useDragAndDrop.ts +367 -0
  222. package/src/utils.tsx +69 -50
  223. package/src/virtualizer/Layout.ts +192 -0
  224. package/dist/index.ssr.js +0 -9785
  225. 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
- } from 'solid-js';
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 '@proyecto-viviana/solidaria';
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
- } from '@proyecto-viviana/solid-stately';
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 './utils';
43
-
44
- // ============================================
45
- // TYPES
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> extends Omit<AriaGridListProps, 'children'>, SlotProps {
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?: 'none' | 'single' | 'multiple';
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?: 'all' | Iterable<Key>;
104
+ selectedKeys?: "all" | Iterable<Key>;
74
105
  /** Default selected keys (uncontrolled). */
75
- defaultSelectedKeys?: 'all' | Iterable<Key>;
106
+ defaultSelectedKeys?: "all" | Iterable<Key>;
76
107
  /** Handler called when selection changes. */
77
- onSelectionChange?: (keys: 'all' | Set<Key>) => void;
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> extends SlotProps {
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
- // CONTEXT
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>(null);
132
-
133
- // ============================================
134
- // HELPER: Build GridCollection from items
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: 'item' as const,
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
- 'items',
228
- 'getKey',
229
- 'getTextValue',
230
- 'getDisabled',
231
- 'disabledKeys',
232
- 'selectionMode',
233
- 'selectedKeys',
234
- 'defaultSelectedKeys',
235
- 'onSelectionChange',
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
- // Create ref signal
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
- 'aria-label': ariaProps['aria-label'],
288
- 'aria-labelledby': ariaProps['aria-labelledby'],
289
- 'aria-describedby': ariaProps['aria-describedby'],
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: 'solidaria-GridList',
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 value={state as unknown as GridState<object, GridCollection<object>>}>
346
- <ul
347
- ref={setRef}
348
- {...domProps()}
349
- {...cleanGridProps()}
350
- {...cleanFocusProps()}
351
- class={renderProps.class()}
352
- style={renderProps.style()}
353
- data-focused={state.isFocused || undefined}
354
- data-focus-visible={isFocusVisible() || undefined}
355
- data-disabled={ariaProps.isDisabled || undefined}
356
- data-empty={isEmpty() || undefined}
357
- >
358
- {isEmpty() && local.renderEmptyState ? (
359
- local.renderEmptyState()
360
- ) : (
361
- <For each={stateProps.items}>{(item) => props.children(item)}</For>
362
- )}
363
- </ul>
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
- 'class',
375
- 'style',
376
- 'slot',
377
- 'id',
378
- 'item',
379
- 'textValue',
380
- 'onAction',
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('GridListItem must be used within a GridList');
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
- // Create ref signal
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: 'item' as const,
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
- // Create item aria props
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: 'solidaria-GridList-item',
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
- <li
472
- ref={setRef}
473
- {...cleanRowProps()}
474
- {...cleanHoverProps()}
475
- {...cleanFocusProps()}
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
- </li>
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: { itemKey: Key }): JSX.Element {
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('GridListSelectionCheckbox must be used within a GridList');
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 { checkboxProps } = createGridListSelectionCheckbox<object, GridCollection<object>>(
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
- return <input {...checkboxProps} />;
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;