@pallasoft/ps-table-components 1.22.2 → 1.23.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 (64) hide show
  1. package/dist/components/ps-data-table.d.ts +2 -1
  2. package/dist/components/ps-data-table.js +45 -10
  3. package/dist/components/ps-table-reorder-frame.js +15 -2
  4. package/dist/components/ps-table-reorder-handle.js +1 -1
  5. package/dist/components/ps-table-sort-header.js +1 -1
  6. package/dist/components/ps-table-sortable-row.d.ts +5 -1
  7. package/dist/components/ps-table-sortable-row.js +12 -4
  8. package/dist/constants/ps-table-drop-classes.d.ts +2 -0
  9. package/dist/constants/ps-table-drop-classes.js +8 -0
  10. package/dist/constants/ps-table-drop-placement.d.ts +6 -0
  11. package/dist/constants/ps-table-drop-placement.js +8 -0
  12. package/dist/constants/ps-table-pin-classes.d.ts +16 -0
  13. package/dist/constants/ps-table-pin-classes.js +19 -0
  14. package/dist/constants/table-breakpoint.d.ts +7 -0
  15. package/dist/constants/table-breakpoint.js +13 -0
  16. package/dist/esm/components/ps-data-table.js +46 -11
  17. package/dist/esm/components/ps-table-reorder-frame.js +15 -2
  18. package/dist/esm/components/ps-table-reorder-handle.js +1 -1
  19. package/dist/esm/components/ps-table-sort-header.js +1 -1
  20. package/dist/esm/components/ps-table-sortable-row.js +12 -4
  21. package/dist/esm/constants/ps-table-drop-classes.js +5 -0
  22. package/dist/esm/constants/ps-table-drop-placement.js +5 -0
  23. package/dist/esm/constants/ps-table-pin-classes.js +16 -0
  24. package/dist/esm/constants/table-breakpoint.js +10 -0
  25. package/dist/esm/hooks/use-ps-table-overflow.js +19 -0
  26. package/dist/esm/hooks/use-ps-table-reorder.js +89 -15
  27. package/dist/esm/index.js +10 -0
  28. package/dist/esm/interfaces/table-drop.js +1 -0
  29. package/dist/esm/interfaces/table-nesting-announcements.js +1 -0
  30. package/dist/esm/interfaces/table-overflow-edges.js +1 -0
  31. package/dist/esm/interfaces/table-pin-classes.js +1 -0
  32. package/dist/esm/interfaces/table-reorder-nesting-binding.js +1 -0
  33. package/dist/esm/interfaces/table-reorder-nesting.js +1 -0
  34. package/dist/esm/services/ps-table-overflow-service.js +11 -0
  35. package/dist/esm/services/ps-table-pin-service.js +22 -0
  36. package/dist/esm/services/ps-table-reorder-service.js +38 -1
  37. package/dist/hooks/use-ps-table-overflow.d.ts +6 -0
  38. package/dist/hooks/use-ps-table-overflow.js +23 -0
  39. package/dist/hooks/use-ps-table-reorder.d.ts +6 -2
  40. package/dist/hooks/use-ps-table-reorder.js +87 -13
  41. package/dist/index.d.ts +10 -0
  42. package/dist/index.js +10 -0
  43. package/dist/interfaces/table-column.d.ts +3 -0
  44. package/dist/interfaces/table-drop.d.ts +5 -0
  45. package/dist/interfaces/table-drop.js +2 -0
  46. package/dist/interfaces/table-nesting-announcements.d.ts +5 -0
  47. package/dist/interfaces/table-nesting-announcements.js +2 -0
  48. package/dist/interfaces/table-overflow-edges.d.ts +4 -0
  49. package/dist/interfaces/table-overflow-edges.js +2 -0
  50. package/dist/interfaces/table-pin-classes.d.ts +5 -0
  51. package/dist/interfaces/table-pin-classes.js +2 -0
  52. package/dist/interfaces/table-reorder-nesting-binding.d.ts +8 -0
  53. package/dist/interfaces/table-reorder-nesting-binding.js +2 -0
  54. package/dist/interfaces/table-reorder-nesting.d.ts +8 -0
  55. package/dist/interfaces/table-reorder-nesting.js +2 -0
  56. package/dist/interfaces/table-reorder-row-props.d.ts +3 -0
  57. package/dist/interfaces/table-reorder.d.ts +4 -2
  58. package/dist/services/ps-table-overflow-service.d.ts +6 -0
  59. package/dist/services/ps-table-overflow-service.js +15 -0
  60. package/dist/services/ps-table-pin-service.d.ts +5 -0
  61. package/dist/services/ps-table-pin-service.js +26 -0
  62. package/dist/services/ps-table-reorder-service.d.ts +11 -0
  63. package/dist/services/ps-table-reorder-service.js +38 -1
  64. package/package.json +1 -1
