@proyecto-viviana/solidaria-components 0.2.5 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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/Table.tsx CHANGED
@@ -8,8 +8,10 @@
8
8
  import {
9
9
  type JSX,
10
10
  createContext,
11
+ createEffect,
11
12
  createMemo,
12
13
  createSignal,
14
+ onCleanup,
13
15
  splitProps,
14
16
  useContext,
15
17
  For,
@@ -25,6 +27,7 @@ import {
25
27
  createTableSelectAllCheckbox,
26
28
  createFocusRing,
27
29
  createHover,
30
+ mergeProps,
28
31
  type AriaTableProps,
29
32
  } from '@proyecto-viviana/solidaria';
30
33
  import {
@@ -36,6 +39,7 @@ import {
36
39
  type SortDescriptor,
37
40
  type ColumnDefinition,
38
41
  type GridNode,
42
+ type DropTarget,
39
43
  } from '@proyecto-viviana/solid-stately';
40
44
  import {
41
45
  type RenderChildren,
@@ -45,6 +49,16 @@ import {
45
49
  useRenderProps,
46
50
  filterDOMProps,
47
51
  } from './utils';
52
+ import { SharedElementTransition } from './SharedElementTransition';
53
+ import { type DragAndDropHooks } from './useDragAndDrop';
54
+ import { CollectionRendererContext, type CollectionRendererContextValue, useCollectionRenderer } from './Collection';
55
+ import { useVirtualizerContext } from './Virtualizer';
56
+ import {
57
+ getNormalizedDropTargetKey,
58
+ mergePersistedKeysIntoVirtualRange,
59
+ useDndPersistedKeys,
60
+ useRenderDropIndicator,
61
+ } from './DragAndDrop';
48
62
 
49
63
  // ============================================
50
64
  // TYPES
@@ -94,6 +108,8 @@ export interface TableProps<T extends object> extends Omit<AriaTableProps, 'chil
94
108
  style?: StyleOrFunction<TableRenderProps>;
95
109
  /** A function to render when the table is empty. */
96
110
  renderEmptyState?: () => JSX.Element;
111
+ /** Drag and drop hooks from `useDragAndDrop`. */
112
+ dragAndDropHooks?: DragAndDropHooks<T>;
97
113
  }
98
114
 
99
115
  export interface TableHeaderRenderProps {
@@ -152,6 +168,12 @@ export interface TableBodyProps<T> extends SlotProps {
152
168
  style?: StyleOrFunction<TableBodyRenderProps>;
153
169
  /** A function to render when the body is empty. */
154
170
  renderEmptyState?: () => JSX.Element;
171
+ /** Whether there are more rows to load. */
172
+ hasMore?: boolean;
173
+ /** Whether additional rows are currently loading. */
174
+ isLoading?: boolean;
175
+ /** Called when the load more sentinel becomes visible. */
176
+ onLoadMore?: () => void | Promise<void>;
155
177
  }
156
178
 
157
179
  export interface TableRowRenderProps {
@@ -206,6 +228,15 @@ export interface TableCellProps extends SlotProps {
206
228
  style?: StyleOrFunction<TableCellRenderProps>;
207
229
  }
208
230
 
231
+ export interface TableLoadMoreItemProps extends SlotProps {
232
+ onLoadMore: () => void | Promise<void>;
233
+ isLoading?: boolean;
234
+ colSpan?: number;
235
+ children?: JSX.Element;
236
+ class?: ClassNameOrFunction<{ isLoading: boolean }>;
237
+ style?: StyleOrFunction<{ isLoading: boolean }>;
238
+ }
239
+
209
240
  // ============================================
210
241
  // CONTEXT
211
242
  // ============================================
@@ -217,10 +248,14 @@ interface TableContextValue<T extends object> {
217
248
  columns: ColumnDefinition<T>[];
218
249
  isDisabled: boolean;
219
250
  showSelectionCheckboxes: boolean;
251
+ dragAndDropHooks?: DragAndDropHooks<T>;
252
+ dragState?: unknown;
253
+ dropState?: unknown;
220
254
  }
221
255
 
222
256
  export const TableContext = createContext<TableContextValue<object> | null>(null);
223
257
  export const TableStateContext = createContext<TableState<object, TableCollection<object>> | null>(null);
258
+ export const TableColumnResizeStateContext = createContext<null>(null);
224
259
 
225
260
  // Row-level context for cells
226
261
  interface TableRowContextValue {
@@ -241,7 +276,7 @@ export const TableRowContext = createContext<TableRowContextValue | null>(null);
241
276
  export function Table<T extends object>(props: TableProps<T>): JSX.Element {
242
277
  const [local, stateProps, ariaProps] = splitProps(
243
278
  props,
244
- ['class', 'style', 'slot', 'renderEmptyState'],
279
+ ['class', 'style', 'slot', 'renderEmptyState', 'dragAndDropHooks'],
245
280
  [
246
281
  'items',
247
282
  'columns',
@@ -312,10 +347,10 @@ export function Table<T extends object>(props: TableProps<T>): JSX.Element {
312
347
  isEmpty: stateProps.items.length === 0,
313
348
  }));
314
349
 
315
- // Resolve render props
350
+ // Resolve render props (class and style only — children rendered directly in JSX
351
+ // to avoid eager evaluation before context providers mount)
316
352
  const renderProps = useRenderProps(
317
353
  {
318
- children: props.children,
319
354
  class: local.class,
320
355
  style: local.style,
321
356
  defaultClassName: 'solidaria-Table',
@@ -338,6 +373,98 @@ export function Table<T extends object>(props: TableProps<T>): JSX.Element {
338
373
  const { ref: _ref2, ...rest } = focusProps as Record<string, unknown>;
339
374
  return rest;
340
375
  };
376
+ const parentCollectionRenderer = useCollectionRenderer<T>();
377
+ const getItemNodes = createMemo(() => Array.from(state.collection).filter((node) => node.type === 'item'));
378
+ const getDropTargetByIndex = (index: number, position: 'before' | 'after' | 'on'): DropTarget | null => {
379
+ const node = getItemNodes()[index];
380
+ if (!node) return null;
381
+ return { type: 'item', key: node.key, dropPosition: position };
382
+ };
383
+ const hasDroppableDnd = createMemo(() => {
384
+ const hooks = local.dragAndDropHooks;
385
+ return Boolean(
386
+ hooks?.useDroppableCollectionState &&
387
+ hooks.useDroppableCollection &&
388
+ (hooks.dropTargetDelegate || parentCollectionRenderer?.dropTargetDelegate || hooks.ListDropTargetDelegate)
389
+ );
390
+ });
391
+ const hasDraggableDnd = createMemo(() => {
392
+ const hooks = local.dragAndDropHooks;
393
+ return Boolean(hooks?.useDraggableCollectionState && hooks.useDraggableCollection);
394
+ });
395
+ const dragState = createMemo(() => {
396
+ if (!hasDraggableDnd()) return undefined;
397
+ return local.dragAndDropHooks?.useDraggableCollectionState?.({
398
+ items: stateProps.items,
399
+ });
400
+ });
401
+ const dropState = createMemo(() => {
402
+ if (!hasDroppableDnd()) return undefined;
403
+ return local.dragAndDropHooks?.useDroppableCollectionState?.({});
404
+ });
405
+ createEffect(() => {
406
+ if (!hasDraggableDnd()) return;
407
+ const hooks = local.dragAndDropHooks;
408
+ const activeDragState = dragState();
409
+ if (!hooks?.useDraggableCollection || !activeDragState) return;
410
+ hooks.useDraggableCollection({}, activeDragState, () => ref());
411
+ });
412
+ const droppableCollection = createMemo(() => {
413
+ if (!hasDroppableDnd()) return undefined;
414
+ const hooks = local.dragAndDropHooks;
415
+ const activeDropState = dropState();
416
+ if (!hooks?.useDroppableCollection || !activeDropState) return undefined;
417
+ const resolveDirection = (): 'ltr' | 'rtl' => {
418
+ const el = ref();
419
+ if (el && typeof window !== 'undefined' && typeof window.getComputedStyle === 'function') {
420
+ const dir = window.getComputedStyle(el).direction;
421
+ if (dir === 'rtl') return 'rtl';
422
+ }
423
+ return typeof document !== 'undefined' && document.dir === 'rtl' ? 'rtl' : 'ltr';
424
+ };
425
+ const dropTargetDelegate = hooks.dropTargetDelegate
426
+ ?? parentCollectionRenderer?.dropTargetDelegate
427
+ ?? (hooks.ListDropTargetDelegate
428
+ ? new hooks.ListDropTargetDelegate(
429
+ () => state.collection,
430
+ () => ref(),
431
+ { layout: 'grid', orientation: 'vertical', direction: resolveDirection() }
432
+ )
433
+ : undefined);
434
+ if (!dropTargetDelegate) return undefined;
435
+ return hooks.useDroppableCollection(
436
+ {
437
+ dropTargetDelegate,
438
+ keyboardDelegate: {
439
+ getFirstKey: () => state.collection.getFirstKey?.() ?? null,
440
+ getLastKey: () => state.collection.getLastKey?.() ?? null,
441
+ getKeyBelow: (key) => state.collection.getKeyAfter?.(key) ?? null,
442
+ getKeyAbove: (key) => state.collection.getKeyBefore?.(key) ?? null,
443
+ getKeyLeftOf: (key) =>
444
+ resolveDirection() === 'rtl'
445
+ ? state.collection.getKeyAfter?.(key) ?? null
446
+ : state.collection.getKeyBefore?.(key) ?? null,
447
+ getKeyRightOf: (key) =>
448
+ resolveDirection() === 'rtl'
449
+ ? state.collection.getKeyBefore?.(key) ?? null
450
+ : state.collection.getKeyAfter?.(key) ?? null,
451
+ getKeyPageBelow: (key) => state.collection.getKeyAfter?.(key) ?? null,
452
+ getKeyPageAbove: (key) => state.collection.getKeyBefore?.(key) ?? null,
453
+ },
454
+ },
455
+ activeDropState,
456
+ () => ref()
457
+ );
458
+ });
459
+ const isRootDropTarget = createMemo(() => {
460
+ return Boolean(dropState()?.target?.type === 'root');
461
+ });
462
+ const dndRenderDropIndicator = createMemo(() => useRenderDropIndicator(local.dragAndDropHooks, dropState()));
463
+ const dndDropIndicator = (index: number, position: 'before' | 'after' | 'on') => {
464
+ const target = getDropTargetByIndex(index, position);
465
+ if (!target || target.type !== 'item') return undefined;
466
+ return dndRenderDropIndicator()?.(target);
467
+ };
341
468
 
342
469
  const contextValue = createMemo<TableContextValue<T>>(() => ({
343
470
  state,
@@ -346,24 +473,41 @@ export function Table<T extends object>(props: TableProps<T>): JSX.Element {
346
473
  columns: stateProps.columns,
347
474
  isDisabled: false,
348
475
  showSelectionCheckboxes: stateProps.showSelectionCheckboxes ?? false,
476
+ dragAndDropHooks: local.dragAndDropHooks,
477
+ dragState: dragState(),
478
+ dropState: dropState(),
479
+ }));
480
+ const collectionRenderer = createMemo<CollectionRendererContextValue<unknown>>(() => ({
481
+ ...parentCollectionRenderer,
482
+ renderItem: (item) => item as JSX.Element,
483
+ renderDropIndicator: (index: number, position: 'before' | 'after' | 'on') =>
484
+ dndDropIndicator(index, position) ?? parentCollectionRenderer?.renderDropIndicator?.(index, position),
349
485
  }));
350
486
 
351
487
  return (
352
- <TableContext.Provider value={contextValue() as TableContextValue<object>}>
488
+ <TableContext.Provider value={contextValue() as unknown as TableContextValue<object>}>
353
489
  <TableStateContext.Provider value={state as unknown as TableState<object, TableCollection<object>>}>
354
- <table
355
- ref={setRef}
356
- {...domProps()}
357
- {...cleanGridProps()}
358
- {...cleanFocusProps()}
359
- class={renderProps.class()}
360
- style={renderProps.style()}
361
- data-focused={state.isFocused || undefined}
362
- data-focus-visible={isFocusVisible() || undefined}
363
- data-empty={stateProps.items.length === 0 || undefined}
364
- >
365
- {renderProps.renderChildren()}
366
- </table>
490
+ <CollectionRendererContext.Provider value={collectionRenderer()}>
491
+ <table
492
+ ref={setRef}
493
+ {...mergeProps(
494
+ domProps(),
495
+ cleanGridProps(),
496
+ cleanFocusProps(),
497
+ (droppableCollection()?.collectionProps as Record<string, unknown> | undefined) ?? {}
498
+ )}
499
+ class={renderProps.class()}
500
+ style={renderProps.style()}
501
+ data-focused={state.isFocused || undefined}
502
+ data-focus-visible={isFocusVisible() || undefined}
503
+ data-empty={stateProps.items.length === 0 || undefined}
504
+ data-drop-target={isRootDropTarget() || undefined}
505
+ >
506
+ {typeof props.children === 'function'
507
+ ? props.children(renderValues())
508
+ : props.children}
509
+ </table>
510
+ </CollectionRendererContext.Provider>
367
511
  </TableStateContext.Provider>
368
512
  </TableContext.Provider>
369
513
  );
@@ -373,7 +517,7 @@ export function Table<T extends object>(props: TableProps<T>): JSX.Element {
373
517
  * A header row in a table containing column headers.
374
518
  */
375
519
  export function TableHeader(props: TableHeaderProps): JSX.Element {
376
- const [local] = splitProps(props, ['class', 'style', 'slot']);
520
+ const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'children']);
377
521
 
378
522
  // Get context
379
523
  const context = useContext(TableContext);
@@ -404,8 +548,8 @@ export function TableHeader(props: TableHeaderProps): JSX.Element {
404
548
  };
405
549
 
406
550
  return (
407
- <thead {...cleanRowGroupProps()} class={renderProps.class()} style={renderProps.style()}>
408
- <tr role="row">{props.children}</tr>
551
+ <thead {...domProps} {...cleanRowGroupProps()} class={renderProps.class()} style={renderProps.style()}>
552
+ <tr role="row">{local.children}</tr>
409
553
  </thead>
410
554
  );
411
555
  }
@@ -414,7 +558,7 @@ export function TableHeader(props: TableHeaderProps): JSX.Element {
414
558
  * A column header in a table.
415
559
  */
416
560
  export function TableColumn(props: TableColumnProps): JSX.Element {
417
- const [local] = splitProps(props, ['class', 'style', 'slot', 'id', 'allowsSorting']);
561
+ const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'id', 'allowsSorting', 'children']);
418
562
 
419
563
  // Get context
420
564
  const context = useContext(TableContext);
@@ -446,7 +590,7 @@ export function TableColumn(props: TableColumnProps): JSX.Element {
446
590
  });
447
591
 
448
592
  // Create column header aria props
449
- const { columnHeaderProps } = createTableColumnHeader<object>(
593
+ const columnHeaderAria = createTableColumnHeader<object>(
450
594
  () => ({
451
595
  node: columnNode(),
452
596
  allowsSorting: local.allowsSorting,
@@ -481,10 +625,9 @@ export function TableColumn(props: TableColumnProps): JSX.Element {
481
625
  isHovered: isHovered(),
482
626
  }));
483
627
 
484
- // Resolve render props
628
+ // Resolve render props (children rendered directly in JSX to avoid eager evaluation)
485
629
  const renderProps = useRenderProps(
486
630
  {
487
- children: props.children,
488
631
  class: local.class,
489
632
  style: local.style,
490
633
  defaultClassName: 'solidaria-Table-column',
@@ -494,7 +637,7 @@ export function TableColumn(props: TableColumnProps): JSX.Element {
494
637
 
495
638
  // Remove ref from spread props
496
639
  const cleanColumnHeaderProps = () => {
497
- const { ref: _ref1, ...rest } = columnHeaderProps as Record<string, unknown>;
640
+ const { ref: _ref1, ...rest } = columnHeaderAria.columnHeaderProps as Record<string, unknown>;
498
641
  return rest;
499
642
  };
500
643
  const cleanHoverProps = () => {
@@ -509,6 +652,7 @@ export function TableColumn(props: TableColumnProps): JSX.Element {
509
652
  return (
510
653
  <th
511
654
  ref={setRef}
655
+ {...domProps}
512
656
  {...cleanColumnHeaderProps()}
513
657
  {...cleanHoverProps()}
514
658
  {...cleanFocusProps()}
@@ -520,7 +664,9 @@ export function TableColumn(props: TableColumnProps): JSX.Element {
520
664
  data-focused={state.focusedKey === local.id || undefined}
521
665
  data-focus-visible={(isFocusVisible() && state.focusedKey === local.id) || undefined}
522
666
  >
523
- {renderProps.renderChildren()}
667
+ {typeof local.children === 'function'
668
+ ? local.children(renderValues())
669
+ : local.children}
524
670
  </th>
525
671
  );
526
672
  }
@@ -529,7 +675,7 @@ export function TableColumn(props: TableColumnProps): JSX.Element {
529
675
  * The body of a table containing data rows.
530
676
  */
531
677
  export function TableBody<T extends object>(props: TableBodyProps<T>): JSX.Element {
532
- const [local] = splitProps(props, ['items', 'class', 'style', 'slot', 'renderEmptyState']);
678
+ const [local, domProps] = splitProps(props, ['items', 'class', 'style', 'slot', 'renderEmptyState', 'hasMore', 'isLoading', 'onLoadMore', 'children']);
533
679
 
534
680
  // Get context
535
681
  const context = useContext(TableContext);
@@ -563,21 +709,173 @@ export function TableBody<T extends object>(props: TableBodyProps<T>): JSX.Eleme
563
709
  };
564
710
 
565
711
  const isEmpty = () => items().length === 0;
712
+ const virtualizer = useVirtualizerContext();
713
+ const parentCollectionRenderer = useCollectionRenderer<T>();
714
+ const rowNodes = createMemo(() => Array.from(context.collection).filter((node) => node.type === 'item'));
715
+ const persistedKeys = useDndPersistedKeys(
716
+ { focusedKey: () => context.state.focusedKey },
717
+ context.dragAndDropHooks,
718
+ context.dropState as { target?: DropTarget | null } | undefined,
719
+ context.collection
720
+ );
721
+ const virtualRange = createMemo(() => {
722
+ if (!virtualizer || !parentCollectionRenderer?.isVirtualized) return null;
723
+ const rowCount = items().length;
724
+ const baseRange = virtualizer.getVisibleRange(rowCount);
725
+ const persistedIndexes = Array.from(persistedKeys())
726
+ .map((key) => rowNodes().findIndex((node) => node.key === key))
727
+ .filter((index) => index >= 0);
728
+ const dropTarget = (context.dropState as { target?: DropTarget | null } | undefined)?.target;
729
+ const normalizedDropKey = getNormalizedDropTargetKey(dropTarget, context.collection);
730
+ const focusedKey = context.state.focusedKey;
731
+ const focusedIndex = focusedKey != null ? rowNodes().findIndex((node) => node.key === focusedKey) : -1;
732
+ const forceIncludeIndexes = [
733
+ dropTarget?.type === 'item' ? rowNodes().findIndex((node) => node.key === dropTarget.key) : -1,
734
+ normalizedDropKey != null ? rowNodes().findIndex((node) => node.key === normalizedDropKey) : -1,
735
+ dropTarget?.type === 'item' ? -1 : focusedIndex,
736
+ ].filter((index) => index >= 0);
737
+ return mergePersistedKeysIntoVirtualRange(baseRange, persistedIndexes, rowCount, virtualizer, 80, {
738
+ forceIncludeIndexes,
739
+ forceIncludeMaxSpan: 320,
740
+ });
741
+ });
742
+ createEffect(() => {
743
+ if (!virtualizer || !parentCollectionRenderer?.isVirtualized) return;
744
+ virtualizer.setDropTargetItemCountResolver(() => items().length);
745
+ virtualizer.setDropTargetIndexResolver((key) => {
746
+ const index = rowNodes().findIndex((node) => node.key === key);
747
+ return index >= 0 ? index : null;
748
+ });
749
+ virtualizer.setDropTargetResolver((target) => {
750
+ const node = rowNodes()[target.index];
751
+ if (!node) return target;
752
+ return {
753
+ ...target,
754
+ key: typeof node.key === 'string' || typeof node.key === 'number' ? node.key : undefined,
755
+ };
756
+ });
757
+ onCleanup(() => {
758
+ virtualizer.setDropTargetIndexResolver(undefined);
759
+ virtualizer.setDropTargetItemCountResolver(undefined);
760
+ virtualizer.setDropTargetResolver(undefined);
761
+ });
762
+ });
763
+ const visibleItems = createMemo(() => {
764
+ const range = virtualRange();
765
+ if (!range) return items();
766
+ return items().slice(range.start, range.end);
767
+ });
768
+ const spacerColSpan = () => context.columns.length + (context.showSelectionCheckboxes ? 1 : 0);
566
769
 
567
770
  return (
568
- <tbody {...cleanRowGroupProps()} class={renderProps.class()} style={renderProps.style()}>
569
- <Show when={isEmpty() && local.renderEmptyState} fallback={<For each={items()}>{(item) => props.children?.(item)}</For>}>
771
+ <tbody {...domProps} {...cleanRowGroupProps()} class={renderProps.class()} style={renderProps.style()}>
772
+ <SharedElementTransition>
773
+ <Show when={isEmpty() && local.renderEmptyState} fallback={
774
+ <>
775
+ {virtualRange()?.offsetTop
776
+ ? (
777
+ <tr role="presentation" aria-hidden="true" data-virtualizer-spacer="top">
778
+ <td colSpan={spacerColSpan()} style={{ height: `${virtualRange()!.offsetTop}px`, padding: '0', border: '0' }} />
779
+ </tr>
780
+ )
781
+ : null}
782
+ <For each={visibleItems()}>
783
+ {(item, index) => {
784
+ const itemIndex = () => (virtualRange()?.start ?? 0) + index();
785
+ const beforeIndicator = () => parentCollectionRenderer?.renderDropIndicator?.(itemIndex(), 'before');
786
+ const onIndicator = () => parentCollectionRenderer?.renderDropIndicator?.(itemIndex(), 'on');
787
+ const afterIndicator = () => parentCollectionRenderer?.renderDropIndicator?.(itemIndex(), 'after');
788
+ return (
789
+ <>
790
+ {beforeIndicator()}
791
+ {onIndicator()}
792
+ {local.children?.(item)}
793
+ {afterIndicator()}
794
+ </>
795
+ );
796
+ }}
797
+ </For>
798
+ {virtualRange()?.offsetBottom
799
+ ? (
800
+ <tr role="presentation" aria-hidden="true" data-virtualizer-spacer="bottom">
801
+ <td colSpan={spacerColSpan()} style={{ height: `${virtualRange()!.offsetBottom}px`, padding: '0', border: '0' }} />
802
+ </tr>
803
+ )
804
+ : null}
805
+ </>
806
+ }>
570
807
  {local.renderEmptyState?.()}
571
808
  </Show>
809
+ </SharedElementTransition>
810
+ <Show when={local.hasMore && local.onLoadMore}>
811
+ <TableLoadMoreItem
812
+ onLoadMore={local.onLoadMore!}
813
+ isLoading={local.isLoading}
814
+ colSpan={spacerColSpan()}
815
+ />
816
+ </Show>
572
817
  </tbody>
573
818
  );
574
819
  }
575
820
 
821
+ export function TableLoadMoreItem(props: TableLoadMoreItemProps): JSX.Element {
822
+ let ref: HTMLTableRowElement | undefined;
823
+ const [isPending, setIsPending] = createSignal(false);
824
+ const isLoading = () => !!props.isLoading || isPending();
825
+
826
+ const triggerLoadMore = async () => {
827
+ if (isLoading()) return;
828
+ setIsPending(true);
829
+ try {
830
+ await props.onLoadMore();
831
+ } finally {
832
+ setIsPending(false);
833
+ }
834
+ };
835
+
836
+ createEffect(() => {
837
+ if (!ref || typeof IntersectionObserver !== 'function') return;
838
+ const observer = new IntersectionObserver((entries) => {
839
+ if (entries[0]?.isIntersecting) {
840
+ void triggerLoadMore();
841
+ }
842
+ });
843
+ observer.observe(ref);
844
+ return () => observer.disconnect();
845
+ });
846
+
847
+ const renderProps = useRenderProps(
848
+ {
849
+ children: props.children ?? (() => (isLoading() ? 'Loading more...' : 'Load more')),
850
+ class: props.class,
851
+ style: props.style,
852
+ defaultClassName: 'solidaria-Table-loadMore',
853
+ },
854
+ () => ({ isLoading: isLoading() })
855
+ );
856
+
857
+ return (
858
+ <tr
859
+ ref={ref}
860
+ role="row"
861
+ tabIndex={0}
862
+ onFocus={() => {
863
+ void triggerLoadMore();
864
+ }}
865
+ class={renderProps.class()}
866
+ style={renderProps.style()}
867
+ data-loading={isLoading() || undefined}
868
+ >
869
+ <td colSpan={props.colSpan ?? 1}>{renderProps.renderChildren()}</td>
870
+ </tr>
871
+ );
872
+ }
873
+
576
874
  /**
577
875
  * A row in a table.
578
876
  */
579
877
  export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element {
580
- const [local] = splitProps(props, ['class', 'style', 'slot', 'id', 'item', 'onAction']);
878
+ const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'id', 'item', 'onAction', 'children']);
581
879
 
582
880
  // Get context
583
881
  const context = useContext(TableContext);
@@ -585,6 +883,7 @@ export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element
585
883
  throw new Error('TableRow must be used within a Table');
586
884
  }
587
885
  const { state, collection } = context;
886
+ const tableContext = context as unknown as TableContextValue<T>;
588
887
 
589
888
  // Create ref signal
590
889
  const [ref, setRef] = createSignal<HTMLTableRowElement | null>(null);
@@ -609,7 +908,7 @@ export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element
609
908
  });
610
909
 
611
910
  // Create row aria props
612
- const { rowProps, isSelected, isDisabled, isPressed } = createTableRow<object>(
911
+ const rowAria = createTableRow<object>(
613
912
  () => ({
614
913
  node: rowNode(),
615
914
  onAction: local.onAction,
@@ -617,11 +916,14 @@ export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element
617
916
  () => state as TableState<object, TableCollection<object>>,
618
917
  ref
619
918
  );
919
+ const isSelected = () => rowAria.isSelected;
920
+ const isDisabled = () => rowAria.isDisabled;
921
+ const isPressed = () => rowAria.isPressed;
620
922
 
621
923
  // Create hover
622
924
  const { isHovered, hoverProps } = createHover({
623
925
  get isDisabled() {
624
- return isDisabled;
926
+ return isDisabled();
625
927
  },
626
928
  });
627
929
 
@@ -630,21 +932,39 @@ export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element
630
932
 
631
933
  // Check if focused
632
934
  const isFocused = createMemo(() => state.focusedKey === local.id);
935
+ const draggableItem = createMemo(() => {
936
+ if (!tableContext.dragAndDropHooks?.useDraggableItem || !tableContext.dragState) return undefined;
937
+ return tableContext.dragAndDropHooks.useDraggableItem(
938
+ {
939
+ key: local.id as string | number,
940
+ },
941
+ tableContext.dragState as Parameters<NonNullable<DragAndDropHooks<T>['useDraggableItem']>>[1]
942
+ );
943
+ });
944
+ const droppableItem = createMemo(() => {
945
+ if (!tableContext.dragAndDropHooks?.useDroppableItem || !tableContext.dropState) return undefined;
946
+ return tableContext.dragAndDropHooks.useDroppableItem(
947
+ {
948
+ key: local.id as string | number,
949
+ },
950
+ tableContext.dropState as Parameters<NonNullable<DragAndDropHooks<T>['useDroppableItem']>>[1],
951
+ () => ref()
952
+ );
953
+ });
633
954
 
634
955
  // Render props values
635
956
  const renderValues = createMemo<TableRowRenderProps>(() => ({
636
- isSelected,
957
+ isSelected: isSelected(),
637
958
  isFocused: isFocused(),
638
959
  isFocusVisible: isFocusVisible() && isFocused(),
639
- isPressed,
960
+ isPressed: isPressed(),
640
961
  isHovered: isHovered(),
641
- isDisabled,
962
+ isDisabled: isDisabled(),
642
963
  }));
643
964
 
644
- // Resolve render props
965
+ // Resolve render props (children rendered directly in JSX to avoid eager evaluation)
645
966
  const renderProps = useRenderProps(
646
967
  {
647
- children: props.children,
648
968
  class: local.class,
649
969
  style: local.style,
650
970
  defaultClassName: 'solidaria-Table-row',
@@ -654,7 +974,7 @@ export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element
654
974
 
655
975
  // Remove ref from spread props
656
976
  const cleanRowProps = () => {
657
- const { ref: _ref1, ...rest } = rowProps as Record<string, unknown>;
977
+ const { ref: _ref1, ...rest } = rowAria.rowProps as Record<string, unknown>;
658
978
  return rest;
659
979
  };
660
980
  const cleanHoverProps = () => {
@@ -675,19 +995,28 @@ export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element
675
995
  <TableRowContext.Provider value={rowContextValue}>
676
996
  <tr
677
997
  ref={setRef}
678
- {...cleanRowProps()}
679
- {...cleanHoverProps()}
680
- {...cleanFocusProps()}
998
+ {...domProps}
999
+ {...mergeProps(
1000
+ cleanRowProps(),
1001
+ cleanHoverProps(),
1002
+ cleanFocusProps(),
1003
+ (draggableItem()?.dragProps as Record<string, unknown> | undefined) ?? {},
1004
+ (droppableItem()?.dropProps as Record<string, unknown> | undefined) ?? {}
1005
+ )}
681
1006
  class={renderProps.class()}
682
1007
  style={renderProps.style()}
683
- data-selected={isSelected || undefined}
1008
+ data-selected={isSelected() || undefined}
684
1009
  data-focused={isFocused() || undefined}
685
1010
  data-focus-visible={(isFocusVisible() && isFocused()) || undefined}
686
- data-pressed={isPressed || undefined}
1011
+ data-pressed={isPressed() || undefined}
687
1012
  data-hovered={isHovered() || undefined}
688
- data-disabled={isDisabled || undefined}
1013
+ data-disabled={isDisabled() || undefined}
1014
+ data-dragging={draggableItem()?.isDragging || undefined}
1015
+ data-drop-target={droppableItem()?.isDropTarget || undefined}
689
1016
  >
690
- {renderProps.renderChildren()}
1017
+ {typeof local.children === 'function'
1018
+ ? local.children(renderValues())
1019
+ : local.children}
691
1020
  </tr>
692
1021
  </TableRowContext.Provider>
693
1022
  );
@@ -697,7 +1026,7 @@ export function TableRow<T extends object>(props: TableRowProps<T>): JSX.Element
697
1026
  * A cell in a table row.
698
1027
  */
699
1028
  export function TableCell(props: TableCellProps): JSX.Element {
700
- const [local] = splitProps(props, ['class', 'style', 'slot', 'id']);
1029
+ const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'id', 'children']);
701
1030
 
702
1031
  // Get context
703
1032
  const tableContext = useContext(TableContext);
@@ -740,13 +1069,14 @@ export function TableCell(props: TableCellProps): JSX.Element {
740
1069
  });
741
1070
 
742
1071
  // Create cell aria props
743
- const { gridCellProps, isPressed } = createTableCell<object>(
1072
+ const cellAria = createTableCell<object>(
744
1073
  () => ({
745
1074
  node: cellNode(),
746
1075
  }),
747
1076
  () => state as TableState<object, TableCollection<object>>,
748
1077
  ref
749
1078
  );
1079
+ const isPressed = () => cellAria.isPressed;
750
1080
 
751
1081
  // Create hover
752
1082
  const { isHovered, hoverProps } = createHover({
@@ -763,14 +1093,13 @@ export function TableCell(props: TableCellProps): JSX.Element {
763
1093
  const renderValues = createMemo<TableCellRenderProps>(() => ({
764
1094
  isFocused: isFocused(),
765
1095
  isFocusVisible: isFocusVisible() && isFocused(),
766
- isPressed,
1096
+ isPressed: isPressed(),
767
1097
  isHovered: isHovered(),
768
1098
  }));
769
1099
 
770
- // Resolve render props
1100
+ // Resolve render props (children rendered directly in JSX to avoid eager evaluation)
771
1101
  const renderProps = useRenderProps(
772
1102
  {
773
- children: props.children,
774
1103
  class: local.class,
775
1104
  style: local.style,
776
1105
  defaultClassName: 'solidaria-Table-cell',
@@ -780,7 +1109,7 @@ export function TableCell(props: TableCellProps): JSX.Element {
780
1109
 
781
1110
  // Remove ref from spread props
782
1111
  const cleanCellProps = () => {
783
- const { ref: _ref1, ...rest } = gridCellProps as Record<string, unknown>;
1112
+ const { ref: _ref1, ...rest } = cellAria.gridCellProps as Record<string, unknown>;
784
1113
  return rest;
785
1114
  };
786
1115
  const cleanHoverProps = () => {
@@ -795,6 +1124,7 @@ export function TableCell(props: TableCellProps): JSX.Element {
795
1124
  return (
796
1125
  <td
797
1126
  ref={setRef}
1127
+ {...domProps}
798
1128
  {...cleanCellProps()}
799
1129
  {...cleanHoverProps()}
800
1130
  {...cleanFocusProps()}
@@ -802,10 +1132,12 @@ export function TableCell(props: TableCellProps): JSX.Element {
802
1132
  style={renderProps.style()}
803
1133
  data-focused={isFocused() || undefined}
804
1134
  data-focus-visible={(isFocusVisible() && isFocused()) || undefined}
805
- data-pressed={isPressed || undefined}
1135
+ data-pressed={isPressed() || undefined}
806
1136
  data-hovered={isHovered() || undefined}
807
1137
  >
808
- {renderProps.renderChildren()}
1138
+ {typeof local.children === 'function'
1139
+ ? local.children(renderValues())
1140
+ : local.children}
809
1141
  </td>
810
1142
  );
811
1143
  }
@@ -821,12 +1153,12 @@ export function TableSelectionCheckbox(props: { rowKey: Key }): JSX.Element {
821
1153
 
822
1154
  const { state } = context;
823
1155
 
824
- const { checkboxProps } = createTableSelectionCheckbox<object>(
1156
+ const selectionCheckboxAria = createTableSelectionCheckbox<object>(
825
1157
  () => ({ key: props.rowKey }),
826
1158
  () => state as TableState<object, TableCollection<object>>
827
1159
  );
828
1160
 
829
- return <input {...checkboxProps} />;
1161
+ return <input {...selectionCheckboxAria.checkboxProps} />;
830
1162
  }
831
1163
 
832
1164
  /**
@@ -840,18 +1172,46 @@ export function TableSelectAllCheckbox(): JSX.Element {
840
1172
 
841
1173
  const { state } = context;
842
1174
 
843
- const { checkboxProps } = createTableSelectAllCheckbox<object>(
1175
+ const selectAllCheckboxAria = createTableSelectAllCheckbox<object>(
844
1176
  () => state as TableState<object, TableCollection<object>>
845
1177
  );
846
1178
 
847
- return <input {...checkboxProps} />;
1179
+ return <input {...selectAllCheckboxAria.checkboxProps} />;
848
1180
  }
849
1181
 
850
1182
  // Attach components as static properties
851
1183
  Table.Header = TableHeader;
852
1184
  Table.Column = TableColumn;
853
1185
  Table.Body = TableBody;
1186
+ Table.LoadMoreItem = TableLoadMoreItem;
854
1187
  Table.Row = TableRow;
855
1188
  Table.Cell = TableCell;
856
1189
  Table.SelectionCheckbox = TableSelectionCheckbox;
857
1190
  Table.SelectAllCheckbox = TableSelectAllCheckbox;
1191
+
1192
+ export interface ColumnResizerProps extends SlotProps {
1193
+ class?: string;
1194
+ style?: JSX.CSSProperties;
1195
+ }
1196
+
1197
+ export function ColumnResizer(props: ColumnResizerProps): JSX.Element {
1198
+ return <div role="separator" class={props.class ?? 'solidaria-Table-columnResizer'} style={props.style} />;
1199
+ }
1200
+
1201
+ export interface ResizableTableContainerProps extends SlotProps {
1202
+ children?: JSX.Element;
1203
+ class?: string;
1204
+ style?: JSX.CSSProperties;
1205
+ }
1206
+
1207
+ export function ResizableTableContainer(props: ResizableTableContainerProps): JSX.Element {
1208
+ return (
1209
+ <div class={props.class ?? 'solidaria-ResizableTableContainer'} style={props.style}>
1210
+ {props.children}
1211
+ </div>
1212
+ );
1213
+ }
1214
+
1215
+ export function useTableOptions() {
1216
+ return useContext(TableContext);
1217
+ }