@pallasoft/ps-table-components 1.25.0 → 1.27.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 (59) hide show
  1. package/dist/components/ps-data-table.d.ts +10 -1
  2. package/dist/components/ps-data-table.js +93 -17
  3. package/dist/components/ps-select-all-matching-banner.d.ts +10 -0
  4. package/dist/components/ps-select-all-matching-banner.js +10 -0
  5. package/dist/components/ps-table-pagination.d.ts +3 -1
  6. package/dist/components/ps-table-pagination.js +11 -3
  7. package/dist/components/ps-table-reorder-frame.d.ts +2 -1
  8. package/dist/components/ps-table-reorder-frame.js +8 -4
  9. package/dist/constants/ps-server-rows-config.d.ts +8 -0
  10. package/dist/constants/ps-server-rows-config.js +11 -0
  11. package/dist/constants/ps-table-drop-classes.js +2 -2
  12. package/dist/constants/ps-table-pagination-position.d.ts +6 -0
  13. package/dist/constants/ps-table-pagination-position.js +8 -0
  14. package/dist/esm/components/ps-data-table.js +94 -18
  15. package/dist/esm/components/ps-select-all-matching-banner.js +6 -0
  16. package/dist/esm/components/ps-table-pagination.js +11 -3
  17. package/dist/esm/components/ps-table-reorder-frame.js +8 -4
  18. package/dist/esm/constants/ps-server-rows-config.js +8 -0
  19. package/dist/esm/constants/ps-table-drop-classes.js +2 -2
  20. package/dist/esm/constants/ps-table-pagination-position.js +5 -0
  21. package/dist/esm/hooks/use-ps-server-rows.js +209 -0
  22. package/dist/esm/hooks/use-ps-table-reorder.js +8 -1
  23. package/dist/esm/index.js +12 -0
  24. package/dist/esm/interfaces/ps-server-entry.js +1 -0
  25. package/dist/esm/interfaces/ps-server-page.js +1 -0
  26. package/dist/esm/interfaces/ps-server-rows-input.js +1 -0
  27. package/dist/esm/interfaces/ps-server-rows-result.js +1 -0
  28. package/dist/esm/interfaces/ps-table-cursor-paging.js +1 -0
  29. package/dist/esm/interfaces/ps-table-select-all-matching.js +1 -0
  30. package/dist/esm/interfaces/table-drag-size.js +1 -0
  31. package/dist/esm/services/ps-server-rows-service.js +36 -0
  32. package/dist/esm/services/ps-table-reorder-service.js +12 -0
  33. package/dist/hooks/use-ps-server-rows.d.ts +3 -0
  34. package/dist/hooks/use-ps-server-rows.js +213 -0
  35. package/dist/hooks/use-ps-table-reorder.d.ts +2 -0
  36. package/dist/hooks/use-ps-table-reorder.js +8 -1
  37. package/dist/index.d.ts +12 -0
  38. package/dist/index.js +12 -0
  39. package/dist/interfaces/ps-server-entry.d.ts +6 -0
  40. package/dist/interfaces/ps-server-entry.js +2 -0
  41. package/dist/interfaces/ps-server-page.d.ts +7 -0
  42. package/dist/interfaces/ps-server-page.js +2 -0
  43. package/dist/interfaces/ps-server-rows-input.d.ts +13 -0
  44. package/dist/interfaces/ps-server-rows-input.js +2 -0
  45. package/dist/interfaces/ps-server-rows-result.d.ts +18 -0
  46. package/dist/interfaces/ps-server-rows-result.js +2 -0
  47. package/dist/interfaces/ps-table-cursor-paging.d.ts +12 -0
  48. package/dist/interfaces/ps-table-cursor-paging.js +2 -0
  49. package/dist/interfaces/ps-table-select-all-matching.d.ts +7 -0
  50. package/dist/interfaces/ps-table-select-all-matching.js +2 -0
  51. package/dist/interfaces/table-drag-size.d.ts +5 -0
  52. package/dist/interfaces/table-drag-size.js +2 -0
  53. package/dist/interfaces/table-reorder-item.d.ts +1 -0
  54. package/dist/interfaces/table-reorder.d.ts +1 -0
  55. package/dist/services/ps-server-rows-service.d.ts +11 -0
  56. package/dist/services/ps-server-rows-service.js +40 -0
  57. package/dist/services/ps-table-reorder-service.d.ts +2 -0
  58. package/dist/services/ps-table-reorder-service.js +12 -0
  59. package/package.json +2 -2
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Fragment, Suspense, lazy, useCallback, useDeferredValue, useEffect, useMemo, useRef, useState, } from 'react';
2
+ import { Fragment, Suspense, lazy, useCallback, useDeferredValue, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
3
3
  import { PsDragGrip, PsSkeletonTable } from '@pallasoft/ps-core-components';
4
4
  import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
5
5
  import { PsCheckbox } from '@pallasoft/ps-input-components';
6
6
  import { PsCollapse } from '@pallasoft/ps-motion-components';
7
7
  import { useVirtualizer, useWindowVirtualizer } from '@tanstack/react-virtual';
8
8
  import { PsTableControlClasses } from '../constants/ps-table-control-classes';
9
+ import { PsTablePaginationPosition } from '../constants/ps-table-pagination-position';
9
10
  import { PsTableHideBelowClasses } from '../constants/table-breakpoint';
10
11
  import { PsTableConfig } from '../constants/table-config';
11
12
  import { usePsIncrementalCount } from '../hooks/use-ps-incremental-count';
@@ -19,6 +20,7 @@ import { PsTablePinService } from '../services/ps-table-pin-service';
19
20
  import { PsTableQueryService } from '../services/ps-table-query-service';
20
21
  import { PsTableReorderService } from '../services/ps-table-reorder-service';
21
22
  import { PsTableScrollService } from '../services/ps-table-scroll-service';
23
+ import { PsSelectAllMatchingBanner } from './ps-select-all-matching-banner';
22
24
  import { PsTableBulkBar } from './ps-table-bulk-bar';
23
25
  import { PsTableCardList } from './ps-table-card-list';
24
26
  import { PsTableColumnPicker } from './ps-table-column-picker';
@@ -48,7 +50,7 @@ const alignClassNames = {
48
50
  left: 'text-left',
49
51
  right: 'text-right tabular-nums',
50
52
  };
51
- export const PsDataTable = ({ caption, className, columns, defaultSort, density = 'normal', expandedRowIds, mobileLayout = 'cards', getBulkActions, getRowId, getRowLabel, getSearchText, grouping, isLoading = false, isPending = false, layout = 'auto', onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, pinFirstColumn = true, query, renderExpandedRow, reorder, rows, searchingLabel = PsTableConfig.searchingLabel, searchPlaceholder = PsTableConfig.searchPlaceholder, toolbarActions, totalRows, }) => {
53
+ export const PsDataTable = ({ caption, className, clearSelectionKey, columns, cursorPaging, defaultSort, density = 'normal', expandedRowIds, mobileLayout = 'cards', getBulkActions, getRowId, getRowLabel, getSearchText, grouping, isLoading = false, isPending = false, isRefreshing = false, layout = 'auto', onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, paginationPosition = PsTablePaginationPosition.bottom, pinFirstColumn = true, query, renderExpandedRow, reorder, rows, searchingLabel = PsTableConfig.searchingLabel, searchPlaceholder = PsTableConfig.searchPlaceholder, selectAllMatching, stickyTop, toolbarActions, totalRows, }) => {
52
54
  const containerRef = useRef(null);
53
55
  const [uncontrolledPage, setUncontrolledPage] = useState(1);
54
56
  const [uncontrolledQuery, setUncontrolledQuery] = useState(() => PsTableQueryService.empty(defaultSort ? defaultSort : null));
@@ -56,7 +58,50 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
56
58
  const [toggledGroupKeys, setToggledGroupKeys] = useState([]);
57
59
  const [closingGroupKeys, setClosingGroupKeys] = useState([]);
58
60
  const [openedGroupKeys, setOpenedGroupKeys] = useState([]);
61
+ const [seenClearKey, setSeenClearKey] = useState(clearSelectionKey);
62
+ const stickyRegionRef = useRef(null);
63
+ const stickySentinelRef = useRef(null);
64
+ const [stickyHeight, setStickyHeight] = useState(0);
65
+ const [isStuck, setIsStuck] = useState(false);
59
66
  const currentQuery = query ? query : uncontrolledQuery;
67
+ if (seenClearKey !== clearSelectionKey) {
68
+ setSeenClearKey(clearSelectionKey);
69
+ setSelectedIds([]);
70
+ }
71
+ useLayoutEffect(() => {
72
+ const region = stickyRegionRef.current;
73
+ if (stickyTop === undefined || region === null)
74
+ return;
75
+ const measure = () => setStickyHeight(region.offsetHeight);
76
+ const observer = new ResizeObserver(measure);
77
+ measure();
78
+ observer.observe(region);
79
+ return () => observer.disconnect();
80
+ }, [stickyTop]);
81
+ useEffect(() => {
82
+ const region = stickyRegionRef.current;
83
+ const sentinel = stickySentinelRef.current;
84
+ if (stickyTop === undefined || region === null || sentinel === null)
85
+ return;
86
+ let frame = 0;
87
+ const check = () => {
88
+ frame = 0;
89
+ setIsStuck(sentinel.getBoundingClientRect().top < Number.parseFloat(getComputedStyle(region).top));
90
+ };
91
+ const schedule = () => {
92
+ if (frame === 0)
93
+ frame = window.requestAnimationFrame(check);
94
+ };
95
+ check();
96
+ window.addEventListener('scroll', schedule, { passive: true });
97
+ window.addEventListener('resize', schedule);
98
+ return () => {
99
+ window.removeEventListener('scroll', schedule);
100
+ window.removeEventListener('resize', schedule);
101
+ if (frame !== 0)
102
+ window.cancelAnimationFrame(frame);
103
+ };
104
+ }, [stickyTop]);
60
105
  const { filters, hiddenColumnIds, search, sort } = currentQuery;
61
106
  const visibleColumns = columns.filter((column) => !hiddenColumnIds.includes(column.id));
62
107
  const fittedWidths = useMemo(() => new Map(columns
@@ -158,7 +203,9 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
158
203
  announcements: nesting.announcements,
159
204
  canDrag: (id) => {
160
205
  const row = find(id);
161
- return row !== undefined && nesting.canDrag(row);
206
+ return (row !== undefined &&
207
+ (reorder.isRowDraggable === undefined || reorder.isRowDraggable(row)) &&
208
+ nesting.canDrag(row));
162
209
  },
163
210
  canDrop: (activeId, targetId, placement) => {
164
211
  const active = find(activeId);
@@ -190,13 +237,16 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
190
237
  groupKey: reorderKey(row),
191
238
  handleLabel: reorder.getHandleLabel(label),
192
239
  id: rowId,
240
+ isDraggable: reorder.isRowDraggable === undefined || reorder.isRowDraggable(row),
193
241
  label,
194
242
  };
195
243
  })
196
244
  : [], [getRowId, getRowLabel, reorder, reorderKey, visibleRows]);
197
- const renderOverlayRow = (rowId) => {
245
+ const showsPagination = Boolean(pageSize) && (!grouping || isServerPaginated);
246
+ const renderPagination = (paginationClassName) => pageSize ? (_jsx(PsTablePagination, { className: paginationClassName, cursor: cursorPaging, isPartial: isPartial, loadedPageCount: pageCountOf(loadedRowCount), onPageChange: changePage, page: safePage, pageCount: pageCount, pageSize: pageSize, totalRows: rowCount })) : null;
247
+ const renderOverlayRow = (rowId, size) => {
198
248
  const row = visibleRows.find((candidate) => getRowId(candidate) === rowId);
199
- return row === undefined ? null : (_jsx("table", { className: 'w-full table-fixed border-collapse bg-surface text-left', children: _jsx("tbody", { children: renderRow(PsTableReorderService.plain, row) }) }));
249
+ return row === undefined ? null : (_jsxs("table", { className: 'pointer-events-none w-full table-fixed border-collapse bg-surface text-left', style: size === null ? undefined : { width: size.width }, children: [size === null ? null : (_jsx("colgroup", { children: size.columnWidths.map((width, index) => (_jsx("col", { style: { width } }, index))) })), _jsx("tbody", { children: renderRow(PsTableReorderService.plain, row, undefined, undefined, true) })] }));
200
250
  };
201
251
  const renderReorder = (children) => reorder === undefined ? (children(PsTableReorderService.plain)) : (_jsx(Suspense, { fallback: children(PsTableReorderService.plain), children: _jsx(LazyReorderFrame, { announcements: reorder.announcements, enabled: reorder.enabled, instructions: reorder.instructions, items: reorderItems, nesting: nestingBinding, onReorder: reorder.onReorder, renderOverlay: renderOverlayRow, children: children }) }));
202
252
  const changePage = (next) => {
@@ -227,12 +277,16 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
227
277
  const selectedRows = selectedIds.length === 0 ? [] : queriedRows.filter((row) => selectedIdSet.has(getRowId(row)));
228
278
  const pageIds = visibleRows.map((row) => getRowId(row));
229
279
  const isPageSelected = pageIds.length > 0 && pageIds.every((rowId) => selectedIdSet.has(rowId));
230
- const togglePage = () => setSelectedIds((current) => {
231
- const pageIdSet = new Set(pageIds);
232
- return isPageSelected
233
- ? current.filter((rowId) => !pageIdSet.has(rowId))
234
- : [...new Set([...current, ...pageIds])];
235
- });
280
+ const togglePage = () => {
281
+ if (isPageSelected && selectAllMatching !== undefined && selectAllMatching.isActive)
282
+ selectAllMatching.onDeactivate();
283
+ setSelectedIds((current) => {
284
+ const pageIdSet = new Set(pageIds);
285
+ return isPageSelected
286
+ ? current.filter((rowId) => !pageIdSet.has(rowId))
287
+ : [...new Set([...current, ...pageIds])];
288
+ });
289
+ };
236
290
  const bodyRef = useRef(null);
237
291
  const [scrollParent, setScrollParent] = useState(null);
238
292
  const [scrollMargin, setScrollMargin] = useState(0);
@@ -276,7 +330,7 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
276
330
  }, [shouldVirtualize, visibleRows.length]);
277
331
  const toggleRow = (rowId) => setSelectedIds((current) => current.includes(rowId) ? current.filter((id) => id !== rowId) : [...current, rowId]);
278
332
  const renderCardList = (cardRows, emptyContent) => (_jsx(PsTableCardList, { columns: visibleColumns, emptyContent: emptyContent, expandedRowIds: expandedRowIds, footerColumns: hasFooter ? visibleColumns.filter((column) => column.footer !== undefined) : [], getRowId: getRowId, getRowLabel: (row) => (getRowLabel ? getRowLabel(row) : getRowId(row)), isSelectable: isSelectable, onRowClick: onRowClick, onRowHover: onRowHover, onToggleRow: toggleRow, renderExpandedRow: renderExpandedRow, rows: cardRows, selectedIds: selectedIds }));
279
- const renderRow = (api, row, motion, virtualItem) => {
333
+ const renderRow = (api, row, motion, virtualItem, isOverlay = false) => {
280
334
  const rowId = getRowId(row);
281
335
  const isExpanded = expandedRowIds ? expandedRowIds.includes(rowId) : false;
282
336
  const rowLabel = getRowLabel ? getRowLabel(row) : rowId;
@@ -288,7 +342,7 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
288
342
  measureRef: virtualItem ? virtualizer.measureElement : undefined,
289
343
  }
290
344
  : { ref: virtualItem ? virtualizer.measureElement : undefined };
291
- return (_jsxs(Fragment, { children: [_jsxs(Row, { ...rowExtras, "data-index": virtualItem ? virtualItem.index : undefined, "aria-expanded": renderExpandedRow ? isExpanded : undefined, "aria-label": onRowClick ? rowLabel : undefined, className: PallasoftClassNameService.merge('align-middle transition-colors', motion ? motion.className : '', onRowClick ? 'ps-focus-ring cursor-pointer hover:bg-mist/60' : 'hover:bg-mist/40', isExpanded ? 'bg-mist/40' : ''), onClick: onRowClick ? () => onRowClick(row) : undefined, onFocus: onRowHover ? () => onRowHover(row) : undefined, onPointerEnter: onRowHover ? () => onRowHover(row) : undefined, onKeyDown: onRowClick
345
+ return (_jsxs(Fragment, { children: [_jsxs(Row, { ...rowExtras, "data-index": virtualItem ? virtualItem.index : undefined, "aria-expanded": renderExpandedRow ? isExpanded : undefined, "aria-label": onRowClick ? rowLabel : undefined, className: PallasoftClassNameService.merge('align-middle hover:bg-mist/60', motion ? motion.className : '', onRowClick ? 'ps-focus-ring cursor-pointer' : '', isExpanded ? 'bg-mist/60' : ''), onClick: onRowClick ? () => onRowClick(row) : undefined, onFocus: onRowHover ? () => onRowHover(row) : undefined, onPointerEnter: onRowHover ? () => onRowHover(row) : undefined, onKeyDown: onRowClick
292
346
  ? (event) => {
293
347
  if (event.target !== event.currentTarget)
294
348
  return;
@@ -303,14 +357,25 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
303
357
  ((layout === 'fixed' && !column.wrap) ||
304
358
  PsTableColumnSizeService.maxWidthOf(column) !== undefined)
305
359
  ? 'truncate'
306
- : '', 'text-sm text-ink'), style: columnStyle(column, fittedWidths.get(column.id)), title: PsTableCellTooltipService.forCell(column, row), children: column.render(row) }, column.id)))] }), renderExpandedRow ? (_jsx("tr", { "aria-hidden": !isExpanded, className: isExpanded ? 'border-t-0' : 'border-0', children: _jsx("td", { className: 'p-0', colSpan: columnCount, children: _jsx(PsCollapse, { isOpen: isExpanded, children: _jsx("div", { className: 'w-0 min-w-full border-l-2 border-primary bg-mist/40 px-4 py-4', children: renderExpandedRow(row) }) }) }) })) : null] }, rowId));
360
+ : '', 'text-sm text-ink'), style: columnStyle(column, fittedWidths.get(column.id)), title: PsTableCellTooltipService.forCell(column, row), children: column.render(row) }, column.id)))] }), renderExpandedRow && !isOverlay ? (_jsx("tr", { "aria-hidden": !isExpanded, className: isExpanded ? 'border-t-0' : 'border-0', children: _jsx("td", { className: 'p-0', colSpan: columnCount, children: _jsx(PsCollapse, { isOpen: isExpanded, children: _jsx("div", { className: 'w-0 min-w-full border-l-2 border-primary bg-mist/40 px-4 py-4', children: renderExpandedRow(row) }) }) }) })) : null] }, rowId));
307
361
  };