@@ -27,6 +27,7 @@ export interface PsDataTableProps<RowData> {
27
27
  onRowHover?: (row: RowData) => void;
28
28
  page?: number;
29
29
  pageSize?: number;
30
+ pinFirstColumn?: boolean;
30
31
  query?: PsTableQuery;
31
32
  renderExpandedRow?: (row: RowData) => ReactNode;
32
33
  reorder?: PsTableReorder<RowData>;
@@ -36,4 +37,4 @@ export interface PsDataTableProps<RowData> {
36
37
  toolbarActions?: ReactNode;
37
38
  totalRows?: number;
38
39
  }
39
- export declare const PsDataTable: <RowData>({ caption, className, columns, defaultSort, density, expandedRowIds, mobileLayout, getBulkActions, getRowId, getRowLabel, getSearchText, grouping, isLoading, isPending, layout, onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, query, renderExpandedRow, reorder, rows, searchingLabel, searchPlaceholder, toolbarActions, totalRows, }: PsDataTableProps<RowData>) => import("react").JSX.Element;
40
+ export declare const PsDataTable: <RowData>({ caption, className, columns, defaultSort, density, expandedRowIds, mobileLayout, getBulkActions, getRowId, getRowLabel, getSearchText, grouping, isLoading, isPending, layout, onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, pinFirstColumn, query, renderExpandedRow, reorder, rows, searchingLabel, searchPlaceholder, toolbarActions, totalRows, }: PsDataTableProps<RowData>) => import("react").JSX.Element;
@@ -42,13 +42,16 @@ const ps_input_components_1 = require("@pallasoft/ps-input-components");
42
42
  const ps_motion_components_1 = require("@pallasoft/ps-motion-components");
43
43
  const react_virtual_1 = require("@tanstack/react-virtual");
44
44
  const ps_table_control_classes_1 = require("../constants/ps-table-control-classes");
45
+ const table_breakpoint_1 = require("../constants/table-breakpoint");
45
46
  const table_config_1 = require("../constants/table-config");
46
47
  const use_ps_incremental_count_1 = require("../hooks/use-ps-incremental-count");
47
48
  const use_ps_scroll_anchor_1 = require("../hooks/use-ps-scroll-anchor");
49
+ const use_ps_table_overflow_1 = require("../hooks/use-ps-table-overflow");
48
50
  const use_ps_table_stream_status_1 = require("../hooks/use-ps-table-stream-status");
49
51
  const ps_table_cell_tooltip_service_1 = require("../services/ps-table-cell-tooltip-service");
50
52
  const ps_table_column_size_service_1 = require("../services/ps-table-column-size-service");
51
53
  const ps_table_group_service_1 = require("../services/ps-table-group-service");
54
+ const ps_table_pin_service_1 = require("../services/ps-table-pin-service");
52
55
  const ps_table_query_service_1 = require("../services/ps-table-query-service");
53
56
  const ps_table_reorder_service_1 = require("../services/ps-table-reorder-service");
54
57
  const ps_table_scroll_service_1 = require("../services/ps-table-scroll-service");
@@ -81,7 +84,7 @@ const alignClassNames = {
81
84
  left: 'text-left',
82
85
  right: 'text-right tabular-nums',
83
86
  };
84
- 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, query, renderExpandedRow, reorder, rows, searchingLabel = table_config_1.PsTableConfig.searchingLabel, searchPlaceholder = table_config_1.PsTableConfig.searchPlaceholder, toolbarActions, totalRows, }) => {
87
+ 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 = table_config_1.PsTableConfig.searchingLabel, searchPlaceholder = table_config_1.PsTableConfig.searchPlaceholder, toolbarActions, totalRows, }) => {
85
88
  const containerRef = (0, react_1.useRef)(null);
86
89
  const [uncontrolledPage, setUncontrolledPage] = (0, react_1.useState)(1);
87
90
  const [uncontrolledQuery, setUncontrolledQuery] = (0, react_1.useState)(() => ps_table_query_service_1.PsTableQueryService.empty(defaultSort ? defaultSort : null));
@@ -106,6 +109,9 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
106
109
  const hasToolbar = isSearchable || hasFilters || Boolean(toolbarActions);
107
110
  const isSelectable = Boolean(getBulkActions);
108
111
  const columnCount = visibleColumns.length + (isSelectable ? 1 : 0) + (reorder ? 1 : 0);
112
+ const pins = ps_table_pin_service_1.PsTablePinService.leading(pinFirstColumn, Boolean(reorder), isSelectable);
113
+ const placementOf = (column, columnIndex) => ps_design_system_1.PallasoftClassNameService.merge(column.hideBelow === undefined ? '' : table_breakpoint_1.PsTableHideBelowClasses[column.hideBelow], columnIndex === 0 ? pins.first : '', ps_table_pin_service_1.PsTablePinService.end(column.pinEnd === true));
114
+ const overflow = (0, use_ps_table_overflow_1.usePsTableOverflow)(`${visibleColumns.map((column) => column.id).join(',')}:${rows.length}`);
109
115
  const deferredSearch = (0, react_1.useDeferredValue)(search);
110
116
  const queriedRows = (0, react_1.useMemo)(() => isServerPaginated
111
117
  ? rows
@@ -175,23 +181,48 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
175
181
  safePage,
176
182
  toggledGroupKeys,
177
183
  ]);
184
+ const reorderKey = (0, react_1.useCallback)((row) => reorder && reorder.groupKey ? reorder.groupKey(row) : ps_table_reorder_service_1.PsTableReorderService.treeKey, [reorder]);
185
+ const nestingBinding = (0, react_1.useMemo)(() => {
186
+ if (reorder === undefined || reorder.nesting === undefined)
187
+ return undefined;
188
+ const { nesting } = reorder;
189
+ const find = (id) => visibleRows.find((candidate) => getRowId(candidate) === id);
190
+ return {
191
+ announcements: nesting.announcements,
192
+ canDrag: (id) => {
193
+ const row = find(id);
194
+ return row !== undefined && nesting.canDrag(row);
195
+ },
196
+ canDrop: (activeId, targetId, placement) => {
197
+ const active = find(activeId);
198
+ const target = find(targetId);
199
+ return (active !== undefined && target !== undefined && nesting.canDrop(active, target, placement));
200
+ },
201
+ onMove: (activeId, targetId, placement) => {
202
+ const active = find(activeId);
203
+ const target = find(targetId);
204
+ if (active !== undefined && target !== undefined)
205
+ nesting.onMove(active, target, placement);
206
+ },
207
+ };
208
+ }, [getRowId, reorder, visibleRows]);
178
209
  const reorderItems = (0, react_1.useMemo)(() => reorder
179
210
  ? visibleRows.map((row) => {
180
211
  const rowId = getRowId(row);
181
212
  const label = getRowLabel ? getRowLabel(row) : rowId;
182
213
  return {
183
- groupKey: reorder.groupKey(row),
214
+ groupKey: reorderKey(row),
184
215
  handleLabel: reorder.getHandleLabel(label),
185
216
  id: rowId,
186
217
  label,
187
218
  };
188
219
  })
189
- : [], [getRowId, getRowLabel, reorder, visibleRows]);
220
+ : [], [getRowId, getRowLabel, reorder, reorderKey, visibleRows]);
190
221
  const renderOverlayRow = (rowId) => {
191
222
  const row = visibleRows.find((candidate) => getRowId(candidate) === rowId);
192
223
  return row === undefined ? null : ((0, jsx_runtime_1.jsx)("table", { className: 'w-full table-fixed border-collapse bg-surface text-left', children: (0, jsx_runtime_1.jsx)("tbody", { children: renderRow(ps_table_reorder_service_1.PsTableReorderService.plain, row) }) }));
193
224
  };
