@pallasoft/ps-table-components 1.18.1 → 1.19.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.js +84 -20
- package/dist/constants/table-config.d.ts +9 -0
- package/dist/constants/table-config.js +6 -0
- package/dist/esm/components/ps-data-table.js +85 -21
- package/dist/esm/constants/table-config.js +6 -0
- package/dist/esm/hooks/use-ps-incremental-count.js +17 -0
- package/dist/esm/hooks/use-ps-streamed-rows.js +58 -15
- package/dist/esm/index.js +2 -0
- package/dist/esm/services/ps-table-query-service.js +25 -13
- package/dist/esm/services/ps-table-scroll-service.js +18 -0
- package/dist/esm/services/ps-table-search-service.js +14 -4
- package/dist/esm/services/ps-table-stream-service.js +18 -0
- package/dist/hooks/use-ps-incremental-count.d.ts +5 -0
- package/dist/hooks/use-ps-incremental-count.js +21 -0
- package/dist/hooks/use-ps-streamed-rows.js +57 -14
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/interfaces/table-stream-state.d.ts +1 -0
- package/dist/services/ps-table-query-service.d.ts +2 -0
- package/dist/services/ps-table-query-service.js +25 -13
- package/dist/services/ps-table-scroll-service.d.ts +5 -0
- package/dist/services/ps-table-scroll-service.js +22 -0
- package/dist/services/ps-table-search-service.d.ts +3 -0
- package/dist/services/ps-table-search-service.js +14 -4
- package/dist/services/ps-table-stream-service.d.ts +2 -0
- package/dist/services/ps-table-stream-service.js +18 -0
- package/package.json +2 -1
|
@@ -7,11 +7,14 @@ const ps_core_components_1 = require("@pallasoft/ps-core-components");
|
|
|
7
7
|
const ps_design_system_1 = require("@pallasoft/ps-design-system");
|
|
8
8
|
const ps_input_components_1 = require("@pallasoft/ps-input-components");
|
|
9
9
|
const ps_motion_components_1 = require("@pallasoft/ps-motion-components");
|
|
10
|
+
const react_virtual_1 = require("@tanstack/react-virtual");
|
|
10
11
|
const table_config_1 = require("../constants/table-config");
|
|
12
|
+
const use_ps_incremental_count_1 = require("../hooks/use-ps-incremental-count");
|
|
11
13
|
const ps_table_cell_tooltip_service_1 = require("../services/ps-table-cell-tooltip-service");
|
|
12
14
|
const ps_table_column_size_service_1 = require("../services/ps-table-column-size-service");
|
|
13
15
|
const ps_table_group_service_1 = require("../services/ps-table-group-service");
|
|
14
16
|
const ps_table_query_service_1 = require("../services/ps-table-query-service");
|
|
17
|
+
const ps_table_scroll_service_1 = require("../services/ps-table-scroll-service");
|
|
15
18
|
const ps_table_bulk_bar_1 = require("./ps-table-bulk-bar");
|
|
16
19
|
const ps_table_card_list_1 = require("./ps-table-card-list");
|
|
17
20
|
const ps_table_column_picker_1 = require("./ps-table-column-picker");
|
|
@@ -64,9 +67,21 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
|
|
|
64
67
|
const hasToolbar = isSearchable || hasFilters || Boolean(toolbarActions);
|
|
65
68
|
const isSelectable = Boolean(getBulkActions);
|
|
66
69
|
const columnCount = visibleColumns.length + (isSelectable ? 1 : 0);
|
|
70
|
+
const deferredRows = (0, react_1.useDeferredValue)(rows);
|
|
71
|
+
const deferredFilters = (0, react_1.useDeferredValue)(filters);
|
|
72
|
+
const deferredSearch = (0, react_1.useDeferredValue)(search);
|
|
73
|
+
const deferredSort = (0, react_1.useDeferredValue)(sort);
|
|
67
74
|
const queriedRows = (0, react_1.useMemo)(() => isServerPaginated
|
|
68
|
-
?
|
|
69
|
-
: ps_table_query_service_1.PsTableQueryService.apply(
|
|
75
|
+
? deferredRows
|
|
76
|
+
: ps_table_query_service_1.PsTableQueryService.apply(deferredRows, columns, deferredFilters, deferredSearch, getSearchText, deferredSort), [
|
|
77
|
+
columns,
|
|
78
|
+
deferredFilters,
|
|
79
|
+
deferredRows,
|
|
80
|
+
deferredSearch,
|
|
81
|
+
deferredSort,
|
|
82
|
+
getSearchText,
|
|
83
|
+
isServerPaginated,
|
|
84
|
+
]);
|
|
70
85
|
const currentPage = page !== null && page !== undefined ? page : uncontrolledPage;
|
|
71
86
|
const rowCount = isServerPaginated ? totalRows : queriedRows.length;
|
|
72
87
|
const pageCount = pageSize ? Math.max(Math.ceil(rowCount / pageSize), 1) : 1;
|
|
@@ -149,19 +164,62 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
|
|
|
149
164
|
updateQuery({ filters: {}, search: '' });
|
|
150
165
|
resetPage();
|
|
151
166
|
};
|
|
152
|
-
const
|
|
167
|
+
const selectedIdSet = (0, react_1.useMemo)(() => new Set(selectedIds), [selectedIds]);
|
|
168
|
+
const selectedRows = selectedIds.length === 0 ? [] : queriedRows.filter((row) => selectedIdSet.has(getRowId(row)));
|
|
153
169
|
const pageIds = visibleRows.map((row) => getRowId(row));
|
|
154
|
-
const isPageSelected = pageIds.length > 0 && pageIds.every((rowId) =>
|
|
155
|
-
const togglePage = () => setSelectedIds((current) =>
|
|
156
|
-
|
|
157
|
-
|
|
170
|
+
const isPageSelected = pageIds.length > 0 && pageIds.every((rowId) => selectedIdSet.has(rowId));
|
|
171
|
+
const togglePage = () => setSelectedIds((current) => {
|
|
172
|
+
const pageIdSet = new Set(pageIds);
|
|
173
|
+
return isPageSelected
|
|
174
|
+
? current.filter((rowId) => !pageIdSet.has(rowId))
|
|
175
|
+
: [...new Set([...current, ...pageIds])];
|
|
176
|
+
});
|
|
177
|
+
const bodyRef = (0, react_1.useRef)(null);
|
|
178
|
+
const [scrollParent, setScrollParent] = (0, react_1.useState)(null);
|
|
179
|
+
const [scrollMargin, setScrollMargin] = (0, react_1.useState)(0);
|
|
180
|
+
const shouldVirtualize = !grouping && visibleRows.length > table_config_1.PsTableConfig.virtualizeAfter;
|
|
181
|
+
const virtualOptions = {
|
|
182
|
+
count: shouldVirtualize ? visibleRows.length : 0,
|
|
183
|
+
estimateSize: () => table_config_1.PsTableConfig.rowEstimatePx[density],
|
|
184
|
+
getItemKey: (index) => getRowId(visibleRows[index]),
|
|
185
|
+
initialRect: {
|
|
186
|
+
height: table_config_1.PsTableConfig.virtualizeAfter * table_config_1.PsTableConfig.rowEstimatePx[density],
|
|
187
|
+
width: 0,
|
|
188
|
+
},
|
|
189
|
+
overscan: table_config_1.PsTableConfig.virtualOverscan,
|
|
190
|
+
scrollMargin,
|
|
191
|
+
};
|
|
192
|
+
const elementVirtualizer = (0, react_virtual_1.useVirtualizer)({
|
|
193
|
+
...virtualOptions,
|
|
194
|
+
count: scrollParent === null ? 0 : virtualOptions.count,
|
|
195
|
+
getScrollElement: () => scrollParent,
|
|
196
|
+
});
|
|
197
|
+
const windowVirtualizer = (0, react_virtual_1.useWindowVirtualizer)({
|
|
198
|
+
...virtualOptions,
|
|
199
|
+
count: scrollParent === null ? virtualOptions.count : 0,
|
|
200
|
+
});
|
|
201
|
+
const virtualizer = scrollParent === null ? windowVirtualizer : elementVirtualizer;
|
|
202
|
+
const virtualItems = shouldVirtualize ? virtualizer.getVirtualItems() : [];
|
|
203
|
+
const virtualTop = virtualItems.length > 0 ? virtualItems[0].start - scrollMargin : 0;
|
|
204
|
+
const virtualBottom = virtualItems.length > 0
|
|
205
|
+
? virtualizer.getTotalSize() - (virtualItems[virtualItems.length - 1].end - scrollMargin)
|
|
206
|
+
: 0;
|
|
207
|
+
const cards = (0, use_ps_incremental_count_1.usePsIncrementalCount)(visibleRows.length, table_config_1.PsTableConfig.cardBatch);
|
|
208
|
+
(0, react_1.useEffect)(() => {
|
|
209
|
+
const body = bodyRef.current;
|
|
210
|
+
if (!shouldVirtualize || body === null)
|
|
211
|
+
return;
|
|
212
|
+
const parent = ps_table_scroll_service_1.PsTableScrollService.parent(body);
|
|
213
|
+
setScrollParent(parent);
|
|
214
|
+
setScrollMargin(ps_table_scroll_service_1.PsTableScrollService.offset(body, parent));
|
|
215
|
+
}, [shouldVirtualize, visibleRows.length]);
|
|
158
216
|
const toggleRow = (rowId) => setSelectedIds((current) => current.includes(rowId) ? current.filter((id) => id !== rowId) : [...current, rowId]);
|
|
159
217
|
const renderCardList = (cardRows, emptyContent) => ((0, jsx_runtime_1.jsx)(ps_table_card_list_1.PsTableCardList, { columns: visibleColumns, emptyContent: emptyContent, expandedRowIds: expandedRowIds, footerColumns: hasFooter ? visibleColumns.filter((column) => column.footer !== undefined) : [], getRowId: getRowId, getRowLabel: (row) => (getRowLabel ? getRowLabel(row) : getRowId(row)), isSelectable: isSelectable, onRowClick: onRowClick, onRowHover: onRowHover, onToggleRow: toggleRow, renderExpandedRow: renderExpandedRow, rows: cardRows, selectedIds: selectedIds }));
|
|
160
|
-
const renderRow = (row, motion) => {
|
|
218
|
+
const renderRow = (row, motion, virtualItem) => {
|
|
161
219
|
const rowId = getRowId(row);
|
|
162
220
|
const isExpanded = expandedRowIds ? expandedRowIds.includes(rowId) : false;
|
|
163
221
|
const rowLabel = getRowLabel ? getRowLabel(row) : rowId;
|
|
164
|
-
return ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("tr", { "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
|
|
222
|
+
return ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("tr", { "data-index": virtualItem ? virtualItem.index : undefined, ref: virtualItem ? virtualizer.measureElement : 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
|
|
165
223
|
? (event) => {
|
|
166
224
|
if (event.target !== event.currentTarget)
|
|
167
225
|
return;
|
|
@@ -172,7 +230,7 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
|
|
|
172
230
|
}
|
|
173
231
|
: undefined, style: motion && motion.delayMs !== undefined
|
|
174
232
|
? { animationDelay: `${motion.delayMs}ms` }
|
|
175
|
-
: undefined, tabIndex: onRowClick ? 0 : undefined, children: [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:
|
|
233
|
+
: undefined, tabIndex: onRowClick ? 0 : undefined, children: [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) ||
|
|
176
234
|
ps_table_column_size_service_1.PsTableColumnSizeService.maxWidthOf(column) !== undefined
|
|
177
235
|
? 'truncate'
|
|
178
236
|
: '', '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));
|
|
@@ -187,14 +245,14 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
|
|
|
187
245
|
}, onSearchChange: (next) => {
|
|
188
246
|
updateQuery({ search: next });
|
|
189
247
|
resetPage();
|
|
190
|
-
}, rows: rows, search: search, searchPlaceholder: searchPlaceholder })) : null, (0, jsx_runtime_1.jsxs)("div", { className: 'min-w-0 overflow-hidden rounded-[1.25rem] border border-line bg-surface', children: [mobileLayout === 'cards' ? ((0, jsx_runtime_1.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
248
|
+
}, rows: rows, search: search, searchPlaceholder: searchPlaceholder })) : null, (0, jsx_runtime_1.jsxs)("div", { className: 'min-w-0 overflow-hidden rounded-[1.25rem] border border-line bg-surface', children: [mobileLayout === 'cards' ? ((0, jsx_runtime_1.jsxs)("div", { className: 'sm:hidden', children: [grouping
|
|
249
|
+
? groups.map((group, groupIndex) => {
|
|
250
|
+
const isExpanded = isGroupExpanded(group.key, groupIndex);
|
|
251
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: 'border-b border-line last:border-b-0', children: [(0, jsx_runtime_1.jsx)(ps_table_group_header_1.PsTableGroupHeader, { count: group.rows.length, isExpanded: isExpanded, label: grouping.getGroupLabel(group.key, group.rows), onToggle: () => toggleGroup(group.key, isExpanded), summary: grouping.renderGroupSummary
|
|
252
|
+
? grouping.renderGroupSummary(group.rows)
|
|
253
|
+
: 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}`));
|
|
254
|
+
})
|
|
255
|
+
: renderCardList(shouldVirtualize ? visibleRows.slice(0, cards.count) : visibleRows, 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-focus-ring mt-2 rounded-full px-3 py-1.5 text-sm font-semibold 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: (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: [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) => {
|
|
198
256
|
const align = column.align !== null && column.align !== undefined ? column.align : 'left';
|
|
199
257
|
const isSorted = sort !== null && sort.columnId === column.id;
|
|
200
258
|
return ((0, jsx_runtime_1.jsx)("th", { "aria-sort": isSorted
|
|
@@ -202,7 +260,7 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
|
|
|
202
260
|
: column.sortValue && isQueryable
|
|
203
261
|
? 'none'
|
|
204
262
|
: 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));
|
|
205
|
-
})] }) }), (0, jsx_runtime_1.jsxs)("tbody", { className: 'divide-y divide-line', children: [visibleRows.length === 0 && isQueried ? ((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-focus-ring mt-3 rounded-full px-3 py-1.5 text-sm font-semibold text-primary hover:underline', onClick: clearFilters, type: 'button', children: "Clear search and filters" })] }) })) : null, grouping
|
|
263
|
+
})] }) }), (0, jsx_runtime_1.jsxs)("tbody", { className: 'divide-y divide-line', ref: bodyRef, children: [visibleRows.length === 0 && isQueried ? ((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-focus-ring mt-3 rounded-full px-3 py-1.5 text-sm font-semibold text-primary hover:underline', onClick: clearFilters, type: 'button', children: "Clear search and filters" })] }) })) : null, grouping
|
|
206
264
|
? groups.map((group, groupIndex) => {
|
|
207
265
|
const isExpanded = isGroupExpanded(group.key, groupIndex);
|
|
208
266
|
return ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [(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_group_header_1.PsTableGroupHeader, { count: group.rows.length, isExpanded: isExpanded, label: grouping.getGroupLabel(group.key, group.rows), onToggle: () => toggleGroup(group.key, isExpanded), summary: grouping.renderGroupSummary
|
|
@@ -211,7 +269,13 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
|
|
|
211
269
|
? group.rows.map((row, rowIndex) => renderRow(row, groupRowMotion(group.key, rowIndex)))
|
|
212
270
|
: null] }, `group-${group.key}`));
|
|
213
271
|
})
|
|
214
|
-
:
|
|
272
|
+
: shouldVirtualize
|
|
273
|
+
? [
|
|
274
|
+
(0, jsx_runtime_1.jsx)("tr", { "aria-hidden": 'true', children: (0, jsx_runtime_1.jsx)("td", { className: 'p-0', colSpan: columnCount, style: { height: virtualTop } }) }, 'virtual-top'),
|
|
275
|
+
...virtualItems.map((item) => renderRow(visibleRows[item.index], undefined, item)),
|
|
276
|
+
(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'),
|
|
277
|
+
]
|
|
278
|
+
: visibleRows.map((row) => renderRow(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: [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
|
|
215
279
|
? column.align
|
|
216
280
|
: 'left'], 'whitespace-nowrap'), children: column.footer }, column.id)))] }) })) : null] }) }), pageSize && (!grouping || isServerPaginated) ? ((0, jsx_runtime_1.jsx)(ps_table_pagination_1.PsTablePagination, { onPageChange: changePage, page: safePage, pageCount: pageCount, pageSize: pageSize, totalRows: rowCount })) : null] })] }));
|
|
217
281
|
};
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
export declare const PsTableConfig: {
|
|
2
|
+
readonly cardBatch: 60;
|
|
2
3
|
readonly groupCloseMs: 160;
|
|
3
4
|
readonly groupOpenMs: 280;
|
|
4
5
|
readonly groupRowStaggerMs: 16;
|
|
5
6
|
readonly groupStaggerLimit: 12;
|
|
6
7
|
readonly noMatchesDescription: "Try a different search or clear the filters.";
|
|
7
8
|
readonly noMatchesTitle: "Nothing matches";
|
|
9
|
+
readonly rowEstimatePx: {
|
|
10
|
+
readonly compact: 37;
|
|
11
|
+
readonly normal: 57;
|
|
12
|
+
};
|
|
8
13
|
readonly searchPlaceholder: "Search";
|
|
9
14
|
readonly skeletonRows: 6;
|
|
15
|
+
readonly streamConcurrency: 3;
|
|
16
|
+
readonly streamFlushMs: 200;
|
|
17
|
+
readonly virtualOverscan: 12;
|
|
18
|
+
readonly virtualizeAfter: 100;
|
|
10
19
|
};
|
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PsTableConfig = void 0;
|
|
4
4
|
exports.PsTableConfig = {
|
|
5
|
+
cardBatch: 60,
|
|
5
6
|
groupCloseMs: 160,
|
|
6
7
|
groupOpenMs: 280,
|
|
7
8
|
groupRowStaggerMs: 16,
|
|
8
9
|
groupStaggerLimit: 12,
|
|
9
10
|
noMatchesDescription: 'Try a different search or clear the filters.',
|
|
10
11
|
noMatchesTitle: 'Nothing matches',
|
|
12
|
+
rowEstimatePx: { compact: 37, normal: 57 },
|
|
11
13
|
searchPlaceholder: 'Search',
|
|
12
14
|
skeletonRows: 6,
|
|
15
|
+
streamConcurrency: 3,
|
|
16
|
+
streamFlushMs: 200,
|
|
17
|
+
virtualOverscan: 12,
|
|
18
|
+
virtualizeAfter: 100,
|
|
13
19
|
};
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Fragment, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { Fragment, useDeferredValue, useEffect, useMemo, useRef, useState, } from 'react';
|
|
3
3
|
import { 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
|
+
import { useVirtualizer, useWindowVirtualizer } from '@tanstack/react-virtual';
|
|
7
8
|
import { PsTableConfig } from '../constants/table-config';
|
|
9
|
+
import { usePsIncrementalCount } from '../hooks/use-ps-incremental-count';
|
|
8
10
|
import { PsTableCellTooltipService } from '../services/ps-table-cell-tooltip-service';
|
|
9
11
|
import { PsTableColumnSizeService } from '../services/ps-table-column-size-service';
|
|
10
12
|
import { PsTableGroupService } from '../services/ps-table-group-service';
|
|
11
13
|
import { PsTableQueryService } from '../services/ps-table-query-service';
|
|
14
|
+
import { PsTableScrollService } from '../services/ps-table-scroll-service';
|
|
12
15
|
import { PsTableBulkBar } from './ps-table-bulk-bar';
|
|
13
16
|
import { PsTableCardList } from './ps-table-card-list';
|
|
14
17
|
import { PsTableColumnPicker } from './ps-table-column-picker';
|
|
@@ -61,9 +64,21 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
61
64
|
const hasToolbar = isSearchable || hasFilters || Boolean(toolbarActions);
|
|
62
65
|
const isSelectable = Boolean(getBulkActions);
|
|
63
66
|
const columnCount = visibleColumns.length + (isSelectable ? 1 : 0);
|
|
67
|
+
const deferredRows = useDeferredValue(rows);
|
|
68
|
+
const deferredFilters = useDeferredValue(filters);
|
|
69
|
+
const deferredSearch = useDeferredValue(search);
|
|
70
|
+
const deferredSort = useDeferredValue(sort);
|
|
64
71
|
const queriedRows = useMemo(() => isServerPaginated
|
|
65
|
-
?
|
|
66
|
-
: PsTableQueryService.apply(
|
|
72
|
+
? deferredRows
|
|
73
|
+
: PsTableQueryService.apply(deferredRows, columns, deferredFilters, deferredSearch, getSearchText, deferredSort), [
|
|
74
|
+
columns,
|
|
75
|
+
deferredFilters,
|
|
76
|
+
deferredRows,
|
|
77
|
+
deferredSearch,
|
|
78
|
+
deferredSort,
|
|
79
|
+
getSearchText,
|
|
80
|
+
isServerPaginated,
|
|
81
|
+
]);
|
|
67
82
|
const currentPage = page !== null && page !== undefined ? page : uncontrolledPage;
|
|
68
83
|
const rowCount = isServerPaginated ? totalRows : queriedRows.length;
|
|
69
84
|
const pageCount = pageSize ? Math.max(Math.ceil(rowCount / pageSize), 1) : 1;
|
|
@@ -146,19 +161,62 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
146
161
|
updateQuery({ filters: {}, search: '' });
|
|
147
162
|
resetPage();
|
|
148
163
|
};
|
|
149
|
-
const
|
|
164
|
+
const selectedIdSet = useMemo(() => new Set(selectedIds), [selectedIds]);
|
|
165
|
+
const selectedRows = selectedIds.length === 0 ? [] : queriedRows.filter((row) => selectedIdSet.has(getRowId(row)));
|
|
150
166
|
const pageIds = visibleRows.map((row) => getRowId(row));
|
|
151
|
-
const isPageSelected = pageIds.length > 0 && pageIds.every((rowId) =>
|
|
152
|
-
const togglePage = () => setSelectedIds((current) =>
|
|
153
|
-
|
|
154
|
-
|
|
167
|
+
const isPageSelected = pageIds.length > 0 && pageIds.every((rowId) => selectedIdSet.has(rowId));
|
|
168
|
+
const togglePage = () => setSelectedIds((current) => {
|
|
169
|
+
const pageIdSet = new Set(pageIds);
|
|
170
|
+
return isPageSelected
|
|
171
|
+
? current.filter((rowId) => !pageIdSet.has(rowId))
|
|
172
|
+
: [...new Set([...current, ...pageIds])];
|
|
173
|
+
});
|
|
174
|
+
const bodyRef = useRef(null);
|
|
175
|
+
const [scrollParent, setScrollParent] = useState(null);
|
|
176
|
+
const [scrollMargin, setScrollMargin] = useState(0);
|
|
177
|
+
const shouldVirtualize = !grouping && visibleRows.length > PsTableConfig.virtualizeAfter;
|
|
178
|
+
const virtualOptions = {
|
|
179
|
+
count: shouldVirtualize ? visibleRows.length : 0,
|
|
180
|
+
estimateSize: () => PsTableConfig.rowEstimatePx[density],
|
|
181
|
+
getItemKey: (index) => getRowId(visibleRows[index]),
|
|
182
|
+
initialRect: {
|
|
183
|
+
height: PsTableConfig.virtualizeAfter * PsTableConfig.rowEstimatePx[density],
|
|
184
|
+
width: 0,
|
|
185
|
+
},
|
|
186
|
+
overscan: PsTableConfig.virtualOverscan,
|
|
187
|
+
scrollMargin,
|
|
188
|
+
};
|
|
189
|
+
const elementVirtualizer = useVirtualizer({
|
|
190
|
+
...virtualOptions,
|
|
191
|
+
count: scrollParent === null ? 0 : virtualOptions.count,
|
|
192
|
+
getScrollElement: () => scrollParent,
|
|
193
|
+
});
|
|
194
|
+
const windowVirtualizer = useWindowVirtualizer({
|
|
195
|
+
...virtualOptions,
|
|
196
|
+
count: scrollParent === null ? virtualOptions.count : 0,
|
|
197
|
+
});
|
|
198
|
+
const virtualizer = scrollParent === null ? windowVirtualizer : elementVirtualizer;
|
|
199
|
+
const virtualItems = shouldVirtualize ? virtualizer.getVirtualItems() : [];
|
|
200
|
+
const virtualTop = virtualItems.length > 0 ? virtualItems[0].start - scrollMargin : 0;
|
|
201
|
+
const virtualBottom = virtualItems.length > 0
|
|
202
|
+
? virtualizer.getTotalSize() - (virtualItems[virtualItems.length - 1].end - scrollMargin)
|
|
203
|
+
: 0;
|
|
204
|
+
const cards = usePsIncrementalCount(visibleRows.length, PsTableConfig.cardBatch);
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
const body = bodyRef.current;
|
|
207
|
+
if (!shouldVirtualize || body === null)
|
|
208
|
+
return;
|
|
209
|
+
const parent = PsTableScrollService.parent(body);
|
|
210
|
+
setScrollParent(parent);
|
|
211
|
+
setScrollMargin(PsTableScrollService.offset(body, parent));
|
|
212
|
+
}, [shouldVirtualize, visibleRows.length]);
|
|
155
213
|
const toggleRow = (rowId) => setSelectedIds((current) => current.includes(rowId) ? current.filter((id) => id !== rowId) : [...current, rowId]);
|
|
156
214
|
const renderCardList = (cardRows, emptyContent) => (_jsx(PsTableCardList, { columns: visibleColumns, emptyContent: emptyContent, expandedRowIds: expandedRowIds, footerColumns: hasFooter ? visibleColumns.filter((column) => column.footer !== undefined) : [], getRowId: getRowId, getRowLabel: (row) => (getRowLabel ? getRowLabel(row) : getRowId(row)), isSelectable: isSelectable, onRowClick: onRowClick, onRowHover: onRowHover, onToggleRow: toggleRow, renderExpandedRow: renderExpandedRow, rows: cardRows, selectedIds: selectedIds }));
|
|
157
|
-
const renderRow = (row, motion) => {
|
|
215
|
+
const renderRow = (row, motion, virtualItem) => {
|
|
158
216
|
const rowId = getRowId(row);
|
|
159
217
|
const isExpanded = expandedRowIds ? expandedRowIds.includes(rowId) : false;
|
|
160
218
|
const rowLabel = getRowLabel ? getRowLabel(row) : rowId;
|
|
161
|
-
return (_jsxs(Fragment, { children: [_jsxs("tr", { "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
|
|
219
|
+
return (_jsxs(Fragment, { children: [_jsxs("tr", { "data-index": virtualItem ? virtualItem.index : undefined, ref: virtualItem ? virtualizer.measureElement : 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
|
|
162
220
|
? (event) => {
|
|
163
221
|
if (event.target !== event.currentTarget)
|
|
164
222
|
return;
|
|
@@ -169,7 +227,7 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
169
227
|
}
|
|
170
228
|
: undefined, style: motion && motion.delayMs !== undefined
|
|
171
229
|
? { animationDelay: `${motion.delayMs}ms` }
|
|
172
|
-
: undefined, tabIndex: onRowClick ? 0 : undefined, children: [isSelectable ? (_jsx("td", { className: 'w-10 pl-4 pr-0', onClick: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), children: _jsx(PsCheckbox, { isSelected:
|
|
230
|
+
: undefined, tabIndex: onRowClick ? 0 : undefined, children: [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) ||
|
|
173
231
|
PsTableColumnSizeService.maxWidthOf(column) !== undefined
|
|
174
232
|
? 'truncate'
|
|
175
233
|
: '', '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));
|
|
@@ -184,14 +242,14 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
184
242
|
}, onSearchChange: (next) => {
|
|
185
243
|
updateQuery({ search: next });
|
|
186
244
|
resetPage();
|
|
187
|
-
}, rows: rows, search: search, searchPlaceholder: searchPlaceholder })) : null, _jsxs("div", { className: 'min-w-0 overflow-hidden rounded-[1.25rem] border border-line bg-surface', children: [mobileLayout === 'cards' ? (
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
245
|
+
}, rows: rows, search: search, searchPlaceholder: searchPlaceholder })) : null, _jsxs("div", { className: 'min-w-0 overflow-hidden rounded-[1.25rem] border border-line bg-surface', children: [mobileLayout === 'cards' ? (_jsxs("div", { className: 'sm:hidden', children: [grouping
|
|
246
|
+
? groups.map((group, groupIndex) => {
|
|
247
|
+
const isExpanded = isGroupExpanded(group.key, groupIndex);
|
|
248
|
+
return (_jsxs("div", { className: 'border-b border-line last:border-b-0', children: [_jsx(PsTableGroupHeader, { count: group.rows.length, isExpanded: isExpanded, label: grouping.getGroupLabel(group.key, group.rows), onToggle: () => toggleGroup(group.key, isExpanded), summary: grouping.renderGroupSummary
|
|
249
|
+
? grouping.renderGroupSummary(group.rows)
|
|
250
|
+
: undefined }), _jsx(PsCollapse, { isOpen: isExpanded && !closingGroupKeys.includes(group.key), children: renderCardList(group.rows, null) })] }, `group-${group.key}`));
|
|
251
|
+
})
|
|
252
|
+
: renderCardList(shouldVirtualize ? visibleRows.slice(0, cards.count) : visibleRows, 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: 'ps-focus-ring mt-2 rounded-full px-3 py-1.5 text-sm font-semibold 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: _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: [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) => {
|
|
195
253
|
const align = column.align !== null && column.align !== undefined ? column.align : 'left';
|
|
196
254
|
const isSorted = sort !== null && sort.columnId === column.id;
|
|
197
255
|
return (_jsx("th", { "aria-sort": isSorted
|
|
@@ -199,7 +257,7 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
199
257
|
: column.sortValue && isQueryable
|
|
200
258
|
? 'none'
|
|
201
259
|
: 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));
|
|
202
|
-
})] }) }), _jsxs("tbody", { className: 'divide-y divide-line', children: [visibleRows.length === 0 && isQueried ? (_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: 'ps-focus-ring mt-3 rounded-full px-3 py-1.5 text-sm font-semibold text-primary hover:underline', onClick: clearFilters, type: 'button', children: "Clear search and filters" })] }) })) : null, grouping
|
|
260
|
+
})] }) }), _jsxs("tbody", { className: 'divide-y divide-line', ref: bodyRef, children: [visibleRows.length === 0 && isQueried ? (_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: 'ps-focus-ring mt-3 rounded-full px-3 py-1.5 text-sm font-semibold text-primary hover:underline', onClick: clearFilters, type: 'button', children: "Clear search and filters" })] }) })) : null, grouping
|
|
203
261
|
? groups.map((group, groupIndex) => {
|
|
204
262
|
const isExpanded = isGroupExpanded(group.key, groupIndex);
|
|
205
263
|
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
|
|
@@ -208,7 +266,13 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
208
266
|
? group.rows.map((row, rowIndex) => renderRow(row, groupRowMotion(group.key, rowIndex)))
|
|
209
267
|
: null] }, `group-${group.key}`));
|
|
210
268
|
})
|
|
211
|
-
:
|
|
269
|
+
: shouldVirtualize
|
|
270
|
+
? [
|
|
271
|
+
_jsx("tr", { "aria-hidden": 'true', children: _jsx("td", { className: 'p-0', colSpan: columnCount, style: { height: virtualTop } }) }, 'virtual-top'),
|
|
272
|
+
...virtualItems.map((item) => renderRow(visibleRows[item.index], undefined, item)),
|
|
273
|
+
_jsx("tr", { "aria-hidden": 'true', children: _jsx("td", { className: 'p-0', colSpan: columnCount, style: { height: virtualBottom } }) }, 'virtual-bottom'),
|
|
274
|
+
]
|
|
275
|
+
: visibleRows.map((row) => renderRow(row))] }), hasFooter ? (_jsx("tfoot", { className: 'border-t-2 border-line bg-mist/50 text-sm font-semibold text-ink', children: _jsxs("tr", { children: [isSelectable ? _jsx("td", {}) : null, visibleColumns.map((column) => (_jsx("td", { className: PallasoftClassNameService.merge(densityClassNames[density].cell, alignClassNames[column.align !== null && column.align !== undefined
|
|
212
276
|
? column.align
|
|
213
277
|
: 'left'], 'whitespace-nowrap'), children: column.footer }, column.id)))] }) })) : null] }) }), pageSize && (!grouping || isServerPaginated) ? (_jsx(PsTablePagination, { onPageChange: changePage, page: safePage, pageCount: pageCount, pageSize: pageSize, totalRows: rowCount })) : null] })] }));
|
|
214
278
|
};
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
export const PsTableConfig = {
|
|
2
|
+
cardBatch: 60,
|
|
2
3
|
groupCloseMs: 160,
|
|
3
4
|
groupOpenMs: 280,
|
|
4
5
|
groupRowStaggerMs: 16,
|
|
5
6
|
groupStaggerLimit: 12,
|
|
6
7
|
noMatchesDescription: 'Try a different search or clear the filters.',
|
|
7
8
|
noMatchesTitle: 'Nothing matches',
|
|
9
|
+
rowEstimatePx: { compact: 37, normal: 57 },
|
|
8
10
|
searchPlaceholder: 'Search',
|
|
9
11
|
skeletonRows: 6,
|
|
12
|
+
streamConcurrency: 3,
|
|
13
|
+
streamFlushMs: 200,
|
|
14
|
+
virtualOverscan: 12,
|
|
15
|
+
virtualizeAfter: 100,
|
|
10
16
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
export const usePsIncrementalCount = (total, batch) => {
|
|
3
|
+
const [count, setCount] = useState(batch);
|
|
4
|
+
const sentinelRef = useRef(null);
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const sentinel = sentinelRef.current;
|
|
7
|
+
if (sentinel === null || count >= total)
|
|
8
|
+
return;
|
|
9
|
+
const observer = new IntersectionObserver((entries) => {
|
|
10
|
+
if (entries.some((entry) => entry.isIntersecting))
|
|
11
|
+
setCount((current) => Math.min(current + batch, total));
|
|
12
|
+
});
|
|
13
|
+
observer.observe(sentinel);
|
|
14
|
+
return () => observer.disconnect();
|
|
15
|
+
}, [batch, count, total]);
|
|
16
|
+
return { count: Math.min(count, total), sentinelRef };
|
|
17
|
+
};
|
|
@@ -1,41 +1,84 @@
|
|
|
1
|
-
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
1
|
+
import { startTransition, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { PsTableConfig } from '../constants/table-config';
|
|
2
3
|
import { PsTableStreamService } from '../services/ps-table-stream-service';
|
|
3
4
|
export const usePsStreamedRows = ({ first, getRowId, loadPage, pageSize, scope, total, }) => {
|
|
4
5
|
const [stream, setStream] = useState({
|
|
5
6
|
first,
|
|
6
7
|
hasFailed: false,
|
|
8
|
+
isDone: first.length >= total,
|
|
7
9
|
previous: [],
|
|
8
10
|
rows: first,
|
|
9
11
|
scope,
|
|
10
12
|
});
|
|
11
13
|
const rowId = useRef(getRowId);
|
|
12
14
|
const load = useRef(loadPage);
|
|
13
|
-
const requested = useRef(null);
|
|
14
15
|
rowId.current = getRowId;
|
|
15
16
|
load.current = loadPage;
|
|
16
17
|
if (stream.first !== first)
|
|
17
18
|
setStream({
|
|
18
19
|
first,
|
|
19
20
|
hasFailed: false,
|
|
21
|
+
isDone: first.length >= total,
|
|
20
22
|
previous: PsTableStreamService.carried(stream.rows, stream.previous, getRowId, total, stream.scope === scope),
|
|
21
23
|
rows: first,
|
|
22
24
|
scope,
|
|
23
25
|
});
|
|
24
|
-
const nextPage = stream.hasFailed
|
|
25
|
-
? null
|
|
26
|
-
: PsTableStreamService.nextPage(stream.rows.length, total, pageSize);
|
|
27
26
|
useEffect(() => {
|
|
28
|
-
|
|
27
|
+
const pages = PsTableStreamService.pages(first.length, total, pageSize);
|
|
28
|
+
if (pages.length === 0)
|
|
29
29
|
return;
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
const seen = new Set(first.map((row) => rowId.current(row)));
|
|
31
|
+
const loaded = new Map();
|
|
32
|
+
let isCancelled = false;
|
|
33
|
+
let nextIndex = 0;
|
|
34
|
+
let nextToAppend = pages[0];
|
|
35
|
+
let pending = [];
|
|
36
|
+
let timer = null;
|
|
37
|
+
const commit = (patch) => {
|
|
38
|
+
const added = pending;
|
|
39
|
+
pending = [];
|
|
40
|
+
timer = null;
|
|
41
|
+
startTransition(() => setStream((current) => current.first !== first
|
|
42
|
+
? current
|
|
43
|
+
: {
|
|
44
|
+
...current,
|
|
45
|
+
...patch,
|
|
46
|
+
rows: added.length === 0 ? current.rows : current.rows.concat(added),
|
|
47
|
+
}));
|
|
48
|
+
};
|
|
49
|
+
const work = async () => {
|
|
50
|
+
while (!isCancelled && nextIndex < pages.length) {
|
|
51
|
+
const page = pages[nextIndex];
|
|
52
|
+
nextIndex += 1;
|
|
53
|
+
loaded.set(page, await load.current(page));
|
|
54
|
+
if (isCancelled)
|
|
55
|
+
return;
|
|
56
|
+
for (let ready = loaded.get(nextToAppend); ready !== undefined;) {
|
|
57
|
+
pending = pending.concat(PsTableStreamService.unseen(ready, seen, rowId.current));
|
|
58
|
+
loaded.delete(nextToAppend);
|
|
59
|
+
nextToAppend += 1;
|
|
60
|
+
ready = loaded.get(nextToAppend);
|
|
61
|
+
}
|
|
62
|
+
if (timer === null)
|
|
63
|
+
timer = setTimeout(() => commit({ hasFailed: false, isDone: false }), PsTableConfig.streamFlushMs);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const finish = (hasFailed) => {
|
|
67
|
+
if (isCancelled)
|
|
68
|
+
return;
|
|
69
|
+
if (timer !== null)
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
isCancelled = true;
|
|
72
|
+
commit({ hasFailed, isDone: !hasFailed });
|
|
73
|
+
};
|
|
74
|
+
void Promise.all(Array.from({ length: Math.min(PsTableConfig.streamConcurrency, pages.length) }, work)).then(() => finish(false), () => finish(true));
|
|
75
|
+
return () => {
|
|
76
|
+
isCancelled = true;
|
|
77
|
+
if (timer !== null)
|
|
78
|
+
clearTimeout(timer);
|
|
79
|
+
};
|
|
80
|
+
}, [first, pageSize, total]);
|
|
81
|
+
const isStreaming = !stream.hasFailed && !stream.isDone;
|
|
39
82
|
const rows = useMemo(() => isStreaming
|
|
40
83
|
? PsTableStreamService.merge(stream.rows, stream.previous, rowId.current, total)
|
|
41
84
|
: stream.rows, [isStreaming, stream.previous, stream.rows, total]);
|
package/dist/esm/index.js
CHANGED
|
@@ -18,6 +18,7 @@ export * from './interfaces/table-sort';
|
|
|
18
18
|
export * from './interfaces/table-stream-input';
|
|
19
19
|
export * from './interfaces/table-stream-result';
|
|
20
20
|
export * from './interfaces/table-stream-status';
|
|
21
|
+
export * from './hooks/use-ps-incremental-count';
|
|
21
22
|
export * from './hooks/use-ps-streamed-rows';
|
|
22
23
|
export * from './hooks/use-ps-table-stream-status';
|
|
23
24
|
export * from './services/ps-table-cell-tooltip-service';
|
|
@@ -25,6 +26,7 @@ export * from './services/ps-table-column-size-service';
|
|
|
25
26
|
export * from './services/ps-table-group-service';
|
|
26
27
|
export * from './services/ps-table-query-service';
|
|
27
28
|
export * from './services/ps-table-search-service';
|
|
29
|
+
export * from './services/ps-table-scroll-service';
|
|
28
30
|
export * from './services/ps-table-stream-service';
|
|
29
31
|
export * from './components/ps-table-export-button';
|
|
30
32
|
export * from './constants/ps-table-download-config';
|
|
@@ -37,26 +37,37 @@ export class PsTableQueryService {
|
|
|
37
37
|
return true;
|
|
38
38
|
}
|
|
39
39
|
static apply(rows, columns, filters, search, getSearchText, sort) {
|
|
40
|
-
const
|
|
41
|
-
|
|
40
|
+
const tokens = getSearchText ? PsTableSearchService.tokens(search) : [];
|
|
41
|
+
const activeColumns = columns.filter((column) => column.filter && PsTableQueryService.isActive(filters[column.id]));
|
|
42
|
+
const filtered = tokens.length === 0 && activeColumns.length === 0
|
|
43
|
+
? rows
|
|
44
|
+
: rows.filter((row) => (tokens.length === 0 ||
|
|
45
|
+
(getSearchText !== undefined &&
|
|
46
|
+
PsTableSearchService.matchesTokens(PsTableSearchService.haystack(row, getSearchText), tokens))) &&
|
|
47
|
+
activeColumns.every((column) => PsTableQueryService.matchesFilter(row, column, filters[column.id])));
|
|
42
48
|
const sortColumn = sort ? columns.find((column) => column.id === sort.columnId) : undefined;
|
|
43
49
|
if (!sort || !sortColumn || !sortColumn.sortValue)
|
|
44
50
|
return filtered;
|
|
45
51
|
const getValue = sortColumn.sortValue;
|
|
46
52
|
const direction = sort.direction === 'ascending' ? 1 : -1;
|
|
47
|
-
return
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (
|
|
51
|
-
return 0;
|
|
52
|
-
if (a === null)
|
|
53
|
+
return filtered
|
|
54
|
+
.map((row, index) => ({ index, row, value: getValue(row) }))
|
|
55
|
+
.sort((left, right) => {
|
|
56
|
+
if (left.value === null && right.value !== null)
|
|
53
57
|
return 1;
|
|
54
|
-
if (
|
|
58
|
+
if (right.value === null && left.value !== null)
|
|
55
59
|
return -1;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
const order = PsTableQueryService.compare(left.value, right.value);
|
|
61
|
+
return order === 0 ? left.index - right.index : order * direction;
|
|
62
|
+
})
|
|
63
|
+
.map((entry) => entry.row);
|
|
64
|
+
}
|
|
65
|
+
static compare(a, b) {
|
|
66
|
+
if (a === b || a === null || b === null)
|
|
67
|
+
return 0;
|
|
68
|
+
if (typeof a === 'number' && typeof b === 'number')
|
|
69
|
+
return a - b;
|
|
70
|
+
return PsTableQueryService.collator.compare(String(a), String(b));
|
|
60
71
|
}
|
|
61
72
|
static options(rows, column) {
|
|
62
73
|
if (!column.filter)
|
|
@@ -91,3 +102,4 @@ export class PsTableQueryService {
|
|
|
91
102
|
return null;
|
|
92
103
|
}
|
|
93
104
|
}
|
|
105
|
+
PsTableQueryService.collator = new Intl.Collator(undefined, { numeric: true });
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export class PsTableScrollService {
|
|
2
|
+
static parent(element) {
|
|
3
|
+
for (let node = element.parentElement; node !== null; node = node.parentElement) {
|
|
4
|
+
if (node === document.body || node === document.documentElement)
|
|
5
|
+
return null;
|
|
6
|
+
if (PsTableScrollService.scrollable.test(getComputedStyle(node).overflowY))
|
|
7
|
+
return node;
|
|
8
|
+
}
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
static offset(element, parent) {
|
|
12
|
+
const top = element.getBoundingClientRect().top;
|
|
13
|
+
return parent === null
|
|
14
|
+
? top + window.scrollY
|
|
15
|
+
: top - parent.getBoundingClientRect().top + parent.scrollTop;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
PsTableScrollService.scrollable = /(auto|scroll|overlay)/;
|
|
@@ -15,10 +15,20 @@ export class PsTableSearchService {
|
|
|
15
15
|
return normalised === '' ? [] : normalised.split(' ');
|
|
16
16
|
}
|
|
17
17
|
static matches(text, query) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const haystack = PsTableSearchService.normalise(text);
|
|
18
|
+
return PsTableSearchService.matchesTokens(PsTableSearchService.normalise(text), PsTableSearchService.tokens(query));
|
|
19
|
+
}
|
|
20
|
+
static matchesTokens(haystack, tokens) {
|
|
22
21
|
return tokens.every((token) => haystack.includes(token));
|
|
23
22
|
}
|
|
23
|
+
static haystack(row, getSearchText) {
|
|
24
|
+
if (typeof row !== 'object' || row === null)
|
|
25
|
+
return PsTableSearchService.normalise(getSearchText(row));
|
|
26
|
+
const cached = PsTableSearchService.haystacks.get(row);
|
|
27
|
+
if (cached !== undefined)
|
|
28
|
+
return cached;
|
|
29
|
+
const haystack = PsTableSearchService.normalise(getSearchText(row));
|
|
30
|
+
PsTableSearchService.haystacks.set(row, haystack);
|
|
31
|
+
return haystack;
|
|
32
|
+
}
|
|
24
33
|
}
|
|
34
|
+
PsTableSearchService.haystacks = new WeakMap();
|
|
@@ -2,7 +2,25 @@ export class PsTableStreamService {
|
|
|
2
2
|
static nextPage(loaded, total, pageSize) {
|
|
3
3
|
return loaded < total ? Math.floor(loaded / pageSize) + 1 : null;
|
|
4
4
|
}
|
|
5
|
+
static pages(loaded, total, pageSize) {
|
|
6
|
+
const first = PsTableStreamService.nextPage(loaded, total, pageSize);
|
|
7
|
+
if (first === null)
|
|
8
|
+
return [];
|
|
9
|
+
const last = Math.ceil(total / pageSize);
|
|
10
|
+
return Array.from({ length: last - first + 1 }, (_, index) => first + index);
|
|
11
|
+
}
|
|
12
|
+
static unseen(page, seen, getRowId) {
|
|
13
|
+
return page.filter((row) => {
|
|
14
|
+
const rowId = getRowId(row);
|
|
15
|
+
if (seen.has(rowId))
|
|
16
|
+
return false;
|
|
17
|
+
seen.add(rowId);
|
|
18
|
+
return true;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
5
21
|
static merge(fresh, previous, getRowId, total) {
|
|
22
|
+
if (previous.length === 0)
|
|
23
|
+
return fresh.length > total ? fresh.slice(0, total) : fresh;
|
|
6
24
|
const freshIds = new Set(fresh.map(getRowId));
|
|
7
25
|
return [...fresh, ...previous.filter((row) => !freshIds.has(getRowId(row)))].slice(0, total);
|
|
8
26
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.usePsIncrementalCount = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const usePsIncrementalCount = (total, batch) => {
|
|
6
|
+
const [count, setCount] = (0, react_1.useState)(batch);
|
|
7
|
+
const sentinelRef = (0, react_1.useRef)(null);
|
|
8
|
+
(0, react_1.useEffect)(() => {
|
|
9
|
+
const sentinel = sentinelRef.current;
|
|
10
|
+
if (sentinel === null || count >= total)
|
|
11
|
+
return;
|
|
12
|
+
const observer = new IntersectionObserver((entries) => {
|
|
13
|
+
if (entries.some((entry) => entry.isIntersecting))
|
|
14
|
+
setCount((current) => Math.min(current + batch, total));
|
|
15
|
+
});
|
|
16
|
+
observer.observe(sentinel);
|
|
17
|
+
return () => observer.disconnect();
|
|
18
|
+
}, [batch, count, total]);
|
|
19
|
+
return { count: Math.min(count, total), sentinelRef };
|
|
20
|
+
};
|
|
21
|
+
exports.usePsIncrementalCount = usePsIncrementalCount;
|
|
@@ -2,43 +2,86 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.usePsStreamedRows = void 0;
|
|
4
4
|
const react_1 = require("react");
|
|
5
|
+
const table_config_1 = require("../constants/table-config");
|
|
5
6
|
const ps_table_stream_service_1 = require("../services/ps-table-stream-service");
|
|
6
7
|
const usePsStreamedRows = ({ first, getRowId, loadPage, pageSize, scope, total, }) => {
|
|
7
8
|
const [stream, setStream] = (0, react_1.useState)({
|
|
8
9
|
first,
|
|
9
10
|
hasFailed: false,
|
|
11
|
+
isDone: first.length >= total,
|
|
10
12
|
previous: [],
|
|
11
13
|
rows: first,
|
|
12
14
|
scope,
|
|
13
15
|
});
|
|
14
16
|
const rowId = (0, react_1.useRef)(getRowId);
|
|
15
17
|
const load = (0, react_1.useRef)(loadPage);
|
|
16
|
-
const requested = (0, react_1.useRef)(null);
|
|
17
18
|
rowId.current = getRowId;
|
|
18
19
|
load.current = loadPage;
|
|
19
20
|
if (stream.first !== first)
|
|
20
21
|
setStream({
|
|
21
22
|
first,
|
|
22
23
|
hasFailed: false,
|
|
24
|
+
isDone: first.length >= total,
|
|
23
25
|
previous: ps_table_stream_service_1.PsTableStreamService.carried(stream.rows, stream.previous, getRowId, total, stream.scope === scope),
|
|
24
26
|
rows: first,
|
|
25
27
|
scope,
|
|
26
28
|
});
|
|
27
|
-
const nextPage = stream.hasFailed
|
|
28
|
-
? null
|
|
29
|
-
: ps_table_stream_service_1.PsTableStreamService.nextPage(stream.rows.length, total, pageSize);
|
|
30
29
|
(0, react_1.useEffect)(() => {
|
|
31
|
-
|
|
30
|
+
const pages = ps_table_stream_service_1.PsTableStreamService.pages(first.length, total, pageSize);
|
|
31
|
+
if (pages.length === 0)
|
|
32
32
|
return;
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
const seen = new Set(first.map((row) => rowId.current(row)));
|
|
34
|
+
const loaded = new Map();
|
|
35
|
+
let isCancelled = false;
|
|
36
|
+
let nextIndex = 0;
|
|
37
|
+
let nextToAppend = pages[0];
|
|
38
|
+
let pending = [];
|
|
39
|
+
let timer = null;
|
|
40
|
+
const commit = (patch) => {
|
|
41
|
+
const added = pending;
|
|
42
|
+
pending = [];
|
|
43
|
+
timer = null;
|
|
44
|
+
(0, react_1.startTransition)(() => setStream((current) => current.first !== first
|
|
45
|
+
? current
|
|
46
|
+
: {
|
|
47
|
+
...current,
|
|
48
|
+
...patch,
|
|
49
|
+
rows: added.length === 0 ? current.rows : current.rows.concat(added),
|
|
50
|
+
}));
|
|
51
|
+
};
|
|
52
|
+
const work = async () => {
|
|
53
|
+
while (!isCancelled && nextIndex < pages.length) {
|
|
54
|
+
const page = pages[nextIndex];
|
|
55
|
+
nextIndex += 1;
|
|
56
|
+
loaded.set(page, await load.current(page));
|
|
57
|
+
if (isCancelled)
|
|
58
|
+
return;
|
|
59
|
+
for (let ready = loaded.get(nextToAppend); ready !== undefined;) {
|
|
60
|
+
pending = pending.concat(ps_table_stream_service_1.PsTableStreamService.unseen(ready, seen, rowId.current));
|
|
61
|
+
loaded.delete(nextToAppend);
|
|
62
|
+
nextToAppend += 1;
|
|
63
|
+
ready = loaded.get(nextToAppend);
|
|
64
|
+
}
|
|
65
|
+
if (timer === null)
|
|
66
|
+
timer = setTimeout(() => commit({ hasFailed: false, isDone: false }), table_config_1.PsTableConfig.streamFlushMs);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const finish = (hasFailed) => {
|
|
70
|
+
if (isCancelled)
|
|
71
|
+
return;
|
|
72
|
+
if (timer !== null)
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
isCancelled = true;
|
|
75
|
+
commit({ hasFailed, isDone: !hasFailed });
|
|
76
|
+
};
|
|
77
|
+
void Promise.all(Array.from({ length: Math.min(table_config_1.PsTableConfig.streamConcurrency, pages.length) }, work)).then(() => finish(false), () => finish(true));
|
|
78
|
+
return () => {
|
|
79
|
+
isCancelled = true;
|
|
80
|
+
if (timer !== null)
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
};
|
|
83
|
+
}, [first, pageSize, total]);
|
|
84
|
+
const isStreaming = !stream.hasFailed && !stream.isDone;
|
|
42
85
|
const rows = (0, react_1.useMemo)(() => isStreaming
|
|
43
86
|
? ps_table_stream_service_1.PsTableStreamService.merge(stream.rows, stream.previous, rowId.current, total)
|
|
44
87
|
: stream.rows, [isStreaming, stream.previous, stream.rows, total]);
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export * from './interfaces/table-sort';
|
|
|
18
18
|
export * from './interfaces/table-stream-input';
|
|
19
19
|
export * from './interfaces/table-stream-result';
|
|
20
20
|
export * from './interfaces/table-stream-status';
|
|
21
|
+
export * from './hooks/use-ps-incremental-count';
|
|
21
22
|
export * from './hooks/use-ps-streamed-rows';
|
|
22
23
|
export * from './hooks/use-ps-table-stream-status';
|
|
23
24
|
export * from './services/ps-table-cell-tooltip-service';
|
|
@@ -25,6 +26,7 @@ export * from './services/ps-table-column-size-service';
|
|
|
25
26
|
export * from './services/ps-table-group-service';
|
|
26
27
|
export * from './services/ps-table-query-service';
|
|
27
28
|
export * from './services/ps-table-search-service';
|
|
29
|
+
export * from './services/ps-table-scroll-service';
|
|
28
30
|
export * from './services/ps-table-stream-service';
|
|
29
31
|
export * from './components/ps-table-export-button';
|
|
30
32
|
export * from './constants/ps-table-download-config';
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,7 @@ __exportStar(require("./interfaces/table-sort"), exports);
|
|
|
34
34
|
__exportStar(require("./interfaces/table-stream-input"), exports);
|
|
35
35
|
__exportStar(require("./interfaces/table-stream-result"), exports);
|
|
36
36
|
__exportStar(require("./interfaces/table-stream-status"), exports);
|
|
37
|
+
__exportStar(require("./hooks/use-ps-incremental-count"), exports);
|
|
37
38
|
__exportStar(require("./hooks/use-ps-streamed-rows"), exports);
|
|
38
39
|
__exportStar(require("./hooks/use-ps-table-stream-status"), exports);
|
|
39
40
|
__exportStar(require("./services/ps-table-cell-tooltip-service"), exports);
|
|
@@ -41,6 +42,7 @@ __exportStar(require("./services/ps-table-column-size-service"), exports);
|
|
|
41
42
|
__exportStar(require("./services/ps-table-group-service"), exports);
|
|
42
43
|
__exportStar(require("./services/ps-table-query-service"), exports);
|
|
43
44
|
__exportStar(require("./services/ps-table-search-service"), exports);
|
|
45
|
+
__exportStar(require("./services/ps-table-scroll-service"), exports);
|
|
44
46
|
__exportStar(require("./services/ps-table-stream-service"), exports);
|
|
45
47
|
__exportStar(require("./components/ps-table-export-button"), exports);
|
|
46
48
|
__exportStar(require("./constants/ps-table-download-config"), exports);
|
|
@@ -6,6 +6,8 @@ export declare class PsTableQueryService {
|
|
|
6
6
|
static isActive(value: PsTableFilterValue | undefined): boolean;
|
|
7
7
|
static matchesFilter<RowData>(row: RowData, column: PsTableColumn<RowData>, value: PsTableFilterValue): boolean;
|
|
8
8
|
static apply<RowData>(rows: RowData[], columns: PsTableColumn<RowData>[], filters: Record<string, PsTableFilterValue>, search: string, getSearchText: ((row: RowData) => string) | undefined, sort: PsTableSort | null): RowData[];
|
|
9
|
+
private static compare;
|
|
10
|
+
private static readonly collator;
|
|
9
11
|
static options<RowData>(rows: RowData[], column: PsTableColumn<RowData>): import("..").PsTableFilterOption[];
|
|
10
12
|
static empty(sort: PsTableSort | null): PsTableQuery;
|
|
11
13
|
static isEqual(left: PsTableQuery, right: PsTableQuery): boolean;
|
|
@@ -40,26 +40,37 @@ class PsTableQueryService {
|
|
|
40
40
|
return true;
|
|
41
41
|
}
|
|
42
42
|
static apply(rows, columns, filters, search, getSearchText, sort) {
|
|
43
|
-
const
|
|
44
|
-
|
|
43
|
+
const tokens = getSearchText ? ps_table_search_service_1.PsTableSearchService.tokens(search) : [];
|
|
44
|
+
const activeColumns = columns.filter((column) => column.filter && PsTableQueryService.isActive(filters[column.id]));
|
|
45
|
+
const filtered = tokens.length === 0 && activeColumns.length === 0
|
|
46
|
+
? rows
|
|
47
|
+
: rows.filter((row) => (tokens.length === 0 ||
|
|
48
|
+
(getSearchText !== undefined &&
|
|
49
|
+
ps_table_search_service_1.PsTableSearchService.matchesTokens(ps_table_search_service_1.PsTableSearchService.haystack(row, getSearchText), tokens))) &&
|
|
50
|
+
activeColumns.every((column) => PsTableQueryService.matchesFilter(row, column, filters[column.id])));
|
|
45
51
|
const sortColumn = sort ? columns.find((column) => column.id === sort.columnId) : undefined;
|
|
46
52
|
if (!sort || !sortColumn || !sortColumn.sortValue)
|
|
47
53
|
return filtered;
|
|
48
54
|
const getValue = sortColumn.sortValue;
|
|
49
55
|
const direction = sort.direction === 'ascending' ? 1 : -1;
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
return 0;
|
|
55
|
-
if (a === null)
|
|
56
|
+
return filtered
|
|
57
|
+
.map((row, index) => ({ index, row, value: getValue(row) }))
|
|
58
|
+
.sort((left, right) => {
|
|
59
|
+
if (left.value === null && right.value !== null)
|
|
56
60
|
return 1;
|
|
57
|
-
if (
|
|
61
|
+
if (right.value === null && left.value !== null)
|
|
58
62
|
return -1;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
const order = PsTableQueryService.compare(left.value, right.value);
|
|
64
|
+
return order === 0 ? left.index - right.index : order * direction;
|
|
65
|
+
})
|
|
66
|
+
.map((entry) => entry.row);
|
|
67
|
+
}
|
|
68
|
+
static compare(a, b) {
|
|
69
|
+
if (a === b || a === null || b === null)
|
|
70
|
+
return 0;
|
|
71
|
+
if (typeof a === 'number' && typeof b === 'number')
|
|
72
|
+
return a - b;
|
|
73
|
+
return PsTableQueryService.collator.compare(String(a), String(b));
|
|
63
74
|
}
|
|
64
75
|
static options(rows, column) {
|
|
65
76
|
if (!column.filter)
|
|
@@ -95,3 +106,4 @@ class PsTableQueryService {
|
|
|
95
106
|
}
|
|
96
107
|
}
|
|
97
108
|
exports.PsTableQueryService = PsTableQueryService;
|
|
109
|
+
PsTableQueryService.collator = new Intl.Collator(undefined, { numeric: true });
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsTableScrollService = void 0;
|
|
4
|
+
class PsTableScrollService {
|
|
5
|
+
static parent(element) {
|
|
6
|
+
for (let node = element.parentElement; node !== null; node = node.parentElement) {
|
|
7
|
+
if (node === document.body || node === document.documentElement)
|
|
8
|
+
return null;
|
|
9
|
+
if (PsTableScrollService.scrollable.test(getComputedStyle(node).overflowY))
|
|
10
|
+
return node;
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
static offset(element, parent) {
|
|
15
|
+
const top = element.getBoundingClientRect().top;
|
|
16
|
+
return parent === null
|
|
17
|
+
? top + window.scrollY
|
|
18
|
+
: top - parent.getBoundingClientRect().top + parent.scrollTop;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.PsTableScrollService = PsTableScrollService;
|
|
22
|
+
PsTableScrollService.scrollable = /(auto|scroll|overlay)/;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export declare class PsTableSearchService {
|
|
2
|
+
private static readonly haystacks;
|
|
2
3
|
static normalise(value: string): string;
|
|
3
4
|
static tokens(query: string): string[];
|
|
4
5
|
static matches(text: string, query: string): boolean;
|
|
6
|
+
static matchesTokens(haystack: string, tokens: string[]): boolean;
|
|
7
|
+
static haystack<RowData>(row: RowData, getSearchText: (row: RowData) => string): string;
|
|
5
8
|
}
|
|
@@ -18,11 +18,21 @@ class PsTableSearchService {
|
|
|
18
18
|
return normalised === '' ? [] : normalised.split(' ');
|
|
19
19
|
}
|
|
20
20
|
static matches(text, query) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const haystack = PsTableSearchService.normalise(text);
|
|
21
|
+
return PsTableSearchService.matchesTokens(PsTableSearchService.normalise(text), PsTableSearchService.tokens(query));
|
|
22
|
+
}
|
|
23
|
+
static matchesTokens(haystack, tokens) {
|
|
25
24
|
return tokens.every((token) => haystack.includes(token));
|
|
26
25
|
}
|
|
26
|
+
static haystack(row, getSearchText) {
|
|
27
|
+
if (typeof row !== 'object' || row === null)
|
|
28
|
+
return PsTableSearchService.normalise(getSearchText(row));
|
|
29
|
+
const cached = PsTableSearchService.haystacks.get(row);
|
|
30
|
+
if (cached !== undefined)
|
|
31
|
+
return cached;
|
|
32
|
+
const haystack = PsTableSearchService.normalise(getSearchText(row));
|
|
33
|
+
PsTableSearchService.haystacks.set(row, haystack);
|
|
34
|
+
return haystack;
|
|
35
|
+
}
|
|
27
36
|
}
|
|
28
37
|
exports.PsTableSearchService = PsTableSearchService;
|
|
38
|
+
PsTableSearchService.haystacks = new WeakMap();
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export declare class PsTableStreamService {
|
|
2
2
|
static nextPage(loaded: number, total: number, pageSize: number): number | null;
|
|
3
|
+
static pages(loaded: number, total: number, pageSize: number): number[];
|
|
4
|
+
static unseen<RowData>(page: RowData[], seen: Set<string>, getRowId: (row: RowData) => string): RowData[];
|
|
3
5
|
static merge<RowData>(fresh: RowData[], previous: RowData[], getRowId: (row: RowData) => string, total: number): RowData[];
|
|
4
6
|
static carried<RowData>(rows: RowData[], previous: RowData[], getRowId: (row: RowData) => string, total: number, isSameScope: boolean): RowData[];
|
|
5
7
|
static append<RowData>(rows: RowData[], page: RowData[], getRowId: (row: RowData) => string): RowData[];
|
|
@@ -5,7 +5,25 @@ class PsTableStreamService {
|
|
|
5
5
|
static nextPage(loaded, total, pageSize) {
|
|
6
6
|
return loaded < total ? Math.floor(loaded / pageSize) + 1 : null;
|
|
7
7
|
}
|
|
8
|
+
static pages(loaded, total, pageSize) {
|
|
9
|
+
const first = PsTableStreamService.nextPage(loaded, total, pageSize);
|
|
10
|
+
if (first === null)
|
|
11
|
+
return [];
|
|
12
|
+
const last = Math.ceil(total / pageSize);
|
|
13
|
+
return Array.from({ length: last - first + 1 }, (_, index) => first + index);
|
|
14
|
+
}
|
|
15
|
+
static unseen(page, seen, getRowId) {
|
|
16
|
+
return page.filter((row) => {
|
|
17
|
+
const rowId = getRowId(row);
|
|
18
|
+
if (seen.has(rowId))
|
|
19
|
+
return false;
|
|
20
|
+
seen.add(rowId);
|
|
21
|
+
return true;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
8
24
|
static merge(fresh, previous, getRowId, total) {
|
|
25
|
+
if (previous.length === 0)
|
|
26
|
+
return fresh.length > total ? fresh.slice(0, total) : fresh;
|
|
9
27
|
const freshIds = new Set(fresh.map(getRowId));
|
|
10
28
|
return [...fresh, ...previous.filter((row) => !freshIds.has(getRowId(row)))].slice(0, total);
|
|
11
29
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pallasoft/ps-table-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
"@pallasoft/ps-icons": "^1.5.4",
|
|
79
79
|
"@pallasoft/ps-input-components": "^1.14.0",
|
|
80
80
|
"@pallasoft/ps-motion-components": "^1.2.0",
|
|
81
|
+
"@tanstack/react-virtual": "^3.14.13",
|
|
81
82
|
"react-aria-components": "^1.21.1"
|
|
82
83
|
}
|
|
83
84
|
}
|