308
362
  const renderEntries = (api, entries) => reorder
309
363
  ? PsTableReorderService.runs(entries, (entry) => reorderKey(entry.row), (entry) => getRowId(entry.row)).map((run) => api.run(run.id, run.items.map((entry) => getRowId(entry.row)), run.items.map((entry) => renderRow(api, entry.row, entry.motion, entry.virtualItem))))
310
364
  : entries.map((entry) => renderRow(api, entry.row, entry.motion, entry.virtualItem));
311
365
  if (isLoading)
312
366
  return (_jsx(PsSkeletonTable, { className: className, columns: visibleColumns.length, hasToolbar: hasToolbar, rows: PsTableConfig.skeletonRows }));
313
- return (_jsxs("div", { ref: containerRef, className: PallasoftClassNameService.merge('flex min-w-0 flex-col gap-3', className), children: [isSelectable && selectedRows.length > 0 && getBulkActions ? (_jsx(PsTableBulkBar, { actions: getBulkActions(selectedRows), onClear: () => setSelectedIds([]), selectedCount: selectedRows.length })) : hasToolbar || columns.some((column) => column.canHide) ? (_jsx(PsTableToolbar, { actions: _jsxs(_Fragment, { children: [toolbarActions, columns.some((column) => column.canHide) ? (_jsx(PsTableColumnPicker, { columns: columns
367
+ const clearSelection = () => {
368
+ setSelectedIds([]);
369
+ if (selectAllMatching !== undefined && selectAllMatching.isActive)
370
+ selectAllMatching.onDeactivate();
371
+ };
372
+ const banner = selectAllMatching !== undefined &&
373
+ selectedRows.length > 0 &&
374
+ selectAllMatching.total > pageIds.length &&
375
+ (isPageSelected || selectAllMatching.isActive) ? (_jsx(PsSelectAllMatchingBanner, { isActive: selectAllMatching.isActive, noun: selectAllMatching.noun, onActivate: selectAllMatching.onActivate, onDeactivate: clearSelection, pageCount: pageIds.length, total: selectAllMatching.total })) : null;
376
+ const topContent = (_jsxs(_Fragment, { children: [isSelectable && selectedRows.length > 0 && getBulkActions ? (_jsx(PsTableBulkBar, { actions: getBulkActions(selectedRows), onClear: clearSelection, selectedCount: selectAllMatching !== undefined && selectAllMatching.isActive
377
+ ? selectAllMatching.total
378
+ : selectedRows.length })) : hasToolbar || columns.some((column) => column.canHide) ? (_jsx(PsTableToolbar, { actions: _jsxs(_Fragment, { children: [toolbarActions, columns.some((column) => column.canHide) ? (_jsx(PsTableColumnPicker, { columns: columns
314
379
  .filter((column) => column.canHide)
315
380
  .map((column) => ({ id: column.id, label: column.label })), hiddenColumnIds: hiddenColumnIds, onChange: (nextHidden) => updateQuery({ hiddenColumnIds: nextHidden }) })) : null] }), columns: hasFilters ? columns : [], filters: filters, isSearchable: isSearchable, onClearFilters: clearFilters, onFilterChange: (columnId, value) => {
316
381
  updateQuery({ filters: { ...filters, [columnId]: value } });
@@ -318,14 +383,23 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
318
383
  }, onSearchChange: (next) => {
319
384
  updateQuery({ search: next });
320
385
  resetPage();
321
- }, rows: rows, search: search, searchPlaceholder: searchPlaceholder })) : null, _jsxs("div", { className: 'min-w-0 overflow-hidden rounded-[1.25rem] border border-line bg-surface', children: [mobileLayout === 'cards' ? (_jsxs("div", { className: 'sm:hidden', children: [reorder && reorder.cardsReason ? (_jsx("p", { className: 'border-b border-line px-4 py-2 text-xs text-ink/55', children: reorder.cardsReason })) : null, grouping
386
+ }, rows: rows, search: search, searchPlaceholder: searchPlaceholder })) : null, banner] }));
387
+ return (_jsxs("div", { ref: containerRef, className: PallasoftClassNameService.merge('flex min-w-0 flex-col gap-3', className), children: [stickyTop === undefined ? (topContent) : (_jsxs(_Fragment, { children: [_jsx("div", { "aria-hidden": 'true', className: '-mb-3 h-0', ref: stickySentinelRef }), _jsx("div", { className: PallasoftClassNameService.merge('sticky z-sticky -mx-1 flex flex-col gap-3 bg-canvas px-1 pb-2 pt-1 transition-shadow', isStuck ? 'shadow-[0_8px_8px_-8px_rgb(var(--ps-color-ink-rgb,15_23_42)/0.18)]' : ''), ref: stickyRegionRef, style: { top: stickyTop }, children: topContent })] })), _jsxs("div", { "aria-busy": isRefreshing, className: PallasoftClassNameService.merge('min-w-0 rounded-[1.25rem] border border-line bg-surface', stickyTop === undefined ? 'overflow-hidden' : 'overflow-clip'), children: [showsPagination && paginationPosition !== PsTablePaginationPosition.bottom
388
+ ? renderPagination('border-b border-t-0')
389
+ : null, mobileLayout === 'cards' ? (_jsxs("div", { className: PallasoftClassNameService.merge('transition-opacity duration-150 sm:hidden', isRefreshing ? 'opacity-60' : ''), children: [reorder && reorder.cardsReason ? (_jsx("p", { className: 'border-b border-line px-4 py-2 text-xs text-ink/55', children: reorder.cardsReason })) : null, grouping
322
390
  ? groups.map((group, groupIndex) => {
323
391
  const isExpanded = isGroupExpanded(group.key, groupIndex);
324
392
  return (_jsxs("div", { className: 'border-b border-line last:border-b-0', children: [_jsx(PsTableGroupHeader, { count: group.rows.length, isExpanded: isExpanded, label: grouping.getGroupLabel(group.key, group.rows), onToggle: () => toggleGroup(group.key, isExpanded), summary: grouping.renderGroupSummary
325
393
  ? grouping.renderGroupSummary(group.rows)
326
394
  : undefined }), _jsx(PsCollapse, { isOpen: isExpanded && !closingGroupKeys.includes(group.key), children: renderCardList(group.rows, null) })] }, `group-${group.key}`));
327
395
  })
328
- : renderCardList(shouldVirtualize ? visibleRows.slice(0, cards.count) : visibleRows, visibleRows.length === 0 && isSearching ? (_jsx(PsTableSearchingState, { label: searchingLabel, skeletonRows: PsTableConfig.searchingSkeletonRows })) : visibleRows.length === 0 && isQueried ? (_jsxs("div", { className: 'px-4 py-10 text-center', children: [_jsx("p", { className: 'text-sm font-semibold text-ink', children: PsTableConfig.noMatchesTitle }), _jsx("button", { className: PallasoftClassNameService.merge(PsTableControlClasses.textButton, 'mt-2 text-primary'), onClick: clearFilters, type: 'button', children: "Clear search and filters" })] })) : null), !grouping && shouldVirtualize && cards.count < visibleRows.length ? (_jsx("div", { "aria-hidden": 'true', className: 'h-px', ref: cards.sentinelRef })) : null] })) : null, _jsx("div", { className: PallasoftClassNameService.merge('ps-scrollbar overflow-x-auto overflow-y-hidden overscroll-x-contain', mobileLayout === 'cards' ? 'hidden sm:block' : ''), "data-overflow-end": overflow.edges.end, "data-overflow-start": overflow.edges.start, onScroll: overflow.update, ref: overflow.ref, children: renderReorder((api) => (_jsxs("table", { className: PallasoftClassNameService.merge('w-full border-collapse text-left', layout === 'fixed' ? 'table-fixed' : ''), children: [caption ? _jsx("caption", { className: 'sr-only', children: caption }) : null, _jsx("thead", { className: 'bg-mist/70 text-[0.68rem] font-semibold uppercase tracking-[0.18em] text-ink/45', children: _jsxs("tr", { children: [reorder ? (_jsx("th", { className: PallasoftClassNameService.merge('w-9 min-w-9 pl-3 pr-0', pins.reorder), scope: 'col', children: reorder.enabled ? (_jsx("span", { className: 'sr-only', children: reorder.columnLabel })) : reorder.disabledReason ? (_jsx("span", { "aria-label": reorder.disabledReason, className: 'inline-flex cursor-help opacity-40', role: 'img', title: reorder.disabledReason, children: _jsx(PsDragGrip, {}) })) : null })) : null, isSelectable ? (_jsx("th", { className: PallasoftClassNameService.merge('w-10 min-w-10 pl-4 pr-0', pins.select), scope: 'col', children: _jsx(PsCheckbox, { isIndeterminate: !isPageSelected && pageIds.some((rowId) => selectedIdSet.has(rowId)), isSelected: isPageSelected, label: 'Select all rows on this page', onChange: togglePage }) })) : null, visibleColumns.map((column, columnIndex) => {
396
+ : renderCardList(shouldVirtualize ? visibleRows.slice(0, cards.count) : visibleRows, visibleRows.length === 0 && isSearching ? (_jsx(PsTableSearchingState, { label: searchingLabel, skeletonRows: PsTableConfig.searchingSkeletonRows })) : visibleRows.length === 0 && isQueried ? (_jsxs("div", { className: 'px-4 py-10 text-center', children: [_jsx("p", { className: 'text-sm font-semibold text-ink', children: PsTableConfig.noMatchesTitle }), _jsx("button", { className: PallasoftClassNameService.merge(PsTableControlClasses.textButton, 'mt-2 text-primary'), onClick: clearFilters, type: 'button', children: "Clear search and filters" })] })) : null), !grouping && shouldVirtualize && cards.count < visibleRows.length ? (_jsx("div", { "aria-hidden": 'true', className: 'h-px', ref: cards.sentinelRef })) : null] })) : null, _jsx("div", { className: PallasoftClassNameService.merge(stickyTop === undefined
397
+ ? 'ps-scrollbar overflow-x-auto overflow-y-hidden overscroll-x-contain'
398
+ : 'overflow-x-clip', 'transition-opacity duration-150', isRefreshing ? 'opacity-60' : '', mobileLayout === 'cards' ? 'hidden sm:block' : ''), "data-overflow-end": overflow.edges.end, "data-overflow-start": overflow.edges.start, onScroll: overflow.update, ref: overflow.ref, children: renderReorder((api) => (_jsxs("table", { className: PallasoftClassNameService.merge('w-full border-collapse text-left', layout === 'fixed' ? 'table-fixed' : ''), children: [caption ? _jsx("caption", { className: 'sr-only', children: caption }) : null, _jsx("thead", { className: PallasoftClassNameService.merge('bg-mist/70 text-[0.68rem] font-semibold uppercase tracking-[0.18em] text-ink/45', stickyTop === undefined
399
+ ? ''
400
+ : 'sticky z-docked bg-surface [&_th]:border-b [&_th]:border-line [&>tr]:bg-mist/70'), style: stickyTop === undefined
401
+ ? undefined
402
+ : { top: `calc(${stickyTop} + ${stickyHeight}px)` }, children: _jsxs("tr", { children: [reorder ? (_jsx("th", { className: PallasoftClassNameService.merge('w-9 min-w-9 pl-3 pr-0', pins.reorder), scope: 'col', children: reorder.enabled ? (_jsx("span", { className: 'sr-only', children: reorder.columnLabel })) : reorder.disabledReason ? (_jsx("span", { "aria-label": reorder.disabledReason, className: 'inline-flex cursor-help opacity-40', role: 'img', title: reorder.disabledReason, children: _jsx(PsDragGrip, {}) })) : null })) : null, isSelectable ? (_jsx("th", { className: PallasoftClassNameService.merge('w-10 min-w-10 pl-4 pr-0', pins.select), scope: 'col', children: _jsx(PsCheckbox, { isIndeterminate: !isPageSelected && pageIds.some((rowId) => selectedIdSet.has(rowId)), isSelected: isPageSelected, label: 'Select all rows on this page', onChange: togglePage }) })) : null, visibleColumns.map((column, columnIndex) => {
329
403
  const align = column.align !== null && column.align !== undefined ? column.align : 'left';
330
404
  const isSorted = sort !== null && sort.columnId === column.id;
331
405
  return (_jsx("th", { "aria-sort": isSorted
@@ -356,5 +430,7 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
356
430
  ]
357
431
  : renderEntries(api, visibleRows.map((row) => ({ row })))] }), hasFooter ? (_jsx("tfoot", { className: 'border-t-2 border-line bg-mist/50 text-sm font-semibold text-ink', children: _jsxs("tr", { children: [reorder ? _jsx("td", { className: pins.reorder }) : null, isSelectable ? _jsx("td", { className: pins.select }) : null, visibleColumns.map((column, columnIndex) => (_jsx("td", { className: PallasoftClassNameService.merge(densityClassNames[density].cell, placementOf(column, columnIndex), alignClassNames[column.align !== null && column.align !== undefined
358
432
  ? column.align
359
- : 'left'], 'whitespace-nowrap'), children: column.footer }, column.id)))] }) })) : null] }))) }), visibleRows.length > 0 && isSearching ? (_jsx(PsTableSearchingState, { className: 'border-t border-line py-3', label: searchingLabel })) : null, pageSize && (!grouping || isServerPaginated) ? (_jsx(PsTablePagination, { isPartial: isPartial, loadedPageCount: pageCountOf(loadedRowCount), onPageChange: changePage, page: safePage, pageCount: pageCount, pageSize: pageSize, totalRows: rowCount })) : null] })] }));
433
+ : 'left'], 'whitespace-nowrap'), children: column.footer }, column.id)))] }) })) : null] }))) }), visibleRows.length > 0 && isSearching ? (_jsx(PsTableSearchingState, { className: 'border-t border-line py-3', label: searchingLabel })) : null, showsPagination && paginationPosition !== PsTablePaginationPosition.top
434
+ ? renderPagination('')
435
+ : null] })] }));
360
436
  };
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
3
+ import { PsTableControlClasses } from '../constants/ps-table-control-classes';
4
+ export const PsSelectAllMatchingBanner = ({ className, isActive, noun = 'rows', onActivate, onDeactivate, pageCount, total, }) => (_jsxs("div", { "aria-live": 'polite', className: PallasoftClassNameService.merge('flex flex-wrap items-center justify-center gap-x-2 gap-y-1 rounded-ps-control bg-primary/10 px-3 py-2 text-sm text-ink', className), role: 'status', children: [_jsx("span", { children: isActive
5
+ ? `All ${total.toLocaleString()} ${noun} matching are selected.`
6
+ : `${pageCount.toLocaleString()} ${noun} on this page are selected.` }), _jsx("button", { className: PallasoftClassNameService.merge(PsTableControlClasses.textButton, 'text-primary'), onClick: isActive ? onDeactivate : onActivate, type: 'button', children: isActive ? 'Clear selection' : `Select all ${total.toLocaleString()} matching` })] }));
@@ -1,11 +1,19 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
3
3
  import { PsTableControlClasses } from '../constants/ps-table-control-classes';
4
- export const PsTablePagination = ({ className, isPartial = false, loadedPageCount, onPageChange, page, pageCount, pageSize, totalRows, }) => {
4
+ export const PsTablePagination = ({ className, cursor, isPartial = false, loadedPageCount, onPageChange, page, pageCount, pageSize, totalRows, }) => {
5
5
  const firstRow = totalRows === 0 ? 0 : (page - 1) * pageSize + 1;
6
6
  const lastRow = Math.min(page * pageSize, totalRows);
7
+ const approximately = cursor !== undefined && cursor.isEstimate ? 'about ' : '';
8
+ const pageNumber = cursor === undefined ? Math.min(page, pageCount) : cursor.pageNumber;
9
+ const cursorFirst = cursor === undefined ? firstRow : (cursor.pageNumber - 1) * pageSize + 1;
10
+ const cursorLast = cursor === undefined ? lastRow : Math.min(cursor.pageNumber * pageSize, totalRows);
7
11
  return (_jsxs("nav", { "aria-label": 'Pagination', className: PallasoftClassNameService.merge('flex flex-wrap items-center justify-between gap-3 border-t border-line px-4 py-3', className), children: [_jsx("p", { className: 'text-sm text-ink/55', children: totalRows === 0
8
12
  ? 'No rows'
9
- : `${firstRow}–${lastRow} of ${totalRows}${isPartial ? '+' : ''}` }), _jsxs("div", { className: 'flex items-center gap-2', children: [_jsx("button", { className: PsTableControlClasses.paginationButton, disabled: page <= 1, onClick: () => onPageChange(page - 1), type: 'button', children: "Previous" }), _jsxs("span", { className: 'hidden text-sm text-ink/65 sm:inline', children: ["Page ", Math.min(page, pageCount), " of ", pageCount] }), _jsx("button", { className: PsTableControlClasses.paginationButton, disabled: page >=
10
- (loadedPageCount === undefined ? pageCount : Math.min(loadedPageCount, pageCount)), onClick: () => onPageChange(page + 1), type: 'button', children: "Next" })] })] }));
13
+ : cursor === undefined
14
+ ? `${firstRow}–${lastRow} of ${totalRows}${isPartial ? '+' : ''}`
15
+ : `${cursorFirst}–${cursorLast} of ${approximately}${totalRows.toLocaleString()}` }), _jsxs("div", { className: 'flex items-center gap-2', children: [cursor !== undefined && cursor.pageSizeOptions !== undefined && cursor.onPageSizeChange ? (_jsxs("label", { className: 'flex items-center gap-2 text-sm text-ink/65', children: [_jsx("span", { className: 'hidden sm:inline', children: "Rows per page" }), _jsx("select", { "aria-label": 'Rows per page', className: 'ps-input-control ps-focus-ring ps-control ps-control-padding rounded-ps-control text-sm', onChange: (event) => cursor.onPageSizeChange?.(Number(event.target.value)), value: pageSize, children: cursor.pageSizeOptions.map((option) => (_jsx("option", { value: option, children: option }, option))) })] })) : null, _jsx("button", { className: PsTableControlClasses.paginationButton, disabled: cursor === undefined ? page <= 1 : !cursor.hasPrevious, onClick: () => (cursor === undefined ? onPageChange(page - 1) : cursor.onPrevious()), type: 'button', children: "Previous" }), _jsxs("span", { className: 'hidden text-sm text-ink/65 sm:inline', children: ["Page ", pageNumber, " of ", approximately, pageCount] }), _jsx("button", { className: PsTableControlClasses.paginationButton, disabled: cursor === undefined
16
+ ? page >=
17
+ (loadedPageCount === undefined ? pageCount : Math.min(loadedPageCount, pageCount))
18
+ : !cursor.hasNext, onClick: () => (cursor === undefined ? onPageChange(page + 1) : cursor.onNext()), onFocus: cursor === undefined ? undefined : cursor.onNextIntent, onPointerEnter: cursor === undefined ? undefined : cursor.onNextIntent, type: 'button', children: "Next" })] })] }));
11
19
  };