194
- const renderReorder = (children) => reorder === undefined ? (children(ps_table_reorder_service_1.PsTableReorderService.plain)) : ((0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: children(ps_table_reorder_service_1.PsTableReorderService.plain), children: (0, jsx_runtime_1.jsx)(LazyReorderFrame, { announcements: reorder.announcements, enabled: reorder.enabled, instructions: reorder.instructions, items: reorderItems, onReorder: reorder.onReorder, renderOverlay: renderOverlayRow, children: children }) }));
225
+ const renderReorder = (children) => reorder === undefined ? (children(ps_table_reorder_service_1.PsTableReorderService.plain)) : ((0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: children(ps_table_reorder_service_1.PsTableReorderService.plain), children: (0, jsx_runtime_1.jsx)(LazyReorderFrame, { announcements: reorder.announcements, enabled: reorder.enabled, instructions: reorder.instructions, items: reorderItems, nesting: nestingBinding, onReorder: reorder.onReorder, renderOverlay: renderOverlayRow, children: children }) }));
195
226
  const changePage = (next) => {
196
227
  if (onPageChange) {
197
228
  onPageChange(next);
@@ -275,7 +306,11 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
275
306
  const rowLabel = getRowLabel ? getRowLabel(row) : rowId;
276
307
  const Row = api.Row;
277
308
  const rowExtras = api.isActive
278
- ? { ...api.rowProps(rowId), measureRef: virtualItem ? virtualizer.measureElement : undefined }
309
+ ? {
310
+ ...api.rowProps(rowId),
311
+ handleCellClassName: pins.reorder,
312
+ measureRef: virtualItem ? virtualizer.measureElement : undefined,
313
+ }
279
314
  : { ref: virtualItem ? virtualizer.measureElement : undefined };
280
315
  return ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(Row, { ...rowExtras, "data-index": virtualItem ? virtualItem.index : undefined, "aria-expanded": renderExpandedRow ? isExpanded : undefined, "aria-label": onRowClick ? rowLabel : undefined, className: ps_design_system_1.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
281
316
  ? (event) => {
@@ -288,13 +323,13 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
288
323
  }
289
324
  : undefined, style: motion && motion.delayMs !== undefined
290
325
  ? { animationDelay: `${motion.delayMs}ms` }
291
- : undefined, tabIndex: onRowClick ? 0 : undefined, children: [reorder && !api.isActive ? (0, jsx_runtime_1.jsx)("td", { className: 'w-9 pl-3 pr-0' }) : null, isSelectable ? ((0, jsx_runtime_1.jsx)("td", { className: 'w-10 pl-4 pr-0', onClick: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), children: (0, jsx_runtime_1.jsx)(ps_input_components_1.PsCheckbox, { isSelected: selectedIdSet.has(rowId), label: `Select ${rowLabel}`, onChange: () => toggleRow(rowId) }) })) : null, visibleColumns.map((column) => ((0, jsx_runtime_1.jsx)("td", { className: ps_design_system_1.PallasoftClassNameService.merge(densityClassNames[density].cell, alignClassNames[column.align !== null && column.align !== undefined ? column.align : 'left'], column.wrap ? '' : 'whitespace-nowrap', (layout === 'fixed' && !column.wrap) ||
326
+ : undefined, tabIndex: onRowClick ? 0 : undefined, children: [reorder && !api.isActive ? ((0, jsx_runtime_1.jsx)("td", { className: ps_design_system_1.PallasoftClassNameService.merge('w-9 min-w-9 pl-3 pr-0', pins.reorder) })) : null, isSelectable ? ((0, jsx_runtime_1.jsx)("td", { className: ps_design_system_1.PallasoftClassNameService.merge('w-10 min-w-10 pl-4 pr-0', pins.select), onClick: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), children: (0, jsx_runtime_1.jsx)(ps_input_components_1.PsCheckbox, { isSelected: selectedIdSet.has(rowId), label: `Select ${rowLabel}`, onChange: () => toggleRow(rowId) }) })) : null, visibleColumns.map((column, columnIndex) => ((0, jsx_runtime_1.jsx)("td", { className: ps_design_system_1.PallasoftClassNameService.merge(densityClassNames[density].cell, placementOf(column, columnIndex), alignClassNames[column.align !== null && column.align !== undefined ? column.align : 'left'], column.wrap ? '' : 'whitespace-nowrap', (layout === 'fixed' && !column.wrap) ||
292
327
  ps_table_column_size_service_1.PsTableColumnSizeService.maxWidthOf(column) !== undefined
293
328
  ? 'truncate'
294
329
  : '', 'text-sm text-ink'), style: columnStyle(column), title: ps_table_cell_tooltip_service_1.PsTableCellTooltipService.forCell(column, row), children: column.render(row) }, column.id)))] }), renderExpandedRow ? ((0, jsx_runtime_1.jsx)("tr", { "aria-hidden": !isExpanded, className: isExpanded ? 'border-t-0' : 'border-0', children: (0, jsx_runtime_1.jsx)("td", { className: 'p-0', colSpan: columnCount, children: (0, jsx_runtime_1.jsx)(ps_motion_components_1.PsCollapse, { isOpen: isExpanded, children: (0, jsx_runtime_1.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));
295
330
  };
296
331
  const renderEntries = (api, entries) => reorder
297
- ? ps_table_reorder_service_1.PsTableReorderService.runs(entries, (entry) => reorder.groupKey(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))))
332
+ ? ps_table_reorder_service_1.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))))
298
333
  : entries.map((entry) => renderRow(api, entry.row, entry.motion, entry.virtualItem));
299
334
  if (isLoading)
300
335
  return ((0, jsx_runtime_1.jsx)(ps_core_components_1.PsSkeletonTable, { className: className, columns: visibleColumns.length, hasToolbar: hasToolbar, rows: table_config_1.PsTableConfig.skeletonRows }));
@@ -313,14 +348,14 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
313
348
  ? grouping.renderGroupSummary(group.rows)
314
349
  : undefined }), (0, jsx_runtime_1.jsx)(ps_motion_components_1.PsCollapse, { isOpen: isExpanded && !closingGroupKeys.includes(group.key), children: renderCardList(group.rows, null) })] }, `group-${group.key}`));
315
350
  })
