@pallasoft/ps-table-components 1.19.0 → 1.21.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.
@@ -10,6 +10,8 @@ const ps_motion_components_1 = require("@pallasoft/ps-motion-components");
10
10
  const react_virtual_1 = require("@tanstack/react-virtual");
11
11
  const table_config_1 = require("../constants/table-config");
12
12
  const use_ps_incremental_count_1 = require("../hooks/use-ps-incremental-count");
13
+ const use_ps_scroll_anchor_1 = require("../hooks/use-ps-scroll-anchor");
14
+ const use_ps_table_stream_status_1 = require("../hooks/use-ps-table-stream-status");
13
15
  const ps_table_cell_tooltip_service_1 = require("../services/ps-table-cell-tooltip-service");
14
16
  const ps_table_column_size_service_1 = require("../services/ps-table-column-size-service");
15
17
  const ps_table_group_service_1 = require("../services/ps-table-group-service");
@@ -67,27 +69,21 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
67
69
  const hasToolbar = isSearchable || hasFilters || Boolean(toolbarActions);
68
70
  const isSelectable = Boolean(getBulkActions);
69
71
  const columnCount = visibleColumns.length + (isSelectable ? 1 : 0);
70
- const deferredRows = (0, react_1.useDeferredValue)(rows);
71
- const deferredFilters = (0, react_1.useDeferredValue)(filters);
72
72
  const deferredSearch = (0, react_1.useDeferredValue)(search);
73
- const deferredSort = (0, react_1.useDeferredValue)(sort);
74
73
  const queriedRows = (0, react_1.useMemo)(() => isServerPaginated
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
- ]);
85
- const currentPage = page !== null && page !== undefined ? page : uncontrolledPage;
86
- const rowCount = isServerPaginated ? totalRows : queriedRows.length;
87
- const pageCount = pageSize ? Math.max(Math.ceil(rowCount / pageSize), 1) : 1;
88
- const safePage = Math.min(Math.max(currentPage, 1), pageCount);
74
+ ? rows
75
+ : ps_table_query_service_1.PsTableQueryService.apply(rows, columns, filters, deferredSearch, getSearchText, sort), [columns, filters, rows, deferredSearch, sort, getSearchText, isServerPaginated]);
76
+ const streamStatus = (0, use_ps_table_stream_status_1.usePsTableStreamStatus)();
89
77
  const isQueried = search.trim() !== '' ||
90
78
  Object.values(filters).some((value) => ps_table_query_service_1.PsTableQueryService.isActive(value));
79
+ const currentPage = page !== null && page !== undefined ? page : uncontrolledPage;
80
+ const loadedRowCount = isServerPaginated ? totalRows : queriedRows.length;
81
+ const rowCount = streamStatus !== null && !isServerPaginated && !isQueried
82
+ ? Math.max(streamStatus.total, loadedRowCount)
83
+ : loadedRowCount;
84
+ const pageCountOf = (count) => (pageSize ? Math.max(Math.ceil(count / pageSize), 1) : 1);
85
+ const pageCount = pageCountOf(rowCount);
86
+ const safePage = Math.min(Math.max(currentPage, 1), pageCountOf(loadedRowCount));
91
87
  const groups = (0, react_1.useMemo)(() => (grouping ? ps_table_group_service_1.PsTableGroupService.group(queriedRows, grouping.getGroupKey) : []), [grouping, queriedRows]);
92
88
  const initialExpandedCount = grouping && grouping.initialExpandedCount !== undefined ? grouping.initialExpandedCount : 1;
93
89
  const isGroupExpanded = (groupKey, groupIndex) => isQueried || groupIndex < initialExpandedCount !== toggledGroupKeys.includes(groupKey);
@@ -200,6 +196,8 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
200
196
  });
201
197
  const virtualizer = scrollParent === null ? windowVirtualizer : elementVirtualizer;
202
198
  const virtualItems = shouldVirtualize ? virtualizer.getVirtualItems() : [];