@@ -6,7 +6,7 @@ import { usePsTableReorder } from '../hooks/use-ps-table-reorder';
6
6
  import { PsTableReorderService } from '../services/ps-table-reorder-service';
7
7
  import { PsTableSortableRow } from './ps-table-sortable-row';
8
8
  export const PsTableReorderFrame = ({ children, renderOverlay, ...input }) => {
9
- const { activeId, activeRunId, blockedReason, contextProps, drop, placements } = usePsTableReorder(input);
9
+ const { activeId, activeRunId, activeSize, blockedReason, contextProps, drop, placements } = usePsTableReorder(input);
10
10
  const itemById = new Map(input.items.map((item) => [item.id, item]));
11
11
  const { nesting } = input;
12
12
  const api = {
@@ -19,7 +19,7 @@ export const PsTableReorderFrame = ({ children, renderOverlay, ...input }) => {
19
19
  return {
20
20
  dropPlacement: drop !== null && drop.overId === rowId ? drop.placement : null,
21
21
  handleLabel: item === undefined ? '' : item.handleLabel,
22
- hasHandle: item !== undefined && input.enabled && nesting.canDrag(rowId),
22
+ hasHandle: item !== undefined && item.isDraggable && input.enabled && nesting.canDrag(rowId),
23
23
  isDisabled: !input.enabled,
24
24
  isDropTarget: item !== undefined,
25
25
  rowId,
@@ -27,7 +27,11 @@ export const PsTableReorderFrame = ({ children, renderOverlay, ...input }) => {
27
27
  return {
28
28
  dropPlacement: null,
29
29
  handleLabel: item === undefined ? '' : item.handleLabel,
30
- hasHandle: item !== undefined && input.enabled && placement !== undefined && placement.size > 1,
30
+ hasHandle: item !== undefined &&
31
+ item.isDraggable &&
32
+ input.enabled &&
33
+ placement !== undefined &&
34
+ placement.size > 1,
31
35
  isDisabled: activeRunId !== null && (placement === undefined || placement.runId !== activeRunId),
32
36
  isDropTarget: false,
33
37
  rowId,
@@ -35,5 +39,5 @@ export const PsTableReorderFrame = ({ children, renderOverlay, ...input }) => {
35
39
  },
36
40
  run: (runId, ids, runChildren) => (_jsx(SortableContext, { items: ids, strategy: nesting === undefined ? verticalListSortingStrategy : PsTableReorderService.still, children: runChildren }, runId)),
37
41
  };
38
- return (_jsxs(DndContext, { ...contextProps, children: [children(api), blockedReason === null ? null : (_jsx("p", { className: 'pointer-events-none fixed bottom-6 left-1/2 z-50 max-w-sm -translate-x-1/2 rounded-ps-control bg-ink px-3 py-2 text-sm text-white shadow-lg', role: 'status', children: blockedReason })), _jsx(PsDragOverlay, { className: PsDragClasses.lifted, frames: PsDragListLiftFrames, children: activeId === null ? null : renderOverlay(activeId) })] }));
42
+ return (_jsxs(DndContext, { ...contextProps, children: [children(api), blockedReason === null ? null : (_jsx("p", { className: 'pointer-events-none fixed bottom-6 left-1/2 z-50 max-w-sm -translate-x-1/2 rounded-ps-control bg-ink px-3 py-2 text-sm text-white shadow-lg', role: 'status', children: blockedReason })), _jsx(PsDragOverlay, { className: PsDragClasses.lifted, frames: PsDragListLiftFrames, isDropAnimated: nesting === undefined, children: activeId === null ? null : renderOverlay(activeId, activeSize) })] }));
39
43
  };
@@ -0,0 +1,8 @@
1
+ export const PsServerRowsConfig = {
2
+ cacheSize: 8,
3
+ debounceMs: 250,
4
+ defaultPageSize: 100,
5
+ idleFallbackMs: 200,
6
+ idleTimeoutMs: 1000,
7
+ pageSizes: [50, 100, 200],
8
+ };
@@ -1,5 +1,5 @@
1
1
  export const PsTableDropClasses = {
2
- after: '[&>td]:shadow-[inset_0_-2px_0_0_rgb(var(--ps-color-primary-rgb))]',
3
- before: '[&>td]:shadow-[inset_0_2px_0_0_rgb(var(--ps-color-primary-rgb))]',
2
+ after: '[&>td]:bg-[linear-gradient(rgb(var(--ps-color-primary-rgb)),rgb(var(--ps-color-primary-rgb)))] [&>td]:bg-[length:100%_2px] [&>td]:bg-bottom [&>td]:bg-no-repeat',
3
+ before: '[&>td]:bg-[linear-gradient(rgb(var(--ps-color-primary-rgb)),rgb(var(--ps-color-primary-rgb)))] [&>td]:bg-[length:100%_2px] [&>td]:bg-top [&>td]:bg-no-repeat',
4
4
  into: 'bg-primary/10 [&>td]:shadow-[inset_0_0_0_2px_rgb(var(--ps-color-primary-rgb)/0.5)]',
5
5
  };
@@ -0,0 +1,5 @@
1
+ export const PsTablePaginationPosition = {
2
+ both: 'both',
3
+ bottom: 'bottom',
4
+ top: 'top',
5
+ };
@@ -0,0 +1,209 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { PsServerRowsConfig } from '../constants/ps-server-rows-config';
3
+ import { PsServerRowsService } from '../services/ps-server-rows-service';
4
+ const scheduleIdle = (run) => {
5
+ if (typeof window.requestIdleCallback === 'function') {
6
+ const handle = window.requestIdleCallback(run, { timeout: PsServerRowsConfig.idleTimeoutMs });
7
+ return () => window.cancelIdleCallback(handle);
8
+ }
9
+ const timer = window.setTimeout(run, PsServerRowsConfig.idleFallbackMs);
10
+ return () => window.clearTimeout(timer);
11
+ };
12
+ export const usePsServerRows = ({ columns, debounceMs = PsServerRowsConfig.debounceMs, getSearchText, initialPage = null, initialQuery, isPrefetching = true, load, pageSize, }) => {
13
+ const [query, setQuery] = useState(initialQuery);
14
+ const [search, setSearch] = useState(initialQuery.search);
15
+ const [reloadToken, setReloadToken] = useState(0);
16
+ const [isLoading, setIsLoading] = useState(initialPage === null);
17
+ const [error, setError] = useState(null);
18
+ const queryKey = PsServerRowsService.queryKey(query, search, pageSize);
19
+ const [navigation, setNavigation] = useState({
20
+ cursor: null,
21
+ pageNumber: 1,
22
+ queryKey,
23
+ });
24
+ const [entry, setEntry] = useState(() => initialPage === null
25
+ ? null
26
+ : {
27
+ key: PsServerRowsService.cacheKey(queryKey, null),
28
+ page: initialPage,
29
+ queryKey,
30
+ });
31
+ const cache = useRef(new Map());
32
+ const inflight = useRef(new Map());
33
+ const loadRef = useRef(load);
34
+ const limitRef = useRef(pageSize);
35
+ const queryRef = useRef(query);
36
+ const searchRef = useRef(search);
37
+ const isPrimed = useRef(false);
38
+ loadRef.current = load;
39
+ limitRef.current = pageSize;
40
+ queryRef.current = query;
41
+ searchRef.current = search;
42
+ if (initialPage !== null && !isPrimed.current) {
43
+ isPrimed.current = true;
44
+ PsServerRowsService.remember(cache.current, PsServerRowsService.cacheKey(queryKey, null), initialPage);
45
+ }
46
+ if (navigation.queryKey !== queryKey)
47
+ setNavigation({ cursor: null, pageNumber: 1, queryKey });
48
+ useEffect(() => {
49
+ if (query.search === search)
50
+ return;
51
+ const timer = window.setTimeout(() => setSearch(query.search), debounceMs);
52
+ return () => window.clearTimeout(timer);
53
+ }, [debounceMs, query.search, search]);
54
+ const fetchPage = useCallback((cursor, signal, key) => {
55
+ const pending = inflight.current.get(key);
56
+ if (pending !== undefined)
57
+ return pending;
58
+ const request = loadRef.current({ ...queryRef.current, search: searchRef.current }, cursor, signal, limitRef.current);
59
+ const tracked = request.then((page) => {
60
+ inflight.current.delete(key);
61
+ if (!signal.aborted)
62
+ PsServerRowsService.remember(cache.current, key, page);
63
+ return page;
64
+ }, (failure) => {
65
+ inflight.current.delete(key);
66
+ throw failure;
67
+ });
68
+ inflight.current.set(key, tracked);
69
+ return tracked;
70
+ }, []);
71
+ const generation = useRef(null);
72
+ useEffect(() => {
73
+ const controller = new AbortController();
74
+ generation.current = controller;
75
+ return () => {
76
+ controller.abort();
77
+ inflight.current.clear();
78
+ };
79
+ }, [queryKey, reloadToken]);
80
+ useEffect(() => {
81
+ const controller = generation.current;
82
+ if (controller === null)
83
+ return;
84
+ const key = PsServerRowsService.cacheKey(queryKey, navigation.cursor);
85
+ const cached = PsServerRowsService.recall(cache.current, key);
86
+ let isStale = false;
87
+ if (cached !== undefined) {
88
+ setEntry({ key, page: cached, queryKey });
89
+ setIsLoading(false);
90
+ setError(null);
91
+ return;
92
+ }
93
+ setIsLoading(true);
94
+ setError(null);
95
+ fetchPage(navigation.cursor, controller.signal, key).then((page) => {
96
+ if (isStale || controller.signal.aborted)
97
+ return;
98
+ setEntry({ key, page, queryKey });
99
+ setIsLoading(false);
100
+ }, (failure) => {
101
+ if (isStale || controller.signal.aborted)
102
+ return;
103
+ setError(failure);
104
+ setIsLoading(false);
105
+ });
106
+ return () => {
107
+ isStale = true;
108
+ };
109
+ }, [fetchPage, navigation.cursor, queryKey, reloadToken]);
110
+ const nextCursor = entry !== null && entry.queryKey === queryKey ? entry.page.nextCursor : null;
111
+ const prefetch = useCallback(() => {
112
+ const controller = generation.current;
113
+ if (controller === null || controller.signal.aborted || nextCursor === null)
114
+ return;
115
+ const key = PsServerRowsService.cacheKey(queryKey, nextCursor);
116
+ if (cache.current.has(key) || inflight.current.has(key))
117
+ return;
118
+ fetchPage(nextCursor, controller.signal, key).catch(() => undefined);
119
+ }, [fetchPage, nextCursor, queryKey]);
120
+ useEffect(() => {
121
+ if (!isPrefetching || isLoading || nextCursor === null)
122
+ return;
123
+ return scheduleIdle(prefetch);
124
+ }, [isLoading, isPrefetching, nextCursor, prefetch]);
125
+ const onQueryChange = useCallback((next) => setQuery(next), []);
126
+ const goNext = useCallback(() => {
127
+ if (nextCursor === null)
128
+ return;
129
+ setNavigation((current) => ({
130
+ ...current,
131
+ cursor: nextCursor,
132
+ pageNumber: current.pageNumber + 1,
133
+ }));
134
+ }, [nextCursor]);
135
+ const previousCursor = entry !== null && entry.queryKey === queryKey ? entry.page.prevCursor : null;
136
+ const goPrevious = useCallback(() => {
137
+ if (previousCursor === null)
138
+ return;
139
+ setNavigation((current) => ({
140
+ ...current,
141
+ cursor: previousCursor,
142
+ pageNumber: Math.max(current.pageNumber - 1, 1),
143
+ }));
144
+ }, [previousCursor]);
145
+ const reload = useCallback(() => {
146
+ cache.current.clear();
147
+ setNavigation((current) => ({ ...current, cursor: null, pageNumber: 1 }));
148
+ setReloadToken((current) => current + 1);
149
+ }, []);
150
+ const retry = useCallback(() => setReloadToken((current) => current + 1), []);
151
+ const updateRows = useCallback((update, totalDelta = 0) => {
152
+ setEntry((current) => {
153
+ if (current === null)
154
+ return current;
155
+ const page = {
156
+ ...current.page,
157
+ rows: update(current.page.rows),
158
+ total: Math.max(current.page.total + totalDelta, 0),
159
+ };
160
+ cache.current.clear();
161
+ PsServerRowsService.remember(cache.current, current.key, page);
162
+ return { ...current, page };
163
+ });
164
+ }, []);
165
+ const isCurrent = entry !== null &&
166
+ entry.queryKey === queryKey &&
167
+ entry.key === PsServerRowsService.cacheKey(queryKey, navigation.cursor);
168
+ const baseRows = useMemo(() => (entry === null ? [] : entry.page.rows), [entry]);
169
+ const isSameQuery = entry !== null && entry.queryKey === queryKey;
170
+ const rows = useMemo(() => isSameQuery ? baseRows : PsServerRowsService.preview(baseRows, columns, query, getSearchText), [baseRows, columns, getSearchText, isSameQuery, query]);
171
+ const total = entry === null ? 0 : entry.page.total;
172
+ const totalIsEstimate = entry === null ? false : entry.page.totalIsEstimate;
173
+ const isPending = isLoading || query.search !== search || !isCurrent;
174
+ const cursorPaging = useMemo(() => ({
175
+ hasNext: nextCursor !== null,
176
+ hasPrevious: previousCursor !== null,
177
+ isEstimate: totalIsEstimate,
178
+ isPending,
179
+ onNext: goNext,
180
+ onNextIntent: prefetch,
181
+ onPrevious: goPrevious,
182
+ pageNumber: navigation.pageNumber,
183
+ }), [
184
+ goNext,
185
+ goPrevious,
186
+ isPending,
187
+ navigation.pageNumber,
188
+ nextCursor,
189
+ prefetch,
190
+ previousCursor,
191
+ totalIsEstimate,
192
+ ]);
193
+ return {
194
+ cursorPaging,
195
+ error,
196
+ hasFailed: error !== null,
197
+ isPending,
198
+ onQueryChange,
199
+ pageCount: PsServerRowsService.pageCount(total, pageSize),
200
+ pageNumber: navigation.pageNumber,
201
+ query,
202
+ reload,
203
+ retry,
204
+ rows,
205
+ total,
206
+ totalIsEstimate,
207
+ updateRows,
208
+ };
209
+ };
@@ -9,6 +9,7 @@ const pointerThenCenter = (args) => {
9
9
  };
10
10
  export const usePsTableReorder = ({ announcements: texts, enabled, instructions, items, nesting, onReorder, }) => {
11
11
  const [activeId, setActiveId] = useState(null);
12
+ const [activeSize, setActiveSize] = useState(null);
12
13
  const [drop, setDrop] = useState(null);
13
14
  const [blockedReason, setBlockedReason] = useState(null);
14
15
  const dropRef = useRef(null);
@@ -101,6 +102,7 @@ export const usePsTableReorder = ({ announcements: texts, enabled, instructions,
101
102
  const { active, over } = event;
102
103
  const target = resolveDrop(event);
103
104
  setActiveId(null);
105
+ setActiveSize(null);
104
106
  setDrop(null);
105
107
  setBlockedReason(null);
106
108
  dropRef.current = null;
@@ -122,6 +124,7 @@ export const usePsTableReorder = ({ announcements: texts, enabled, instructions,
122
124
  };
123
125
  return {
124
126
  activeId,
127
+ activeSize,
125
128
  blockedReason,
126
129
  activeRunId: activePlacement === undefined ? null : activePlacement.runId,
127
130
  contextProps: {
@@ -130,13 +133,17 @@ export const usePsTableReorder = ({ announcements: texts, enabled, instructions,
130
133
  modifiers: [PsTableReorderService.lockToVerticalAxis],
131
134
  onDragCancel: () => {
132
135
  setActiveId(null);
136
+ setActiveSize(null);
133
137
  setDrop(null);
134
138
  setBlockedReason(null);
135
139
  dropRef.current = null;
136
140
  },
137
141
  onDragEnd: handleDragEnd,
138
142
  onDragMove: nesting === undefined ? undefined : handleDragMove,
139
- onDragStart: ({ active }) => setActiveId(String(active.id)),
143
+ onDragStart: ({ active, activatorEvent }) => {
144
+ setActiveSize(PsTableReorderService.measure(activatorEvent));
145
+ setActiveId(String(active.id));
146
+ },
140
147
  sensors,
141
148
  },
142
149
  drop,
package/dist/esm/index.js CHANGED
@@ -47,3 +47,15 @@ export * from './interfaces/table-reorder-nesting';
47
47
  export * from './interfaces/table-nesting-announcements';
48
48
  export * from './constants/ps-table-drop-placement';
49
49
  export * from './services/ps-table-reorder-service';
50
+ export * from './constants/ps-table-pagination-position';
51
+ export * from './interfaces/table-drag-size';
52
+ export * from './components/ps-select-all-matching-banner';
53
+ export * from './constants/ps-server-rows-config';
54
+ export * from './hooks/use-ps-server-rows';
55
+ export * from './interfaces/ps-server-entry';
56
+ export * from './interfaces/ps-server-page';
57
+ export * from './interfaces/ps-server-rows-input';
58
+ export * from './interfaces/ps-server-rows-result';
59
+ export * from './interfaces/ps-table-cursor-paging';
60
+ export * from './interfaces/ps-table-select-all-matching';
61
+ export * from './services/ps-server-rows-service';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};