316
- : renderCardList(shouldVirtualize ? visibleRows.slice(0, cards.count) : visibleRows, visibleRows.length === 0 && isSearching ? ((0, jsx_runtime_1.jsx)(ps_table_searching_state_1.PsTableSearchingState, { label: searchingLabel, skeletonRows: table_config_1.PsTableConfig.searchingSkeletonRows })) : visibleRows.length === 0 && isQueried ? ((0, jsx_runtime_1.jsxs)("div", { className: 'px-4 py-10 text-center', children: [(0, jsx_runtime_1.jsx)("p", { className: 'text-sm font-semibold text-ink', children: table_config_1.PsTableConfig.noMatchesTitle }), (0, jsx_runtime_1.jsx)("button", { className: ps_design_system_1.PallasoftClassNameService.merge(ps_table_control_classes_1.PsTableControlClasses.textButton, 'mt-2 text-primary'), onClick: clearFilters, type: 'button', children: "Clear search and filters" })] })) : null), !grouping && shouldVirtualize && cards.count < visibleRows.length ? ((0, jsx_runtime_1.jsx)("div", { "aria-hidden": 'true', className: 'h-px', ref: cards.sentinelRef })) : null] })) : null, (0, jsx_runtime_1.jsx)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-scrollbar overflow-x-auto overflow-y-hidden', mobileLayout === 'cards' ? 'hidden sm:block' : ''), children: renderReorder((api) => ((0, jsx_runtime_1.jsxs)("table", { className: ps_design_system_1.PallasoftClassNameService.merge('w-full border-collapse text-left', layout === 'fixed' ? 'table-fixed' : ''), children: [caption ? (0, jsx_runtime_1.jsx)("caption", { className: 'sr-only', children: caption }) : null, (0, jsx_runtime_1.jsx)("thead", { className: 'bg-mist/70 text-[0.68rem] font-semibold uppercase tracking-[0.18em] text-ink/45', children: (0, jsx_runtime_1.jsxs)("tr", { children: [reorder ? ((0, jsx_runtime_1.jsx)("th", { className: 'w-9 pl-3 pr-0', scope: 'col', children: reorder.enabled ? ((0, jsx_runtime_1.jsx)("span", { className: 'sr-only', children: reorder.columnLabel })) : reorder.disabledReason ? ((0, jsx_runtime_1.jsx)("span", { "aria-label": reorder.disabledReason, className: 'inline-flex cursor-help opacity-40', role: 'img', title: reorder.disabledReason, children: (0, jsx_runtime_1.jsx)(ps_core_components_1.PsDragGrip, {}) })) : null })) : null, isSelectable ? ((0, jsx_runtime_1.jsx)("th", { className: 'w-10 pl-4 pr-0', scope: 'col', children: (0, jsx_runtime_1.jsx)(ps_input_components_1.PsCheckbox, { isIndeterminate: !isPageSelected && pageIds.some((rowId) => selectedIdSet.has(rowId)), isSelected: isPageSelected, label: 'Select all rows on this page', onChange: togglePage }) })) : null, visibleColumns.map((column) => {
351
+ : renderCardList(shouldVirtualize ? visibleRows.slice(0, cards.count) : visibleRows, visibleRows.length === 0 && isSearching ? ((0, jsx_runtime_1.jsx)(ps_table_searching_state_1.PsTableSearchingState, { label: searchingLabel, skeletonRows: table_config_1.PsTableConfig.searchingSkeletonRows })) : visibleRows.length === 0 && isQueried ? ((0, jsx_runtime_1.jsxs)("div", { className: 'px-4 py-10 text-center', children: [(0, jsx_runtime_1.jsx)("p", { className: 'text-sm font-semibold text-ink', children: table_config_1.PsTableConfig.noMatchesTitle }), (0, jsx_runtime_1.jsx)("button", { className: ps_design_system_1.PallasoftClassNameService.merge(ps_table_control_classes_1.PsTableControlClasses.textButton, 'mt-2 text-primary'), onClick: clearFilters, type: 'button', children: "Clear search and filters" })] })) : null), !grouping && shouldVirtualize && cards.count < visibleRows.length ? ((0, jsx_runtime_1.jsx)("div", { "aria-hidden": 'true', className: 'h-px', ref: cards.sentinelRef })) : null] })) : null, (0, jsx_runtime_1.jsx)("div", { className: ps_design_system_1.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) => ((0, jsx_runtime_1.jsxs)("table", { className: ps_design_system_1.PallasoftClassNameService.merge('w-full border-collapse text-left', layout === 'fixed' ? 'table-fixed' : ''), children: [caption ? (0, jsx_runtime_1.jsx)("caption", { className: 'sr-only', children: caption }) : null, (0, jsx_runtime_1.jsx)("thead", { className: 'bg-mist/70 text-[0.68rem] font-semibold uppercase tracking-[0.18em] text-ink/45', children: (0, jsx_runtime_1.jsxs)("tr", { children: [reorder ? ((0, jsx_runtime_1.jsx)("th", { className: ps_design_system_1.PallasoftClassNameService.merge('w-9 min-w-9 pl-3 pr-0', pins.reorder), scope: 'col', children: reorder.enabled ? ((0, jsx_runtime_1.jsx)("span", { className: 'sr-only', children: reorder.columnLabel })) : reorder.disabledReason ? ((0, jsx_runtime_1.jsx)("span", { "aria-label": reorder.disabledReason, className: 'inline-flex cursor-help opacity-40', role: 'img', title: reorder.disabledReason, children: (0, jsx_runtime_1.jsx)(ps_core_components_1.PsDragGrip, {}) })) : null })) : null, isSelectable ? ((0, jsx_runtime_1.jsx)("th", { className: ps_design_system_1.PallasoftClassNameService.merge('w-10 min-w-10 pl-4 pr-0', pins.select), scope: 'col', children: (0, jsx_runtime_1.jsx)(ps_input_components_1.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) => {
317
352
  const align = column.align !== null && column.align !== undefined ? column.align : 'left';
318
353
  const isSorted = sort !== null && sort.columnId === column.id;
319
354
  return ((0, jsx_runtime_1.jsx)("th", { "aria-sort": isSorted
320
355
  ? sort.direction
321
356
  : column.sortValue && isQueryable
322
357
  ? 'none'
323
- : undefined, className: ps_design_system_1.PallasoftClassNameService.merge(densityClassNames[density].head, alignClassNames[align], 'whitespace-nowrap'), scope: 'col', style: columnStyle(column), children: column.sortValue && isQueryable ? ((0, jsx_runtime_1.jsx)(ps_table_sort_header_1.PsTableSortHeader, { align: align, columnId: column.id, label: column.label, onSort: updateSort, sort: sort })) : (column.label) }, column.id));
358
+ : undefined, className: ps_design_system_1.PallasoftClassNameService.merge(densityClassNames[density].head, alignClassNames[align], placementOf(column, columnIndex), 'whitespace-nowrap'), scope: 'col', style: columnStyle(column), children: column.sortValue && isQueryable ? ((0, jsx_runtime_1.jsx)(ps_table_sort_header_1.PsTableSortHeader, { align: align, columnId: column.id, label: column.label, onSort: updateSort, sort: sort })) : (column.label) }, column.id));
324
359
  })] }) }), (0, jsx_runtime_1.jsxs)("tbody", { className: 'divide-y divide-line', ref: bodyRef, children: [visibleRows.length === 0 && isSearching ? ((0, jsx_runtime_1.jsx)("tr", { children: (0, jsx_runtime_1.jsx)("td", { className: 'p-0', colSpan: columnCount, children: (0, jsx_runtime_1.jsx)(ps_table_searching_state_1.PsTableSearchingState, { label: searchingLabel, skeletonRows: table_config_1.PsTableConfig.searchingSkeletonRows }) }) })) : null, visibleRows.length === 0 && isQueried && !isSearching ? ((0, jsx_runtime_1.jsx)("tr", { children: (0, jsx_runtime_1.jsxs)("td", { className: 'px-4 py-12 text-center', colSpan: columnCount, children: [(0, jsx_runtime_1.jsx)("p", { className: 'text-sm font-semibold text-ink', children: table_config_1.PsTableConfig.noMatchesTitle }), (0, jsx_runtime_1.jsx)("p", { className: 'mt-1 text-sm text-ink/55', children: table_config_1.PsTableConfig.noMatchesDescription }), (0, jsx_runtime_1.jsx)("button", { className: ps_design_system_1.PallasoftClassNameService.merge(ps_table_control_classes_1.PsTableControlClasses.textButton, 'mt-3 text-primary hover:underline'), onClick: clearFilters, type: 'button', children: "Clear search and filters" })] }) })) : null, grouping
325
360
  ? groups.map((group, groupIndex) => {
326
361
  const isExpanded = isGroupExpanded(group.key, groupIndex);
@@ -342,7 +377,7 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
342
377
  }))),
343
378
  (0, jsx_runtime_1.jsx)("tr", { "aria-hidden": 'true', children: (0, jsx_runtime_1.jsx)("td", { className: 'p-0', colSpan: columnCount, style: { height: virtualBottom } }) }, 'virtual-bottom'),
344
379
  ]
345
- : renderEntries(api, visibleRows.map((row) => ({ row })))] }), hasFooter ? ((0, jsx_runtime_1.jsx)("tfoot", { className: 'border-t-2 border-line bg-mist/50 text-sm font-semibold text-ink', children: (0, jsx_runtime_1.jsxs)("tr", { children: [reorder ? (0, jsx_runtime_1.jsx)("td", {}) : null, isSelectable ? (0, jsx_runtime_1.jsx)("td", {}) : null, visibleColumns.map((column) => ((0, jsx_runtime_1.jsx)("td", { className: ps_design_system_1.PallasoftClassNameService.merge(densityClassNames[density].cell, alignClassNames[column.align !== null && column.align !== undefined
380
+ : renderEntries(api, visibleRows.map((row) => ({ row })))] }), hasFooter ? ((0, jsx_runtime_1.jsx)("tfoot", { className: 'border-t-2 border-line bg-mist/50 text-sm font-semibold text-ink', children: (0, jsx_runtime_1.jsxs)("tr", { children: [reorder ? (0, jsx_runtime_1.jsx)("td", { className: pins.reorder }) : null, isSelectable ? (0, jsx_runtime_1.jsx)("td", { className: pins.select }) : null, visibleColumns.map((column, columnIndex) => ((0, jsx_runtime_1.jsx)("td", { className: ps_design_system_1.PallasoftClassNameService.merge(densityClassNames[density].cell, placementOf(column, columnIndex), alignClassNames[column.align !== null && column.align !== undefined
346
381
  ? column.align
347
382
  : 'left'], 'whitespace-nowrap'), children: column.footer }, column.id)))] }) })) : null] }))) }), visibleRows.length > 0 && isSearching ? ((0, jsx_runtime_1.jsx)(ps_table_searching_state_1.PsTableSearchingState, { className: 'border-t border-line py-3', label: searchingLabel })) : null, pageSize && (!grouping || isServerPaginated) ? ((0, jsx_runtime_1.jsx)(ps_table_pagination_1.PsTablePagination, { isPartial: isPartial, loadedPageCount: pageCountOf(loadedRowCount), onPageChange: changePage, page: safePage, pageCount: pageCount, pageSize: pageSize, totalRows: rowCount })) : null] })] }));