199
+ const visibleRowIds = (0, react_1.useMemo)(() => (shouldVirtualize ? visibleRows.map((row) => getRowId(row)) : []), [getRowId, shouldVirtualize, visibleRows]);
200
+ (0, use_ps_scroll_anchor_1.usePsScrollAnchor)(virtualizer, visibleRowIds, shouldVirtualize && streamStatus !== null);
203
201
  const virtualTop = virtualItems.length > 0 ? virtualItems[0].start - scrollMargin : 0;
204
202
  const virtualBottom = virtualItems.length > 0
205
203
  ? virtualizer.getTotalSize() - (virtualItems[virtualItems.length - 1].end - scrollMargin)
@@ -277,6 +275,6 @@ const PsDataTable = ({ caption, className, columns, defaultSort, density = 'norm
277
275
  ]
278
276
  : 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
279
277
  ? column.align
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] })] }));
278
+ : 'left'], 'whitespace-nowrap'), children: column.footer }, column.id)))] }) })) : null] }) }), pageSize && (!grouping || isServerPaginated) ? ((0, jsx_runtime_1.jsx)(ps_table_pagination_1.PsTablePagination, { loadedPageCount: pageCountOf(loadedRowCount), onPageChange: changePage, page: safePage, pageCount: pageCount, pageSize: pageSize, totalRows: rowCount })) : null] })] }));
281
279
  };
282
280
  exports.PsDataTable = PsDataTable;
@@ -1,9 +1,10 @@
1
1
  export interface PsTablePaginationProps {
2
2
  className?: string;
3
+ loadedPageCount?: number;
3
4
  onPageChange: (page: number) => void;
4
5
  page: number;
5
6
  pageCount: number;
6
7
  pageSize: number;
7
8
  totalRows: number;
8
9
  }
9
- export declare const PsTablePagination: ({ className, onPageChange, page, pageCount, pageSize, totalRows, }: PsTablePaginationProps) => import("react").JSX.Element;
10
+ export declare const PsTablePagination: ({ className, loadedPageCount, onPageChange, page, pageCount, pageSize, totalRows, }: PsTablePaginationProps) => import("react").JSX.Element;
@@ -3,10 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  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
- const PsTablePagination = ({ className, onPageChange, page, pageCount, pageSize, totalRows, }) => {
6
+ const PsTablePagination = ({ className, loadedPageCount, onPageChange, page, pageCount, pageSize, totalRows, }) => {
7
7
  const firstRow = totalRows === 0 ? 0 : (page - 1) * pageSize + 1;
8
8
  const lastRow = Math.min(page * pageSize, totalRows);
9
9
  const buttonClassName = 'ps-focus-ring inline-flex min-h-9 items-center rounded-full border border-line px-3.5 text-sm font-semibold text-ink transition-colors disabled:cursor-not-allowed disabled:opacity-40 enabled:hover:border-primary/40';
10
- 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 ? 'No rows' : `${firstRow}–${lastRow} of ${totalRows}` }), (0, jsx_runtime_1.jsxs)("div", { className: 'flex items-center gap-2', children: [(0, jsx_runtime_1.jsx)("button", { className: buttonClassName, disabled: page <= 1, onClick: () => onPageChange(page - 1), type: 'button', children: "Previous" }), (0, jsx_runtime_1.jsxs)("span", { className: 'hidden text-sm text-ink/65 sm:inline', children: ["Page ", Math.min(page, pageCount), " of ", pageCount] }), (0, jsx_runtime_1.jsx)("button", { className: buttonClassName, disabled: page >= pageCount, onClick: () => onPageChange(page + 1), type: 'button', children: "Next" })] })] }));
10
+ 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 ? 'No rows' : `${firstRow}–${lastRow} of ${totalRows}` }), (0, jsx_runtime_1.jsxs)("div", { className: 'flex items-center gap-2', children: [(0, jsx_runtime_1.jsx)("button", { className: buttonClassName, disabled: page <= 1, onClick: () => onPageChange(page - 1), type: 'button', children: "Previous" }), (0, jsx_runtime_1.jsxs)("span", { className: 'hidden text-sm text-ink/65 sm:inline', children: ["Page ", Math.min(page, pageCount), " of ", pageCount] }), (0, jsx_runtime_1.jsx)("button", { className: buttonClassName, disabled: page >=
11
+ (loadedPageCount === undefined ? pageCount : Math.min(loadedPageCount, pageCount)), onClick: () => onPageChange(page + 1), type: 'button', children: "Next" })] })] }));
11
12
  };
