@pallasoft/ps-table-components 1.22.1 → 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.
- package/dist/components/ps-data-table.d.ts +2 -1
- package/dist/components/ps-data-table.js +47 -11
- package/dist/components/ps-table-bulk-bar.js +3 -2
- package/dist/components/ps-table-column-picker.js +2 -1
- package/dist/components/ps-table-export-button.js +1 -1
- package/dist/components/ps-table-group-header.js +1 -1
- package/dist/components/ps-table-pagination.js +2 -2
- package/dist/components/ps-table-reorder-frame.js +15 -2
- package/dist/components/ps-table-reorder-handle.js +1 -1
- package/dist/components/ps-table-sort-header.js +2 -2
- package/dist/components/ps-table-sortable-row.d.ts +5 -1
- package/dist/components/ps-table-sortable-row.js +12 -4
- package/dist/components/ps-table-toolbar.js +2 -2
- package/dist/constants/ps-table-control-classes.d.ts +9 -0
- package/dist/constants/ps-table-control-classes.js +12 -0
- package/dist/constants/ps-table-drop-classes.d.ts +2 -0
- package/dist/constants/ps-table-drop-classes.js +8 -0
- package/dist/constants/ps-table-drop-placement.d.ts +6 -0
- package/dist/constants/ps-table-drop-placement.js +8 -0
- package/dist/constants/ps-table-pin-classes.d.ts +16 -0
- package/dist/constants/ps-table-pin-classes.js +19 -0
- package/dist/constants/table-breakpoint.d.ts +7 -0
- package/dist/constants/table-breakpoint.js +13 -0
- package/dist/esm/components/ps-data-table.js +48 -12
- package/dist/esm/components/ps-table-bulk-bar.js +3 -2
- package/dist/esm/components/ps-table-column-picker.js +2 -1
- package/dist/esm/components/ps-table-export-button.js +1 -1
- package/dist/esm/components/ps-table-group-header.js +1 -1
- package/dist/esm/components/ps-table-pagination.js +2 -2
- package/dist/esm/components/ps-table-reorder-frame.js +15 -2
- package/dist/esm/components/ps-table-reorder-handle.js +1 -1
- package/dist/esm/components/ps-table-sort-header.js +2 -2
- package/dist/esm/components/ps-table-sortable-row.js +12 -4
- package/dist/esm/components/ps-table-toolbar.js +2 -2
- package/dist/esm/constants/ps-table-control-classes.js +9 -0
- package/dist/esm/constants/ps-table-drop-classes.js +5 -0
- package/dist/esm/constants/ps-table-drop-placement.js +5 -0
- package/dist/esm/constants/ps-table-pin-classes.js +16 -0
- package/dist/esm/constants/table-breakpoint.js +10 -0
- package/dist/esm/hooks/use-ps-table-overflow.js +19 -0
- package/dist/esm/hooks/use-ps-table-reorder.js +89 -15
- package/dist/esm/index.js +10 -0
- package/dist/esm/interfaces/table-drop.js +1 -0
- package/dist/esm/interfaces/table-nesting-announcements.js +1 -0
- package/dist/esm/interfaces/table-overflow-edges.js +1 -0
- package/dist/esm/interfaces/table-pin-classes.js +1 -0
- package/dist/esm/interfaces/table-reorder-nesting-binding.js +1 -0
- package/dist/esm/interfaces/table-reorder-nesting.js +1 -0
- package/dist/esm/services/ps-table-overflow-service.js +11 -0
- package/dist/esm/services/ps-table-pin-service.js +22 -0
- package/dist/esm/services/ps-table-reorder-service.js +38 -1
- package/dist/hooks/use-ps-table-overflow.d.ts +6 -0
- package/dist/hooks/use-ps-table-overflow.js +23 -0
- package/dist/hooks/use-ps-table-reorder.d.ts +6 -2
- package/dist/hooks/use-ps-table-reorder.js +87 -13
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/interfaces/table-column.d.ts +3 -0
- package/dist/interfaces/table-drop.d.ts +5 -0
- package/dist/interfaces/table-drop.js +2 -0
- package/dist/interfaces/table-nesting-announcements.d.ts +5 -0
- package/dist/interfaces/table-nesting-announcements.js +2 -0
- package/dist/interfaces/table-overflow-edges.d.ts +4 -0
- package/dist/interfaces/table-overflow-edges.js +2 -0
- package/dist/interfaces/table-pin-classes.d.ts +5 -0
- package/dist/interfaces/table-pin-classes.js +2 -0
- package/dist/interfaces/table-reorder-nesting-binding.d.ts +8 -0
- package/dist/interfaces/table-reorder-nesting-binding.js +2 -0
- package/dist/interfaces/table-reorder-nesting.d.ts +8 -0
- package/dist/interfaces/table-reorder-nesting.js +2 -0
- package/dist/interfaces/table-reorder-row-props.d.ts +3 -0
- package/dist/interfaces/table-reorder.d.ts +4 -2
- package/dist/services/ps-table-overflow-service.d.ts +6 -0
- package/dist/services/ps-table-overflow-service.js +15 -0
- package/dist/services/ps-table-pin-service.d.ts +5 -0
- package/dist/services/ps-table-pin-service.js +26 -0
- package/dist/services/ps-table-reorder-service.d.ts +11 -0
- package/dist/services/ps-table-reorder-service.js +38 -1
- package/package.json +2 -2
|
@@ -1,17 +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
|
+
import { PsTableControlClasses } from '../constants/ps-table-control-classes';
|
|
9
|
+
import { PsTableHideBelowClasses } from '../constants/table-breakpoint';
|
|
8
10
|
import { PsTableConfig } from '../constants/table-config';
|
|
9
11
|
import { usePsIncrementalCount } from '../hooks/use-ps-incremental-count';
|
|
10
12
|
import { usePsScrollAnchor } from '../hooks/use-ps-scroll-anchor';
|
|
13
|
+
import { usePsTableOverflow } from '../hooks/use-ps-table-overflow';
|
|
11
14
|
import { usePsTableStreamStatus } from '../hooks/use-ps-table-stream-status';
|
|
12
15
|
import { PsTableCellTooltipService } from '../services/ps-table-cell-tooltip-service';
|
|
13
16
|
import { PsTableColumnSizeService } from '../services/ps-table-column-size-service';
|
|
14
17
|
import { PsTableGroupService } from '../services/ps-table-group-service';
|
|
18
|
+
import { PsTablePinService } from '../services/ps-table-pin-service';
|
|
15
19
|
import { PsTableQueryService } from '../services/ps-table-query-service';
|
|
16
20
|
import { PsTableReorderService } from '../services/ps-table-reorder-service';
|
|
17
21
|
import { PsTableScrollService } from '../services/ps-table-scroll-service';
|
|
@@ -44,7 +48,7 @@ const alignClassNames = {
|
|
|
44
48
|
left: 'text-left',
|
|
45
49
|
right: 'text-right tabular-nums',
|
|
46
50
|
};
|
|
47
|
-
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, }) => {
|
|
48
52
|
const containerRef = useRef(null);
|
|
49
53
|
const [uncontrolledPage, setUncontrolledPage] = useState(1);
|
|
50
54
|
const [uncontrolledQuery, setUncontrolledQuery] = useState(() => PsTableQueryService.empty(defaultSort ? defaultSort : null));
|
|
@@ -69,6 +73,9 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
69
73
|
const hasToolbar = isSearchable || hasFilters || Boolean(toolbarActions);
|
|
70
74
|
const isSelectable = Boolean(getBulkActions);
|
|
71
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}`);
|
|
72
79
|
const deferredSearch = useDeferredValue(search);
|
|
73
80
|
const queriedRows = useMemo(() => isServerPaginated
|
|
74
81
|
? rows
|
|
@@ -138,23 +145,48 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
138
145
|
safePage,
|
|
139
146
|
toggledGroupKeys,
|
|
140
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]);
|
|
141
173
|
const reorderItems = useMemo(() => reorder
|
|
142
174
|
? visibleRows.map((row) => {
|
|
143
175
|
const rowId = getRowId(row);
|
|
144
176
|
const label = getRowLabel ? getRowLabel(row) : rowId;
|
|
145
177
|
return {
|
|
146
|
-
groupKey:
|
|
178
|
+
groupKey: reorderKey(row),
|
|
147
179
|
handleLabel: reorder.getHandleLabel(label),
|
|
148
180
|
id: rowId,
|
|
149
181
|
label,
|
|
150
182
|
};
|
|
151
183
|
})
|
|
152
|
-
: [], [getRowId, getRowLabel, reorder, visibleRows]);
|
|
184
|
+
: [], [getRowId, getRowLabel, reorder, reorderKey, visibleRows]);
|
|
153
185
|
const renderOverlayRow = (rowId) => {
|
|
154
186
|
const row = visibleRows.find((candidate) => getRowId(candidate) === rowId);
|
|
155
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) }) }));
|
|
156
188
|
};
|
|
157
|
-
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 }) }));
|
|
158
190
|
const changePage = (next) => {
|
|
159
191
|
if (onPageChange) {
|
|
160
192
|
onPageChange(next);
|
|
@@ -238,7 +270,11 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
238
270
|
const rowLabel = getRowLabel ? getRowLabel(row) : rowId;
|
|
239
271
|
const Row = api.Row;
|
|
240
272
|
const rowExtras = api.isActive
|
|
241
|
-
? {
|
|
273
|
+
? {
|
|
274
|
+
...api.rowProps(rowId),
|
|
275
|
+
handleCellClassName: pins.reorder,
|
|
276
|
+
measureRef: virtualItem ? virtualizer.measureElement : undefined,
|
|
277
|
+
}
|
|
242
278
|
: { ref: virtualItem ? virtualizer.measureElement : undefined };
|
|
243
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
|
|
244
280
|
? (event) => {
|
|
@@ -251,13 +287,13 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
251
287
|
}
|
|
252
288
|
: undefined, style: motion && motion.delayMs !== undefined
|
|
253
289
|
? { animationDelay: `${motion.delayMs}ms` }
|
|
254
|
-
: 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) ||
|
|
255
291
|
PsTableColumnSizeService.maxWidthOf(column) !== undefined
|
|
256
292
|
? 'truncate'
|
|
257
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));
|
|
258
294
|
};
|
|
259
295
|
const renderEntries = (api, entries) => reorder
|
|
260
|
-
? PsTableReorderService.runs(entries, (entry) =>
|
|
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))))
|
|
261
297
|
: entries.map((entry) => renderRow(api, entry.row, entry.motion, entry.virtualItem));
|
|
262
298
|
if (isLoading)
|
|
263
299
|
return (_jsx(PsSkeletonTable, { className: className, columns: visibleColumns.length, hasToolbar: hasToolbar, rows: PsTableConfig.skeletonRows }));
|
|
@@ -276,15 +312,15 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
276
312
|
? grouping.renderGroupSummary(group.rows)
|
|
277
313
|
: undefined }), _jsx(PsCollapse, { isOpen: isExpanded && !closingGroupKeys.includes(group.key), children: renderCardList(group.rows, null) })] }, `group-${group.key}`));
|
|
278
314
|
})
|
|
279
|
-
: 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: '
|
|
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) => {
|
|
280
316
|
const align = column.align !== null && column.align !== undefined ? column.align : 'left';
|
|
281
317
|
const isSorted = sort !== null && sort.columnId === column.id;
|
|
282
318
|
return (_jsx("th", { "aria-sort": isSorted
|
|
283
319
|
? sort.direction
|
|
284
320
|
: column.sortValue && isQueryable
|
|
285
321
|
? 'none'
|
|
286
|
-
: 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));
|
|
287
|
-
})] }) }), _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: '
|
|
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));
|
|
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
|
|
288
324
|
? groups.map((group, groupIndex) => {
|
|
289
325
|
const isExpanded = isGroupExpanded(group.key, groupIndex);
|
|
290
326
|
return (_jsxs(Fragment, { children: [_jsx("tr", { children: _jsx("td", { className: 'p-0', colSpan: columnCount, children: _jsx(PsTableGroupHeader, { count: group.rows.length, isExpanded: isExpanded, label: grouping.getGroupLabel(group.key, group.rows), onToggle: () => toggleGroup(group.key, isExpanded), summary: grouping.renderGroupSummary
|
|
@@ -305,7 +341,7 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
305
341
|
}))),
|
|
306
342
|
_jsx("tr", { "aria-hidden": 'true', children: _jsx("td", { className: 'p-0', colSpan: columnCount, style: { height: virtualBottom } }) }, 'virtual-bottom'),
|
|
307
343
|
]
|
|
308
|
-
: 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
|
|
309
345
|
? column.align
|
|
310
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] })] }));
|
|
311
347
|
};
|
|
@@ -1,9 +1,10 @@
|
|
|
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 { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
4
|
+
import { PsTableControlClasses } from '../constants/ps-table-control-classes';
|
|
4
5
|
export const PsTableBulkBar = ({ actions, onClear, selectedCount }) => {
|
|
5
6
|
const safeActions = actions.filter((action) => !action.isDanger);
|
|
6
7
|
const dangerActions = actions.filter((action) => action.isDanger);
|
|
7
|
-
const renderAction = (action) => (_jsx("button", { className: PallasoftClassNameService.merge(
|
|
8
|
-
return (_jsxs("div", { "aria-live": 'polite', className:
|
|
8
|
+
const renderAction = (action) => (_jsx("button", { className: PallasoftClassNameService.merge(PsTableControlClasses.bulkAction, action.isDanger ? 'text-danger hover:bg-danger/10' : 'text-ink hover:bg-ink/5'), disabled: action.isDisabled, onClick: action.onSelect, type: 'button', children: action.label }, action.id));
|
|
9
|
+
return (_jsxs("div", { "aria-live": 'polite', className: PsTableControlClasses.bulkBar, children: [_jsxs("span", { className: PsTableControlClasses.bulkCount, children: [selectedCount, " selected"] }), _jsxs("div", { className: 'ps-scrollbar-hidden flex min-w-0 flex-1 items-center gap-1 overflow-x-auto', children: [safeActions.map(renderAction), dangerActions.length > 0 && safeActions.length > 0 ? (_jsx("span", { "aria-hidden": 'true', className: 'mx-1 h-5 w-px shrink-0 bg-line' })) : null, dangerActions.map(renderAction)] }), _jsx("button", { "aria-label": 'Clear selection', className: PsTableControlClasses.iconButton, onClick: onClear, title: 'Clear selection', type: 'button', children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'h-4 w-4', iconKey: PsIconKey.Close }) })] }));
|
|
9
10
|
};
|
|
@@ -4,10 +4,11 @@ import { PallasoftZIndex } from '@pallasoft/ps-design-system';
|
|
|
4
4
|
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
5
5
|
import { PsCheckbox } from '@pallasoft/ps-input-components';
|
|
6
6
|
import { Button, Dialog, DialogTrigger, Popover } from 'react-aria-components';
|
|
7
|
+
import { PsTableControlClasses } from '../constants/ps-table-control-classes';
|
|
7
8
|
export const PsTableColumnPicker = ({ columns, hiddenColumnIds, onChange, }) => {
|
|
8
9
|
const zIndex = usePsLayerZIndex(PallasoftZIndex.Dropdown);
|
|
9
10
|
const visibleCount = columns.filter((column) => !hiddenColumnIds.includes(column.id)).length;
|
|
10
|
-
return (_jsxs(DialogTrigger, { children: [_jsxs(Button, { className:
|
|
11
|
+
return (_jsxs(DialogTrigger, { children: [_jsxs(Button, { className: PsTableControlClasses.toolbarButton, children: [_jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4 sm:hidden', iconKey: PsIconKey.Columns }), _jsx("span", { className: 'sr-only sm:not-sr-only', children: "Columns" }), _jsx(PsIcon, { "aria-hidden": 'true', className: 'hidden h-4 w-4 sm:block', iconKey: PsIconKey.Chevron })] }), _jsx(Popover, { className: 'ps-floating-panel w-60 outline-none', offset: 6, placement: 'bottom end', style: { zIndex }, children: _jsx(Dialog, { "aria-label": 'Columns', className: 'flex flex-col gap-0.5 p-1.5 outline-none', children: _jsx(PsLayer, { zIndex: zIndex, children: columns.map((column) => {
|
|
11
12
|
const isVisible = !hiddenColumnIds.includes(column.id);
|
|
12
13
|
return (_jsx(PsCheckbox, { className: 'ps-floating-option w-full', isDisabled: isVisible && visibleCount <= 1, isSelected: isVisible, label: column.label, onChange: () => onChange(isVisible
|
|
13
14
|
? [...hiddenColumnIds, column.id]
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
3
|
-
export const PsTableExportButton = ({ label, onExport }) => (_jsxs("button", { className: 'ps-focus-ring ps-liquid ps-liquid-control
|
|
3
|
+
export const PsTableExportButton = ({ label, onExport }) => (_jsxs("button", { className: 'ps-focus-ring ps-liquid ps-liquid-control ps-control ps-control-padding inline-flex min-w-ps-control items-center justify-center gap-2 rounded-ps-control border border-ps-control text-sm font-semibold text-ink transition-colors hover:border-primary/40', onClick: onExport, type: 'button', children: [_jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: PsIconKey.Download }), _jsx("span", { className: 'sr-only sm:not-sr-only', children: label })] }));
|
|
@@ -1,4 +1,4 @@
|
|
|
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 { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
4
|
-
export const PsTableGroupHeader = ({ count, isExpanded, label, onToggle, summary, }) => (_jsxs("button", { "aria-expanded": isExpanded, className: 'ps-focus-ring flex w-full flex-wrap items-center gap-x-3 gap-y-2 bg-mist/60 px-4 py-3 text-left transition-colors hover:bg-mist', onClick: onToggle, type: 'button', children: [_jsx(PsIcon, { "aria-hidden": 'true', className: PallasoftClassNameService.merge('h-4 w-4 shrink-0 text-ink/50 transition-transform duration-200', isExpanded ? '' : '-rotate-90'), iconKey: PsIconKey.Chevron }), _jsx("span", { className: 'text-sm font-semibold text-ink', children: label }), _jsx("span", { className: 'rounded-
|
|
4
|
+
export const PsTableGroupHeader = ({ count, isExpanded, label, onToggle, summary, }) => (_jsxs("button", { "aria-expanded": isExpanded, className: 'ps-focus-ring flex w-full flex-wrap items-center gap-x-3 gap-y-2 bg-mist/60 px-4 py-3 text-left transition-colors hover:bg-mist', onClick: onToggle, type: 'button', children: [_jsx(PsIcon, { "aria-hidden": 'true', className: PallasoftClassNameService.merge('h-4 w-4 shrink-0 text-ink/50 transition-transform duration-200', isExpanded ? '' : '-rotate-90'), iconKey: PsIconKey.Chevron }), _jsx("span", { className: 'text-sm font-semibold text-ink', children: label }), _jsx("span", { className: 'rounded-ps-control bg-surface px-2 py-0.5 text-xs font-semibold tabular-nums text-ink/60', children: count }), summary ? (_jsx("span", { className: 'ml-auto flex flex-wrap items-center justify-end gap-2', children: summary })) : null] }));
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
3
|
+
import { PsTableControlClasses } from '../constants/ps-table-control-classes';
|
|
3
4
|
export const PsTablePagination = ({ className, isPartial = false, loadedPageCount, onPageChange, page, pageCount, pageSize, totalRows, }) => {
|
|
4
5
|
const firstRow = totalRows === 0 ? 0 : (page - 1) * pageSize + 1;
|
|
5
6
|
const lastRow = Math.min(page * pageSize, totalRows);
|
|
6
|
-
const buttonClassName = 'ps-focus-ring inline-flex min-h-9 items-center rounded-full border border-line px-3.5 text-sm font-semibold text-ink transition-colors disabled:cursor-not-allowed disabled:opacity-40 enabled:hover:border-primary/40';
|
|
7
7
|
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
8
|
? 'No rows'
|
|
9
|
-
: `${firstRow}–${lastRow} of ${totalRows}${isPartial ? '+' : ''}` }), _jsxs("div", { className: 'flex items-center gap-2', children: [_jsx("button", { className:
|
|
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
10
|
(loadedPageCount === undefined ? pageCount : Math.min(loadedPageCount, pageCount)), onClick: () => onPageChange(page + 1), type: 'button', children: "Next" })] })] }));
|
|
11
11
|
};
|
|
@@ -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
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { PsDragGrip } from '@pallasoft/ps-core-components';
|
|
3
3
|
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
4
|
-
export const PsTableReorderHandle = ({ activatorRef, className, label, ...buttonProps }) => (_jsx("button", { ...buttonProps, "aria-label": label, className: PallasoftClassNameService.merge('ps-focus-ring flex h-7 w-6 shrink-0 cursor-grab touch-none items-center justify-center rounded-
|
|
4
|
+
export const PsTableReorderHandle = ({ activatorRef, className, label, ...buttonProps }) => (_jsx("button", { ...buttonProps, "aria-label": label, className: 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: _jsx(PsDragGrip, {}) }));
|
|
@@ -8,7 +8,7 @@ export const PsTableSortHeader = ({ align, columnId, label, onSort, sort, }) =>
|
|
|
8
8
|
: sort.direction === 'ascending'
|
|
9
9
|
? PsIconKey.SortAscending
|
|
10
10
|
: PsIconKey.SortDescending;
|
|
11
|
-
return (_jsxs("button", { className: PallasoftClassNameService.merge('ps-focus-ring group inline-flex items-center gap-1 rounded-
|
|
11
|
+
return (_jsxs("button", { className: 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: [_jsx("span", { children: label }), _jsx(PsIcon, { "aria-hidden": 'true', className: PallasoftClassNameService.merge('block h-3.5 w-3.5 shrink-0', isSorted
|
|
12
12
|
? 'text-primary'
|
|
13
|
-
: 'opacity-0 group-hover:opacity-60 group-focus-visible:opacity-60'), iconKey: iconKey })] }));
|
|
13
|
+
: 'opacity-0 group-hover:opacity-60 group-focus-visible:opacity-60 [@media(hover:none)]:opacity-40'), iconKey: iconKey })] }));
|
|
14
14
|
};
|
|
@@ -2,10 +2,18 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useSortable } from '@dnd-kit/sortable';
|
|
3
3
|
import { CSS } from '@dnd-kit/utilities';
|
|
4
4
|
import { PsDragConfig } from '@pallasoft/ps-core-components';
|
|
5
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
6
|
+
import { PsTableDropClasses } from '../constants/ps-table-drop-classes';
|
|
5
7
|
import { PsTableReorderHandle } from './ps-table-reorder-handle';
|
|
6
|
-
export const PsTableSortableRow = ({ children, className, handleLabel, hasHandle, isDisabled, measureRef, rowId, style, ...rowProps }) => {
|
|
7
|
-
const { attributes, isDragging, listeners, setActivatorNodeRef, setNodeRef, transform, transition, } = useSortable({
|
|
8
|
-
|
|
8
|
+
export const PsTableSortableRow = ({ children, className, dropPlacement = null, handleCellClassName, handleLabel, hasHandle, isDisabled, isDropTarget = false, measureRef, rowId, style, ...rowProps }) => {
|
|
9
|
+
const { attributes, isDragging, listeners, setActivatorNodeRef, setNodeRef, transform, transition, } = useSortable({
|
|
10
|
+
disabled: {
|
|
11
|
+
draggable: isDisabled || !hasHandle,
|
|
12
|
+
droppable: isDisabled || (!hasHandle && !isDropTarget),
|
|
13
|
+
},
|
|
14
|
+
id: rowId,
|
|
15
|
+
});
|
|
16
|
+
return (_jsxs("tr", { ...rowProps, className: PallasoftClassNameService.merge(className, dropPlacement === null ? '' : PsTableDropClasses[dropPlacement]), ref: (element) => {
|
|
9
17
|
setNodeRef(element);
|
|
10
18
|
if (measureRef)
|
|
11
19
|
measureRef(element);
|
|
@@ -14,5 +22,5 @@ export const PsTableSortableRow = ({ children, className, handleLabel, hasHandle
|
|
|
14
22
|
opacity: isDragging ? PsDragConfig.placeholderOpacity : style?.opacity,
|
|
15
23
|
transform: CSS.Translate.toString(transform),
|
|
16
24
|
transition,
|
|
17
|
-
}, children: [_jsx("td", { className: 'w-9 pl-3 pr-0', onClick: (event) => event.stopPropagation(), children: hasHandle ? (_jsx(PsTableReorderHandle, { ...attributes, ...listeners, activatorRef: setActivatorNodeRef, label: handleLabel })) : null }), children] }));
|
|
25
|
+
}, children: [_jsx("td", { className: PallasoftClassNameService.merge('w-9 min-w-9 pl-3 pr-0', handleCellClassName), onClick: (event) => event.stopPropagation(), children: hasHandle ? (_jsx(PsTableReorderHandle, { ...attributes, ...listeners, activatorRef: setActivatorNodeRef, label: handleLabel })) : null }), children] }));
|
|
18
26
|
};
|
|
@@ -3,12 +3,12 @@ import { PsLayer, usePsLayerZIndex } from '@pallasoft/ps-core-components';
|
|
|
3
3
|
import { PallasoftClassNameService, PallasoftZIndex } from '@pallasoft/ps-design-system';
|
|
4
4
|
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
5
5
|
import { Button, Dialog, DialogTrigger, Popover } from 'react-aria-components';
|
|
6
|
+
import { PsTableControlClasses } from '../constants/ps-table-control-classes';
|
|
6
7
|
import { PsTableQueryService } from '../services/ps-table-query-service';
|
|
7
8
|
import { PsTableFilterField } from './ps-table-filter-field';
|
|
8
|
-
const toolbarButtonClassName = 'ps-input-control ps-control-trigger ps-focus-ring ps-control ps-control-padding inline-flex min-w-11 items-center justify-center gap-2 rounded-ps-control text-sm font-semibold text-ink outline-none transition-colors hover:border-primary/40';
|
|
9
9
|
export const PsTableToolbar = ({ actions, columns, filters, isSearchable, onClearFilters, onFilterChange, onSearchChange, rows, search, searchPlaceholder, }) => {
|
|
10
10
|
const filterableColumns = columns.filter((column) => column.filter);
|
|
11
11
|
const zIndex = usePsLayerZIndex(PallasoftZIndex.Dropdown);
|
|
12
12
|
const activeCount = filterableColumns.filter((column) => PsTableQueryService.isActive(filters[column.id])).length;
|
|
13
|
-
return (_jsxs("div", { className: 'flex flex-wrap items-center gap-2', children: [isSearchable ? (_jsxs("label", { className: 'relative basis-full sm:min-w-[12rem] sm:flex-1 sm:basis-auto sm:max-w-xs', children: [_jsx("span", { className: 'sr-only', children: searchPlaceholder }), _jsx(PsIcon, { "aria-hidden": 'true', className: 'pointer-events-none absolute left-3.5 top-1/2 h-4 w-4 -translate-y-1/2 text-ink/45', iconKey: PsIconKey.Search }), _jsx("input", { className: 'ps-input-control ps-focus-ring ps-control rounded-ps-control pl-10 text-sm', onChange: (event) => onSearchChange(event.target.value), placeholder: searchPlaceholder, type: 'search', value: search })] })) : null, _jsxs("div", { className: 'ps-scrollbar-hidden flex w-full min-w-0 flex-1 items-center gap-2 overflow-x-auto sm:w-auto', children: [filterableColumns.length > 0 ? (_jsxs(DialogTrigger, { children: [_jsxs(Button, { className:
|
|
13
|
+
return (_jsxs("div", { className: 'flex flex-wrap items-center gap-2', children: [isSearchable ? (_jsxs("label", { className: 'relative basis-full sm:min-w-[12rem] sm:flex-1 sm:basis-auto sm:max-w-xs', children: [_jsx("span", { className: 'sr-only', children: searchPlaceholder }), _jsx(PsIcon, { "aria-hidden": 'true', className: 'pointer-events-none absolute left-3.5 top-1/2 h-4 w-4 -translate-y-1/2 text-ink/45', iconKey: PsIconKey.Search }), _jsx("input", { className: 'ps-input-control ps-focus-ring ps-control rounded-ps-control pl-10 text-sm', onChange: (event) => onSearchChange(event.target.value), placeholder: searchPlaceholder, type: 'search', value: search })] })) : null, _jsxs("div", { className: 'ps-scrollbar-hidden flex w-full min-w-0 flex-1 items-center gap-2 overflow-x-auto sm:w-auto', children: [filterableColumns.length > 0 ? (_jsxs(DialogTrigger, { children: [_jsxs(Button, { className: PsTableControlClasses.toolbarButton, children: [_jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: PsIconKey.Filter }), _jsx("span", { className: 'sr-only sm:not-sr-only', children: "Filters" }), activeCount > 0 ? (_jsx("span", { className: 'inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1.5 text-xs font-semibold text-[color:var(--ps-color-primary-contrast)]', children: activeCount })) : null] }), _jsx(Popover, { className: 'ps-floating-panel ps-scrollbar-hidden max-h-[min(34rem,80vh)] w-[min(22rem,calc(100vw-2rem))] overflow-y-auto outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: _jsx(Dialog, { "aria-label": 'Filters', className: 'flex flex-col gap-4 p-4 outline-none', children: _jsxs(PsLayer, { zIndex: zIndex, children: [filterableColumns.map((column) => (_jsx(PsTableFilterField, { kind: column.filter ? column.filter.kind : 'text', label: column.label, onChange: (value) => onFilterChange(column.id, value), options: PsTableQueryService.options(rows, column), value: filters[column.id] ? filters[column.id] : {} }, column.id))), _jsx("div", { className: 'flex justify-end border-t border-line pt-3', children: _jsx("button", { className: PallasoftClassNameService.merge(PsTableControlClasses.textButton, 'text-ink/65 hover:text-ink disabled:opacity-40'), disabled: activeCount === 0, onClick: onClearFilters, type: 'button', children: "Clear all" }) })] }) }) })] })) : null, activeCount > 0 ? (_jsx("button", { className: PallasoftClassNameService.merge(PsTableControlClasses.textButton, 'text-ink/65 hover:text-ink'), onClick: onClearFilters, type: 'button', children: "Clear filters" })) : null, actions ? _jsx("div", { className: 'ml-auto flex shrink-0 items-center gap-2', children: actions }) : null] })] }));
|
|
14
14
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const PsTableControlClasses = {
|
|
2
|
+
bulkAction: 'ps-focus-ring ps-control-compact ps-control-padding-compact inline-flex shrink-0 items-center whitespace-nowrap rounded-ps-control text-sm font-semibold transition-colors disabled:opacity-40',
|
|
3
|
+
bulkBar: 'ps-control-large flex items-center gap-2 rounded-ps-control border border-primary/30 bg-primary/10 py-1 pl-1 pr-1',
|
|
4
|
+
bulkCount: 'ps-control-compact ps-control-padding-compact inline-flex shrink-0 items-center rounded-ps-control bg-primary text-sm font-semibold tabular-nums text-white',
|
|
5
|
+
iconButton: 'ps-focus-ring ps-control-square ps-control-compact inline-flex shrink-0 items-center justify-center rounded-ps-control text-ink/60 transition-colors hover:bg-ink/5 hover:text-ink',
|
|
6
|
+
paginationButton: 'ps-focus-ring ps-control-compact ps-control-padding-compact inline-flex items-center rounded-ps-control border border-ps-control border-line text-sm font-semibold text-ink transition-colors disabled:cursor-not-allowed disabled:opacity-40 enabled:hover:border-primary/40',
|
|
7
|
+
textButton: 'ps-focus-ring ps-control-compact ps-control-padding-compact inline-flex items-center rounded-ps-control text-sm font-semibold',
|
|
8
|
+
toolbarButton: 'ps-input-control ps-control-trigger ps-focus-ring ps-control ps-control-padding inline-flex min-w-ps-control items-center justify-center gap-2 rounded-ps-control text-sm font-semibold text-ink outline-none transition-colors hover:border-primary/40',
|
|
9
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
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))]',
|
|
4
|
+
into: 'bg-primary/10 [&>td]:shadow-[inset_0_0_0_2px_rgb(var(--ps-color-primary-rgb)/0.5)]',
|
|
5
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const PsTablePinClasses = {
|
|
2
|
+
cell: 'ps-table-pin',
|
|
3
|
+
edge: 'ps-table-pin-start-edge',
|
|
4
|
+
end: 'ps-table-pin ps-table-pin-end right-0',
|
|
5
|
+
first: {
|
|
6
|
+
both: 'left-[4.75rem]',
|
|
7
|
+
none: 'left-0',
|
|
8
|
+
reorder: 'left-9',
|
|
9
|
+
select: 'left-10',
|
|
10
|
+
},
|
|
11
|
+
reorder: 'left-0',
|
|
12
|
+
select: {
|
|
13
|
+
alone: 'left-0',
|
|
14
|
+
withReorder: 'left-9',
|
|
15
|
+
},
|
|
16
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { PsTableOverflowService } from '../services/ps-table-overflow-service';
|
|
3
|
+
export const usePsTableOverflow = (layoutKey) => {
|
|
4
|
+
const ref = useRef(null);
|
|
5
|
+
const [edges, setEdges] = useState(PsTableOverflowService.none);
|
|
6
|
+
const update = useCallback(() => {
|
|
7
|
+
const element = ref.current;
|
|
8
|
+
if (element === null)
|
|
9
|
+
return;
|
|
10
|
+
const next = PsTableOverflowService.edges(element.scrollLeft, element.clientWidth, element.scrollWidth);
|
|
11
|
+
setEdges((current) => (PsTableOverflowService.isSame(current, next) ? current : next));
|
|
12
|
+
}, []);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
update();
|
|
15
|
+
window.addEventListener('resize', update);
|
|
16
|
+
return () => window.removeEventListener('resize', update);
|
|
17
|
+
}, [layoutKey, update]);
|
|
18
|
+
return { edges, ref, update };
|
|
19
|
+
};
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import { useMemo, useState } from 'react';
|
|
2
|
-
import { closestCenter, } from '@dnd-kit/core';
|
|
1
|
+
import { useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { closestCenter, pointerWithin, } from '@dnd-kit/core';
|
|
3
3
|
import { usePsDragSensors } from '@pallasoft/ps-core-components';
|
|
4
|
+
import { PsTableDropPlacement } from '../constants/ps-table-drop-placement';
|
|
4
5
|
import { PsTableReorderService } from '../services/ps-table-reorder-service';
|
|
5
|
-
|
|
6
|
+
const pointerThenCenter = (args) => {
|
|
7
|
+
const within = pointerWithin(args);
|
|
8
|
+
return within.length > 0 ? within : closestCenter(args);
|
|
9
|
+
};
|
|
10
|
+
export const usePsTableReorder = ({ announcements: texts, enabled, instructions, items, nesting, onReorder, }) => {
|
|
6
11
|
const [activeId, setActiveId] = useState(null);
|
|
12
|
+
const [drop, setDrop] = useState(null);
|
|
13
|
+
const dropRef = useRef(null);
|
|
7
14
|
const sensors = usePsDragSensors();
|
|
8
15
|
const runs = useMemo(() => enabled
|
|
9
16
|
? PsTableReorderService.runs(items, (item) => item.groupKey, (item) => item.id)
|
|
@@ -11,6 +18,7 @@ export const usePsTableReorder = ({ announcements: texts, enabled, instructions,
|
|
|
11
18
|
const placements = useMemo(() => PsTableReorderService.placements(runs, (item) => item.id), [runs]);
|
|
12
19
|
const activePlacement = activeId === null ? undefined : placements.get(activeId);
|
|
13
20
|
const itemById = useMemo(() => new Map(items.map((item) => [item.id, item])), [items]);
|
|
21
|
+
const indexById = useMemo(() => new Map(items.map((item, index) => [item.id, index])), [items]);
|
|
14
22
|
const labelOf = (id) => {
|
|
15
23
|
const item = itemById.get(id);
|
|
16
24
|
return item === undefined ? id : item.label;
|
|
@@ -23,23 +31,83 @@ export const usePsTableReorder = ({ announcements: texts, enabled, instructions,
|
|
|
23
31
|
const placement = placements.get(id);
|
|
24
32
|
return placement === undefined ? 0 : placement.size;
|
|
25
33
|
};
|
|
34
|
+
const edgeFor = (activeKey, overId) => {
|
|
35
|
+
const activeIndex = indexById.get(activeKey);
|
|
36
|
+
const overIndex = indexById.get(overId);
|
|
37
|
+
return activeIndex !== undefined && overIndex !== undefined && activeIndex < overIndex
|
|
38
|
+
? PsTableDropPlacement.after
|
|
39
|
+
: PsTableDropPlacement.before;
|
|
40
|
+
};
|
|
41
|
+
const resolveDrop = (event) => {
|
|
42
|
+
if (nesting === undefined || event.over === null || event.over.id === event.active.id)
|
|
43
|
+
return null;
|
|
44
|
+
const activeKey = String(event.active.id);
|
|
45
|
+
const overId = String(event.over.id);
|
|
46
|
+
const placement = PsTableReorderService.dropPlacement(PsTableReorderService.pointerY(event.activatorEvent, event.delta.y), event.over.rect, edgeFor(activeKey, overId), (candidate) => nesting.canDrop(activeKey, overId, candidate));
|
|
47
|
+
return placement === null ? null : { overId, placement };
|
|
48
|
+
};
|
|
49
|
+
const announcedDrop = (activeKey, overId) => {
|
|
50
|
+
if (nesting === undefined)
|
|
51
|
+
return null;
|
|
52
|
+
const known = dropRef.current;
|
|
53
|
+
if (known !== null && known.overId === overId)
|
|
54
|
+
return known;
|
|
55
|
+
const placement = edgeFor(activeKey, overId);
|
|
56
|
+
return nesting.canDrop(activeKey, overId, placement) ? { overId, placement } : null;
|
|
57
|
+
};
|
|
58
|
+
const describeDrop = (active, target) => nesting === undefined
|
|
59
|
+
? undefined
|
|
60
|
+
: nesting.announcements[target.placement](labelOf(active), labelOf(target.overId));
|
|
26
61
|
const announcements = {
|
|
27
62
|
onDragCancel: ({ active }) => texts.cancelled(labelOf(String(active.id))),
|
|
28
|
-
onDragEnd: ({ active, over }) =>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
63
|
+
onDragEnd: ({ active, over }) => {
|
|
64
|
+
if (nesting !== undefined) {
|
|
65
|
+
const target = over === null ? null : announcedDrop(String(active.id), String(over.id));
|
|
66
|
+
return target === null
|
|
67
|
+
? texts.cancelled(labelOf(String(active.id)))
|
|
68
|
+
: describeDrop(String(active.id), target);
|
|
69
|
+
}
|
|
70
|
+
return over === null
|
|
71
|
+
? texts.cancelled(labelOf(String(active.id)))
|
|
72
|
+
: texts.dropped(labelOf(String(active.id)), positionOf(String(over.id)), countOf(String(over.id)));
|
|
73
|
+
},
|
|
74
|
+
onDragOver: ({ active, over }) => {
|
|
75
|
+
if (over === null)
|
|
76
|
+
return undefined;
|
|
77
|
+
if (nesting !== undefined) {
|
|
78
|
+
const target = announcedDrop(String(active.id), String(over.id));
|
|
79
|
+
return target === null ? undefined : describeDrop(String(active.id), target);
|
|
80
|
+
}
|
|
81
|
+
return texts.moved(labelOf(String(active.id)), positionOf(String(over.id)), countOf(String(over.id)));
|
|
82
|
+
},
|
|
34
83
|
onDragStart: ({ active }) => texts.lifted(labelOf(String(active.id)), positionOf(String(active.id)), countOf(String(active.id))),
|
|
35
84
|
};
|
|
36
|
-
const
|
|
85
|
+
const handleDragMove = (event) => {
|
|
86
|
+
const next = resolveDrop(event);
|
|
87
|
+
dropRef.current = next;
|
|
88
|
+
setDrop((current) => current !== null &&
|
|
89
|
+
next !== null &&
|
|
90
|
+
current.overId === next.overId &&
|
|
91
|
+
current.placement === next.placement
|
|
92
|
+
? current
|
|
93
|
+
: next);
|
|
94
|
+
};
|
|
95
|
+
const handleDragEnd = (event) => {
|
|
96
|
+
const { active, over } = event;
|
|
97
|
+
const target = resolveDrop(event);
|
|
37
98
|
setActiveId(null);
|
|
38
|
-
|
|
99
|
+
setDrop(null);
|
|
100
|
+
dropRef.current = null;
|
|
101
|
+
if (nesting !== undefined) {
|
|
102
|
+
if (target !== null)
|
|
103
|
+
nesting.onMove(String(active.id), target.overId, target.placement);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (over === null || active.id === over.id || onReorder === undefined)
|
|
39
107
|
return;
|
|
40
108
|
const placement = placements.get(String(active.id));
|
|
41
|
-
const
|
|
42
|
-
if (placement === undefined ||
|
|
109
|
+
const sibling = placements.get(String(over.id));
|
|
110
|
+
if (placement === undefined || sibling === undefined || placement.runId !== sibling.runId)
|
|
43
111
|
return;
|
|
44
112
|
const run = runs.find((candidate) => candidate.id === placement.runId);
|
|
45
113
|
if (run === undefined)
|
|
@@ -51,13 +119,19 @@ export const usePsTableReorder = ({ announcements: texts, enabled, instructions,
|
|
|
51
119
|
activeRunId: activePlacement === undefined ? null : activePlacement.runId,
|
|
52
120
|
contextProps: {
|
|
53
121
|
accessibility: { announcements, screenReaderInstructions: { draggable: instructions } },
|
|
54
|
-
collisionDetection: closestCenter,
|
|
122
|
+
collisionDetection: nesting === undefined ? closestCenter : pointerThenCenter,
|
|
55
123
|
modifiers: [PsTableReorderService.lockToVerticalAxis],
|
|
56
|
-
onDragCancel: () =>
|
|
124
|
+
onDragCancel: () => {
|
|
125
|
+
setActiveId(null);
|
|
126
|
+
setDrop(null);
|
|
127
|
+
dropRef.current = null;
|
|
128
|
+
},
|
|
57
129
|
onDragEnd: handleDragEnd,
|
|
130
|
+
onDragMove: nesting === undefined ? undefined : handleDragMove,
|
|
58
131
|
onDragStart: ({ active }) => setActiveId(String(active.id)),
|
|
59
132
|
sensors,
|
|
60
133
|
},
|
|
134
|
+
drop,
|
|
61
135
|
placements,
|
|
62
136
|
};
|
|
63
137
|
};
|