348
383
  };
@@ -6,24 +6,37 @@ const core_1 = require("@dnd-kit/core");
6
6
  const sortable_1 = require("@dnd-kit/sortable");
7
7
  const ps_core_components_1 = require("@pallasoft/ps-core-components");
8
8
  const use_ps_table_reorder_1 = require("../hooks/use-ps-table-reorder");
9
+ const ps_table_reorder_service_1 = require("../services/ps-table-reorder-service");
9
10
  const ps_table_sortable_row_1 = require("./ps-table-sortable-row");
10
11
  const PsTableReorderFrame = ({ children, renderOverlay, ...input }) => {
11
- const { activeId, activeRunId, contextProps, placements } = (0, use_ps_table_reorder_1.usePsTableReorder)(input);
12
+ const { activeId, activeRunId, contextProps, drop, placements } = (0, use_ps_table_reorder_1.usePsTableReorder)(input);
12
13
  const itemById = new Map(input.items.map((item) => [item.id, item]));
14
+ const { nesting } = input;
13
15
  const api = {
14
16
  isActive: true,
15
17
  Row: ps_table_sortable_row_1.PsTableSortableRow,
16
18
  rowProps: (rowId) => {
17
19
  const item = itemById.get(rowId);
18
20
  const placement = placements.get(rowId);
21
+ if (nesting !== undefined)
22
+ return {
23
+ dropPlacement: drop !== null && drop.overId === rowId ? drop.placement : null,
24
+ handleLabel: item === undefined ? '' : item.handleLabel,
25
+ hasHandle: item !== undefined && input.enabled && nesting.canDrag(rowId),
26
+ isDisabled: !input.enabled,
27
+ isDropTarget: item !== undefined,
28
+ rowId,
29
+ };
19
30
  return {
31
+ dropPlacement: null,
20
32
  handleLabel: item === undefined ? '' : item.handleLabel,
21
33
  hasHandle: item !== undefined && input.enabled && placement !== undefined && placement.size > 1,
22
34
  isDisabled: activeRunId !== null && (placement === undefined || placement.runId !== activeRunId),
35
+ isDropTarget: false,
23
36
  rowId,
24
37
  };
25
38
  },
26
- run: (runId, ids, runChildren) => ((0, jsx_runtime_1.jsx)(sortable_1.SortableContext, { items: ids, strategy: sortable_1.verticalListSortingStrategy, children: runChildren }, runId)),
39
+ run: (runId, ids, runChildren) => ((0, jsx_runtime_1.jsx)(sortable_1.SortableContext, { items: ids, strategy: nesting === undefined ? sortable_1.verticalListSortingStrategy : ps_table_reorder_service_1.PsTableReorderService.still, children: runChildren }, runId)),
27
40
  };
28
41
  return ((0, jsx_runtime_1.jsxs)(core_1.DndContext, { ...contextProps, children: [children(api), (0, jsx_runtime_1.jsx)(ps_core_components_1.PsDragOverlay, { className: ps_core_components_1.PsDragClasses.lifted, frames: ps_core_components_1.PsDragListLiftFrames, children: activeId === null ? null : renderOverlay(activeId) })] }));
29
42
  };
@@ -4,5 +4,5 @@ exports.PsTableReorderHandle = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const ps_core_components_1 = require("@pallasoft/ps-core-components");
6
6
  const ps_design_system_1 = require("@pallasoft/ps-design-system");
7
- const PsTableReorderHandle = ({ activatorRef, className, label, ...buttonProps }) => ((0, jsx_runtime_1.jsx)("button", { ...buttonProps, "aria-label": label, className: ps_design_system_1.PallasoftClassNameService.merge('ps-focus-ring flex h-7 w-6 shrink-0 cursor-grab touch-none items-center justify-center rounded-ps-control text-ink/40 transition-colors hover:bg-mist hover:text-ink active:cursor-grabbing', className), onClick: (event) => event.stopPropagation(), ref: activatorRef, title: label, type: 'button', children: (0, jsx_runtime_1.jsx)(ps_core_components_1.PsDragGrip, {}) }));
7
+ const PsTableReorderHandle = ({ activatorRef, className, label, ...buttonProps }) => ((0, jsx_runtime_1.jsx)("button", { ...buttonProps, "aria-label": label, className: ps_design_system_1.PallasoftClassNameService.merge('ps-focus-ring ps-touch-target relative flex h-7 w-6 shrink-0 cursor-grab touch-none items-center justify-center rounded-ps-control text-ink/40 transition-colors hover:bg-mist hover:text-ink active:cursor-grabbing', className), onClick: (event) => event.stopPropagation(), ref: activatorRef, title: label, type: 'button', children: (0, jsx_runtime_1.jsx)(ps_core_components_1.PsDragGrip, {}) }));
8
8
  exports.PsTableReorderHandle = PsTableReorderHandle;
@@ -13,6 +13,6 @@ const PsTableSortHeader = ({ align, columnId, label, onSort, sort, }) => {
13
13
  : ps_icons_1.PsIconKey.SortDescending;
14
14
  return ((0, jsx_runtime_1.jsxs)("button", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-focus-ring group inline-flex items-center gap-1 rounded-ps-control uppercase tracking-[0.18em] transition-colors hover:text-ink', align === 'right' ? 'flex-row-reverse' : '', isSorted ? 'text-ink' : ''), onClick: () => onSort(columnId), type: 'button', children: [(0, jsx_runtime_1.jsx)("span", { children: label }), (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: ps_design_system_1.PallasoftClassNameService.merge('block h-3.5 w-3.5 shrink-0', isSorted
15
15
  ? 'text-primary'
16
- : 'opacity-0 group-hover:opacity-60 group-focus-visible:opacity-60'), iconKey: iconKey })] }));
16
+ : 'opacity-0 group-hover:opacity-60 group-focus-visible:opacity-60 [@media(hover:none)]:opacity-40'), iconKey: iconKey })] }));
17
17
  };
18
18
  exports.PsTableSortHeader = PsTableSortHeader;
@@ -1,9 +1,13 @@
1
1
  import type { ComponentProps } from 'react';
2
+ import type { PsTableDropPlacement } from '../constants/ps-table-drop-placement';
2
3
  export interface PsTableSortableRowProps extends Omit<ComponentProps<'tr'>, 'ref'> {
4
+ dropPlacement?: PsTableDropPlacement | null;
5
+ handleCellClassName?: string;
3
6
  handleLabel: string;
4
7
  hasHandle: boolean;
5
8
  isDisabled: boolean;
9
+ isDropTarget?: boolean;
6
10
  measureRef?: (element: HTMLTableRowElement | null) => void;
7
11
  rowId: string;
8
12
  }