12
13
  exports.PsTablePagination = PsTablePagination;
@@ -7,6 +7,8 @@ import { PsCollapse } from '@pallasoft/ps-motion-components';
7
7
  import { useVirtualizer, useWindowVirtualizer } from '@tanstack/react-virtual';
8
8
  import { PsTableConfig } from '../constants/table-config';
9
9
  import { usePsIncrementalCount } from '../hooks/use-ps-incremental-count';
10
+ import { usePsScrollAnchor } from '../hooks/use-ps-scroll-anchor';
11
+ import { usePsTableStreamStatus } from '../hooks/use-ps-table-stream-status';
10
12
  import { PsTableCellTooltipService } from '../services/ps-table-cell-tooltip-service';
11
13
  import { PsTableColumnSizeService } from '../services/ps-table-column-size-service';
12
14
  import { PsTableGroupService } from '../services/ps-table-group-service';
@@ -64,27 +66,21 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
64
66
  const hasToolbar = isSearchable || hasFilters || Boolean(toolbarActions);
65
67
  const isSelectable = Boolean(getBulkActions);
66
68
  const columnCount = visibleColumns.length + (isSelectable ? 1 : 0);
67
- const deferredRows = useDeferredValue(rows);
68
- const deferredFilters = useDeferredValue(filters);
69
69
  const deferredSearch = useDeferredValue(search);
70
- const deferredSort = useDeferredValue(sort);
71
70
  const queriedRows = useMemo(() => isServerPaginated
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
- ]);
82
- const currentPage = page !== null && page !== undefined ? page : uncontrolledPage;
83
- const rowCount = isServerPaginated ? totalRows : queriedRows.length;
84
- const pageCount = pageSize ? Math.max(Math.ceil(rowCount / pageSize), 1) : 1;
85
- const safePage = Math.min(Math.max(currentPage, 1), pageCount);
71
+ ? rows
72
+ : PsTableQueryService.apply(rows, columns, filters, deferredSearch, getSearchText, sort), [columns, filters, rows, deferredSearch, sort, getSearchText, isServerPaginated]);
73
+ const streamStatus = usePsTableStreamStatus();
86
74
  const isQueried = search.trim() !== '' ||
87
75
  Object.values(filters).some((value) => PsTableQueryService.isActive(value));
76
+ const currentPage = page !== null && page !== undefined ? page : uncontrolledPage;
77
+ const loadedRowCount = isServerPaginated ? totalRows : queriedRows.length;
78
+ const rowCount = streamStatus !== null && !isServerPaginated && !isQueried
79
+ ? Math.max(streamStatus.total, loadedRowCount)
80
+ : loadedRowCount;
81
+ const pageCountOf = (count) => (pageSize ? Math.max(Math.ceil(count / pageSize), 1) : 1);
82
+ const pageCount = pageCountOf(rowCount);
83
+ const safePage = Math.min(Math.max(currentPage, 1), pageCountOf(loadedRowCount));
88
84
  const groups = useMemo(() => (grouping ? PsTableGroupService.group(queriedRows, grouping.getGroupKey) : []), [grouping, queriedRows]);
89
85
  const initialExpandedCount = grouping && grouping.initialExpandedCount !== undefined ? grouping.initialExpandedCount : 1;
90
86
  const isGroupExpanded = (groupKey, groupIndex) => isQueried || groupIndex < initialExpandedCount !== toggledGroupKeys.includes(groupKey);
@@ -197,6 +193,8 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
197
193
  });
198
194
  const virtualizer = scrollParent === null ? windowVirtualizer : elementVirtualizer;
199
195
  const virtualItems = shouldVirtualize ? virtualizer.getVirtualItems() : [];
196
+ const visibleRowIds = useMemo(() => (shouldVirtualize ? visibleRows.map((row) => getRowId(row)) : []), [getRowId, shouldVirtualize, visibleRows]);
197
+ usePsScrollAnchor(virtualizer, visibleRowIds, shouldVirtualize && streamStatus !== null);
200
198
  const virtualTop = virtualItems.length > 0 ? virtualItems[0].start - scrollMargin : 0;
