@proyecto-viviana/solidaria-components 0.2.4 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/LICENSE +21 -0
  2. package/dist/ActionBar.d.ts +71 -0
  3. package/dist/ActionBar.d.ts.map +1 -0
  4. package/dist/ActionGroup.d.ts +74 -0
  5. package/dist/ActionGroup.d.ts.map +1 -0
  6. package/dist/Alert.d.ts +70 -0
  7. package/dist/Alert.d.ts.map +1 -0
  8. package/dist/Breadcrumbs.d.ts +10 -2
  9. package/dist/Breadcrumbs.d.ts.map +1 -1
  10. package/dist/Button.d.ts +4 -0
  11. package/dist/Button.d.ts.map +1 -1
  12. package/dist/Calendar.d.ts +13 -0
  13. package/dist/Calendar.d.ts.map +1 -1
  14. package/dist/Checkbox.d.ts +2 -2
  15. package/dist/Checkbox.d.ts.map +1 -1
  16. package/dist/Collection.d.ts +125 -0
  17. package/dist/Collection.d.ts.map +1 -0
  18. package/dist/Color.d.ts +114 -2
  19. package/dist/Color.d.ts.map +1 -1
  20. package/dist/ColorEditor.d.ts +42 -0
  21. package/dist/ColorEditor.d.ts.map +1 -0
  22. package/dist/ComboBox.d.ts +64 -0
  23. package/dist/ComboBox.d.ts.map +1 -1
  24. package/dist/ContextualHelpTrigger.d.ts +40 -0
  25. package/dist/ContextualHelpTrigger.d.ts.map +1 -0
  26. package/dist/DateField.d.ts +27 -2
  27. package/dist/DateField.d.ts.map +1 -1
  28. package/dist/DatePicker.d.ts +67 -2
  29. package/dist/DatePicker.d.ts.map +1 -1
  30. package/dist/Dialog.d.ts.map +1 -1
  31. package/dist/Disclosure.d.ts +2 -0
  32. package/dist/Disclosure.d.ts.map +1 -1
  33. package/dist/DragAndDrop.d.ts +80 -0
  34. package/dist/DragAndDrop.d.ts.map +1 -0
  35. package/dist/DragPreview.d.ts +14 -0
  36. package/dist/DragPreview.d.ts.map +1 -0
  37. package/dist/DropZone.d.ts +27 -0
  38. package/dist/DropZone.d.ts.map +1 -0
  39. package/dist/FieldError.d.ts +23 -0
  40. package/dist/FieldError.d.ts.map +1 -0
  41. package/dist/FileTrigger.d.ts +26 -0
  42. package/dist/FileTrigger.d.ts.map +1 -0
  43. package/dist/Focusable.d.ts +27 -0
  44. package/dist/Focusable.d.ts.map +1 -0
  45. package/dist/Form.d.ts +27 -0
  46. package/dist/Form.d.ts.map +1 -0
  47. package/dist/GridList.d.ts +40 -1
  48. package/dist/GridList.d.ts.map +1 -1
  49. package/dist/Icon.d.ts +57 -0
  50. package/dist/Icon.d.ts.map +1 -0
  51. package/dist/Keyboard.d.ts +13 -0
  52. package/dist/Keyboard.d.ts.map +1 -0
  53. package/dist/Link.d.ts.map +1 -1
  54. package/dist/ListBox.d.ts +43 -1
  55. package/dist/ListBox.d.ts.map +1 -1
  56. package/dist/ListDropTargetDelegate.d.ts +38 -0
  57. package/dist/ListDropTargetDelegate.d.ts.map +1 -0
  58. package/dist/Menu.d.ts +20 -2
  59. package/dist/Menu.d.ts.map +1 -1
  60. package/dist/Meter.d.ts +2 -2
  61. package/dist/Meter.d.ts.map +1 -1
  62. package/dist/Modal.d.ts +2 -0
  63. package/dist/Modal.d.ts.map +1 -1
  64. package/dist/NumberField.d.ts +2 -0
  65. package/dist/NumberField.d.ts.map +1 -1
  66. package/dist/Popover.d.ts +4 -2
  67. package/dist/Popover.d.ts.map +1 -1
  68. package/dist/Pressable.d.ts +27 -0
  69. package/dist/Pressable.d.ts.map +1 -0
  70. package/dist/ProgressBar.d.ts +2 -2
  71. package/dist/ProgressBar.d.ts.map +1 -1
  72. package/dist/RadioGroup.d.ts.map +1 -1
  73. package/dist/RangeCalendar.d.ts +5 -0
  74. package/dist/RangeCalendar.d.ts.map +1 -1
  75. package/dist/RouterProvider.d.ts +75 -0
  76. package/dist/RouterProvider.d.ts.map +1 -0
  77. package/dist/SearchField.d.ts +2 -3
  78. package/dist/SearchField.d.ts.map +1 -1
  79. package/dist/Select.d.ts +11 -0
  80. package/dist/Select.d.ts.map +1 -1
  81. package/dist/SelectionIndicator.d.ts +30 -0
  82. package/dist/SelectionIndicator.d.ts.map +1 -0
  83. package/dist/SharedElementTransition.d.ts +39 -0
  84. package/dist/SharedElementTransition.d.ts.map +1 -0
  85. package/dist/Slider.d.ts +6 -3
  86. package/dist/Slider.d.ts.map +1 -1
  87. package/dist/Table.d.ts +39 -0
  88. package/dist/Table.d.ts.map +1 -1
  89. package/dist/Tabs.d.ts +4 -3
  90. package/dist/Tabs.d.ts.map +1 -1
  91. package/dist/TagGroup.d.ts +12 -2
  92. package/dist/TagGroup.d.ts.map +1 -1
  93. package/dist/Text.d.ts +10 -0
  94. package/dist/Text.d.ts.map +1 -0
  95. package/dist/TextField.d.ts +4 -0
  96. package/dist/TextField.d.ts.map +1 -1
  97. package/dist/TimeField.d.ts +26 -1
  98. package/dist/TimeField.d.ts.map +1 -1
  99. package/dist/Toast.d.ts.map +1 -1
  100. package/dist/ToggleButton.d.ts +30 -0
  101. package/dist/ToggleButton.d.ts.map +1 -0
  102. package/dist/ToggleButtonGroup.d.ts +33 -0
  103. package/dist/ToggleButtonGroup.d.ts.map +1 -0
  104. package/dist/Toolbar.d.ts.map +1 -1
  105. package/dist/Tooltip.d.ts +9 -0
  106. package/dist/Tooltip.d.ts.map +1 -1
  107. package/dist/Tree.d.ts +44 -2
  108. package/dist/Tree.d.ts.map +1 -1
  109. package/dist/Virtualizer.d.ts +61 -0
  110. package/dist/Virtualizer.d.ts.map +1 -0
  111. package/dist/VirtualizerLayouts.d.ts +82 -0
  112. package/dist/VirtualizerLayouts.d.ts.map +1 -0
  113. package/dist/VisuallyHidden.d.ts +3 -1
  114. package/dist/VisuallyHidden.d.ts.map +1 -1
  115. package/dist/contexts.d.ts +1 -0
  116. package/dist/contexts.d.ts.map +1 -1
  117. package/dist/index.d.ts +57 -25
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +13961 -5946
  120. package/dist/index.js.map +1 -7
  121. package/dist/index.ssr.js +9612 -2401
  122. package/dist/index.ssr.js.map +1 -7
  123. package/dist/useDragAndDrop.d.ts +93 -0
  124. package/dist/useDragAndDrop.d.ts.map +1 -0
  125. package/dist/utils.d.ts +7 -1
  126. package/dist/utils.d.ts.map +1 -1
  127. package/dist/virtualizer/Layout.d.ts +79 -0
  128. package/dist/virtualizer/Layout.d.ts.map +1 -0
  129. package/package.json +8 -6
  130. package/src/ActionBar.tsx +248 -0
  131. package/src/ActionGroup.tsx +285 -0
  132. package/src/Alert.tsx +177 -0
  133. package/src/Autocomplete.tsx +1 -1
  134. package/src/Breadcrumbs.tsx +103 -17
  135. package/src/Button.tsx +65 -21
  136. package/src/Calendar.tsx +179 -53
  137. package/src/Checkbox.tsx +1 -2
  138. package/src/Collection.tsx +341 -0
  139. package/src/Color.tsx +652 -34
  140. package/src/ColorEditor.tsx +231 -0
  141. package/src/ComboBox.tsx +315 -81
  142. package/src/ContextualHelpTrigger.tsx +183 -0
  143. package/src/DateField.tsx +93 -19
  144. package/src/DatePicker.tsx +495 -25
  145. package/src/Dialog.tsx +40 -9
  146. package/src/Disclosure.tsx +33 -27
  147. package/src/DragAndDrop.tsx +334 -0
  148. package/src/DragPreview.tsx +45 -0
  149. package/src/DropZone.tsx +213 -0
  150. package/src/FieldError.tsx +67 -0
  151. package/src/FileTrigger.tsx +83 -0
  152. package/src/Focusable.tsx +106 -0
  153. package/src/Form.tsx +85 -0
  154. package/src/GridList.tsx +379 -41
  155. package/src/Icon.tsx +154 -0
  156. package/src/Keyboard.tsx +26 -0
  157. package/src/Link.tsx +14 -1
  158. package/src/ListBox.tsx +484 -33
  159. package/src/ListDropTargetDelegate.ts +282 -0
  160. package/src/Menu.tsx +388 -35
  161. package/src/Meter.tsx +7 -3
  162. package/src/Modal.tsx +32 -4
  163. package/src/NumberField.tsx +163 -43
  164. package/src/Popover.tsx +136 -180
  165. package/src/Pressable.tsx +108 -0
  166. package/src/ProgressBar.tsx +7 -3
  167. package/src/RadioGroup.tsx +35 -25
  168. package/src/RangeCalendar.tsx +100 -68
  169. package/src/RouterProvider.tsx +240 -0
  170. package/src/SearchField.tsx +142 -34
  171. package/src/Select.tsx +221 -73
  172. package/src/SelectionIndicator.tsx +105 -0
  173. package/src/SharedElementTransition.tsx +258 -0
  174. package/src/Slider.tsx +16 -6
  175. package/src/Table.tsx +417 -57
  176. package/src/Tabs.tsx +68 -35
  177. package/src/TagGroup.tsx +121 -36
  178. package/src/Text.tsx +18 -0
  179. package/src/TextField.tsx +25 -8
  180. package/src/TimeField.tsx +101 -151
  181. package/src/Toast.tsx +108 -14
  182. package/src/ToggleButton.tsx +159 -0
  183. package/src/ToggleButtonGroup.tsx +136 -0
  184. package/src/Toolbar.tsx +14 -8
  185. package/src/Tooltip.tsx +108 -19
  186. package/src/Tree.tsx +1143 -87
  187. package/src/Virtualizer.tsx +702 -0
  188. package/src/VirtualizerLayouts.ts +265 -0
  189. package/src/VisuallyHidden.tsx +15 -21
  190. package/src/contexts.ts +1 -0
  191. package/src/index.ts +1057 -620
  192. package/src/useDragAndDrop.ts +351 -0
  193. package/src/utils.tsx +37 -3
  194. package/src/virtualizer/Layout.ts +200 -0