9
- export declare const PsTableSortableRow: ({ children, className, handleLabel, hasHandle, isDisabled, measureRef, rowId, style, ...rowProps }: PsTableSortableRowProps) => import("react").JSX.Element;
13
+ export declare const PsTableSortableRow: ({ children, className, dropPlacement, handleCellClassName, handleLabel, hasHandle, isDisabled, isDropTarget, measureRef, rowId, style, ...rowProps }: PsTableSortableRowProps) => import("react").JSX.Element;
@@ -5,10 +5,18 @@ const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const sortable_1 = require("@dnd-kit/sortable");
6
6
  const utilities_1 = require("@dnd-kit/utilities");
7
7
  const ps_core_components_1 = require("@pallasoft/ps-core-components");
8
+ const ps_design_system_1 = require("@pallasoft/ps-design-system");
9
+ const ps_table_drop_classes_1 = require("../constants/ps-table-drop-classes");
8
10
  const ps_table_reorder_handle_1 = require("./ps-table-reorder-handle");
9
- const PsTableSortableRow = ({ children, className, handleLabel, hasHandle, isDisabled, measureRef, rowId, style, ...rowProps }) => {
10
- const { attributes, isDragging, listeners, setActivatorNodeRef, setNodeRef, transform, transition, } = (0, sortable_1.useSortable)({ disabled: isDisabled || !hasHandle, id: rowId });
11
- return ((0, jsx_runtime_1.jsxs)("tr", { ...rowProps, className: className, ref: (element) => {
11
+ const PsTableSortableRow = ({ children, className, dropPlacement = null, handleCellClassName, handleLabel, hasHandle, isDisabled, isDropTarget = false, measureRef, rowId, style, ...rowProps }) => {
12
+ const { attributes, isDragging, listeners, setActivatorNodeRef, setNodeRef, transform, transition, } = (0, sortable_1.useSortable)({
13
+ disabled: {
14
+ draggable: isDisabled || !hasHandle,
15
+ droppable: isDisabled || (!hasHandle && !isDropTarget),
16
+ },
17
+ id: rowId,
18
+ });
19
+ return ((0, jsx_runtime_1.jsxs)("tr", { ...rowProps, className: ps_design_system_1.PallasoftClassNameService.merge(className, dropPlacement === null ? '' : ps_table_drop_classes_1.PsTableDropClasses[dropPlacement]), ref: (element) => {
12
20
  setNodeRef(element);
13
21
  if (measureRef)
14
22
  measureRef(element);
@@ -17,6 +25,6 @@ const PsTableSortableRow = ({ children, className, handleLabel, hasHandle, isDis
17
25
  opacity: isDragging ? ps_core_components_1.PsDragConfig.placeholderOpacity : style?.opacity,
18
26
  transform: utilities_1.CSS.Translate.toString(transform),
19
27
  transition,
20
- }, children: [(0, jsx_runtime_1.jsx)("td", { className: 'w-9 pl-3 pr-0', onClick: (event) => event.stopPropagation(), children: hasHandle ? ((0, jsx_runtime_1.jsx)(ps_table_reorder_handle_1.PsTableReorderHandle, { ...attributes, ...listeners, activatorRef: setActivatorNodeRef, label: handleLabel })) : null }), children] }));
28
+ }, children: [(0, jsx_runtime_1.jsx)("td", { className: ps_design_system_1.PallasoftClassNameService.merge('w-9 min-w-9 pl-3 pr-0', handleCellClassName), onClick: (event) => event.stopPropagation(), children: hasHandle ? ((0, jsx_runtime_1.jsx)(ps_table_reorder_handle_1.PsTableReorderHandle, { ...attributes, ...listeners, activatorRef: setActivatorNodeRef, label: handleLabel })) : null }), children] }));
21
29
  };
22
30
  exports.PsTableSortableRow = PsTableSortableRow;
@@ -0,0 +1,2 @@
1
+ import { PsTableDropPlacement } from './ps-table-drop-placement';
2
+ export declare const PsTableDropClasses: Record<PsTableDropPlacement, string>;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTableDropClasses = void 0;
4
+ exports.PsTableDropClasses = {
5
+ after: '[&>td]:shadow-[inset_0_-2px_0_0_rgb(var(--ps-color-primary-rgb))]',
6
+ before: '[&>td]:shadow-[inset_0_2px_0_0_rgb(var(--ps-color-primary-rgb))]',
7
+ into: 'bg-primary/10 [&>td]:shadow-[inset_0_0_0_2px_rgb(var(--ps-color-primary-rgb)/0.5)]',
8
+ };
@@ -0,0 +1,6 @@
1
+ export declare const PsTableDropPlacement: {
2
+ readonly after: "after";
3
+ readonly before: "before";
4
+ readonly into: "into";
5
+ };
6
+ export type PsTableDropPlacement = (typeof PsTableDropPlacement)[keyof typeof PsTableDropPlacement];
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTableDropPlacement = void 0;
4
+ exports.PsTableDropPlacement = {
5
+ after: 'after',
6
+ before: 'before',
7
+ into: 'into',
8
+ };
@@ -0,0 +1,16 @@
1
+ export declare const PsTablePinClasses: {
2
+ readonly cell: "ps-table-pin";
3
+ readonly edge: "ps-table-pin-start-edge";
4
+ readonly end: "ps-table-pin ps-table-pin-end right-0";
5
+ readonly first: {
6
+ readonly both: "left-[4.75rem]";
7
+ readonly none: "left-0";
8
+ readonly reorder: "left-9";
9
+ readonly select: "left-10";
10
+ };
11
+ readonly reorder: "left-0";
12
+ readonly select: {
13
+ readonly alone: "left-0";
14
+ readonly withReorder: "left-9";
15
+ };
16
+ };
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTablePinClasses = void 0;
4
+ exports.PsTablePinClasses = {
5
+ cell: 'ps-table-pin',
6
+ edge: 'ps-table-pin-start-edge',
7
+ end: 'ps-table-pin ps-table-pin-end right-0',
8
+ first: {
9
+ both: 'left-[4.75rem]',
10
+ none: 'left-0',
11
+ reorder: 'left-9',
12
+ select: 'left-10',
13
+ },
14
+ reorder: 'left-0',
15
+ select: {
16
+ alone: 'left-0',
17
+ withReorder: 'left-9',
18
+ },
19
+ };
@@ -0,0 +1,7 @@
1
+ export declare const PsTableBreakpoint: {
2
+ readonly md: "md";
3
+ readonly lg: "lg";
4
+ readonly xl: "xl";
5
+ };
6
+ export type PsTableBreakpoint = (typeof PsTableBreakpoint)[keyof typeof PsTableBreakpoint];
7
+ export declare const PsTableHideBelowClasses: Record<PsTableBreakpoint, string>;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTableHideBelowClasses = exports.PsTableBreakpoint = void 0;
4
+ exports.PsTableBreakpoint = {
5
+ md: 'md',
6
+ lg: 'lg',
7
+ xl: 'xl',
8
+ };
9
+ exports.PsTableHideBelowClasses = {
10
+ md: 'hidden md:table-cell',
11
+ lg: 'hidden lg:table-cell',
12
+ xl: 'hidden xl:table-cell',
13
+ };
@@ -1,18 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Fragment, Suspense, lazy, useDeferredValue, useEffect, useMemo, useRef, useState, } from 'react';
2
+ import { Fragment, Suspense, lazy, useCallback, useDeferredValue, useEffect, 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 { PsTableHideBelowClasses } from '../constants/table-breakpoint';
9
10
  import { PsTableConfig } from '../constants/table-config';
10
11
  import { usePsIncrementalCount } from '../hooks/use-ps-incremental-count';
11
12
  import { usePsScrollAnchor } from '../hooks/use-ps-scroll-anchor';
13
+ import { usePsTableOverflow } from '../hooks/use-ps-table-overflow';
12
14
  import { usePsTableStreamStatus } from '../hooks/use-ps-table-stream-status';
13
15
  import { PsTableCellTooltipService } from '../services/ps-table-cell-tooltip-service';
14
16
  import { PsTableColumnSizeService } from '../services/ps-table-column-size-service';
15
17
  import { PsTableGroupService } from '../services/ps-table-group-service';
18
+ import { PsTablePinService } from '../services/ps-table-pin-service';
16
19
  import { PsTableQueryService } from '../services/ps-table-query-service';
17
20
  import { PsTableReorderService } from '../services/ps-table-reorder-service';
18
21
  import { PsTableScrollService } from '../services/ps-table-scroll-service';
@@ -45,7 +48,7 @@ const alignClassNames = {
45
48
  left: 'text-left',
46
49
  right: 'text-right tabular-nums',
47
50
  };
48
- 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, query, renderExpandedRow, reorder, rows, searchingLabel = PsTableConfig.searchingLabel, searchPlaceholder = PsTableConfig.searchPlaceholder, toolbarActions, totalRows, }) => {
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, }) => {
49
52
  const containerRef = useRef(null);
50
53
  const [uncontrolledPage, setUncontrolledPage] = useState(1);
51
54
  const [uncontrolledQuery, setUncontrolledQuery] = useState(() => PsTableQueryService.empty(defaultSort ? defaultSort : null));
@@ -70,6 +73,9 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
70
73
  const hasToolbar = isSearchable || hasFilters || Boolean(toolbarActions);
71
74
  const isSelectable = Boolean(getBulkActions);
72
75
  const columnCount = visibleColumns.length + (isSelectable ? 1 : 0) + (reorder ? 1 : 0);
76
+ const pins = PsTablePinService.leading(pinFirstColumn, Boolean(reorder), isSelectable);
77
+ const placementOf = (column, columnIndex) => PallasoftClassNameService.merge(column.hideBelow === undefined ? '' : PsTableHideBelowClasses[column.hideBelow], columnIndex === 0 ? pins.first : '', PsTablePinService.end(column.pinEnd === true));
78
+ const overflow = usePsTableOverflow(`${visibleColumns.map((column) => column.id).join(',')}:${rows.length}`);
73
79
  const deferredSearch = useDeferredValue(search);
74
80
  const queriedRows = useMemo(() => isServerPaginated
75
81
  ? rows
@@ -139,23 +145,48 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
139
145
  safePage,
140
146
  toggledGroupKeys,
141
147
  ]);
