@pallasoft/ps-table-components 1.26.0 → 1.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ps-data-table.d.ts +8 -1
- package/dist/components/ps-data-table.js +78 -12
- package/dist/components/ps-select-all-matching-banner.d.ts +10 -0
- package/dist/components/ps-select-all-matching-banner.js +10 -0
- package/dist/components/ps-table-pagination.d.ts +3 -1
- package/dist/components/ps-table-pagination.js +11 -3
- package/dist/constants/ps-server-rows-config.d.ts +8 -0
- package/dist/constants/ps-server-rows-config.js +11 -0
- package/dist/esm/components/ps-data-table.js +79 -13
- package/dist/esm/components/ps-select-all-matching-banner.js +6 -0
- package/dist/esm/components/ps-table-pagination.js +11 -3
- package/dist/esm/constants/ps-server-rows-config.js +8 -0
- package/dist/esm/hooks/use-ps-server-rows.js +209 -0
- package/dist/esm/index.js +10 -0
- package/dist/esm/interfaces/ps-server-entry.js +1 -0
- package/dist/esm/interfaces/ps-server-page.js +1 -0
- package/dist/esm/interfaces/ps-server-rows-input.js +1 -0
- package/dist/esm/interfaces/ps-server-rows-result.js +1 -0
- package/dist/esm/interfaces/ps-table-cursor-paging.js +1 -0
- package/dist/esm/interfaces/ps-table-select-all-matching.js +1 -0
- package/dist/esm/services/ps-server-rows-service.js +36 -0
- package/dist/hooks/use-ps-server-rows.d.ts +3 -0
- package/dist/hooks/use-ps-server-rows.js +213 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/interfaces/ps-server-entry.d.ts +6 -0
- package/dist/interfaces/ps-server-entry.js +2 -0
- package/dist/interfaces/ps-server-page.d.ts +7 -0
- package/dist/interfaces/ps-server-page.js +2 -0
- package/dist/interfaces/ps-server-rows-input.d.ts +13 -0
- package/dist/interfaces/ps-server-rows-input.js +2 -0
- package/dist/interfaces/ps-server-rows-result.d.ts +18 -0
- package/dist/interfaces/ps-server-rows-result.js +2 -0
- package/dist/interfaces/ps-table-cursor-paging.d.ts +12 -0
- package/dist/interfaces/ps-table-cursor-paging.js +2 -0
- package/dist/interfaces/ps-table-select-all-matching.d.ts +7 -0
- package/dist/interfaces/ps-table-select-all-matching.js +2 -0
- package/dist/services/ps-server-rows-service.d.ts +11 -0
- package/dist/services/ps-server-rows-service.js +40 -0
- package/package.json +1 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import { PsTablePaginationPosition } from '../constants/ps-table-pagination-position';
|
|
3
|
+
import type { PsTableCursorPaging } from '../interfaces/ps-table-cursor-paging';
|
|
4
|
+
import type { PsTableSelectAllMatching } from '../interfaces/ps-table-select-all-matching';
|
|
3
5
|
import type { PsTableColumn } from '../interfaces/table-column';
|
|
4
6
|
import type { PsTableGrouping } from '../interfaces/table-grouping';
|
|
5
7
|
import type { PsTableQuery } from '../interfaces/table-query';
|
|
@@ -9,7 +11,9 @@ import type { PsTableSort } from '../interfaces/table-sort';
|
|
|
9
11
|
export interface PsDataTableProps<RowData> {
|
|
10
12
|
caption?: string;
|
|
11
13
|
className?: string;
|
|
14
|
+
clearSelectionKey?: number | string;
|
|
12
15
|
columns: PsTableColumn<RowData>[];
|
|
16
|
+
cursorPaging?: PsTableCursorPaging;
|
|
13
17
|
defaultSort?: PsTableSort;
|
|
14
18
|
density?: 'compact' | 'normal';
|
|
15
19
|
expandedRowIds?: string[];
|
|
@@ -21,6 +25,7 @@ export interface PsDataTableProps<RowData> {
|
|
|
21
25
|
grouping?: PsTableGrouping<RowData>;
|
|
22
26
|
isLoading?: boolean;
|
|
23
27
|
isPending?: boolean;
|
|
28
|
+
isRefreshing?: boolean;
|
|
24
29
|
layout?: 'auto' | 'fixed';
|
|
25
30
|
onPageChange?: (page: number) => void;
|
|
26
31
|
onQueryChange?: (query: PsTableQuery) => void;
|
|
@@ -36,7 +41,9 @@ export interface PsDataTableProps<RowData> {
|
|
|
36
41
|
rows: RowData[];
|
|
37
42
|
searchingLabel?: string;
|
|
38
43
|
searchPlaceholder?: string;
|
|
44
|
+
selectAllMatching?: PsTableSelectAllMatching;
|
|
45
|
+
stickyTop?: string;
|
|
39
46
|
toolbarActions?: ReactNode;
|
|
40
47
|
totalRows?: number;
|
|
41
48
|
}
|
|
42
|
-
export declare const PsDataTable: <RowData>({ caption, className, columns, defaultSort, density, expandedRowIds, mobileLayout, getBulkActions, getRowId, getRowLabel, getSearchText, grouping, isLoading, isPending, layout, onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, paginationPosition, pinFirstColumn, query, renderExpandedRow, reorder, rows, searchingLabel, searchPlaceholder, toolbarActions, totalRows, }: PsDataTableProps<RowData>) => import("react").JSX.Element;
|
|
49
|
+
export declare const PsDataTable: <RowData>({ caption, className, clearSelectionKey, columns, cursorPaging, defaultSort, density, expandedRowIds, mobileLayout, getBulkActions, getRowId, getRowLabel, getSearchText, grouping, isLoading, isPending, isRefreshing, layout, onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, paginationPosition, pinFirstColumn, query, renderExpandedRow, reorder, rows, searchingLabel, searchPlaceholder, selectAllMatching, stickyTop, toolbarActions, totalRows, }: PsDataTableProps<RowData>) => import("react").JSX.Element;
|
|
@@ -56,6 +56,7 @@ const ps_table_pin_service_1 = require("../services/ps-table-pin-service");
|
|
|
56
56
|
const ps_table_query_service_1 = require("../services/ps-table-query-service");
|
|
57
57
|
const ps_table_reorder_service_1 = require("../services/ps-table-reorder-service");
|
|
58
58
|
const ps_table_scroll_service_1 = require("../services/ps-table-scroll-service");
|
|
59
|
+
const ps_select_all_matching_banner_1 = require("./ps-select-all-matching-banner");
|
|
59
60
|
const ps_table_bulk_bar_1 = require("./ps-table-bulk-bar");
|
|
60
61
|
const ps_table_card_list_1 = require("./ps-table-card-list");
|
|
61
62
|
const ps_table_column_picker_1 = require("./ps-table-column-picker");
|
|
@@ -85,7 +86,7 @@ const alignClassNames = {
|
|
|
85
86
|
left: 'text-left',
|
|
86
87
|
right: 'text-right tabular-nums',
|
|
87
88
|
};
|
|
88
|
-
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, paginationPosition = ps_table_pagination_position_1.PsTablePaginationPosition.bottom, pinFirstColumn = true, query, renderExpandedRow, reorder, rows, searchingLabel = table_config_1.PsTableConfig.searchingLabel, searchPlaceholder = table_config_1.PsTableConfig.searchPlaceholder, toolbarActions, totalRows, }) => {
|
|
89
|
+
const PsDataTable = ({ caption, className, clearSelectionKey, columns, cursorPaging, defaultSort, density = 'normal', expandedRowIds, mobileLayout = 'cards', getBulkActions, getRowId, getRowLabel, getSearchText, grouping, isLoading = false, isPending = false, isRefreshing = false, layout = 'auto', onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, paginationPosition = ps_table_pagination_position_1.PsTablePaginationPosition.bottom, pinFirstColumn = true, query, renderExpandedRow, reorder, rows, searchingLabel = table_config_1.PsTableConfig.searchingLabel, searchPlaceholder = table_config_1.PsTableConfig.searchPlaceholder, selectAllMatching, stickyTop, toolbarActions, totalRows, }) => {
|
|
89
90
|
const containerRef = (0, react_1.useRef)(null);
|
|
90
91
|
const [uncontrolledPage, setUncontrolledPage] = (0, react_1.useState)(1);
|
|
91
92
|
const [uncontrolledQuery, setUncontrolledQuery] = (0, react_1.useState)(() => ps_table_query_service_1.PsTableQueryService.empty(defaultSort ? defaultSort : null));
|
|
@@ -93,7 +94,50 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
|
|
|
93
94
|
const [toggledGroupKeys, setToggledGroupKeys] = (0, react_1.useState)([]);
|
|
94
95
|
const [closingGroupKeys, setClosingGroupKeys] = (0, react_1.useState)([]);
|
|
95
96
|
const [openedGroupKeys, setOpenedGroupKeys] = (0, react_1.useState)([]);
|
|
97
|
+
const [seenClearKey, setSeenClearKey] = (0, react_1.useState)(clearSelectionKey);
|
|
98
|
+
const stickyRegionRef = (0, react_1.useRef)(null);
|
|
99
|
+
const stickySentinelRef = (0, react_1.useRef)(null);
|
|
100
|
+
const [stickyHeight, setStickyHeight] = (0, react_1.useState)(0);
|
|
101
|
+
const [isStuck, setIsStuck] = (0, react_1.useState)(false);
|
|
96
102
|
const currentQuery = query ? query : uncontrolledQuery;
|
|
103
|
+
if (seenClearKey !== clearSelectionKey) {
|
|
104
|
+
setSeenClearKey(clearSelectionKey);
|
|
105
|
+
setSelectedIds([]);
|
|
106
|
+
}
|
|
107
|
+
(0, react_1.useLayoutEffect)(() => {
|
|
108
|
+
const region = stickyRegionRef.current;
|
|
109
|
+
if (stickyTop === undefined || region === null)
|
|
110
|
+
return;
|
|
111
|
+
const measure = () => setStickyHeight(region.offsetHeight);
|
|
112
|
+
const observer = new ResizeObserver(measure);
|
|
113
|
+
measure();
|
|
114
|
+
observer.observe(region);
|
|
115
|
+
return () => observer.disconnect();
|
|
116
|
+
}, [stickyTop]);
|
|
117
|
+
(0, react_1.useEffect)(() => {
|
|
118
|
+
const region = stickyRegionRef.current;
|
|
119
|
+
const sentinel = stickySentinelRef.current;
|
|
120
|
+
if (stickyTop === undefined || region === null || sentinel === null)
|
|
121
|
+
return;
|
|
122
|
+
let frame = 0;
|
|
123
|
+
const check = () => {
|
|
124
|
+
frame = 0;
|
|
125
|
+
setIsStuck(sentinel.getBoundingClientRect().top < Number.parseFloat(getComputedStyle(region).top));
|
|
126
|
+
};
|
|
127
|
+
const schedule = () => {
|
|
128
|
+
if (frame === 0)
|
|
129
|
+
frame = window.requestAnimationFrame(check);
|
|
130
|
+
};
|
|
131
|
+
check();
|
|
132
|
+
window.addEventListener('scroll', schedule, { passive: true });
|
|
133
|
+
window.addEventListener('resize', schedule);
|
|
134
|
+
return () => {
|
|
135
|
+
window.removeEventListener('scroll', schedule);
|
|
136
|
+
window.removeEventListener('resize', schedule);
|
|
137
|
+
if (frame !== 0)
|
|
138
|
+
window.cancelAnimationFrame(frame);
|
|
139
|
+
};
|
|
140
|
+
}, [stickyTop]);
|
|
97
141
|
const { filters, hiddenColumnIds, search, sort } = currentQuery;
|
|
98
142
|
const visibleColumns = columns.filter((column) => !hiddenColumnIds.includes(column.id));
|
|
99
143
|
const fittedWidths = (0, react_1.useMemo)(() => new Map(columns
|
|
@@ -235,7 +279,7 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
|
|
|
235
279
|
})
|
|
236
280
|
: [], [getRowId, getRowLabel, reorder, reorderKey, visibleRows]);
|
|
237
281
|
const showsPagination = Boolean(pageSize) && (!grouping || isServerPaginated);
|
|
238
|
-
const renderPagination = (paginationClassName) => pageSize ? ((0, jsx_runtime_1.jsx)(ps_table_pagination_1.PsTablePagination, { className: paginationClassName, isPartial: isPartial, loadedPageCount: pageCountOf(loadedRowCount), onPageChange: changePage, page: safePage, pageCount: pageCount, pageSize: pageSize, totalRows: rowCount })) : null;
|
|
282
|
+
const renderPagination = (paginationClassName) => pageSize ? ((0, jsx_runtime_1.jsx)(ps_table_pagination_1.PsTablePagination, { className: paginationClassName, cursor: cursorPaging, isPartial: isPartial, loadedPageCount: pageCountOf(loadedRowCount), onPageChange: changePage, page: safePage, pageCount: pageCount, pageSize: pageSize, totalRows: rowCount })) : null;
|
|
239
283
|
const renderOverlayRow = (rowId, size) => {
|
|
240
284
|
const row = visibleRows.find((candidate) => getRowId(candidate) === rowId);
|
|
241
285
|
return row === undefined ? null : ((0, jsx_runtime_1.jsxs)("table", { className: 'pointer-events-none w-full table-fixed border-collapse bg-surface text-left', style: size === null ? undefined : { width: size.width }, children: [size === null ? null : ((0, jsx_runtime_1.jsx)("colgroup", { children: size.columnWidths.map((width, index) => ((0, jsx_runtime_1.jsx)("col", { style: { width } }, index))) })), (0, jsx_runtime_1.jsx)("tbody", { children: renderRow(ps_table_reorder_service_1.PsTableReorderService.plain, row, undefined, undefined, true) })] }));
|
|
@@ -269,12 +313,16 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
|
|
|
269
313
|
const selectedRows = selectedIds.length === 0 ? [] : queriedRows.filter((row) => selectedIdSet.has(getRowId(row)));
|
|
270
314
|
const pageIds = visibleRows.map((row) => getRowId(row));
|
|
271
315
|
const isPageSelected = pageIds.length > 0 && pageIds.every((rowId) => selectedIdSet.has(rowId));
|
|
272
|
-
const togglePage = () =>
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
316
|
+
const togglePage = () => {
|
|
317
|
+
if (isPageSelected && selectAllMatching !== undefined && selectAllMatching.isActive)
|
|
318
|
+
selectAllMatching.onDeactivate();
|
|
319
|
+
setSelectedIds((current) => {
|
|
320
|
+
const pageIdSet = new Set(pageIds);
|
|
321
|
+
return isPageSelected
|
|
322
|
+
? current.filter((rowId) => !pageIdSet.has(rowId))
|
|
323
|
+
: [...new Set([...current, ...pageIds])];
|
|
324
|
+
});
|
|
325
|
+
};
|
|
278
326
|
const bodyRef = (0, react_1.useRef)(null);
|
|
279
327
|
const [scrollParent, setScrollParent] = (0, react_1.useState)(null);
|
|
280
328
|
const [scrollMargin, setScrollMargin] = (0, react_1.useState)(0);
|
|
@@ -352,7 +400,18 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
|
|
|
352
400
|
: entries.map((entry) => renderRow(api, entry.row, entry.motion, entry.virtualItem));
|
|
353
401
|
if (isLoading)
|
|
354
402
|
return ((0, jsx_runtime_1.jsx)(ps_core_components_1.PsSkeletonTable, { className: className, columns: visibleColumns.length, hasToolbar: hasToolbar, rows: table_config_1.PsTableConfig.skeletonRows }));
|
|
355
|
-
|
|
403
|
+
const clearSelection = () => {
|
|
404
|
+
setSelectedIds([]);
|
|
405
|
+
if (selectAllMatching !== undefined && selectAllMatching.isActive)
|
|
406
|
+
selectAllMatching.onDeactivate();
|
|
407
|
+
};
|
|
408
|
+
const banner = selectAllMatching !== undefined &&
|
|
409
|
+
selectedRows.length > 0 &&
|
|
410
|
+
selectAllMatching.total > pageIds.length &&
|
|
411
|
+
(isPageSelected || selectAllMatching.isActive) ? ((0, jsx_runtime_1.jsx)(ps_select_all_matching_banner_1.PsSelectAllMatchingBanner, { isActive: selectAllMatching.isActive, noun: selectAllMatching.noun, onActivate: selectAllMatching.onActivate, onDeactivate: clearSelection, pageCount: pageIds.length, total: selectAllMatching.total })) : null;
|
|
412
|
+
const topContent = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [isSelectable && selectedRows.length > 0 && getBulkActions ? ((0, jsx_runtime_1.jsx)(ps_table_bulk_bar_1.PsTableBulkBar, { actions: getBulkActions(selectedRows), onClear: clearSelection, selectedCount: selectAllMatching !== undefined && selectAllMatching.isActive
|
|
413
|
+
? selectAllMatching.total
|
|
414
|
+
: selectedRows.length })) : hasToolbar || columns.some((column) => column.canHide) ? ((0, jsx_runtime_1.jsx)(ps_table_toolbar_1.PsTableToolbar, { actions: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [toolbarActions, columns.some((column) => column.canHide) ? ((0, jsx_runtime_1.jsx)(ps_table_column_picker_1.PsTableColumnPicker, { columns: columns
|
|
356
415
|
.filter((column) => column.canHide)
|
|
357
416
|
.map((column) => ({ id: column.id, label: column.label })), hiddenColumnIds: hiddenColumnIds, onChange: (nextHidden) => updateQuery({ hiddenColumnIds: nextHidden }) })) : null] }), columns: hasFilters ? columns : [], filters: filters, isSearchable: isSearchable, onClearFilters: clearFilters, onFilterChange: (columnId, value) => {
|
|
358
417
|
updateQuery({ filters: { ...filters, [columnId]: value } });
|
|
@@ -360,16 +419,23 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
|
|
|
360
419
|
}, onSearchChange: (next) => {
|
|
361
420
|
updateQuery({ search: next });
|
|
362
421
|
resetPage();
|
|
363
|
-
}, rows: rows, search: search, searchPlaceholder: searchPlaceholder })) : null,
|
|
422
|
+
}, rows: rows, search: search, searchPlaceholder: searchPlaceholder })) : null, banner] }));
|
|
423
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: containerRef, className: ps_design_system_1.PallasoftClassNameService.merge('flex min-w-0 flex-col gap-3', className), children: [stickyTop === undefined ? (topContent) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { "aria-hidden": 'true', className: '-mb-3 h-0', ref: stickySentinelRef }), (0, jsx_runtime_1.jsx)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('sticky z-sticky -mx-1 flex flex-col gap-3 bg-canvas px-1 pb-2 pt-1 transition-shadow', isStuck ? 'shadow-[0_8px_8px_-8px_rgb(var(--ps-color-ink-rgb,15_23_42)/0.18)]' : ''), ref: stickyRegionRef, style: { top: stickyTop }, children: topContent })] })), (0, jsx_runtime_1.jsxs)("div", { "aria-busy": isRefreshing, className: ps_design_system_1.PallasoftClassNameService.merge('min-w-0 rounded-[1.25rem] border border-line bg-surface', stickyTop === undefined ? 'overflow-hidden' : 'overflow-clip'), children: [showsPagination && paginationPosition !== ps_table_pagination_position_1.PsTablePaginationPosition.bottom
|
|
364
424
|
? renderPagination('border-b border-t-0')
|
|
365
|
-
: null, mobileLayout === 'cards' ? ((0, jsx_runtime_1.jsxs)("div", { className: 'sm:hidden', children: [reorder && reorder.cardsReason ? ((0, jsx_runtime_1.jsx)("p", { className: 'border-b border-line px-4 py-2 text-xs text-ink/55', children: reorder.cardsReason })) : null, grouping
|
|
425
|
+
: null, mobileLayout === 'cards' ? ((0, jsx_runtime_1.jsxs)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('transition-opacity duration-150 sm:hidden', isRefreshing ? 'opacity-60' : ''), children: [reorder && reorder.cardsReason ? ((0, jsx_runtime_1.jsx)("p", { className: 'border-b border-line px-4 py-2 text-xs text-ink/55', children: reorder.cardsReason })) : null, grouping
|
|
366
426
|
? groups.map((group, groupIndex) => {
|
|
367
427
|
const isExpanded = isGroupExpanded(group.key, groupIndex);
|
|
368
428
|
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
|
|
369
429
|
? grouping.renderGroupSummary(group.rows)
|
|
370
430
|
: 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}`));
|
|
371
431
|
})
|
|
372
|
-
: renderCardList(shouldVirtualize ? visibleRows.slice(0, cards.count) : visibleRows, visibleRows.length === 0 && isSearching ? ((0, jsx_runtime_1.jsx)(ps_table_searching_state_1.PsTableSearchingState, { label: searchingLabel, skeletonRows: table_config_1.PsTableConfig.searchingSkeletonRows })) : visibleRows.length === 0 && isQueried ? ((0, jsx_runtime_1.jsxs)("div", { className: 'px-4 py-10 text-center', children: [(0, jsx_runtime_1.jsx)("p", { className: 'text-sm font-semibold text-ink', children: table_config_1.PsTableConfig.noMatchesTitle }), (0, jsx_runtime_1.jsx)("button", { className: ps_design_system_1.PallasoftClassNameService.merge(ps_table_control_classes_1.PsTableControlClasses.textButton, 'mt-2 text-primary'), onClick: clearFilters, type: 'button', children: "Clear search and filters" })] })) : null), !grouping && shouldVirtualize && cards.count < visibleRows.length ? ((0, jsx_runtime_1.jsx)("div", { "aria-hidden": 'true', className: 'h-px', ref: cards.sentinelRef })) : null] })) : null, (0, jsx_runtime_1.jsx)("div", { className: ps_design_system_1.PallasoftClassNameService.merge(
|
|
432
|
+
: renderCardList(shouldVirtualize ? visibleRows.slice(0, cards.count) : visibleRows, visibleRows.length === 0 && isSearching ? ((0, jsx_runtime_1.jsx)(ps_table_searching_state_1.PsTableSearchingState, { label: searchingLabel, skeletonRows: table_config_1.PsTableConfig.searchingSkeletonRows })) : visibleRows.length === 0 && isQueried ? ((0, jsx_runtime_1.jsxs)("div", { className: 'px-4 py-10 text-center', children: [(0, jsx_runtime_1.jsx)("p", { className: 'text-sm font-semibold text-ink', children: table_config_1.PsTableConfig.noMatchesTitle }), (0, jsx_runtime_1.jsx)("button", { className: ps_design_system_1.PallasoftClassNameService.merge(ps_table_control_classes_1.PsTableControlClasses.textButton, 'mt-2 text-primary'), onClick: clearFilters, type: 'button', children: "Clear search and filters" })] })) : null), !grouping && shouldVirtualize && cards.count < visibleRows.length ? ((0, jsx_runtime_1.jsx)("div", { "aria-hidden": 'true', className: 'h-px', ref: cards.sentinelRef })) : null] })) : null, (0, jsx_runtime_1.jsx)("div", { className: ps_design_system_1.PallasoftClassNameService.merge(stickyTop === undefined
|
|
433
|
+
? 'ps-scrollbar overflow-x-auto overflow-y-hidden overscroll-x-contain'
|
|
434
|
+
: 'overflow-x-clip', 'transition-opacity duration-150', isRefreshing ? 'opacity-60' : '', mobileLayout === 'cards' ? 'hidden sm:block' : ''), "data-overflow-end": overflow.edges.end, "data-overflow-start": overflow.edges.start, onScroll: overflow.update, ref: overflow.ref, children: renderReorder((api) => ((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: ps_design_system_1.PallasoftClassNameService.merge('bg-mist/70 text-[0.68rem] font-semibold uppercase tracking-[0.18em] text-ink/45', stickyTop === undefined
|
|
435
|
+
? ''
|
|
436
|
+
: 'sticky z-docked bg-surface [&_th]:border-b [&_th]:border-line [&>tr]:bg-mist/70'), style: stickyTop === undefined
|
|
437
|
+
? undefined
|
|
438
|
+
: { top: `calc(${stickyTop} + ${stickyHeight}px)` }, children: (0, jsx_runtime_1.jsxs)("tr", { children: [reorder ? ((0, jsx_runtime_1.jsx)("th", { className: ps_design_system_1.PallasoftClassNameService.merge('w-9 min-w-9 pl-3 pr-0', pins.reorder), scope: 'col', children: reorder.enabled ? ((0, jsx_runtime_1.jsx)("span", { className: 'sr-only', children: reorder.columnLabel })) : reorder.disabledReason ? ((0, jsx_runtime_1.jsx)("span", { "aria-label": reorder.disabledReason, className: 'inline-flex cursor-help opacity-40', role: 'img', title: reorder.disabledReason, children: (0, jsx_runtime_1.jsx)(ps_core_components_1.PsDragGrip, {}) })) : null })) : null, isSelectable ? ((0, jsx_runtime_1.jsx)("th", { className: ps_design_system_1.PallasoftClassNameService.merge('w-10 min-w-10 pl-4 pr-0', pins.select), scope: 'col', children: (0, jsx_runtime_1.jsx)(ps_input_components_1.PsCheckbox, { isIndeterminate: !isPageSelected && pageIds.some((rowId) => selectedIdSet.has(rowId)), isSelected: isPageSelected, label: 'Select all rows on this page', onChange: togglePage }) })) : null, visibleColumns.map((column, columnIndex) => {
|
|
373
439
|
const align = column.align !== null && column.align !== undefined ? column.align : 'left';
|
|
374
440
|
const isSorted = sort !== null && sort.columnId === column.id;
|
|
375
441
|
return ((0, jsx_runtime_1.jsx)("th", { "aria-sort": isSorted
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface PsSelectAllMatchingBannerProps {
|
|
2
|
+
className?: string;
|
|
3
|
+
isActive: boolean;
|
|
4
|
+
noun?: string;
|
|
5
|
+
onActivate: () => void;
|
|
6
|
+
onDeactivate: () => void;
|
|
7
|
+
pageCount: number;
|
|
8
|
+
total: number;
|
|
9
|
+
}
|
|
10
|
+
export declare const PsSelectAllMatchingBanner: ({ className, isActive, noun, onActivate, onDeactivate, pageCount, total, }: PsSelectAllMatchingBannerProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsSelectAllMatchingBanner = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const ps_design_system_1 = require("@pallasoft/ps-design-system");
|
|
6
|
+
const ps_table_control_classes_1 = require("../constants/ps-table-control-classes");
|
|
7
|
+
const PsSelectAllMatchingBanner = ({ className, isActive, noun = 'rows', onActivate, onDeactivate, pageCount, total, }) => ((0, jsx_runtime_1.jsxs)("div", { "aria-live": 'polite', className: ps_design_system_1.PallasoftClassNameService.merge('flex flex-wrap items-center justify-center gap-x-2 gap-y-1 rounded-ps-control bg-primary/10 px-3 py-2 text-sm text-ink', className), role: 'status', children: [(0, jsx_runtime_1.jsx)("span", { children: isActive
|
|
8
|
+
? `All ${total.toLocaleString()} ${noun} matching are selected.`
|
|
9
|
+
: `${pageCount.toLocaleString()} ${noun} on this page are selected.` }), (0, jsx_runtime_1.jsx)("button", { className: ps_design_system_1.PallasoftClassNameService.merge(ps_table_control_classes_1.PsTableControlClasses.textButton, 'text-primary'), onClick: isActive ? onDeactivate : onActivate, type: 'button', children: isActive ? 'Clear selection' : `Select all ${total.toLocaleString()} matching` })] }));
|
|
10
|
+
exports.PsSelectAllMatchingBanner = PsSelectAllMatchingBanner;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { PsTableCursorPaging } from '../interfaces/ps-table-cursor-paging';
|
|
1
2
|
export interface PsTablePaginationProps {
|
|
2
3
|
className?: string;
|
|
4
|
+
cursor?: PsTableCursorPaging;
|
|
3
5
|
isPartial?: boolean;
|
|
4
6
|
loadedPageCount?: number;
|
|
5
7
|
onPageChange: (page: number) => void;
|
|
@@ -8,4 +10,4 @@ export interface PsTablePaginationProps {
|
|
|
8
10
|
pageSize: number;
|
|
9
11
|
totalRows: number;
|
|
10
12
|
}
|
|
11
|
-
export declare const PsTablePagination: ({ className, isPartial, loadedPageCount, onPageChange, page, pageCount, pageSize, totalRows, }: PsTablePaginationProps) => import("react").JSX.Element;
|
|
13
|
+
export declare const PsTablePagination: ({ className, cursor, isPartial, loadedPageCount, onPageChange, page, pageCount, pageSize, totalRows, }: PsTablePaginationProps) => import("react").JSX.Element;
|
|
@@ -4,12 +4,20 @@ exports.PsTablePagination = void 0;
|
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const ps_design_system_1 = require("@pallasoft/ps-design-system");
|
|
6
6
|
const ps_table_control_classes_1 = require("../constants/ps-table-control-classes");
|
|
7
|
-
const PsTablePagination = ({ className, isPartial = false, loadedPageCount, onPageChange, page, pageCount, pageSize, totalRows, }) => {
|
|
7
|
+
const PsTablePagination = ({ className, cursor, isPartial = false, loadedPageCount, onPageChange, page, pageCount, pageSize, totalRows, }) => {
|
|
8
8
|
const firstRow = totalRows === 0 ? 0 : (page - 1) * pageSize + 1;
|
|
9
9
|
const lastRow = Math.min(page * pageSize, totalRows);
|
|
10
|
+
const approximately = cursor !== undefined && cursor.isEstimate ? 'about ' : '';
|
|
11
|
+
const pageNumber = cursor === undefined ? Math.min(page, pageCount) : cursor.pageNumber;
|
|
12
|
+
const cursorFirst = cursor === undefined ? firstRow : (cursor.pageNumber - 1) * pageSize + 1;
|
|
13
|
+
const cursorLast = cursor === undefined ? lastRow : Math.min(cursor.pageNumber * pageSize, totalRows);
|
|
10
14
|
return ((0, jsx_runtime_1.jsxs)("nav", { "aria-label": 'Pagination', className: ps_design_system_1.PallasoftClassNameService.merge('flex flex-wrap items-center justify-between gap-3 border-t border-line px-4 py-3', className), children: [(0, jsx_runtime_1.jsx)("p", { className: 'text-sm text-ink/55', children: totalRows === 0
|
|
11
15
|
? 'No rows'
|
|
12
|
-
:
|
|
13
|
-
|
|
16
|
+
: cursor === undefined
|
|
17
|
+
? `${firstRow}–${lastRow} of ${totalRows}${isPartial ? '+' : ''}`
|
|
18
|
+
: `${cursorFirst}–${cursorLast} of ${approximately}${totalRows.toLocaleString()}` }), (0, jsx_runtime_1.jsxs)("div", { className: 'flex items-center gap-2', children: [cursor !== undefined && cursor.pageSizeOptions !== undefined && cursor.onPageSizeChange ? ((0, jsx_runtime_1.jsxs)("label", { className: 'flex items-center gap-2 text-sm text-ink/65', children: [(0, jsx_runtime_1.jsx)("span", { className: 'hidden sm:inline', children: "Rows per page" }), (0, jsx_runtime_1.jsx)("select", { "aria-label": 'Rows per page', className: 'ps-input-control ps-focus-ring ps-control ps-control-padding rounded-ps-control text-sm', onChange: (event) => cursor.onPageSizeChange?.(Number(event.target.value)), value: pageSize, children: cursor.pageSizeOptions.map((option) => ((0, jsx_runtime_1.jsx)("option", { value: option, children: option }, option))) })] })) : null, (0, jsx_runtime_1.jsx)("button", { className: ps_table_control_classes_1.PsTableControlClasses.paginationButton, disabled: cursor === undefined ? page <= 1 : !cursor.hasPrevious, onClick: () => (cursor === undefined ? onPageChange(page - 1) : cursor.onPrevious()), type: 'button', children: "Previous" }), (0, jsx_runtime_1.jsxs)("span", { className: 'hidden text-sm text-ink/65 sm:inline', children: ["Page ", pageNumber, " of ", approximately, pageCount] }), (0, jsx_runtime_1.jsx)("button", { className: ps_table_control_classes_1.PsTableControlClasses.paginationButton, disabled: cursor === undefined
|
|
19
|
+
? page >=
|
|
20
|
+
(loadedPageCount === undefined ? pageCount : Math.min(loadedPageCount, pageCount))
|
|
21
|
+
: !cursor.hasNext, onClick: () => (cursor === undefined ? onPageChange(page + 1) : cursor.onNext()), onFocus: cursor === undefined ? undefined : cursor.onNextIntent, onPointerEnter: cursor === undefined ? undefined : cursor.onNextIntent, type: 'button', children: "Next" })] })] }));
|
|
14
22
|
};
|
|
15
23
|
exports.PsTablePagination = PsTablePagination;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsServerRowsConfig = void 0;
|
|
4
|
+
exports.PsServerRowsConfig = {
|
|
5
|
+
cacheSize: 8,
|
|
6
|
+
debounceMs: 250,
|
|
7
|
+
defaultPageSize: 100,
|
|
8
|
+
idleFallbackMs: 200,
|
|
9
|
+
idleTimeoutMs: 1000,
|
|
10
|
+
pageSizes: [50, 100, 200],
|
|
11
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Fragment, Suspense, lazy, useCallback, useDeferredValue, useEffect, useMemo, useRef, useState, } from 'react';
|
|
2
|
+
import { Fragment, Suspense, lazy, useCallback, useDeferredValue, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
3
3
|
import { PsDragGrip, PsSkeletonTable } from '@pallasoft/ps-core-components';
|
|
4
4
|
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
5
5
|
import { PsCheckbox } from '@pallasoft/ps-input-components';
|
|
@@ -20,6 +20,7 @@ import { PsTablePinService } from '../services/ps-table-pin-service';
|
|
|
20
20
|
import { PsTableQueryService } from '../services/ps-table-query-service';
|
|
21
21
|
import { PsTableReorderService } from '../services/ps-table-reorder-service';
|
|
22
22
|
import { PsTableScrollService } from '../services/ps-table-scroll-service';
|
|
23
|
+
import { PsSelectAllMatchingBanner } from './ps-select-all-matching-banner';
|
|
23
24
|
import { PsTableBulkBar } from './ps-table-bulk-bar';
|
|
24
25
|
import { PsTableCardList } from './ps-table-card-list';
|
|
25
26
|
import { PsTableColumnPicker } from './ps-table-column-picker';
|
|
@@ -49,7 +50,7 @@ const alignClassNames = {
|
|
|
49
50
|
left: 'text-left',
|
|
50
51
|
right: 'text-right tabular-nums',
|
|
51
52
|
};
|
|
52
|
-
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, paginationPosition = PsTablePaginationPosition.bottom, pinFirstColumn = true, query, renderExpandedRow, reorder, rows, searchingLabel = PsTableConfig.searchingLabel, searchPlaceholder = PsTableConfig.searchPlaceholder, toolbarActions, totalRows, }) => {
|
|
53
|
+
export const PsDataTable = ({ caption, className, clearSelectionKey, columns, cursorPaging, defaultSort, density = 'normal', expandedRowIds, mobileLayout = 'cards', getBulkActions, getRowId, getRowLabel, getSearchText, grouping, isLoading = false, isPending = false, isRefreshing = false, layout = 'auto', onPageChange, onQueryChange, onRowClick, onRowHover, page, pageSize, paginationPosition = PsTablePaginationPosition.bottom, pinFirstColumn = true, query, renderExpandedRow, reorder, rows, searchingLabel = PsTableConfig.searchingLabel, searchPlaceholder = PsTableConfig.searchPlaceholder, selectAllMatching, stickyTop, toolbarActions, totalRows, }) => {
|
|
53
54
|
const containerRef = useRef(null);
|
|
54
55
|
const [uncontrolledPage, setUncontrolledPage] = useState(1);
|
|
55
56
|
const [uncontrolledQuery, setUncontrolledQuery] = useState(() => PsTableQueryService.empty(defaultSort ? defaultSort : null));
|
|
@@ -57,7 +58,50 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
57
58
|
const [toggledGroupKeys, setToggledGroupKeys] = useState([]);
|
|
58
59
|
const [closingGroupKeys, setClosingGroupKeys] = useState([]);
|
|
59
60
|
const [openedGroupKeys, setOpenedGroupKeys] = useState([]);
|
|
61
|
+
const [seenClearKey, setSeenClearKey] = useState(clearSelectionKey);
|
|
62
|
+
const stickyRegionRef = useRef(null);
|
|
63
|
+
const stickySentinelRef = useRef(null);
|
|
64
|
+
const [stickyHeight, setStickyHeight] = useState(0);
|
|
65
|
+
const [isStuck, setIsStuck] = useState(false);
|
|
60
66
|
const currentQuery = query ? query : uncontrolledQuery;
|
|
67
|
+
if (seenClearKey !== clearSelectionKey) {
|
|
68
|
+
setSeenClearKey(clearSelectionKey);
|
|
69
|
+
setSelectedIds([]);
|
|
70
|
+
}
|
|
71
|
+
useLayoutEffect(() => {
|
|
72
|
+
const region = stickyRegionRef.current;
|
|
73
|
+
if (stickyTop === undefined || region === null)
|
|
74
|
+
return;
|
|
75
|
+
const measure = () => setStickyHeight(region.offsetHeight);
|
|
76
|
+
const observer = new ResizeObserver(measure);
|
|
77
|
+
measure();
|
|
78
|
+
observer.observe(region);
|
|
79
|
+
return () => observer.disconnect();
|
|
80
|
+
}, [stickyTop]);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const region = stickyRegionRef.current;
|
|
83
|
+
const sentinel = stickySentinelRef.current;
|
|
84
|
+
if (stickyTop === undefined || region === null || sentinel === null)
|
|
85
|
+
return;
|
|
86
|
+
let frame = 0;
|
|
87
|
+
const check = () => {
|
|
88
|
+
frame = 0;
|
|
89
|
+
setIsStuck(sentinel.getBoundingClientRect().top < Number.parseFloat(getComputedStyle(region).top));
|
|
90
|
+
};
|
|
91
|
+
const schedule = () => {
|
|
92
|
+
if (frame === 0)
|
|
93
|
+
frame = window.requestAnimationFrame(check);
|
|
94
|
+
};
|
|
95
|
+
check();
|
|
96
|
+
window.addEventListener('scroll', schedule, { passive: true });
|
|
97
|
+
window.addEventListener('resize', schedule);
|
|
98
|
+
return () => {
|
|
99
|
+
window.removeEventListener('scroll', schedule);
|
|
100
|
+
window.removeEventListener('resize', schedule);
|
|
101
|
+
if (frame !== 0)
|
|
102
|
+
window.cancelAnimationFrame(frame);
|
|
103
|
+
};
|
|
104
|
+
}, [stickyTop]);
|
|
61
105
|
const { filters, hiddenColumnIds, search, sort } = currentQuery;
|
|
62
106
|
const visibleColumns = columns.filter((column) => !hiddenColumnIds.includes(column.id));
|
|
63
107
|
const fittedWidths = useMemo(() => new Map(columns
|
|
@@ -199,7 +243,7 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
199
243
|
})
|
|
200
244
|
: [], [getRowId, getRowLabel, reorder, reorderKey, visibleRows]);
|
|
201
245
|
const showsPagination = Boolean(pageSize) && (!grouping || isServerPaginated);
|
|
202
|
-
const renderPagination = (paginationClassName) => pageSize ? (_jsx(PsTablePagination, { className: paginationClassName, isPartial: isPartial, loadedPageCount: pageCountOf(loadedRowCount), onPageChange: changePage, page: safePage, pageCount: pageCount, pageSize: pageSize, totalRows: rowCount })) : null;
|
|
246
|
+
const renderPagination = (paginationClassName) => pageSize ? (_jsx(PsTablePagination, { className: paginationClassName, cursor: cursorPaging, isPartial: isPartial, loadedPageCount: pageCountOf(loadedRowCount), onPageChange: changePage, page: safePage, pageCount: pageCount, pageSize: pageSize, totalRows: rowCount })) : null;
|
|
203
247
|
const renderOverlayRow = (rowId, size) => {
|
|
204
248
|
const row = visibleRows.find((candidate) => getRowId(candidate) === rowId);
|
|
205
249
|
return row === undefined ? null : (_jsxs("table", { className: 'pointer-events-none w-full table-fixed border-collapse bg-surface text-left', style: size === null ? undefined : { width: size.width }, children: [size === null ? null : (_jsx("colgroup", { children: size.columnWidths.map((width, index) => (_jsx("col", { style: { width } }, index))) })), _jsx("tbody", { children: renderRow(PsTableReorderService.plain, row, undefined, undefined, true) })] }));
|
|
@@ -233,12 +277,16 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
233
277
|
const selectedRows = selectedIds.length === 0 ? [] : queriedRows.filter((row) => selectedIdSet.has(getRowId(row)));
|
|
234
278
|
const pageIds = visibleRows.map((row) => getRowId(row));
|
|
235
279
|
const isPageSelected = pageIds.length > 0 && pageIds.every((rowId) => selectedIdSet.has(rowId));
|
|
236
|
-
const togglePage = () =>
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
280
|
+
const togglePage = () => {
|
|
281
|
+
if (isPageSelected && selectAllMatching !== undefined && selectAllMatching.isActive)
|
|
282
|
+
selectAllMatching.onDeactivate();
|
|
283
|
+
setSelectedIds((current) => {
|
|
284
|
+
const pageIdSet = new Set(pageIds);
|
|
285
|
+
return isPageSelected
|
|
286
|
+
? current.filter((rowId) => !pageIdSet.has(rowId))
|
|
287
|
+
: [...new Set([...current, ...pageIds])];
|
|
288
|
+
});
|
|
289
|
+
};
|
|
242
290
|
const bodyRef = useRef(null);
|
|
243
291
|
const [scrollParent, setScrollParent] = useState(null);
|
|
244
292
|
const [scrollMargin, setScrollMargin] = useState(0);
|
|
@@ -316,7 +364,18 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
316
364
|
: entries.map((entry) => renderRow(api, entry.row, entry.motion, entry.virtualItem));
|
|
317
365
|
if (isLoading)
|
|
318
366
|
return (_jsx(PsSkeletonTable, { className: className, columns: visibleColumns.length, hasToolbar: hasToolbar, rows: PsTableConfig.skeletonRows }));
|
|
319
|
-
|
|
367
|
+
const clearSelection = () => {
|
|
368
|
+
setSelectedIds([]);
|
|
369
|
+
if (selectAllMatching !== undefined && selectAllMatching.isActive)
|
|
370
|
+
selectAllMatching.onDeactivate();
|
|
371
|
+
};
|
|
372
|
+
const banner = selectAllMatching !== undefined &&
|
|
373
|
+
selectedRows.length > 0 &&
|
|
374
|
+
selectAllMatching.total > pageIds.length &&
|
|
375
|
+
(isPageSelected || selectAllMatching.isActive) ? (_jsx(PsSelectAllMatchingBanner, { isActive: selectAllMatching.isActive, noun: selectAllMatching.noun, onActivate: selectAllMatching.onActivate, onDeactivate: clearSelection, pageCount: pageIds.length, total: selectAllMatching.total })) : null;
|
|
376
|
+
const topContent = (_jsxs(_Fragment, { children: [isSelectable && selectedRows.length > 0 && getBulkActions ? (_jsx(PsTableBulkBar, { actions: getBulkActions(selectedRows), onClear: clearSelection, selectedCount: selectAllMatching !== undefined && selectAllMatching.isActive
|
|
377
|
+
? selectAllMatching.total
|
|
378
|
+
: selectedRows.length })) : hasToolbar || columns.some((column) => column.canHide) ? (_jsx(PsTableToolbar, { actions: _jsxs(_Fragment, { children: [toolbarActions, columns.some((column) => column.canHide) ? (_jsx(PsTableColumnPicker, { columns: columns
|
|
320
379
|
.filter((column) => column.canHide)
|
|
321
380
|
.map((column) => ({ id: column.id, label: column.label })), hiddenColumnIds: hiddenColumnIds, onChange: (nextHidden) => updateQuery({ hiddenColumnIds: nextHidden }) })) : null] }), columns: hasFilters ? columns : [], filters: filters, isSearchable: isSearchable, onClearFilters: clearFilters, onFilterChange: (columnId, value) => {
|
|
322
381
|
updateQuery({ filters: { ...filters, [columnId]: value } });
|
|
@@ -324,16 +383,23 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
|
|
|
324
383
|
}, onSearchChange: (next) => {
|
|
325
384
|
updateQuery({ search: next });
|
|
326
385
|
resetPage();
|
|
327
|
-
}, rows: rows, search: search, searchPlaceholder: searchPlaceholder })) : null,
|
|
386
|
+
}, rows: rows, search: search, searchPlaceholder: searchPlaceholder })) : null, banner] }));
|
|
387
|
+
return (_jsxs("div", { ref: containerRef, className: PallasoftClassNameService.merge('flex min-w-0 flex-col gap-3', className), children: [stickyTop === undefined ? (topContent) : (_jsxs(_Fragment, { children: [_jsx("div", { "aria-hidden": 'true', className: '-mb-3 h-0', ref: stickySentinelRef }), _jsx("div", { className: PallasoftClassNameService.merge('sticky z-sticky -mx-1 flex flex-col gap-3 bg-canvas px-1 pb-2 pt-1 transition-shadow', isStuck ? 'shadow-[0_8px_8px_-8px_rgb(var(--ps-color-ink-rgb,15_23_42)/0.18)]' : ''), ref: stickyRegionRef, style: { top: stickyTop }, children: topContent })] })), _jsxs("div", { "aria-busy": isRefreshing, className: PallasoftClassNameService.merge('min-w-0 rounded-[1.25rem] border border-line bg-surface', stickyTop === undefined ? 'overflow-hidden' : 'overflow-clip'), children: [showsPagination && paginationPosition !== PsTablePaginationPosition.bottom
|
|
328
388
|
? renderPagination('border-b border-t-0')
|
|
329
|
-
: null, mobileLayout === 'cards' ? (_jsxs("div", { className: 'sm:hidden', children: [reorder && reorder.cardsReason ? (_jsx("p", { className: 'border-b border-line px-4 py-2 text-xs text-ink/55', children: reorder.cardsReason })) : null, grouping
|
|
389
|
+
: null, mobileLayout === 'cards' ? (_jsxs("div", { className: PallasoftClassNameService.merge('transition-opacity duration-150 sm:hidden', isRefreshing ? 'opacity-60' : ''), children: [reorder && reorder.cardsReason ? (_jsx("p", { className: 'border-b border-line px-4 py-2 text-xs text-ink/55', children: reorder.cardsReason })) : null, grouping
|
|
330
390
|
? groups.map((group, groupIndex) => {
|
|
331
391
|
const isExpanded = isGroupExpanded(group.key, groupIndex);
|
|
332
392
|
return (_jsxs("div", { className: 'border-b border-line last:border-b-0', children: [_jsx(PsTableGroupHeader, { count: group.rows.length, isExpanded: isExpanded, label: grouping.getGroupLabel(group.key, group.rows), onToggle: () => toggleGroup(group.key, isExpanded), summary: grouping.renderGroupSummary
|
|
333
393
|
? grouping.renderGroupSummary(group.rows)
|
|
334
394
|
: undefined }), _jsx(PsCollapse, { isOpen: isExpanded && !closingGroupKeys.includes(group.key), children: renderCardList(group.rows, null) })] }, `group-${group.key}`));
|
|
335
395
|
})
|
|
336
|
-
: 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(
|
|
396
|
+
: renderCardList(shouldVirtualize ? visibleRows.slice(0, cards.count) : visibleRows, visibleRows.length === 0 && isSearching ? (_jsx(PsTableSearchingState, { label: searchingLabel, skeletonRows: PsTableConfig.searchingSkeletonRows })) : visibleRows.length === 0 && isQueried ? (_jsxs("div", { className: 'px-4 py-10 text-center', children: [_jsx("p", { className: 'text-sm font-semibold text-ink', children: PsTableConfig.noMatchesTitle }), _jsx("button", { className: PallasoftClassNameService.merge(PsTableControlClasses.textButton, 'mt-2 text-primary'), onClick: clearFilters, type: 'button', children: "Clear search and filters" })] })) : null), !grouping && shouldVirtualize && cards.count < visibleRows.length ? (_jsx("div", { "aria-hidden": 'true', className: 'h-px', ref: cards.sentinelRef })) : null] })) : null, _jsx("div", { className: PallasoftClassNameService.merge(stickyTop === undefined
|
|
397
|
+
? 'ps-scrollbar overflow-x-auto overflow-y-hidden overscroll-x-contain'
|
|
398
|
+
: 'overflow-x-clip', 'transition-opacity duration-150', isRefreshing ? 'opacity-60' : '', mobileLayout === 'cards' ? 'hidden sm:block' : ''), "data-overflow-end": overflow.edges.end, "data-overflow-start": overflow.edges.start, onScroll: overflow.update, ref: overflow.ref, children: renderReorder((api) => (_jsxs("table", { className: PallasoftClassNameService.merge('w-full border-collapse text-left', layout === 'fixed' ? 'table-fixed' : ''), children: [caption ? _jsx("caption", { className: 'sr-only', children: caption }) : null, _jsx("thead", { className: PallasoftClassNameService.merge('bg-mist/70 text-[0.68rem] font-semibold uppercase tracking-[0.18em] text-ink/45', stickyTop === undefined
|
|
399
|
+
? ''
|
|
400
|
+
: 'sticky z-docked bg-surface [&_th]:border-b [&_th]:border-line [&>tr]:bg-mist/70'), style: stickyTop === undefined
|
|
401
|
+
? undefined
|
|
402
|
+
: { top: `calc(${stickyTop} + ${stickyHeight}px)` }, children: _jsxs("tr", { children: [reorder ? (_jsx("th", { className: PallasoftClassNameService.merge('w-9 min-w-9 pl-3 pr-0', pins.reorder), scope: 'col', children: reorder.enabled ? (_jsx("span", { className: 'sr-only', children: reorder.columnLabel })) : reorder.disabledReason ? (_jsx("span", { "aria-label": reorder.disabledReason, className: 'inline-flex cursor-help opacity-40', role: 'img', title: reorder.disabledReason, children: _jsx(PsDragGrip, {}) })) : null })) : null, isSelectable ? (_jsx("th", { className: PallasoftClassNameService.merge('w-10 min-w-10 pl-4 pr-0', pins.select), scope: 'col', children: _jsx(PsCheckbox, { isIndeterminate: !isPageSelected && pageIds.some((rowId) => selectedIdSet.has(rowId)), isSelected: isPageSelected, label: 'Select all rows on this page', onChange: togglePage }) })) : null, visibleColumns.map((column, columnIndex) => {
|
|
337
403
|
const align = column.align !== null && column.align !== undefined ? column.align : 'left';
|
|
338
404
|
const isSorted = sort !== null && sort.columnId === column.id;
|
|
339
405
|
return (_jsx("th", { "aria-sort": isSorted
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
3
|
+
import { PsTableControlClasses } from '../constants/ps-table-control-classes';
|
|
4
|
+
export const PsSelectAllMatchingBanner = ({ className, isActive, noun = 'rows', onActivate, onDeactivate, pageCount, total, }) => (_jsxs("div", { "aria-live": 'polite', className: PallasoftClassNameService.merge('flex flex-wrap items-center justify-center gap-x-2 gap-y-1 rounded-ps-control bg-primary/10 px-3 py-2 text-sm text-ink', className), role: 'status', children: [_jsx("span", { children: isActive
|
|
5
|
+
? `All ${total.toLocaleString()} ${noun} matching are selected.`
|
|
6
|
+
: `${pageCount.toLocaleString()} ${noun} on this page are selected.` }), _jsx("button", { className: PallasoftClassNameService.merge(PsTableControlClasses.textButton, 'text-primary'), onClick: isActive ? onDeactivate : onActivate, type: 'button', children: isActive ? 'Clear selection' : `Select all ${total.toLocaleString()} matching` })] }));
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
3
3
|
import { PsTableControlClasses } from '../constants/ps-table-control-classes';
|
|
4
|
-
export const PsTablePagination = ({ className, isPartial = false, loadedPageCount, onPageChange, page, pageCount, pageSize, totalRows, }) => {
|
|
4
|
+
export const PsTablePagination = ({ className, cursor, isPartial = false, loadedPageCount, onPageChange, page, pageCount, pageSize, totalRows, }) => {
|
|
5
5
|
const firstRow = totalRows === 0 ? 0 : (page - 1) * pageSize + 1;
|
|
6
6
|
const lastRow = Math.min(page * pageSize, totalRows);
|
|
7
|
+
const approximately = cursor !== undefined && cursor.isEstimate ? 'about ' : '';
|
|
8
|
+
const pageNumber = cursor === undefined ? Math.min(page, pageCount) : cursor.pageNumber;
|
|
9
|
+
const cursorFirst = cursor === undefined ? firstRow : (cursor.pageNumber - 1) * pageSize + 1;
|
|
10
|
+
const cursorLast = cursor === undefined ? lastRow : Math.min(cursor.pageNumber * pageSize, totalRows);
|
|
7
11
|
return (_jsxs("nav", { "aria-label": 'Pagination', className: PallasoftClassNameService.merge('flex flex-wrap items-center justify-between gap-3 border-t border-line px-4 py-3', className), children: [_jsx("p", { className: 'text-sm text-ink/55', children: totalRows === 0
|
|
8
12
|
? 'No rows'
|
|
9
|
-
:
|
|
10
|
-
|
|
13
|
+
: cursor === undefined
|
|
14
|
+
? `${firstRow}–${lastRow} of ${totalRows}${isPartial ? '+' : ''}`
|
|
15
|
+
: `${cursorFirst}–${cursorLast} of ${approximately}${totalRows.toLocaleString()}` }), _jsxs("div", { className: 'flex items-center gap-2', children: [cursor !== undefined && cursor.pageSizeOptions !== undefined && cursor.onPageSizeChange ? (_jsxs("label", { className: 'flex items-center gap-2 text-sm text-ink/65', children: [_jsx("span", { className: 'hidden sm:inline', children: "Rows per page" }), _jsx("select", { "aria-label": 'Rows per page', className: 'ps-input-control ps-focus-ring ps-control ps-control-padding rounded-ps-control text-sm', onChange: (event) => cursor.onPageSizeChange?.(Number(event.target.value)), value: pageSize, children: cursor.pageSizeOptions.map((option) => (_jsx("option", { value: option, children: option }, option))) })] })) : null, _jsx("button", { className: PsTableControlClasses.paginationButton, disabled: cursor === undefined ? page <= 1 : !cursor.hasPrevious, onClick: () => (cursor === undefined ? onPageChange(page - 1) : cursor.onPrevious()), type: 'button', children: "Previous" }), _jsxs("span", { className: 'hidden text-sm text-ink/65 sm:inline', children: ["Page ", pageNumber, " of ", approximately, pageCount] }), _jsx("button", { className: PsTableControlClasses.paginationButton, disabled: cursor === undefined
|
|
16
|
+
? page >=
|
|
17
|
+
(loadedPageCount === undefined ? pageCount : Math.min(loadedPageCount, pageCount))
|
|
18
|
+
: !cursor.hasNext, onClick: () => (cursor === undefined ? onPageChange(page + 1) : cursor.onNext()), onFocus: cursor === undefined ? undefined : cursor.onNextIntent, onPointerEnter: cursor === undefined ? undefined : cursor.onNextIntent, type: 'button', children: "Next" })] })] }));
|
|
11
19
|
};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { PsServerRowsConfig } from '../constants/ps-server-rows-config';
|
|
3
|
+
import { PsServerRowsService } from '../services/ps-server-rows-service';
|
|
4
|
+
const scheduleIdle = (run) => {
|
|
5
|
+
if (typeof window.requestIdleCallback === 'function') {
|
|
6
|
+
const handle = window.requestIdleCallback(run, { timeout: PsServerRowsConfig.idleTimeoutMs });
|
|
7
|
+
return () => window.cancelIdleCallback(handle);
|
|
8
|
+
}
|
|
9
|
+
const timer = window.setTimeout(run, PsServerRowsConfig.idleFallbackMs);
|
|
10
|
+
return () => window.clearTimeout(timer);
|
|
11
|
+
};
|
|
12
|
+
export const usePsServerRows = ({ columns, debounceMs = PsServerRowsConfig.debounceMs, getSearchText, initialPage = null, initialQuery, isPrefetching = true, load, pageSize, }) => {
|
|
13
|
+
const [query, setQuery] = useState(initialQuery);
|
|
14
|
+
const [search, setSearch] = useState(initialQuery.search);
|
|
15
|
+
const [reloadToken, setReloadToken] = useState(0);
|
|
16
|
+
const [isLoading, setIsLoading] = useState(initialPage === null);
|
|
17
|
+
const [error, setError] = useState(null);
|
|
18
|
+
const queryKey = PsServerRowsService.queryKey(query, search, pageSize);
|
|
19
|
+
const [navigation, setNavigation] = useState({
|
|
20
|
+
cursor: null,
|
|
21
|
+
pageNumber: 1,
|
|
22
|
+
queryKey,
|
|
23
|
+
});
|
|
24
|
+
const [entry, setEntry] = useState(() => initialPage === null
|
|
25
|
+
? null
|
|
26
|
+
: {
|
|
27
|
+
key: PsServerRowsService.cacheKey(queryKey, null),
|
|
28
|
+
page: initialPage,
|
|
29
|
+
queryKey,
|
|
30
|
+
});
|
|
31
|
+
const cache = useRef(new Map());
|
|
32
|
+
const inflight = useRef(new Map());
|
|
33
|
+
const loadRef = useRef(load);
|
|
34
|
+
const limitRef = useRef(pageSize);
|
|
35
|
+
const queryRef = useRef(query);
|
|
36
|
+
const searchRef = useRef(search);
|
|
37
|
+
const isPrimed = useRef(false);
|
|
38
|
+
loadRef.current = load;
|
|
39
|
+
limitRef.current = pageSize;
|
|
40
|
+
queryRef.current = query;
|
|
41
|
+
searchRef.current = search;
|
|
42
|
+
if (initialPage !== null && !isPrimed.current) {
|
|
43
|
+
isPrimed.current = true;
|
|
44
|
+
PsServerRowsService.remember(cache.current, PsServerRowsService.cacheKey(queryKey, null), initialPage);
|
|
45
|
+
}
|
|
46
|
+
if (navigation.queryKey !== queryKey)
|
|
47
|
+
setNavigation({ cursor: null, pageNumber: 1, queryKey });
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (query.search === search)
|
|
50
|
+
return;
|
|
51
|
+
const timer = window.setTimeout(() => setSearch(query.search), debounceMs);
|
|
52
|
+
return () => window.clearTimeout(timer);
|
|
53
|
+
}, [debounceMs, query.search, search]);
|
|
54
|
+
const fetchPage = useCallback((cursor, signal, key) => {
|
|
55
|
+
const pending = inflight.current.get(key);
|
|
56
|
+
if (pending !== undefined)
|
|
57
|
+
return pending;
|
|
58
|
+
const request = loadRef.current({ ...queryRef.current, search: searchRef.current }, cursor, signal, limitRef.current);
|
|
59
|
+
const tracked = request.then((page) => {
|
|
60
|
+
inflight.current.delete(key);
|
|
61
|
+
if (!signal.aborted)
|
|
62
|
+
PsServerRowsService.remember(cache.current, key, page);
|
|
63
|
+
return page;
|
|
64
|
+
}, (failure) => {
|
|
65
|
+
inflight.current.delete(key);
|
|
66
|
+
throw failure;
|
|
67
|
+
});
|
|
68
|
+
inflight.current.set(key, tracked);
|
|
69
|
+
return tracked;
|
|
70
|
+
}, []);
|
|
71
|
+
const generation = useRef(null);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const controller = new AbortController();
|
|
74
|
+
generation.current = controller;
|
|
75
|
+
return () => {
|
|
76
|
+
controller.abort();
|
|
77
|
+
inflight.current.clear();
|
|
78
|
+
};
|
|
79
|
+
}, [queryKey, reloadToken]);
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
const controller = generation.current;
|
|
82
|
+
if (controller === null)
|
|
83
|
+
return;
|
|
84
|
+
const key = PsServerRowsService.cacheKey(queryKey, navigation.cursor);
|
|
85
|
+
const cached = PsServerRowsService.recall(cache.current, key);
|
|
86
|
+
let isStale = false;
|
|
87
|
+
if (cached !== undefined) {
|
|
88
|
+
setEntry({ key, page: cached, queryKey });
|
|
89
|
+
setIsLoading(false);
|
|
90
|
+
setError(null);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
setIsLoading(true);
|
|
94
|
+
setError(null);
|
|
95
|
+
fetchPage(navigation.cursor, controller.signal, key).then((page) => {
|
|
96
|
+
if (isStale || controller.signal.aborted)
|
|
97
|
+
return;
|
|
98
|
+
setEntry({ key, page, queryKey });
|
|
99
|
+
setIsLoading(false);
|
|
100
|
+
}, (failure) => {
|
|
101
|
+
if (isStale || controller.signal.aborted)
|
|
102
|
+
return;
|
|
103
|
+
setError(failure);
|
|
104
|
+
setIsLoading(false);
|
|
105
|
+
});
|
|
106
|
+
return () => {
|
|
107
|
+
isStale = true;
|
|
108
|
+
};
|
|
109
|
+
}, [fetchPage, navigation.cursor, queryKey, reloadToken]);
|
|
110
|
+
const nextCursor = entry !== null && entry.queryKey === queryKey ? entry.page.nextCursor : null;
|
|
111
|
+
const prefetch = useCallback(() => {
|
|
112
|
+
const controller = generation.current;
|
|
113
|
+
if (controller === null || controller.signal.aborted || nextCursor === null)
|
|
114
|
+
return;
|
|
115
|
+
const key = PsServerRowsService.cacheKey(queryKey, nextCursor);
|
|
116
|
+
if (cache.current.has(key) || inflight.current.has(key))
|
|
117
|
+
return;
|
|
118
|
+
fetchPage(nextCursor, controller.signal, key).catch(() => undefined);
|
|
119
|
+
}, [fetchPage, nextCursor, queryKey]);
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (!isPrefetching || isLoading || nextCursor === null)
|
|
122
|
+
return;
|
|
123
|
+
return scheduleIdle(prefetch);
|
|
124
|
+
}, [isLoading, isPrefetching, nextCursor, prefetch]);
|
|
125
|
+
const onQueryChange = useCallback((next) => setQuery(next), []);
|
|
126
|
+
const goNext = useCallback(() => {
|
|
127
|
+
if (nextCursor === null)
|
|
128
|
+
return;
|
|
129
|
+
setNavigation((current) => ({
|
|
130
|
+
...current,
|
|
131
|
+
cursor: nextCursor,
|
|
132
|
+
pageNumber: current.pageNumber + 1,
|
|
133
|
+
}));
|
|
134
|
+
}, [nextCursor]);
|
|
135
|
+
const previousCursor = entry !== null && entry.queryKey === queryKey ? entry.page.prevCursor : null;
|
|
136
|
+
const goPrevious = useCallback(() => {
|
|
137
|
+
if (previousCursor === null)
|
|
138
|
+
return;
|
|
139
|
+
setNavigation((current) => ({
|
|
140
|
+
...current,
|
|
141
|
+
cursor: previousCursor,
|
|
142
|
+
pageNumber: Math.max(current.pageNumber - 1, 1),
|
|
143
|
+
}));
|
|
144
|
+
}, [previousCursor]);
|
|
145
|
+
const reload = useCallback(() => {
|
|
146
|
+
cache.current.clear();
|
|
147
|
+
setNavigation((current) => ({ ...current, cursor: null, pageNumber: 1 }));
|
|
148
|
+
setReloadToken((current) => current + 1);
|
|
149
|
+
}, []);
|
|
150
|
+
const retry = useCallback(() => setReloadToken((current) => current + 1), []);
|
|
151
|
+
const updateRows = useCallback((update, totalDelta = 0) => {
|
|
152
|
+
setEntry((current) => {
|
|
153
|
+
if (current === null)
|
|
154
|
+
return current;
|
|
155
|
+
const page = {
|
|
156
|
+
...current.page,
|
|
157
|
+
rows: update(current.page.rows),
|
|
158
|
+
total: Math.max(current.page.total + totalDelta, 0),
|
|
159
|
+
};
|
|
160
|
+
cache.current.clear();
|
|
161
|
+
PsServerRowsService.remember(cache.current, current.key, page);
|
|
162
|
+
return { ...current, page };
|
|
163
|
+
});
|
|
164
|
+
}, []);
|
|
165
|
+
const isCurrent = entry !== null &&
|
|
166
|
+
entry.queryKey === queryKey &&
|
|
167
|
+
entry.key === PsServerRowsService.cacheKey(queryKey, navigation.cursor);
|
|
168
|
+
const baseRows = useMemo(() => (entry === null ? [] : entry.page.rows), [entry]);
|
|
169
|
+
const isSameQuery = entry !== null && entry.queryKey === queryKey;
|
|
170
|
+
const rows = useMemo(() => isSameQuery ? baseRows : PsServerRowsService.preview(baseRows, columns, query, getSearchText), [baseRows, columns, getSearchText, isSameQuery, query]);
|
|
171
|
+
const total = entry === null ? 0 : entry.page.total;
|
|
172
|
+
const totalIsEstimate = entry === null ? false : entry.page.totalIsEstimate;
|
|
173
|
+
const isPending = isLoading || query.search !== search || !isCurrent;
|
|
174
|
+
const cursorPaging = useMemo(() => ({
|
|
175
|
+
hasNext: nextCursor !== null,
|
|
176
|
+
hasPrevious: previousCursor !== null,
|
|
177
|
+
isEstimate: totalIsEstimate,
|
|
178
|
+
isPending,
|
|
179
|
+
onNext: goNext,
|
|
180
|
+
onNextIntent: prefetch,
|
|
181
|
+
onPrevious: goPrevious,
|
|
182
|
+
pageNumber: navigation.pageNumber,
|
|
183
|
+
}), [
|
|
184
|
+
goNext,
|
|
185
|
+
goPrevious,
|
|
186
|
+
isPending,
|
|
187
|
+
navigation.pageNumber,
|
|
188
|
+
nextCursor,
|
|
189
|
+
prefetch,
|
|
190
|
+
previousCursor,
|
|
191
|
+
totalIsEstimate,
|
|
192
|
+
]);
|
|
193
|
+
return {
|
|
194
|
+
cursorPaging,
|
|
195
|
+
error,
|
|
196
|
+
hasFailed: error !== null,
|
|
197
|
+
isPending,
|
|
198
|
+
onQueryChange,
|
|
199
|
+
pageCount: PsServerRowsService.pageCount(total, pageSize),
|
|
200
|
+
pageNumber: navigation.pageNumber,
|
|
201
|
+
query,
|
|
202
|
+
reload,
|
|
203
|
+
retry,
|
|
204
|
+
rows,
|
|
205
|
+
total,
|
|
206
|
+
totalIsEstimate,
|
|
207
|
+
updateRows,
|
|
208
|
+
};
|
|
209
|
+
};
|
package/dist/esm/index.js
CHANGED
|
@@ -49,3 +49,13 @@ export * from './constants/ps-table-drop-placement';
|
|
|
49
49
|
export * from './services/ps-table-reorder-service';
|
|
50
50
|
export * from './constants/ps-table-pagination-position';
|
|
51
51
|
export * from './interfaces/table-drag-size';
|
|
52
|
+
export * from './components/ps-select-all-matching-banner';
|
|
53
|
+
export * from './constants/ps-server-rows-config';
|
|
54
|
+
export * from './hooks/use-ps-server-rows';
|
|
55
|
+
export * from './interfaces/ps-server-entry';
|
|
56
|
+
export * from './interfaces/ps-server-page';
|
|
57
|
+
export * from './interfaces/ps-server-rows-input';
|
|
58
|
+
export * from './interfaces/ps-server-rows-result';
|
|
59
|
+
export * from './interfaces/ps-table-cursor-paging';
|
|
60
|
+
export * from './interfaces/ps-table-select-all-matching';
|
|
61
|
+
export * from './services/ps-server-rows-service';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { PsServerRowsConfig } from '../constants/ps-server-rows-config';
|
|
2
|
+
import { PsTableQueryService } from './ps-table-query-service';
|
|
3
|
+
export class PsServerRowsService {
|
|
4
|
+
static queryKey(query, search, pageSize) {
|
|
5
|
+
const filters = Object.entries(query.filters)
|
|
6
|
+
.filter(([, value]) => PsTableQueryService.isActive(value))
|
|
7
|
+
.sort(([left], [right]) => left.localeCompare(right));
|
|
8
|
+
return JSON.stringify([filters, search.trim(), query.sort, pageSize]);
|
|
9
|
+
}
|
|
10
|
+
static cacheKey(queryKey, cursor) {
|
|
11
|
+
return `${queryKey}|${cursor === null ? '' : cursor}`;
|
|
12
|
+
}
|
|
13
|
+
static remember(cache, key, page) {
|
|
14
|
+
cache.delete(key);
|
|
15
|
+
cache.set(key, page);
|
|
16
|
+
for (const oldest of cache.keys()) {
|
|
17
|
+
if (cache.size <= PsServerRowsConfig.cacheSize)
|
|
18
|
+
return;
|
|
19
|
+
cache.delete(oldest);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
static recall(cache, key) {
|
|
23
|
+
const page = cache.get(key);
|
|
24
|
+
if (page === undefined)
|
|
25
|
+
return undefined;
|
|
26
|
+
cache.delete(key);
|
|
27
|
+
cache.set(key, page);
|
|
28
|
+
return page;
|
|
29
|
+
}
|
|
30
|
+
static preview(rows, columns, query, getSearchText) {
|
|
31
|
+
return PsTableQueryService.apply(rows, columns, query.filters, query.search, getSearchText, query.sort);
|
|
32
|
+
}
|
|
33
|
+
static pageCount(total, pageSize) {
|
|
34
|
+
return Math.max(Math.ceil(total / pageSize), 1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { PsServerRowsInput } from '../interfaces/ps-server-rows-input';
|
|
2
|
+
import type { PsServerRowsResult } from '../interfaces/ps-server-rows-result';
|
|
3
|
+
export declare const usePsServerRows: <RowData>({ columns, debounceMs, getSearchText, initialPage, initialQuery, isPrefetching, load, pageSize, }: PsServerRowsInput<RowData>) => PsServerRowsResult<RowData>;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.usePsServerRows = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const ps_server_rows_config_1 = require("../constants/ps-server-rows-config");
|
|
6
|
+
const ps_server_rows_service_1 = require("../services/ps-server-rows-service");
|
|
7
|
+
const scheduleIdle = (run) => {
|
|
8
|
+
if (typeof window.requestIdleCallback === 'function') {
|
|
9
|
+
const handle = window.requestIdleCallback(run, { timeout: ps_server_rows_config_1.PsServerRowsConfig.idleTimeoutMs });
|
|
10
|
+
return () => window.cancelIdleCallback(handle);
|
|
11
|
+
}
|
|
12
|
+
const timer = window.setTimeout(run, ps_server_rows_config_1.PsServerRowsConfig.idleFallbackMs);
|
|
13
|
+
return () => window.clearTimeout(timer);
|
|
14
|
+
};
|
|
15
|
+
const usePsServerRows = ({ columns, debounceMs = ps_server_rows_config_1.PsServerRowsConfig.debounceMs, getSearchText, initialPage = null, initialQuery, isPrefetching = true, load, pageSize, }) => {
|
|
16
|
+
const [query, setQuery] = (0, react_1.useState)(initialQuery);
|
|
17
|
+
const [search, setSearch] = (0, react_1.useState)(initialQuery.search);
|
|
18
|
+
const [reloadToken, setReloadToken] = (0, react_1.useState)(0);
|
|
19
|
+
const [isLoading, setIsLoading] = (0, react_1.useState)(initialPage === null);
|
|
20
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
21
|
+
const queryKey = ps_server_rows_service_1.PsServerRowsService.queryKey(query, search, pageSize);
|
|
22
|
+
const [navigation, setNavigation] = (0, react_1.useState)({
|
|
23
|
+
cursor: null,
|
|
24
|
+
pageNumber: 1,
|
|
25
|
+
queryKey,
|
|
26
|
+
});
|
|
27
|
+
const [entry, setEntry] = (0, react_1.useState)(() => initialPage === null
|
|
28
|
+
? null
|
|
29
|
+
: {
|
|
30
|
+
key: ps_server_rows_service_1.PsServerRowsService.cacheKey(queryKey, null),
|
|
31
|
+
page: initialPage,
|
|
32
|
+
queryKey,
|
|
33
|
+
});
|
|
34
|
+
const cache = (0, react_1.useRef)(new Map());
|
|
35
|
+
const inflight = (0, react_1.useRef)(new Map());
|
|
36
|
+
const loadRef = (0, react_1.useRef)(load);
|
|
37
|
+
const limitRef = (0, react_1.useRef)(pageSize);
|
|
38
|
+
const queryRef = (0, react_1.useRef)(query);
|
|
39
|
+
const searchRef = (0, react_1.useRef)(search);
|
|
40
|
+
const isPrimed = (0, react_1.useRef)(false);
|
|
41
|
+
loadRef.current = load;
|
|
42
|
+
limitRef.current = pageSize;
|
|
43
|
+
queryRef.current = query;
|
|
44
|
+
searchRef.current = search;
|
|
45
|
+
if (initialPage !== null && !isPrimed.current) {
|
|
46
|
+
isPrimed.current = true;
|
|
47
|
+
ps_server_rows_service_1.PsServerRowsService.remember(cache.current, ps_server_rows_service_1.PsServerRowsService.cacheKey(queryKey, null), initialPage);
|
|
48
|
+
}
|
|
49
|
+
if (navigation.queryKey !== queryKey)
|
|
50
|
+
setNavigation({ cursor: null, pageNumber: 1, queryKey });
|
|
51
|
+
(0, react_1.useEffect)(() => {
|
|
52
|
+
if (query.search === search)
|
|
53
|
+
return;
|
|
54
|
+
const timer = window.setTimeout(() => setSearch(query.search), debounceMs);
|
|
55
|
+
return () => window.clearTimeout(timer);
|
|
56
|
+
}, [debounceMs, query.search, search]);
|
|
57
|
+
const fetchPage = (0, react_1.useCallback)((cursor, signal, key) => {
|
|
58
|
+
const pending = inflight.current.get(key);
|
|
59
|
+
if (pending !== undefined)
|
|
60
|
+
return pending;
|
|
61
|
+
const request = loadRef.current({ ...queryRef.current, search: searchRef.current }, cursor, signal, limitRef.current);
|
|
62
|
+
const tracked = request.then((page) => {
|
|
63
|
+
inflight.current.delete(key);
|
|
64
|
+
if (!signal.aborted)
|
|
65
|
+
ps_server_rows_service_1.PsServerRowsService.remember(cache.current, key, page);
|
|
66
|
+
return page;
|
|
67
|
+
}, (failure) => {
|
|
68
|
+
inflight.current.delete(key);
|
|
69
|
+
throw failure;
|
|
70
|
+
});
|
|
71
|
+
inflight.current.set(key, tracked);
|
|
72
|
+
return tracked;
|
|
73
|
+
}, []);
|
|
74
|
+
const generation = (0, react_1.useRef)(null);
|
|
75
|
+
(0, react_1.useEffect)(() => {
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
generation.current = controller;
|
|
78
|
+
return () => {
|
|
79
|
+
controller.abort();
|
|
80
|
+
inflight.current.clear();
|
|
81
|
+
};
|
|
82
|
+
}, [queryKey, reloadToken]);
|
|
83
|
+
(0, react_1.useEffect)(() => {
|
|
84
|
+
const controller = generation.current;
|
|
85
|
+
if (controller === null)
|
|
86
|
+
return;
|
|
87
|
+
const key = ps_server_rows_service_1.PsServerRowsService.cacheKey(queryKey, navigation.cursor);
|
|
88
|
+
const cached = ps_server_rows_service_1.PsServerRowsService.recall(cache.current, key);
|
|
89
|
+
let isStale = false;
|
|
90
|
+
if (cached !== undefined) {
|
|
91
|
+
setEntry({ key, page: cached, queryKey });
|
|
92
|
+
setIsLoading(false);
|
|
93
|
+
setError(null);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
setIsLoading(true);
|
|
97
|
+
setError(null);
|
|
98
|
+
fetchPage(navigation.cursor, controller.signal, key).then((page) => {
|
|
99
|
+
if (isStale || controller.signal.aborted)
|
|
100
|
+
return;
|
|
101
|
+
setEntry({ key, page, queryKey });
|
|
102
|
+
setIsLoading(false);
|
|
103
|
+
}, (failure) => {
|
|
104
|
+
if (isStale || controller.signal.aborted)
|
|
105
|
+
return;
|
|
106
|
+
setError(failure);
|
|
107
|
+
setIsLoading(false);
|
|
108
|
+
});
|
|
109
|
+
return () => {
|
|
110
|
+
isStale = true;
|
|
111
|
+
};
|
|
112
|
+
}, [fetchPage, navigation.cursor, queryKey, reloadToken]);
|
|
113
|
+
const nextCursor = entry !== null && entry.queryKey === queryKey ? entry.page.nextCursor : null;
|
|
114
|
+
const prefetch = (0, react_1.useCallback)(() => {
|
|
115
|
+
const controller = generation.current;
|
|
116
|
+
if (controller === null || controller.signal.aborted || nextCursor === null)
|
|
117
|
+
return;
|
|
118
|
+
const key = ps_server_rows_service_1.PsServerRowsService.cacheKey(queryKey, nextCursor);
|
|
119
|
+
if (cache.current.has(key) || inflight.current.has(key))
|
|
120
|
+
return;
|
|
121
|
+
fetchPage(nextCursor, controller.signal, key).catch(() => undefined);
|
|
122
|
+
}, [fetchPage, nextCursor, queryKey]);
|
|
123
|
+
(0, react_1.useEffect)(() => {
|
|
124
|
+
if (!isPrefetching || isLoading || nextCursor === null)
|
|
125
|
+
return;
|
|
126
|
+
return scheduleIdle(prefetch);
|
|
127
|
+
}, [isLoading, isPrefetching, nextCursor, prefetch]);
|
|
128
|
+
const onQueryChange = (0, react_1.useCallback)((next) => setQuery(next), []);
|
|
129
|
+
const goNext = (0, react_1.useCallback)(() => {
|
|
130
|
+
if (nextCursor === null)
|
|
131
|
+
return;
|
|
132
|
+
setNavigation((current) => ({
|
|
133
|
+
...current,
|
|
134
|
+
cursor: nextCursor,
|
|
135
|
+
pageNumber: current.pageNumber + 1,
|
|
136
|
+
}));
|
|
137
|
+
}, [nextCursor]);
|
|
138
|
+
const previousCursor = entry !== null && entry.queryKey === queryKey ? entry.page.prevCursor : null;
|
|
139
|
+
const goPrevious = (0, react_1.useCallback)(() => {
|
|
140
|
+
if (previousCursor === null)
|
|
141
|
+
return;
|
|
142
|
+
setNavigation((current) => ({
|
|
143
|
+
...current,
|
|
144
|
+
cursor: previousCursor,
|
|
145
|
+
pageNumber: Math.max(current.pageNumber - 1, 1),
|
|
146
|
+
}));
|
|
147
|
+
}, [previousCursor]);
|
|
148
|
+
const reload = (0, react_1.useCallback)(() => {
|
|
149
|
+
cache.current.clear();
|
|
150
|
+
setNavigation((current) => ({ ...current, cursor: null, pageNumber: 1 }));
|
|
151
|
+
setReloadToken((current) => current + 1);
|
|
152
|
+
}, []);
|
|
153
|
+
const retry = (0, react_1.useCallback)(() => setReloadToken((current) => current + 1), []);
|
|
154
|
+
const updateRows = (0, react_1.useCallback)((update, totalDelta = 0) => {
|
|
155
|
+
setEntry((current) => {
|
|
156
|
+
if (current === null)
|
|
157
|
+
return current;
|
|
158
|
+
const page = {
|
|
159
|
+
...current.page,
|
|
160
|
+
rows: update(current.page.rows),
|
|
161
|
+
total: Math.max(current.page.total + totalDelta, 0),
|
|
162
|
+
};
|
|
163
|
+
cache.current.clear();
|
|
164
|
+
ps_server_rows_service_1.PsServerRowsService.remember(cache.current, current.key, page);
|
|
165
|
+
return { ...current, page };
|
|
166
|
+
});
|
|
167
|
+
}, []);
|
|
168
|
+
const isCurrent = entry !== null &&
|
|
169
|
+
entry.queryKey === queryKey &&
|
|
170
|
+
entry.key === ps_server_rows_service_1.PsServerRowsService.cacheKey(queryKey, navigation.cursor);
|
|
171
|
+
const baseRows = (0, react_1.useMemo)(() => (entry === null ? [] : entry.page.rows), [entry]);
|
|
172
|
+
const isSameQuery = entry !== null && entry.queryKey === queryKey;
|
|
173
|
+
const rows = (0, react_1.useMemo)(() => isSameQuery ? baseRows : ps_server_rows_service_1.PsServerRowsService.preview(baseRows, columns, query, getSearchText), [baseRows, columns, getSearchText, isSameQuery, query]);
|
|
174
|
+
const total = entry === null ? 0 : entry.page.total;
|
|
175
|
+
const totalIsEstimate = entry === null ? false : entry.page.totalIsEstimate;
|
|
176
|
+
const isPending = isLoading || query.search !== search || !isCurrent;
|
|
177
|
+
const cursorPaging = (0, react_1.useMemo)(() => ({
|
|
178
|
+
hasNext: nextCursor !== null,
|
|
179
|
+
hasPrevious: previousCursor !== null,
|
|
180
|
+
isEstimate: totalIsEstimate,
|
|
181
|
+
isPending,
|
|
182
|
+
onNext: goNext,
|
|
183
|
+
onNextIntent: prefetch,
|
|
184
|
+
onPrevious: goPrevious,
|
|
185
|
+
pageNumber: navigation.pageNumber,
|
|
186
|
+
}), [
|
|
187
|
+
goNext,
|
|
188
|
+
goPrevious,
|
|
189
|
+
isPending,
|
|
190
|
+
navigation.pageNumber,
|
|
191
|
+
nextCursor,
|
|
192
|
+
prefetch,
|
|
193
|
+
previousCursor,
|
|
194
|
+
totalIsEstimate,
|
|
195
|
+
]);
|
|
196
|
+
return {
|
|
197
|
+
cursorPaging,
|
|
198
|
+
error,
|
|
199
|
+
hasFailed: error !== null,
|
|
200
|
+
isPending,
|
|
201
|
+
onQueryChange,
|
|
202
|
+
pageCount: ps_server_rows_service_1.PsServerRowsService.pageCount(total, pageSize),
|
|
203
|
+
pageNumber: navigation.pageNumber,
|
|
204
|
+
query,
|
|
205
|
+
reload,
|
|
206
|
+
retry,
|
|
207
|
+
rows,
|
|
208
|
+
total,
|
|
209
|
+
totalIsEstimate,
|
|
210
|
+
updateRows,
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
exports.usePsServerRows = usePsServerRows;
|
package/dist/index.d.ts
CHANGED
|
@@ -49,3 +49,13 @@ export * from './constants/ps-table-drop-placement';
|
|
|
49
49
|
export * from './services/ps-table-reorder-service';
|
|
50
50
|
export * from './constants/ps-table-pagination-position';
|
|
51
51
|
export * from './interfaces/table-drag-size';
|
|
52
|
+
export * from './components/ps-select-all-matching-banner';
|
|
53
|
+
export * from './constants/ps-server-rows-config';
|
|
54
|
+
export * from './hooks/use-ps-server-rows';
|
|
55
|
+
export * from './interfaces/ps-server-entry';
|
|
56
|
+
export * from './interfaces/ps-server-page';
|
|
57
|
+
export * from './interfaces/ps-server-rows-input';
|
|
58
|
+
export * from './interfaces/ps-server-rows-result';
|
|
59
|
+
export * from './interfaces/ps-table-cursor-paging';
|
|
60
|
+
export * from './interfaces/ps-table-select-all-matching';
|
|
61
|
+
export * from './services/ps-server-rows-service';
|
package/dist/index.js
CHANGED
|
@@ -65,3 +65,13 @@ __exportStar(require("./constants/ps-table-drop-placement"), exports);
|
|
|
65
65
|
__exportStar(require("./services/ps-table-reorder-service"), exports);
|
|
66
66
|
__exportStar(require("./constants/ps-table-pagination-position"), exports);
|
|
67
67
|
__exportStar(require("./interfaces/table-drag-size"), exports);
|
|
68
|
+
__exportStar(require("./components/ps-select-all-matching-banner"), exports);
|
|
69
|
+
__exportStar(require("./constants/ps-server-rows-config"), exports);
|
|
70
|
+
__exportStar(require("./hooks/use-ps-server-rows"), exports);
|
|
71
|
+
__exportStar(require("./interfaces/ps-server-entry"), exports);
|
|
72
|
+
__exportStar(require("./interfaces/ps-server-page"), exports);
|
|
73
|
+
__exportStar(require("./interfaces/ps-server-rows-input"), exports);
|
|
74
|
+
__exportStar(require("./interfaces/ps-server-rows-result"), exports);
|
|
75
|
+
__exportStar(require("./interfaces/ps-table-cursor-paging"), exports);
|
|
76
|
+
__exportStar(require("./interfaces/ps-table-select-all-matching"), exports);
|
|
77
|
+
__exportStar(require("./services/ps-server-rows-service"), exports);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PsServerPage } from './ps-server-page';
|
|
2
|
+
import type { PsTableColumn } from './table-column';
|
|
3
|
+
import type { PsTableQuery } from './table-query';
|
|
4
|
+
export interface PsServerRowsInput<RowData> {
|
|
5
|
+
columns: PsTableColumn<RowData>[];
|
|
6
|
+
debounceMs?: number;
|
|
7
|
+
getSearchText?: (row: RowData) => string;
|
|
8
|
+
initialPage?: PsServerPage<RowData> | null;
|
|
9
|
+
initialQuery: PsTableQuery;
|
|
10
|
+
isPrefetching?: boolean;
|
|
11
|
+
load: (query: PsTableQuery, cursor: string | null, signal: AbortSignal, limit: number) => Promise<PsServerPage<RowData>>;
|
|
12
|
+
pageSize: number;
|
|
13
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PsTableCursorPaging } from './ps-table-cursor-paging';
|
|
2
|
+
import type { PsTableQuery } from './table-query';
|
|
3
|
+
export interface PsServerRowsResult<RowData> {
|
|
4
|
+
cursorPaging: PsTableCursorPaging;
|
|
5
|
+
error: unknown;
|
|
6
|
+
hasFailed: boolean;
|
|
7
|
+
isPending: boolean;
|
|
8
|
+
onQueryChange: (query: PsTableQuery) => void;
|
|
9
|
+
pageCount: number;
|
|
10
|
+
pageNumber: number;
|
|
11
|
+
query: PsTableQuery;
|
|
12
|
+
reload: () => void;
|
|
13
|
+
retry: () => void;
|
|
14
|
+
rows: RowData[];
|
|
15
|
+
total: number;
|
|
16
|
+
totalIsEstimate: boolean;
|
|
17
|
+
updateRows: (update: (rows: RowData[]) => RowData[], totalDelta?: number) => void;
|
|
18
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface PsTableCursorPaging {
|
|
2
|
+
hasNext: boolean;
|
|
3
|
+
hasPrevious: boolean;
|
|
4
|
+
isEstimate: boolean;
|
|
5
|
+
isPending?: boolean;
|
|
6
|
+
onNext: () => void;
|
|
7
|
+
onNextIntent?: () => void;
|
|
8
|
+
onPageSizeChange?: (pageSize: number) => void;
|
|
9
|
+
onPrevious: () => void;
|
|
10
|
+
pageNumber: number;
|
|
11
|
+
pageSizeOptions?: number[];
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PsServerPage } from '../interfaces/ps-server-page';
|
|
2
|
+
import type { PsTableColumn } from '../interfaces/table-column';
|
|
3
|
+
import type { PsTableQuery } from '../interfaces/table-query';
|
|
4
|
+
export declare class PsServerRowsService {
|
|
5
|
+
static queryKey(query: PsTableQuery, search: string, pageSize: number): string;
|
|
6
|
+
static cacheKey(queryKey: string, cursor: string | null): string;
|
|
7
|
+
static remember<RowData>(cache: Map<string, PsServerPage<RowData>>, key: string, page: PsServerPage<RowData>): void;
|
|
8
|
+
static recall<RowData>(cache: Map<string, PsServerPage<RowData>>, key: string): PsServerPage<RowData> | undefined;
|
|
9
|
+
static preview<RowData>(rows: RowData[], columns: PsTableColumn<RowData>[], query: PsTableQuery, getSearchText: ((row: RowData) => string) | undefined): RowData[];
|
|
10
|
+
static pageCount(total: number, pageSize: number): number;
|
|
11
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PsServerRowsService = void 0;
|
|
4
|
+
const ps_server_rows_config_1 = require("../constants/ps-server-rows-config");
|
|
5
|
+
const ps_table_query_service_1 = require("./ps-table-query-service");
|
|
6
|
+
class PsServerRowsService {
|
|
7
|
+
static queryKey(query, search, pageSize) {
|
|
8
|
+
const filters = Object.entries(query.filters)
|
|
9
|
+
.filter(([, value]) => ps_table_query_service_1.PsTableQueryService.isActive(value))
|
|
10
|
+
.sort(([left], [right]) => left.localeCompare(right));
|
|
11
|
+
return JSON.stringify([filters, search.trim(), query.sort, pageSize]);
|
|
12
|
+
}
|
|
13
|
+
static cacheKey(queryKey, cursor) {
|
|
14
|
+
return `${queryKey}|${cursor === null ? '' : cursor}`;
|
|
15
|
+
}
|
|
16
|
+
static remember(cache, key, page) {
|
|
17
|
+
cache.delete(key);
|
|
18
|
+
cache.set(key, page);
|
|
19
|
+
for (const oldest of cache.keys()) {
|
|
20
|
+
if (cache.size <= ps_server_rows_config_1.PsServerRowsConfig.cacheSize)
|
|
21
|
+
return;
|
|
22
|
+
cache.delete(oldest);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
static recall(cache, key) {
|
|
26
|
+
const page = cache.get(key);
|
|
27
|
+
if (page === undefined)
|
|
28
|
+
return undefined;
|
|
29
|
+
cache.delete(key);
|
|
30
|
+
cache.set(key, page);
|
|
31
|
+
return page;
|
|
32
|
+
}
|
|
33
|
+
static preview(rows, columns, query, getSearchText) {
|
|
34
|
+
return ps_table_query_service_1.PsTableQueryService.apply(rows, columns, query.filters, query.search, getSearchText, query.sort);
|
|
35
|
+
}
|
|
36
|
+
static pageCount(total, pageSize) {
|
|
37
|
+
return Math.max(Math.ceil(total / pageSize), 1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.PsServerRowsService = PsServerRowsService;
|