package/src/GridList.tsx CHANGED
@@ -9,10 +9,13 @@
9
9
  */
10
10
 
11
11
  import {
12
+ type Accessor,
12
13
  type JSX,
13
14
  createContext,
15
+ createEffect,
14
16
  createMemo,
15
17
  createSignal,
18
+ onCleanup,
16
19
  splitProps,
17
20
  useContext,
18
21
  For,
@@ -23,6 +26,7 @@ import {
23
26
  createGridListSelectionCheckbox,
24
27
  createFocusRing,
25
28
  createHover,
29
+ mergeProps,
26
30
  type AriaGridListProps,
27
31
  } from '@proyecto-viviana/solidaria';
28
32
  import {
@@ -31,6 +35,7 @@ import {
31
35
  type GridCollection,
32
36
  type GridNode,
33
37
  type Key,
38
+ type DropTarget,
34
39
  } from '@proyecto-viviana/solid-stately';
35
40
  import {
36
41
  type RenderChildren,
@@ -40,6 +45,22 @@ import {
40
45
  useRenderProps,
41
46
  filterDOMProps,
42
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';
43
64
 
44
65
  // ============================================
45
66
  // TYPES
@@ -83,6 +104,14 @@ export interface GridListProps<T extends object> extends Omit<AriaGridListProps,
83
104
  style?: StyleOrFunction<GridListRenderProps>;
84
105
  /** A function to render when the grid list is empty. */
85
106
  renderEmptyState?: () => JSX.Element;
107
+ /** Whether there are more items to load. */
108
+ hasMore?: boolean;
109
+ /** Whether additional items are currently loading. */
110
+ isLoading?: boolean;
111
+ /** Called when the load more sentinel becomes visible. */
112
+ onLoadMore?: () => void | Promise<void>;
113
+ /** Drag and drop hooks from `useDragAndDrop`. */
114
+ dragAndDropHooks?: DragAndDropHooks<T>;
86
115
  }
87
116
 
88
117
  export interface GridListItemRenderProps {
@@ -100,7 +129,7 @@ export interface GridListItemRenderProps {
100
129
  isDisabled: boolean;
101
130
  }
102
131
 
103
- export interface GridListItemProps<T extends object> extends SlotProps {
132
+ export interface GridListItemProps<T extends object> extends SlotProps, Omit<JSX.HTMLAttributes<HTMLDivElement>, 'class' | 'style' | 'children' | 'id'> {
104
133
  /** The unique key for the item. */
105
134
  id: Key;
106
135
  /** The item value. */
@@ -117,6 +146,21 @@ export interface GridListItemProps<T extends object> extends SlotProps {
117
146
  onAction?: () => void;
118
147
  }
119
148
 
149
+ export interface GridListLoadMoreItemProps extends SlotProps {
150
+ onLoadMore: () => void | Promise<void>;
151
+ isLoading?: boolean;
152
+ children?: JSX.Element;
153
+ class?: ClassNameOrFunction<{ isLoading: boolean }>;
154
+ style?: StyleOrFunction<{ isLoading: boolean }>;
155
+ }
156
+
157
+ export interface GridListSectionProps extends SectionProps {}
158
+ export interface GridListHeaderProps extends SlotProps {
159
+ children?: JSX.Element;
160
+ class?: string;
161
+ style?: JSX.CSSProperties;
162
+ }
163
+
120
164
  // ============================================
121
165
  // CONTEXT
122
166
  // ============================================
@@ -125,10 +169,14 @@ interface GridListContextValue<T extends object> {
125
169
  state: GridState<T, GridCollection<T>>;
126
170
  collection: GridCollection<T>;
127
171
  isDisabled: boolean;
172
+ dragAndDropHooks?: DragAndDropHooks<T>;
173
+ dragState?: unknown;
174
+ dropState?: unknown;
128
175
  }
129
176
 
130
177
  export const GridListContext = createContext<GridListContextValue<object> | null>(null);
131
178
  export const GridListStateContext = createContext<GridState<object, GridCollection<object>> | null>(null);
179
+ export const GridListHeaderContext = createContext<null>(null);
132
180
 
133
181
  // ============================================
134
182
  // HELPER: Build GridCollection from items
@@ -222,7 +270,7 @@ function buildGridCollection<T extends object>(
222
270
  export function GridList<T extends object>(props: GridListProps<T>): JSX.Element {
223
271
  const [local, stateProps, ariaProps] = splitProps(
224
272
  props,
225
- ['children', 'class', 'style', 'slot', 'renderEmptyState'],
273
+ ['children', 'class', 'style', 'slot', 'renderEmptyState', 'hasMore', 'isLoading', 'onLoadMore', 'dragAndDropHooks'],
226
274
  [
227
275
  'items',
228
276
  'getKey',
@@ -333,34 +381,224 @@ export function GridList<T extends object>(props: GridListProps<T>): JSX.Element
333
381
  };
334
382
 
335
383
  const isEmpty = () => stateProps.items.length === 0;
384
+ const virtualizer = useVirtualizerContext();
385
+ const parentCollectionRenderer = useCollectionRenderer<T>();
386
+ const getItemNodes = createMemo(() => Array.from(state.collection).filter((node) => node.type === 'item'));
387
+ const getDropTargetByIndex = (index: number, position: 'before' | 'after' | 'on'): DropTarget | null => {
388
+ const node = getItemNodes()[index];
389
+ if (!node) return null;
390
+ return { type: 'item', key: node.key, dropPosition: position };
391
+ };
392
+ const hasDroppableDnd = createMemo(() => {
393
+ const hooks = local.dragAndDropHooks;
394
+ return Boolean(
395
+ hooks?.useDroppableCollectionState &&
396
+ hooks.useDroppableCollection &&
397
+ (hooks.dropTargetDelegate || parentCollectionRenderer?.dropTargetDelegate || hooks.ListDropTargetDelegate)
398
+ );
399
+ });
400
+ const hasDraggableDnd = createMemo(() => {
401
+ const hooks = local.dragAndDropHooks;
402
+ return Boolean(hooks?.useDraggableCollectionState && hooks.useDraggableCollection);
403
+ });
404
+ const dragState = createMemo(() => {
405
+ if (!hasDraggableDnd()) return undefined;
406
+ return local.dragAndDropHooks?.useDraggableCollectionState?.({
407
+ items: stateProps.items,
408
+ });
409
+ });
410
+ const dropState = createMemo(() => {
411
+ if (!hasDroppableDnd()) return undefined;
412
+ return local.dragAndDropHooks?.useDroppableCollectionState?.({});
413
+ });
414
+ createEffect(() => {
415
+ if (!hasDraggableDnd()) return;
416
+ const hooks = local.dragAndDropHooks;
417
+ const activeDragState = dragState();
418
+ if (!hooks?.useDraggableCollection || !activeDragState) return;
419
+ hooks.useDraggableCollection({}, activeDragState, () => ref());
420
+ });
421
+ const droppableCollection = createMemo(() => {
422
+ if (!hasDroppableDnd()) return undefined;
423
+ const hooks = local.dragAndDropHooks;
424
+ const activeDropState = dropState();
425
+ if (!hooks?.useDroppableCollection || !activeDropState) return undefined;
426
+ const resolveDirection = (): 'ltr' | 'rtl' => {
427
+ const el = ref();
428
+ if (el && typeof window !== 'undefined' && typeof window.getComputedStyle === 'function') {
429
+ const dir = window.getComputedStyle(el).direction;
430
+ if (dir === 'rtl') return 'rtl';
431
+ }
432
+ return typeof document !== 'undefined' && document.dir === 'rtl' ? 'rtl' : 'ltr';
433
+ };
434
+ const dropTargetDelegate = hooks.dropTargetDelegate
435
+ ?? parentCollectionRenderer?.dropTargetDelegate
436
+ ?? (hooks.ListDropTargetDelegate
437
+ ? new hooks.ListDropTargetDelegate(
438
+ () => state.collection,
439
+ () => ref(),
440
+ { layout: 'grid', orientation: 'vertical', direction: resolveDirection() }
441
+ )
442
+ : undefined);
443
+ if (!dropTargetDelegate) return undefined;
444
+ return hooks.useDroppableCollection(
445
+ {
446
+ dropTargetDelegate,
447
+ keyboardDelegate: {
448
+ getFirstKey: () => state.collection.getFirstKey?.() ?? null,
449
+ getLastKey: () => state.collection.getLastKey?.() ?? null,
450
+ getKeyBelow: (key) => state.collection.getKeyAfter?.(key) ?? null,
451
+ getKeyAbove: (key) => state.collection.getKeyBefore?.(key) ?? null,
452
+ getKeyLeftOf: (key) =>
453
+ resolveDirection() === 'rtl'
454
+ ? state.collection.getKeyAfter?.(key) ?? null
455
+ : state.collection.getKeyBefore?.(key) ?? null,
456
+ getKeyRightOf: (key) =>
457
+ resolveDirection() === 'rtl'
458
+ ? state.collection.getKeyBefore?.(key) ?? null
459
+ : state.collection.getKeyAfter?.(key) ?? null,
460
+ getKeyPageBelow: (key) => state.collection.getKeyAfter?.(key) ?? null,
461
+ getKeyPageAbove: (key) => state.collection.getKeyBefore?.(key) ?? null,
462
+ },
463
+ },
464
+ activeDropState,
465
+ () => ref()
466
+ );
467
+ });
468
+ const isRootDropTarget = createMemo(() => {
469
+ return Boolean(dropState()?.target?.type === 'root');
470
+ });
471
+ const dndRenderDropIndicator = createMemo(() => useRenderDropIndicator(local.dragAndDropHooks, dropState()));
472
+ const dndDropIndicator = (index: number, position: 'before' | 'after' | 'on') => {
473
+ const target = getDropTargetByIndex(index, position);
474
+ if (!target || target.type !== 'item') return undefined;
475
+ return dndRenderDropIndicator()?.(target);
476
+ };
477
+ const persistedKeys = useDndPersistedKeys(
478
+ { focusedKey: () => state.focusedKey },
479
+ local.dragAndDropHooks,
480
+ dropState(),
481
+ state.collection
482
+ );
483
+ const virtualRange = createMemo(() => {
484
+ if (!virtualizer || !parentCollectionRenderer?.isVirtualized) return null;
485
+ const baseRange = virtualizer.getVisibleRange(stateProps.items.length);
486
+ const itemNodes = getItemNodes();
487
+ const persistedIndexes = Array.from(persistedKeys())
488
+ .map((key) => itemNodes.findIndex((node) => node.key === key))
489
+ .filter((index) => index >= 0);
490
+ const dropTarget = dropState()?.target;
491
+ const normalizedDropKey = getNormalizedDropTargetKey(dropTarget, state.collection);
492
+ const focusedKey = state.focusedKey;
493
+ const focusedIndex = focusedKey != null ? itemNodes.findIndex((node) => node.key === focusedKey) : -1;
494
+ const forceIncludeIndexes = [
495
+ dropTarget?.type === 'item' ? itemNodes.findIndex((node) => node.key === dropTarget.key) : -1,
496
+ normalizedDropKey != null ? itemNodes.findIndex((node) => node.key === normalizedDropKey) : -1,
497
+ dropTarget?.type === 'item' ? -1 : focusedIndex,
498
+ ].filter((index) => index >= 0);
499
+ return mergePersistedKeysIntoVirtualRange(baseRange, persistedIndexes, stateProps.items.length, virtualizer, 80, {
500
+ forceIncludeIndexes,
501
+ forceIncludeMaxSpan: 320,
502
+ });
503
+ });
504
+ createEffect(() => {
505
+ if (!virtualizer || !parentCollectionRenderer?.isVirtualized) return;
506
+ virtualizer.setDropTargetItemCountResolver(() => state.collection.size);
507
+ virtualizer.setDropTargetIndexResolver((key) => {
508
+ const entries = Array.from(state.collection);
509
+ const index = entries.findIndex((node) => node.key === key);
510
+ return index >= 0 ? index : null;
511
+ });
512
+ virtualizer.setDropTargetResolver((target) => {
513
+ const node = Array.from(state.collection)[target.index];
514
+ if (!node) return target;
515
+ return {
516
+ ...target,
517
+ key: typeof node.key === 'string' || typeof node.key === 'number' ? node.key : undefined,
518
+ };
519
+ });
520
+ onCleanup(() => {
521
+ virtualizer.setDropTargetIndexResolver(undefined);
522
+ virtualizer.setDropTargetItemCountResolver(undefined);
523
+ virtualizer.setDropTargetResolver(undefined);
524
+ });
525
+ });
526
+ const visibleItems = createMemo(() => {
527
+ const range = virtualRange();
528
+ if (!range) return stateProps.items;
529
+ return stateProps.items.slice(range.start, range.end);
530
+ });
336
531
 
337
532
  const contextValue = createMemo<GridListContextValue<T>>(() => ({
338
533
  state,
339
534
  collection: collection(),
340
535
  isDisabled: ariaProps.isDisabled ?? false,
536
+ dragAndDropHooks: local.dragAndDropHooks,
537
+ dragState: dragState(),
538
+ dropState: dropState(),
539
+ }));
540
+ const collectionRenderer = createMemo<CollectionRendererContextValue<unknown>>(() => ({
541
+ ...parentCollectionRenderer,
542
+ renderItem: (item) => props.children(item as T),
543
+ renderDropIndicator: (index: number, position: 'before' | 'after' | 'on') =>
544
+ dndDropIndicator(index, position) ?? parentCollectionRenderer?.renderDropIndicator?.(index, position),
341
545
  }));
342
546
 
343
547
  return (
344
- <GridListContext.Provider value={contextValue() as GridListContextValue<object>}>
548
+ <GridListContext.Provider value={contextValue() as unknown as GridListContextValue<object>}>
345
549
  <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>
550
+ <CollectionRendererContext.Provider value={collectionRenderer()}>
551
+ <div
552
+ ref={setRef}
553
+ {...mergeProps(
554
+ domProps(),
555
+ cleanGridProps(),
556
+ cleanFocusProps(),
557
+ (droppableCollection()?.collectionProps as Record<string, unknown> | undefined) ?? {}
558
+ )}
559
+ class={renderProps.class()}
560
+ style={renderProps.style()}
561
+ data-focused={state.isFocused || undefined}
562
+ data-focus-visible={isFocusVisible() || undefined}
563
+ data-disabled={ariaProps.isDisabled || undefined}
564
+ data-empty={isEmpty() || undefined}
565
+ data-drop-target={isRootDropTarget() || undefined}
566
+ >
567
+ <SharedElementTransition>
568
+ {isEmpty() && local.renderEmptyState ? (
569
+ local.renderEmptyState()
570
+ ) : (
571
+ <>
572
+ {virtualRange()?.offsetTop
573
+ ? <div role="presentation" aria-hidden="true" style={{ height: `${virtualRange()!.offsetTop}px` }} data-virtualizer-spacer="top" />
574
+ : null}
575
+ <For each={visibleItems()}>
576
+ {(item, index) => {
577
+ const itemIndex = () => (virtualRange()?.start ?? 0) + index();
578
+ const beforeIndicator = () => collectionRenderer().renderDropIndicator?.(itemIndex(), 'before');
579
+ const onIndicator = () => collectionRenderer().renderDropIndicator?.(itemIndex(), 'on');
580
+ const afterIndicator = () => collectionRenderer().renderDropIndicator?.(itemIndex(), 'after');
581
+ return (
582
+ <>
583
+ {beforeIndicator()}
584
+ {onIndicator()}
585
+ {props.children(item)}
586
+ {afterIndicator()}
587
+ </>
588
+ );
589
+ }}
590
+ </For>
591
+ {virtualRange()?.offsetBottom
592
+ ? <div role="presentation" aria-hidden="true" style={{ height: `${virtualRange()!.offsetBottom}px` }} data-virtualizer-spacer="bottom" />
593
+ : null}
594
+ </>
595
+ )}
596
+ </SharedElementTransition>
597
+ {local.hasMore && local.onLoadMore && (
598
+ <GridListLoadMoreItem onLoadMore={local.onLoadMore} isLoading={local.isLoading} />
599
+ )}
600
+ </div>
601
+ </CollectionRendererContext.Provider>
364
602
  </GridListStateContext.Provider>
365
603
  </GridListContext.Provider>
366
604
  );
@@ -370,7 +608,7 @@ export function GridList<T extends object>(props: GridListProps<T>): JSX.Element
370
608
  * An item in a grid list.
371
609
  */
372
610
  export function GridListItem<T extends object>(props: GridListItemProps<T>): JSX.Element {
373
- const [local] = splitProps(props, [
611
+ const [local, domProps] = splitProps(props, [
374
612
  'class',
375
613
  'style',
376
614
  'slot',
@@ -378,6 +616,7 @@ export function GridListItem<T extends object>(props: GridListItemProps<T>): JSX
378
616
  'item',
379
617
  'textValue',
380
618
  'onAction',
619
+ 'children',
381
620
  ]);
382
621
 
383
622
  // Get state from context
@@ -386,9 +625,10 @@ export function GridListItem<T extends object>(props: GridListItemProps<T>): JSX
386
625
  throw new Error('GridListItem must be used within a GridList');
387
626
  }
388
627
  const state = context as GridState<T, GridCollection<T>>;
628
+ const listContext = useContext(GridListContext) as GridListContextValue<T> | null;
389
629
 
390
630
  // Create ref signal
391
- const [ref, setRef] = createSignal<HTMLLIElement | null>(null);
631
+ const [ref, setRef] = createSignal<HTMLDivElement | null>(null);
392
632
 
393
633
  // Find or create the item node
394
634
  const itemNode = createMemo(() => {
@@ -410,19 +650,22 @@ export function GridListItem<T extends object>(props: GridListItemProps<T>): JSX
410
650
  });
411
651
 
412
652
  // Create item aria props
413
- const { rowProps, gridCellProps, isSelected, isDisabled, isPressed } = createGridListItem<T, GridCollection<T>>(
653
+ const itemAria = createGridListItem<T, GridCollection<T>>(
414
654
  () => ({
415
655
  node: itemNode(),
416
656
  onAction: local.onAction,
417
657
  }),
418
658
  () => state,
419
- ref
659
+ ref as Accessor<HTMLLIElement | null>
420
660
  );
661
+ const isSelected = () => itemAria.isSelected;
662
+ const isDisabled = () => itemAria.isDisabled;
663
+ const isPressed = () => itemAria.isPressed;
421
664
 
422
665
  // Create hover
423
666
  const { isHovered, hoverProps } = createHover({
424
667
  get isDisabled() {
425
- return isDisabled;
668
+ return isDisabled();
426
669
  },
427
670
  });
428
671
 
@@ -431,15 +674,34 @@ export function GridListItem<T extends object>(props: GridListItemProps<T>): JSX
431
674
 
432
675
  // Check if focused
433
676
  const isFocused = createMemo(() => state.focusedKey === local.id);
677
+ const draggableItem = createMemo(() => {
678
+ if (!listContext?.dragAndDropHooks?.useDraggableItem || !listContext.dragState) return undefined;
679
+ return listContext.dragAndDropHooks.useDraggableItem(
680
+ {
681
+ key: local.id as string | number,
682
+ },
683
+ listContext.dragState as Parameters<NonNullable<DragAndDropHooks<T>['useDraggableItem']>>[1]
684
+ );
685
+ });
686
+ const droppableItem = createMemo(() => {
687
+ if (!listContext?.dragAndDropHooks?.useDroppableItem || !listContext.dropState) return undefined;
688
+ return listContext.dragAndDropHooks.useDroppableItem(
689
+ {
690
+ key: local.id as string | number,
691
+ },
692
+ listContext.dropState as Parameters<NonNullable<DragAndDropHooks<T>['useDroppableItem']>>[1],
693
+ () => ref()
694
+ );
695
+ });
434
696
 
435
697
  // Render props values
436
698
  const renderValues = createMemo<GridListItemRenderProps>(() => ({
437
- isSelected,
699
+ isSelected: isSelected(),
438
700
  isFocused: isFocused(),
439
701
  isFocusVisible: isFocusVisible() && isFocused(),
440
- isPressed,
702
+ isPressed: isPressed(),
441
703
  isHovered: isHovered(),
442
- isDisabled,
704
+ isDisabled: isDisabled(),
443
705
  }));
444
706
 
445
707
  // Resolve render props
@@ -455,7 +717,7 @@ export function GridListItem<T extends object>(props: GridListItemProps<T>): JSX
455
717
 
456
718
  // Remove ref from spread props
457
719
  const cleanRowProps = () => {
458
- const { ref: _ref1, ...rest } = rowProps as Record<string, unknown>;
720
+ const { ref: _ref1, ...rest } = itemAria.rowProps as Record<string, unknown>;
459
721
  return rest;
460
722
  };
461
723
  const cleanHoverProps = () => {
@@ -468,22 +730,29 @@ export function GridListItem<T extends object>(props: GridListItemProps<T>): JSX
468
730
  };
469
731
 
470
732
  return (
471
- <li
733
+ <div
472
734
  ref={setRef}
473
- {...cleanRowProps()}
474
- {...cleanHoverProps()}
475
- {...cleanFocusProps()}
735
+ {...domProps}
736
+ {...mergeProps(
737
+ cleanRowProps(),
738
+ cleanHoverProps(),
739
+ cleanFocusProps(),
740
+ (draggableItem()?.dragProps as Record<string, unknown> | undefined) ?? {},
741
+ (droppableItem()?.dropProps as Record<string, unknown> | undefined) ?? {}
742
+ )}
476
743
  class={renderProps.class()}
477
744
  style={renderProps.style()}
478
- data-selected={isSelected || undefined}
745
+ data-selected={isSelected() || undefined}
479
746
  data-focused={isFocused() || undefined}
480
747
  data-focus-visible={(isFocusVisible() && isFocused()) || undefined}
481
- data-pressed={isPressed || undefined}
748
+ data-pressed={isPressed() || undefined}
482
749
  data-hovered={isHovered() || undefined}
483
- data-disabled={isDisabled || undefined}
750
+ data-disabled={isDisabled() || undefined}
751
+ data-dragging={draggableItem()?.isDragging || undefined}
752
+ data-drop-target={droppableItem()?.isDropTarget || undefined}
484
753
  >
485
- <div {...gridCellProps}>{renderProps.renderChildren()}</div>
486
- </li>
754
+ <div {...itemAria.gridCellProps}>{renderProps.renderChildren()}</div>
755
+ </div>
487
756
  );
488
757
  }
489
758
 
@@ -498,14 +767,83 @@ export function GridListSelectionCheckbox(props: { itemKey: Key }): JSX.Element
498
767
 
499
768
  const state = context as GridState<object, GridCollection<object>>;
500
769
 
501
- const { checkboxProps } = createGridListSelectionCheckbox<object, GridCollection<object>>(
770
+ const checkboxAria = createGridListSelectionCheckbox<object, GridCollection<object>>(
502
771
  () => ({ key: props.itemKey }),
503
772
  () => state
504
773
  );
505
774
 
506
- return <input {...checkboxProps} />;
775
+ return <input {...checkboxAria.checkboxProps} />;
776
+ }
777
+
778
+ export function GridListLoadMoreItem(props: GridListLoadMoreItemProps): JSX.Element {
779
+ let ref: HTMLDivElement | undefined;
780
+ const [isPending, setIsPending] = createSignal(false);
781
+ const isLoading = () => !!props.isLoading || isPending();
782
+
783
+ const triggerLoadMore = async () => {
784
+ if (isLoading()) return;
785
+ setIsPending(true);
786
+ try {
787
+ await props.onLoadMore();
788
+ } finally {
789
+ setIsPending(false);
790
+ }
791
+ };
792
+
793
+ createEffect(() => {
794
+ if (!ref || typeof IntersectionObserver !== 'function') return;
795
+ const observer = new IntersectionObserver((entries) => {
796
+ if (entries[0]?.isIntersecting) {
797
+ void triggerLoadMore();
798
+ }
799
+ });
800
+ observer.observe(ref);
801
+ return () => observer.disconnect();
802
+ });
803
+
804
+ const renderProps = useRenderProps(
805
+ {
806
+ children: props.children ?? (() => (isLoading() ? 'Loading more...' : 'Load more')),
807
+ class: props.class,
808
+ style: props.style,
809
+ defaultClassName: 'solidaria-GridList-loadMore',
810
+ },
811
+ () => ({ isLoading: isLoading() })
812
+ );
813
+
814
+ return (
815
+ <div
816
+ ref={ref}
817
+ role="row"
818
+ tabIndex={0}
819
+ onFocus={() => {
820
+ void triggerLoadMore();
821
+ }}
822
+ class={renderProps.class()}
823
+ style={renderProps.style()}
824
+ data-loading={isLoading() || undefined}
825
+ >
826
+ {renderProps.renderChildren()}
827
+ </div>
828
+ );
829
+ }
830
+
831
+ export function GridListHeader(props: GridListHeaderProps): JSX.Element {
832
+ return (
833
+ <div class={props.class ?? 'solidaria-GridListHeader'} style={props.style}>
834
+ {props.children}
835
+ </div>
836
+ );
837
+ }
838
+
839
+ /**
840
+ * Section primitive alias for GridList composition parity.
841
+ */
842
+ export function GridListSection(props: GridListSectionProps): JSX.Element {
843
+ return <Section {...props} />;
507
844
  }
508
845
 
509
846
  // Attach Item and SelectionCheckbox as static properties
510
847
  GridList.Item = GridListItem;
511
848
  GridList.SelectionCheckbox = GridListSelectionCheckbox;
849
+ GridList.LoadMoreItem = GridListLoadMoreItem;