201
199
  const virtualBottom = virtualItems.length > 0
202
200
  ? virtualizer.getTotalSize() - (virtualItems[virtualItems.length - 1].end - scrollMargin)
@@ -274,5 +272,5 @@ export const PsDataTable = ({ caption, className, columns, defaultSort, density
274
272
  ]
275
273
  : 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
276
274
  ? column.align
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] })] }));
275
+ : 'left'], 'whitespace-nowrap'), children: column.footer }, column.id)))] }) })) : null] }) }), pageSize && (!grouping || isServerPaginated) ? (_jsx(PsTablePagination, { loadedPageCount: pageCountOf(loadedRowCount), onPageChange: changePage, page: safePage, pageCount: pageCount, pageSize: pageSize, totalRows: rowCount })) : null] })] }));
278
276
  };
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
3
- export const PsTablePagination = ({ className, onPageChange, page, pageCount, pageSize, totalRows, }) => {
3
+ export const PsTablePagination = ({ className, loadedPageCount, onPageChange, page, pageCount, pageSize, totalRows, }) => {
4
4
  const firstRow = totalRows === 0 ? 0 : (page - 1) * pageSize + 1;
5
5
  const lastRow = Math.min(page * pageSize, totalRows);
6
6
  const buttonClassName = 'ps-focus-ring inline-flex min-h-9 items-center rounded-full border border-line px-3.5 text-sm font-semibold text-ink transition-colors disabled:cursor-not-allowed disabled:opacity-40 enabled:hover:border-primary/40';
7
- 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 ? 'No rows' : `${firstRow}–${lastRow} of ${totalRows}` }), _jsxs("div", { className: 'flex items-center gap-2', children: [_jsx("button", { className: buttonClassName, disabled: page <= 1, onClick: () => onPageChange(page - 1), type: 'button', children: "Previous" }), _jsxs("span", { className: 'hidden text-sm text-ink/65 sm:inline', children: ["Page ", Math.min(page, pageCount), " of ", pageCount] }), _jsx("button", { className: buttonClassName, disabled: page >= pageCount, onClick: () => onPageChange(page + 1), type: 'button', children: "Next" })] })] }));
7
+ return (_jsxs("nav", { "aria-label": 'Pagination', className: PallasoftClassNameService.merge('flex flex-wrap items-center justify-between gap-3 border-t border-line px-4 py-3', className), children: [_jsx("p", { className: 'text-sm text-ink/55', children: totalRows === 0 ? 'No rows' : `${firstRow}–${lastRow} of ${totalRows}` }), _jsxs("div", { className: 'flex items-center gap-2', children: [_jsx("button", { className: buttonClassName, disabled: page <= 1, onClick: () => onPageChange(page - 1), type: 'button', children: "Previous" }), _jsxs("span", { className: 'hidden text-sm text-ink/65 sm:inline', children: ["Page ", Math.min(page, pageCount), " of ", pageCount] }), _jsx("button", { className: buttonClassName, disabled: page >=
8
+ (loadedPageCount === undefined ? pageCount : Math.min(loadedPageCount, pageCount)), onClick: () => onPageChange(page + 1), type: 'button', children: "Next" })] })] }));
8
9
  };
@@ -0,0 +1,26 @@
1
+ import { useLayoutEffect, useRef } from 'react';
2
+ import { PsTableScrollAnchorService } from '../services/ps-table-scroll-anchor-service';
3
+ export const usePsScrollAnchor = (target, rowIds, isActive) => {
4
+ const anchor = useRef(null);
5
+ const previousRowIds = useRef(rowIds);
6
+ useLayoutEffect(() => {
7
+ const offset = target.scrollOffset;
8
+ if (!isActive || offset === null || offset <= 0) {
9
+ anchor.current = null;
10
+ previousRowIds.current = rowIds;
11
+ return;
12
+ }
13
+ const previous = anchor.current;
14
+ if (previous !== null && previousRowIds.current !== rowIds) {
15
+ const index = PsTableScrollAnchorService.movedTo(previous, previousRowIds.current, rowIds);
16
+ const start = index === null ? undefined : target.getOffsetForIndex(index, 'start');
17
+ previousRowIds.current = rowIds;
18
+ if (start !== undefined) {
19
+ target.scrollToOffset(PsTableScrollAnchorService.offset(start[0], previous));
20
+ return;
21
+ }
22
+ }
23
+ previousRowIds.current = rowIds;
24
+ anchor.current = PsTableScrollAnchorService.capture(target.getVirtualItems(), rowIds, offset);
25
+ });
26
+ };
@@ -0,0 +1,5 @@
1
+ import { useState } from 'react';
2
+ export const usePsTablePage = () => {
3
+ const [page, setPage] = useState(1);
4
+ return { onPageChange: setPage, page };
5
+ };
package/dist/esm/index.js CHANGED
@@ -31,3 +31,6 @@ export * from './services/ps-table-stream-service';
31
31
  export * from './components/ps-table-export-button';