148
+ const reorderKey = useCallback((row) => reorder && reorder.groupKey ? reorder.groupKey(row) : PsTableReorderService.treeKey, [reorder]);
149
+ const nestingBinding = useMemo(() => {
150
+ if (reorder === undefined || reorder.nesting === undefined)
151
+ return undefined;
152
+ const { nesting } = reorder;
153
+ const find = (id) => visibleRows.find((candidate) => getRowId(candidate) === id);
154
+ return {
155
+ announcements: nesting.announcements,
156
+ canDrag: (id) => {
157
+ const row = find(id);
158
+ return row !== undefined && nesting.canDrag(row);
159
+ },
160
+ canDrop: (activeId, targetId, placement) => {
161
+ const active = find(activeId);
162
+ const target = find(targetId);
163
+ return (active !== undefined && target !== undefined && nesting.canDrop(active, target, placement));
164
+ },
165
+ onMove: (activeId, targetId, placement) => {
166
+ const active = find(activeId);
167
+ const target = find(targetId);
168
+ if (active !== undefined && target !== undefined)
169
+ nesting.onMove(active, target, placement);
170
+ },
171
+ };
172
+ }, [getRowId, reorder, visibleRows]);
142
173
  const reorderItems = useMemo(() => reorder
143
174
  ? visibleRows.map((row) => {
144
175
  const rowId = getRowId(row);
145
176
  const label = getRowLabel ? getRowLabel(row) : rowId;
146
177
  return {
147
- groupKey: reorder.groupKey(row),
178
+ groupKey: reorderKey(row),
148
179
  handleLabel: reorder.getHandleLabel(label),
149
180
  id: rowId,
150
181
  label,
151
182
  };
152
183
  })
153
- : [], [getRowId, getRowLabel, reorder, visibleRows]);
184
+ : [], [getRowId, getRowLabel, reorder, reorderKey, visibleRows]);
154
185
  const renderOverlayRow = (rowId) => {
155
186
  const row = visibleRows.find((candidate) => getRowId(candidate) === rowId);
156
187
  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) }) }));
157
188
  };
158
- 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, onReorder: reorder.onReorder, renderOverlay: renderOverlayRow, children: children }) }));
189
+ 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 }) }));
159
190
  const changePage = (next) => {
160
191
  if (onPageChange) {
161
192
  onPageChange(next);
@@ -239,7 +270,11 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
239
270
  const rowLabel = getRowLabel ? getRowLabel(row) : rowId;
240
271
  const Row = api.Row;
241
272
  const rowExtras = api.isActive
242
- ? { ...api.rowProps(rowId), measureRef: virtualItem ? virtualizer.measureElement : undefined }
273
+ ? {
274
+ ...api.rowProps(rowId),
275
+ handleCellClassName: pins.reorder,
276
+ measureRef: virtualItem ? virtualizer.measureElement : undefined,
277
+ }
243
278
  : { ref: virtualItem ? virtualizer.measureElement : undefined };
244
279
  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
245
280
  ? (event) => {
@@ -252,13 +287,13 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
252
287
  }
253
288
  : undefined, style: motion && motion.delayMs !== undefined
254
289
  ? { animationDelay: `${motion.delayMs}ms` }
255
- : undefined, tabIndex: onRowClick ? 0 : undefined, children: [reorder && !api.isActive ? _jsx("td", { className: 'w-9 pl-3 pr-0' }) : null, isSelectable ? (_jsx("td", { className: 'w-10 pl-4 pr-0', onClick: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), children: _jsx(PsCheckbox, { isSelected: selectedIdSet.has(rowId), label: `Select ${rowLabel}`, onChange: () => toggleRow(rowId) }) })) : null, visibleColumns.map((column) => (_jsx("td", { className: PallasoftClassNameService.merge(densityClassNames[density].cell, alignClassNames[column.align !== null && column.align !== undefined ? column.align : 'left'], column.wrap ? '' : 'whitespace-nowrap', (layout === 'fixed' && !column.wrap) ||
290
+ : undefined, tabIndex: onRowClick ? 0 : undefined, children: [reorder && !api.isActive ? (_jsx("td", { className: PallasoftClassNameService.merge('w-9 min-w-9 pl-3 pr-0', pins.reorder) })) : null, isSelectable ? (_jsx("td", { className: PallasoftClassNameService.merge('w-10 min-w-10 pl-4 pr-0', pins.select), onClick: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), children: _jsx(PsCheckbox, { isSelected: selectedIdSet.has(rowId), label: `Select ${rowLabel}`, onChange: () => toggleRow(rowId) }) })) : null, visibleColumns.map((column, columnIndex) => (_jsx("td", { className: PallasoftClassNameService.merge(densityClassNames[density].cell, placementOf(column, columnIndex), alignClassNames[column.align !== null && column.align !== undefined ? column.align : 'left'], column.wrap ? '' : 'whitespace-nowrap', (layout === 'fixed' && !column.wrap) ||
256
291
  PsTableColumnSizeService.maxWidthOf(column) !== undefined
257
292
  ? 'truncate'
258
293
  : '', 'text-sm text-ink'), style: columnStyle(column), 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));
259
294
  };
260
295
  const renderEntries = (api, entries) => reorder
261
- ? PsTableReorderService.runs(entries, (entry) => reorder.groupKey(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))))
296
+ ? 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))))
262
297
  : entries.map((entry) => renderRow(api, entry.row, entry.motion, entry.virtualItem));
263
298
  if (isLoading)
264
299
  return (_jsx(PsSkeletonTable, { className: className, columns: visibleColumns.length, hasToolbar: hasToolbar, rows: PsTableConfig.skeletonRows }));
@@ -277,14 +312,14 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
277
312
  ? grouping.renderGroupSummary(group.rows)
278
313
  : undefined }), _jsx(PsCollapse, { isOpen: isExpanded && !closingGroupKeys.includes(group.key), children: renderCardList(group.rows, null) })] }, `group-${group.key}`));