32
32
  export * from './constants/ps-table-download-config';
33
33
  export * from './services/ps-table-download-service';
34
+ export * from './services/ps-table-scroll-anchor-service';
35
+ export * from './hooks/use-ps-table-page';
36
+ export * from './interfaces/ps-table-page-state';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ export class PsTableScrollAnchorService {
2
+ static capture(items, rowIds, scrollOffset) {
3
+ const item = items.find((candidate) => candidate.start >= scrollOffset);
4
+ const anchored = item === undefined ? items[0] : item;
5
+ if (anchored === undefined || rowIds[anchored.index] === undefined)
6
+ return null;
7
+ return { distance: anchored.start - scrollOffset, id: rowIds[anchored.index] };
8
+ }
9
+ static movedTo(anchor, previousRowIds, rowIds) {
10
+ const previousIndex = previousRowIds.indexOf(anchor.id);
11
+ const index = rowIds.indexOf(anchor.id);
12
+ return index < 0 || previousIndex < 0 || index === previousIndex ? null : index;
13
+ }
14
+ static offset(rowStart, anchor) {
15
+ return Math.max(rowStart - anchor.distance, 0);
16
+ }
17
+ }
@@ -0,0 +1,2 @@
1
+ import type { PsTableScrollAnchorTarget } from '../interfaces/table-scroll-anchor-target';
2
+ export declare const usePsScrollAnchor: (target: PsTableScrollAnchorTarget, rowIds: string[], isActive: boolean) => void;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usePsScrollAnchor = void 0;
4
+ const react_1 = require("react");
5
+ const ps_table_scroll_anchor_service_1 = require("../services/ps-table-scroll-anchor-service");
6
+ const usePsScrollAnchor = (target, rowIds, isActive) => {
7
+ const anchor = (0, react_1.useRef)(null);
8
+ const previousRowIds = (0, react_1.useRef)(rowIds);
9
+ (0, react_1.useLayoutEffect)(() => {
10
+ const offset = target.scrollOffset;
11
+ if (!isActive || offset === null || offset <= 0) {
12
+ anchor.current = null;
13
+ previousRowIds.current = rowIds;
14
+ return;
15
+ }
16
+ const previous = anchor.current;
17
+ if (previous !== null && previousRowIds.current !== rowIds) {
18
+ const index = ps_table_scroll_anchor_service_1.PsTableScrollAnchorService.movedTo(previous, previousRowIds.current, rowIds);
19
+ const start = index === null ? undefined : target.getOffsetForIndex(index, 'start');
20
+ previousRowIds.current = rowIds;
21
+ if (start !== undefined) {
22
+ target.scrollToOffset(ps_table_scroll_anchor_service_1.PsTableScrollAnchorService.offset(start[0], previous));
23
+ return;
24
+ }
25
+ }
26
+ previousRowIds.current = rowIds;
27
+ anchor.current = ps_table_scroll_anchor_service_1.PsTableScrollAnchorService.capture(target.getVirtualItems(), rowIds, offset);
28
+ });
29
+ };
30
+ exports.usePsScrollAnchor = usePsScrollAnchor;
@@ -0,0 +1,2 @@
1
+ import type { PsTablePageState } from '../interfaces/ps-table-page-state';
2
+ export declare const usePsTablePage: () => PsTablePageState;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usePsTablePage = void 0;
4
+ const react_1 = require("react");
5
+ const usePsTablePage = () => {
6
+ const [page, setPage] = (0, react_1.useState)(1);
7
+ return { onPageChange: setPage, page };
8
+ };
9
+ exports.usePsTablePage = usePsTablePage;
package/dist/index.d.ts CHANGED
@@ -31,3 +31,6 @@ export * from './services/ps-table-stream-service';
31
31
  export * from './components/ps-table-export-button';
32
32
  export * from './constants/ps-table-download-config';
33
33
  export * from './services/ps-table-download-service';
34
+ export * from './services/ps-table-scroll-anchor-service';
35
+ export * from './hooks/use-ps-table-page';
36
+ export * from './interfaces/ps-table-page-state';
package/dist/index.js CHANGED
@@ -47,3 +47,6 @@ __exportStar(require("./services/ps-table-stream-service"), exports);
47
47
  __exportStar(require("./components/ps-table-export-button"), exports);
48
48
  __exportStar(require("./constants/ps-table-download-config"), exports);
49
49
  __exportStar(require("./services/ps-table-download-service"), exports);
50
+ __exportStar(require("./services/ps-table-scroll-anchor-service"), exports);
51
+ __exportStar(require("./hooks/use-ps-table-page"), exports);
52
+ __exportStar(require("./interfaces/ps-table-page-state"), exports);
@@ -0,0 +1,4 @@
1
+ export interface PsTablePageState {
2
+ onPageChange: (page: number) => void;
3
+ page: number;
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,10 @@
1
+ export interface PsTableScrollAnchorItem {
2
+ index: number;
3
+ start: number;
4
+ }
5
+ export interface PsTableScrollAnchorTarget {
6
+ getOffsetForIndex: (index: number, align: 'start') => readonly [number, string] | undefined;
7
+ getVirtualItems: () => PsTableScrollAnchorItem[];
8
+ scrollOffset: number | null;
9
+ scrollToOffset: (offset: number) => void;
10
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
1
+ export interface PsTableScrollAnchor {
2
+ distance: number;
3
+ id: string;
4
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,7 @@
1
+ import type { PsTableScrollAnchor } from '../interfaces/table-scroll-anchor';
2
+ import type { PsTableScrollAnchorItem } from '../interfaces/table-scroll-anchor-target';
3
+ export declare class PsTableScrollAnchorService {
4
+ static capture(items: PsTableScrollAnchorItem[], rowIds: string[], scrollOffset: number): PsTableScrollAnchor | null;
5
+ static movedTo(anchor: PsTableScrollAnchor, previousRowIds: string[], rowIds: string[]): number | null;
6
+ static offset(rowStart: number, anchor: PsTableScrollAnchor): number;
7
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTableScrollAnchorService = void 0;
4
+ class PsTableScrollAnchorService {
5
+ static capture(items, rowIds, scrollOffset) {
6
+ const item = items.find((candidate) => candidate.start >= scrollOffset);
7
+ const anchored = item === undefined ? items[0] : item;
8
+ if (anchored === undefined || rowIds[anchored.index] === undefined)
9
+ return null;
10
+ return { distance: anchored.start - scrollOffset, id: rowIds[anchored.index] };
11
+ }
12
+ static movedTo(anchor, previousRowIds, rowIds) {
13
+ const previousIndex = previousRowIds.indexOf(anchor.id);
14
+ const index = rowIds.indexOf(anchor.id);
15
+ return index < 0 || previousIndex < 0 || index === previousIndex ? null : index;
16
+ }
17
+ static offset(rowStart, anchor) {
18
+ return Math.max(rowStart - anchor.distance, 0);
19
+ }
20
+ }
21
+ exports.PsTableScrollAnchorService = PsTableScrollAnchorService;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pallasoft/ps-table-components",
3
- "version": "1.19.0",
3
+ "version": "1.21.0",
4
4
  "private": false,
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -55,6 +55,7 @@
55
55
  "eslint-plugin-import": "^2.32.0",
56
56
  "eslint-plugin-prettier": "^5.5.4",
57
57
  "husky": "^9.1.7",
58
+ "jsdom": "^30.1.1",
58
59
  "lint-staged": "^16.2.7",
59
60
  "prettier": "^3.6.2",
60
61
  "react": "^19.3.0",