279
314
  })
280
- : 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', mobileLayout === 'cards' ? 'hidden sm:block' : ''), 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: 'w-9 pl-3 pr-0', 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: 'w-10 pl-4 pr-0', 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) => {
315
+ : 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) => {
281
316
  const align = column.align !== null && column.align !== undefined ? column.align : 'left';
282
317
  const isSorted = sort !== null && sort.columnId === column.id;
283
318
  return (_jsx("th", { "aria-sort": isSorted
284
319
  ? sort.direction
285
320
  : column.sortValue && isQueryable
286
321
  ? 'none'
287
- : undefined, className: PallasoftClassNameService.merge(densityClassNames[density].head, alignClassNames[align], 'whitespace-nowrap'), scope: 'col', style: columnStyle(column), children: column.sortValue && isQueryable ? (_jsx(PsTableSortHeader, { align: align, columnId: column.id, label: column.label, onSort: updateSort, sort: sort })) : (column.label) }, column.id));
322
+ : undefined, className: PallasoftClassNameService.merge(densityClassNames[density].head, alignClassNames[align], placementOf(column, columnIndex), 'whitespace-nowrap'), scope: 'col', style: columnStyle(column), children: column.sortValue && isQueryable ? (_jsx(PsTableSortHeader, { align: align, columnId: column.id, label: column.label, onSort: updateSort, sort: sort })) : (column.label) }, column.id));
288
323
  })] }) }), _jsxs("tbody", { className: 'divide-y divide-line', ref: bodyRef, children: [visibleRows.length === 0 && isSearching ? (_jsx("tr", { children: _jsx("td", { className: 'p-0', colSpan: columnCount, children: _jsx(PsTableSearchingState, { label: searchingLabel, skeletonRows: PsTableConfig.searchingSkeletonRows }) }) })) : null, visibleRows.length === 0 && isQueried && !isSearching ? (_jsx("tr", { children: _jsxs("td", { className: 'px-4 py-12 text-center', colSpan: columnCount, children: [_jsx("p", { className: 'text-sm font-semibold text-ink', children: PsTableConfig.noMatchesTitle }), _jsx("p", { className: 'mt-1 text-sm text-ink/55', children: PsTableConfig.noMatchesDescription }), _jsx("button", { className: PallasoftClassNameService.merge(PsTableControlClasses.textButton, 'mt-3 text-primary hover:underline'), onClick: clearFilters, type: 'button', children: "Clear search and filters" })] }) })) : null, grouping
289
324
  ? groups.map((group, groupIndex) => {
290
325
  const isExpanded = isGroupExpanded(group.key, groupIndex);
@@ -306,7 +341,7 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
306
341
  }))),
307
342
  _jsx("tr", { "aria-hidden": 'true', children: _jsx("td", { className: 'p-0', colSpan: columnCount, style: { height: virtualBottom } }) }, 'virtual-bottom'),
308
343
  ]
309
- : 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", {}) : null, isSelectable ? _jsx("td", {}) : null, visibleColumns.map((column) => (_jsx("td", { className: PallasoftClassNameService.merge(densityClassNames[density].cell, alignClassNames[column.align !== null && column.align !== undefined
344
+ : 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
310
345
  ? column.align
311
346
  : '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] })] }));
312
347
  };
@@ -3,24 +3,37 @@ import { DndContext } from '@dnd-kit/core';
3
3
  import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
4
4
  import { PsDragClasses, PsDragListLiftFrames, PsDragOverlay } from '@pallasoft/ps-core-components';
5
5
  import { usePsTableReorder } from '../hooks/use-ps-table-reorder';
6
+ import { PsTableReorderService } from '../services/ps-table-reorder-service';
6
7
  import { PsTableSortableRow } from './ps-table-sortable-row';
7
8
  export const PsTableReorderFrame = ({ children, renderOverlay, ...input }) => {
8
- const { activeId, activeRunId, contextProps, placements } = usePsTableReorder(input);
9
+ const { activeId, activeRunId, contextProps, drop, placements } = usePsTableReorder(input);
9
10
  const itemById = new Map(input.items.map((item) => [item.id, item]));
11
+ const { nesting } = input;
10
12
  const api = {
11
13
  isActive: true,
12
14
  Row: PsTableSortableRow,
13
15
  rowProps: (rowId) => {
14
16
  const item = itemById.get(rowId);
15
17
  const placement = placements.get(rowId);
18
+ if (nesting !== undefined)
19
+ return {
20
+ dropPlacement: drop !== null && drop.overId === rowId ? drop.placement : null,
21
+ handleLabel: item === undefined ? '' : item.handleLabel,
22
+ hasHandle: item !== undefined && input.enabled && nesting.canDrag(rowId),
23
+ isDisabled: !input.enabled,
24
+ isDropTarget: item !== undefined,
25
+ rowId,
26
+ };
16
27
  return {
28
+ dropPlacement: null,
17
29
  handleLabel: item === undefined ? '' : item.handleLabel,
18
30
  hasHandle: item !== undefined && input.enabled && placement !== undefined && placement.size > 1,
19
31
  isDisabled: activeRunId !== null && (placement === undefined || placement.runId !== activeRunId),
32
+ isDropTarget: false,
20
33
  rowId,
21
34
  };
22
35
  },
23
- run: (runId, ids, runChildren) => (_jsx(SortableContext, { items: ids, strategy: verticalListSortingStrategy, children: runChildren }, runId)),
36
+ run: (runId, ids, runChildren) => (_jsx(SortableContext, { items: ids, strategy: nesting === undefined ? verticalListSortingStrategy : PsTableReorderService.still, children: runChildren }, runId)),
24
37
  };
25
38
  return (_jsxs(DndContext, { ...contextProps, children: [children(api), _jsx(PsDragOverlay, { className: PsDragClasses.lifted, frames: PsDragListLiftFrames, children: activeId === null ? null : renderOverlay(activeId) })] }));
26
39
  };