@pallasoft/ps-table-components 1.18.1 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/components/ps-data-table.js +86 -24
  2. package/dist/components/ps-table-pagination.d.ts +2 -1
  3. package/dist/components/ps-table-pagination.js +3 -2
  4. package/dist/constants/table-config.d.ts +9 -0
  5. package/dist/constants/table-config.js +6 -0
  6. package/dist/esm/components/ps-data-table.js +87 -25
  7. package/dist/esm/components/ps-table-pagination.js +3 -2
  8. package/dist/esm/constants/table-config.js +6 -0
  9. package/dist/esm/hooks/use-ps-incremental-count.js +17 -0
  10. package/dist/esm/hooks/use-ps-scroll-anchor.js +26 -0
  11. package/dist/esm/hooks/use-ps-streamed-rows.js +58 -15
  12. package/dist/esm/index.js +3 -0
  13. package/dist/esm/interfaces/table-scroll-anchor-target.js +1 -0
  14. package/dist/esm/interfaces/table-scroll-anchor.js +1 -0
  15. package/dist/esm/services/ps-table-query-service.js +25 -13
  16. package/dist/esm/services/ps-table-scroll-anchor-service.js +17 -0
  17. package/dist/esm/services/ps-table-scroll-service.js +18 -0
  18. package/dist/esm/services/ps-table-search-service.js +14 -4
  19. package/dist/esm/services/ps-table-stream-service.js +18 -0
  20. package/dist/hooks/use-ps-incremental-count.d.ts +5 -0
  21. package/dist/hooks/use-ps-incremental-count.js +21 -0
  22. package/dist/hooks/use-ps-scroll-anchor.d.ts +2 -0
  23. package/dist/hooks/use-ps-scroll-anchor.js +30 -0
  24. package/dist/hooks/use-ps-streamed-rows.js +57 -14
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.js +3 -0
  27. package/dist/interfaces/table-scroll-anchor-target.d.ts +10 -0
  28. package/dist/interfaces/table-scroll-anchor-target.js +2 -0
  29. package/dist/interfaces/table-scroll-anchor.d.ts +4 -0
  30. package/dist/interfaces/table-scroll-anchor.js +2 -0
  31. package/dist/interfaces/table-stream-state.d.ts +1 -0
  32. package/dist/services/ps-table-query-service.d.ts +2 -0
  33. package/dist/services/ps-table-query-service.js +25 -13
  34. package/dist/services/ps-table-scroll-anchor-service.d.ts +7 -0
  35. package/dist/services/ps-table-scroll-anchor-service.js +21 -0
  36. package/dist/services/ps-table-scroll-service.d.ts +5 -0
  37. package/dist/services/ps-table-scroll-service.js +22 -0
  38. package/dist/services/ps-table-search-service.d.ts +3 -0
  39. package/dist/services/ps-table-search-service.js +14 -4
  40. package/dist/services/ps-table-stream-service.d.ts +2 -0
  41. package/dist/services/ps-table-stream-service.js +18 -0
  42. package/package.json +2 -1
@@ -1,41 +1,84 @@
1
- import { useEffect, useMemo, useRef, useState } from 'react';
1
+ import { startTransition, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { PsTableConfig } from '../constants/table-config';
2
3
  import { PsTableStreamService } from '../services/ps-table-stream-service';
3
4
  export const usePsStreamedRows = ({ first, getRowId, loadPage, pageSize, scope, total, }) => {
4
5
  const [stream, setStream] = useState({
5
6
  first,
6
7
  hasFailed: false,
8
+ isDone: first.length >= total,
7
9
  previous: [],
8
10
  rows: first,
9
11
  scope,
10
12
  });
11
13
  const rowId = useRef(getRowId);
12
14
  const load = useRef(loadPage);
13
- const requested = useRef(null);
14
15
  rowId.current = getRowId;
15
16
  load.current = loadPage;
16
17
  if (stream.first !== first)
17
18
  setStream({
18
19
  first,
19
20
  hasFailed: false,
21
+ isDone: first.length >= total,
20
22
  previous: PsTableStreamService.carried(stream.rows, stream.previous, getRowId, total, stream.scope === scope),
21
23
  rows: first,
22
24
  scope,
23
25
  });
24
- const nextPage = stream.hasFailed
25
- ? null
26
- : PsTableStreamService.nextPage(stream.rows.length, total, pageSize);
27
26
  useEffect(() => {
28
- if (nextPage === null || requested.current === stream.rows)
27
+ const pages = PsTableStreamService.pages(first.length, total, pageSize);
28
+ if (pages.length === 0)
29
29
  return;
30
- const base = stream.rows;
31
- requested.current = base;
32
- load.current(nextPage).then((page) => setStream((current) => current.rows !== base
33
- ? current
34
- : page.length === 0
35
- ? { ...current, hasFailed: true }
36
- : { ...current, rows: PsTableStreamService.append(current.rows, page, rowId.current) }), () => setStream((current) => (current.rows !== base ? current : { ...current, hasFailed: true })));
37
- }, [nextPage, stream.rows]);
38
- const isStreaming = nextPage !== null;
30
+ const seen = new Set(first.map((row) => rowId.current(row)));
31
+ const loaded = new Map();
32
+ let isCancelled = false;
33
+ let nextIndex = 0;
34
+ let nextToAppend = pages[0];
35
+ let pending = [];
36
+ let timer = null;
37
+ const commit = (patch) => {
38
+ const added = pending;
39
+ pending = [];
40
+ timer = null;
41
+ startTransition(() => setStream((current) => current.first !== first
42
+ ? current
43
+ : {
44
+ ...current,
45
+ ...patch,
46
+ rows: added.length === 0 ? current.rows : current.rows.concat(added),
47
+ }));
48
+ };
49
+ const work = async () => {
50
+ while (!isCancelled && nextIndex < pages.length) {
51
+ const page = pages[nextIndex];
52
+ nextIndex += 1;
53
+ loaded.set(page, await load.current(page));
54
+ if (isCancelled)
55
+ return;
56
+ for (let ready = loaded.get(nextToAppend); ready !== undefined;) {
57
+ pending = pending.concat(PsTableStreamService.unseen(ready, seen, rowId.current));
58
+ loaded.delete(nextToAppend);
59
+ nextToAppend += 1;
60
+ ready = loaded.get(nextToAppend);
61
+ }
62
+ if (timer === null)
63
+ timer = setTimeout(() => commit({ hasFailed: false, isDone: false }), PsTableConfig.streamFlushMs);
64
+ }
65
+ };
66
+ const finish = (hasFailed) => {
67
+ if (isCancelled)
68
+ return;
69
+ if (timer !== null)
70
+ clearTimeout(timer);
71
+ isCancelled = true;
72
+ commit({ hasFailed, isDone: !hasFailed });
73
+ };
74
+ void Promise.all(Array.from({ length: Math.min(PsTableConfig.streamConcurrency, pages.length) }, work)).then(() => finish(false), () => finish(true));
75
+ return () => {
76
+ isCancelled = true;
77
+ if (timer !== null)
78
+ clearTimeout(timer);
79
+ };
80
+ }, [first, pageSize, total]);
81
+ const isStreaming = !stream.hasFailed && !stream.isDone;
39
82
  const rows = useMemo(() => isStreaming
40
83
  ? PsTableStreamService.merge(stream.rows, stream.previous, rowId.current, total)
41
84
  : stream.rows, [isStreaming, stream.previous, stream.rows, total]);
package/dist/esm/index.js CHANGED
@@ -18,6 +18,7 @@ export * from './interfaces/table-sort';
18
18
  export * from './interfaces/table-stream-input';
19
19
  export * from './interfaces/table-stream-result';
20
20
  export * from './interfaces/table-stream-status';
21
+ export * from './hooks/use-ps-incremental-count';
21
22
  export * from './hooks/use-ps-streamed-rows';
22
23
  export * from './hooks/use-ps-table-stream-status';
23
24
  export * from './services/ps-table-cell-tooltip-service';
@@ -25,7 +26,9 @@ export * from './services/ps-table-column-size-service';
25
26
  export * from './services/ps-table-group-service';
26
27
  export * from './services/ps-table-query-service';
27
28
  export * from './services/ps-table-search-service';
29
+ export * from './services/ps-table-scroll-service';
28
30
  export * from './services/ps-table-stream-service';
29
31
  export * from './components/ps-table-export-button';
30
32
  export * from './constants/ps-table-download-config';
31
33
  export * from './services/ps-table-download-service';
34
+ export * from './services/ps-table-scroll-anchor-service';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -37,26 +37,37 @@ export class PsTableQueryService {
37
37
  return true;
38
38
  }
39
39
  static apply(rows, columns, filters, search, getSearchText, sort) {
40
- const filtered = rows.filter((row) => (!getSearchText || PsTableSearchService.matches(getSearchText(row), search)) &&
41
- columns.every((column) => PsTableQueryService.matchesFilter(row, column, filters[column.id] ? filters[column.id] : {})));
40
+ const tokens = getSearchText ? PsTableSearchService.tokens(search) : [];
41
+ const activeColumns = columns.filter((column) => column.filter && PsTableQueryService.isActive(filters[column.id]));
42
+ const filtered = tokens.length === 0 && activeColumns.length === 0
43
+ ? rows
44
+ : rows.filter((row) => (tokens.length === 0 ||
45
+ (getSearchText !== undefined &&
46
+ PsTableSearchService.matchesTokens(PsTableSearchService.haystack(row, getSearchText), tokens))) &&
47
+ activeColumns.every((column) => PsTableQueryService.matchesFilter(row, column, filters[column.id])));
42
48
  const sortColumn = sort ? columns.find((column) => column.id === sort.columnId) : undefined;
43
49
  if (!sort || !sortColumn || !sortColumn.sortValue)
44
50
  return filtered;
45
51
  const getValue = sortColumn.sortValue;
46
52
  const direction = sort.direction === 'ascending' ? 1 : -1;
47
- return [...filtered].sort((left, right) => {
48
- const a = getValue(left);
49
- const b = getValue(right);
50
- if (a === b)
51
- return 0;
52
- if (a === null)
53
+ return filtered
54
+ .map((row, index) => ({ index, row, value: getValue(row) }))
55
+ .sort((left, right) => {
56
+ if (left.value === null && right.value !== null)
53
57
  return 1;
54
- if (b === null)
58
+ if (right.value === null && left.value !== null)
55
59
  return -1;
56
- if (typeof a === 'number' && typeof b === 'number')
57
- return (a - b) * direction;
58
- return String(a).localeCompare(String(b), undefined, { numeric: true }) * direction;
59
- });
60
+ const order = PsTableQueryService.compare(left.value, right.value);
61
+ return order === 0 ? left.index - right.index : order * direction;
62
+ })
63
+ .map((entry) => entry.row);
64
+ }
65
+ static compare(a, b) {
66
+ if (a === b || a === null || b === null)
67
+ return 0;
68
+ if (typeof a === 'number' && typeof b === 'number')
69
+ return a - b;
70
+ return PsTableQueryService.collator.compare(String(a), String(b));
60
71
  }
61
72
  static options(rows, column) {
62
73
  if (!column.filter)
@@ -91,3 +102,4 @@ export class PsTableQueryService {
91
102
  return null;
92
103
  }
93
104
  }
105
+ PsTableQueryService.collator = new Intl.Collator(undefined, { numeric: true });
@@ -0,0 +1,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,18 @@
1
+ export class PsTableScrollService {
2
+ static parent(element) {
3
+ for (let node = element.parentElement; node !== null; node = node.parentElement) {
4
+ if (node === document.body || node === document.documentElement)
5
+ return null;
6
+ if (PsTableScrollService.scrollable.test(getComputedStyle(node).overflowY))
7
+ return node;
8
+ }
9
+ return null;
10
+ }
11
+ static offset(element, parent) {
12
+ const top = element.getBoundingClientRect().top;
13
+ return parent === null
14
+ ? top + window.scrollY
15
+ : top - parent.getBoundingClientRect().top + parent.scrollTop;
16
+ }
17
+ }
18
+ PsTableScrollService.scrollable = /(auto|scroll|overlay)/;
@@ -15,10 +15,20 @@ export class PsTableSearchService {
15
15
  return normalised === '' ? [] : normalised.split(' ');
16
16
  }
17
17
  static matches(text, query) {
18
- const tokens = PsTableSearchService.tokens(query);
19
- if (tokens.length === 0)
20
- return true;
21
- const haystack = PsTableSearchService.normalise(text);
18
+ return PsTableSearchService.matchesTokens(PsTableSearchService.normalise(text), PsTableSearchService.tokens(query));
19
+ }
20
+ static matchesTokens(haystack, tokens) {
22
21
  return tokens.every((token) => haystack.includes(token));
23
22
  }
23
+ static haystack(row, getSearchText) {
24
+ if (typeof row !== 'object' || row === null)
25
+ return PsTableSearchService.normalise(getSearchText(row));
26
+ const cached = PsTableSearchService.haystacks.get(row);
27
+ if (cached !== undefined)
28
+ return cached;
29
+ const haystack = PsTableSearchService.normalise(getSearchText(row));
30
+ PsTableSearchService.haystacks.set(row, haystack);
31
+ return haystack;
32
+ }
24
33
  }
34
+ PsTableSearchService.haystacks = new WeakMap();
@@ -2,7 +2,25 @@ export class PsTableStreamService {
2
2
  static nextPage(loaded, total, pageSize) {
3
3
  return loaded < total ? Math.floor(loaded / pageSize) + 1 : null;
4
4
  }
5
+ static pages(loaded, total, pageSize) {
6
+ const first = PsTableStreamService.nextPage(loaded, total, pageSize);
7
+ if (first === null)
8
+ return [];
9
+ const last = Math.ceil(total / pageSize);
10
+ return Array.from({ length: last - first + 1 }, (_, index) => first + index);
11
+ }
12
+ static unseen(page, seen, getRowId) {
13
+ return page.filter((row) => {
14
+ const rowId = getRowId(row);
15
+ if (seen.has(rowId))
16
+ return false;
17
+ seen.add(rowId);
18
+ return true;
19
+ });
20
+ }
5
21
  static merge(fresh, previous, getRowId, total) {
22
+ if (previous.length === 0)
23
+ return fresh.length > total ? fresh.slice(0, total) : fresh;
6
24
  const freshIds = new Set(fresh.map(getRowId));
7
25
  return [...fresh, ...previous.filter((row) => !freshIds.has(getRowId(row)))].slice(0, total);
8
26
  }
@@ -0,0 +1,5 @@
1
+ import { type RefObject } from 'react';
2
+ export declare const usePsIncrementalCount: (total: number, batch: number) => {
3
+ count: number;
4
+ sentinelRef: RefObject<HTMLDivElement>;
5
+ };
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usePsIncrementalCount = void 0;
4
+ const react_1 = require("react");
5
+ const usePsIncrementalCount = (total, batch) => {
6
+ const [count, setCount] = (0, react_1.useState)(batch);
7
+ const sentinelRef = (0, react_1.useRef)(null);
8
+ (0, react_1.useEffect)(() => {
9
+ const sentinel = sentinelRef.current;
10
+ if (sentinel === null || count >= total)
11
+ return;
12
+ const observer = new IntersectionObserver((entries) => {
13
+ if (entries.some((entry) => entry.isIntersecting))
14
+ setCount((current) => Math.min(current + batch, total));
15
+ });
16
+ observer.observe(sentinel);
17
+ return () => observer.disconnect();
18
+ }, [batch, count, total]);
19
+ return { count: Math.min(count, total), sentinelRef };
20
+ };
21
+ exports.usePsIncrementalCount = usePsIncrementalCount;
@@ -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;
@@ -2,43 +2,86 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.usePsStreamedRows = void 0;
4
4
  const react_1 = require("react");
5
+ const table_config_1 = require("../constants/table-config");
5
6
  const ps_table_stream_service_1 = require("../services/ps-table-stream-service");
6
7
  const usePsStreamedRows = ({ first, getRowId, loadPage, pageSize, scope, total, }) => {
7
8
  const [stream, setStream] = (0, react_1.useState)({
8
9
  first,
9
10
  hasFailed: false,
11
+ isDone: first.length >= total,
10
12
  previous: [],
11
13
  rows: first,
12
14
  scope,
13
15
  });
14
16
  const rowId = (0, react_1.useRef)(getRowId);
15
17
  const load = (0, react_1.useRef)(loadPage);
16
- const requested = (0, react_1.useRef)(null);
17
18
  rowId.current = getRowId;
18
19
  load.current = loadPage;
19
20
  if (stream.first !== first)
20
21
  setStream({
21
22
  first,
22
23
  hasFailed: false,
24
+ isDone: first.length >= total,
23
25
  previous: ps_table_stream_service_1.PsTableStreamService.carried(stream.rows, stream.previous, getRowId, total, stream.scope === scope),
24
26
  rows: first,
25
27
  scope,
26
28
  });
27
- const nextPage = stream.hasFailed
28
- ? null
29
- : ps_table_stream_service_1.PsTableStreamService.nextPage(stream.rows.length, total, pageSize);
30
29
  (0, react_1.useEffect)(() => {
31
- if (nextPage === null || requested.current === stream.rows)
30
+ const pages = ps_table_stream_service_1.PsTableStreamService.pages(first.length, total, pageSize);
31
+ if (pages.length === 0)
32
32
  return;
33
- const base = stream.rows;
34
- requested.current = base;
35
- load.current(nextPage).then((page) => setStream((current) => current.rows !== base
36
- ? current
37
- : page.length === 0
38
- ? { ...current, hasFailed: true }
39
- : { ...current, rows: ps_table_stream_service_1.PsTableStreamService.append(current.rows, page, rowId.current) }), () => setStream((current) => (current.rows !== base ? current : { ...current, hasFailed: true })));
40
- }, [nextPage, stream.rows]);
41
- const isStreaming = nextPage !== null;
33
+ const seen = new Set(first.map((row) => rowId.current(row)));
34
+ const loaded = new Map();
35
+ let isCancelled = false;
36
+ let nextIndex = 0;
37
+ let nextToAppend = pages[0];
38
+ let pending = [];
39
+ let timer = null;
40
+ const commit = (patch) => {
41
+ const added = pending;
42
+ pending = [];
43
+ timer = null;
44
+ (0, react_1.startTransition)(() => setStream((current) => current.first !== first
45
+ ? current
46
+ : {
47
+ ...current,
48
+ ...patch,
49
+ rows: added.length === 0 ? current.rows : current.rows.concat(added),
50
+ }));
51
+ };
52
+ const work = async () => {
53
+ while (!isCancelled && nextIndex < pages.length) {
54
+ const page = pages[nextIndex];
55
+ nextIndex += 1;
56
+ loaded.set(page, await load.current(page));
57
+ if (isCancelled)
58
+ return;
59
+ for (let ready = loaded.get(nextToAppend); ready !== undefined;) {
60
+ pending = pending.concat(ps_table_stream_service_1.PsTableStreamService.unseen(ready, seen, rowId.current));
61
+ loaded.delete(nextToAppend);
62
+ nextToAppend += 1;
63
+ ready = loaded.get(nextToAppend);
64
+ }
65
+ if (timer === null)
66
+ timer = setTimeout(() => commit({ hasFailed: false, isDone: false }), table_config_1.PsTableConfig.streamFlushMs);
67
+ }
68
+ };
69
+ const finish = (hasFailed) => {
70
+ if (isCancelled)
71
+ return;
72
+ if (timer !== null)
73
+ clearTimeout(timer);
74
+ isCancelled = true;
75
+ commit({ hasFailed, isDone: !hasFailed });
76
+ };
77
+ void Promise.all(Array.from({ length: Math.min(table_config_1.PsTableConfig.streamConcurrency, pages.length) }, work)).then(() => finish(false), () => finish(true));
78
+ return () => {
79
+ isCancelled = true;
80
+ if (timer !== null)
81
+ clearTimeout(timer);
82
+ };
83
+ }, [first, pageSize, total]);
84
+ const isStreaming = !stream.hasFailed && !stream.isDone;
42
85
  const rows = (0, react_1.useMemo)(() => isStreaming
43
86
  ? ps_table_stream_service_1.PsTableStreamService.merge(stream.rows, stream.previous, rowId.current, total)
44
87
  : stream.rows, [isStreaming, stream.previous, stream.rows, total]);
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export * from './interfaces/table-sort';
18
18
  export * from './interfaces/table-stream-input';
19
19
  export * from './interfaces/table-stream-result';
20
20
  export * from './interfaces/table-stream-status';
21
+ export * from './hooks/use-ps-incremental-count';
21
22
  export * from './hooks/use-ps-streamed-rows';
22
23
  export * from './hooks/use-ps-table-stream-status';
23
24
  export * from './services/ps-table-cell-tooltip-service';
@@ -25,7 +26,9 @@ export * from './services/ps-table-column-size-service';
25
26
  export * from './services/ps-table-group-service';
26
27
  export * from './services/ps-table-query-service';
27
28
  export * from './services/ps-table-search-service';
29
+ export * from './services/ps-table-scroll-service';
28
30
  export * from './services/ps-table-stream-service';
29
31
  export * from './components/ps-table-export-button';
30
32
  export * from './constants/ps-table-download-config';
31
33
  export * from './services/ps-table-download-service';
34
+ export * from './services/ps-table-scroll-anchor-service';
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ __exportStar(require("./interfaces/table-sort"), exports);
34
34
  __exportStar(require("./interfaces/table-stream-input"), exports);
35
35
  __exportStar(require("./interfaces/table-stream-result"), exports);
36
36
  __exportStar(require("./interfaces/table-stream-status"), exports);
37
+ __exportStar(require("./hooks/use-ps-incremental-count"), exports);
37
38
  __exportStar(require("./hooks/use-ps-streamed-rows"), exports);
38
39
  __exportStar(require("./hooks/use-ps-table-stream-status"), exports);
39
40
  __exportStar(require("./services/ps-table-cell-tooltip-service"), exports);
@@ -41,7 +42,9 @@ __exportStar(require("./services/ps-table-column-size-service"), exports);
41
42
  __exportStar(require("./services/ps-table-group-service"), exports);
42
43
  __exportStar(require("./services/ps-table-query-service"), exports);
43
44
  __exportStar(require("./services/ps-table-search-service"), exports);
45
+ __exportStar(require("./services/ps-table-scroll-service"), exports);
44
46
  __exportStar(require("./services/ps-table-stream-service"), exports);
45
47
  __exportStar(require("./components/ps-table-export-button"), exports);
46
48
  __exportStar(require("./constants/ps-table-download-config"), exports);
47
49
  __exportStar(require("./services/ps-table-download-service"), exports);
50
+ __exportStar(require("./services/ps-table-scroll-anchor-service"), exports);
@@ -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 });
@@ -1,6 +1,7 @@
1
1
  export interface PsTableStreamState<RowData> {
2
2
  first: RowData[];
3
3
  hasFailed: boolean;
4
+ isDone: boolean;
4
5
  previous: RowData[];
5
6
  rows: RowData[];
6
7
  scope: string | undefined;
@@ -6,6 +6,8 @@ export declare class PsTableQueryService {
6
6
  static isActive(value: PsTableFilterValue | undefined): boolean;
7
7
  static matchesFilter<RowData>(row: RowData, column: PsTableColumn<RowData>, value: PsTableFilterValue): boolean;
8
8
  static apply<RowData>(rows: RowData[], columns: PsTableColumn<RowData>[], filters: Record<string, PsTableFilterValue>, search: string, getSearchText: ((row: RowData) => string) | undefined, sort: PsTableSort | null): RowData[];
9
+ private static compare;
10
+ private static readonly collator;
9
11
  static options<RowData>(rows: RowData[], column: PsTableColumn<RowData>): import("..").PsTableFilterOption[];
10
12
  static empty(sort: PsTableSort | null): PsTableQuery;
11
13
  static isEqual(left: PsTableQuery, right: PsTableQuery): boolean;
@@ -40,26 +40,37 @@ class PsTableQueryService {
40
40
  return true;
41
41
  }
42
42
  static apply(rows, columns, filters, search, getSearchText, sort) {
43
- const filtered = rows.filter((row) => (!getSearchText || ps_table_search_service_1.PsTableSearchService.matches(getSearchText(row), search)) &&
44
- columns.every((column) => PsTableQueryService.matchesFilter(row, column, filters[column.id] ? filters[column.id] : {})));
43
+ const tokens = getSearchText ? ps_table_search_service_1.PsTableSearchService.tokens(search) : [];
44
+ const activeColumns = columns.filter((column) => column.filter && PsTableQueryService.isActive(filters[column.id]));
45
+ const filtered = tokens.length === 0 && activeColumns.length === 0
46
+ ? rows
47
+ : rows.filter((row) => (tokens.length === 0 ||
48
+ (getSearchText !== undefined &&
49
+ ps_table_search_service_1.PsTableSearchService.matchesTokens(ps_table_search_service_1.PsTableSearchService.haystack(row, getSearchText), tokens))) &&
50
+ activeColumns.every((column) => PsTableQueryService.matchesFilter(row, column, filters[column.id])));
45
51
  const sortColumn = sort ? columns.find((column) => column.id === sort.columnId) : undefined;
46
52
  if (!sort || !sortColumn || !sortColumn.sortValue)
47
53
  return filtered;
48
54
  const getValue = sortColumn.sortValue;
49
55
  const direction = sort.direction === 'ascending' ? 1 : -1;
50
- return [...filtered].sort((left, right) => {
51
- const a = getValue(left);
52
- const b = getValue(right);
53
- if (a === b)
54
- return 0;
55
- if (a === null)
56
+ return filtered
57
+ .map((row, index) => ({ index, row, value: getValue(row) }))
58
+ .sort((left, right) => {
59
+ if (left.value === null && right.value !== null)
56
60
  return 1;
57
- if (b === null)
61
+ if (right.value === null && left.value !== null)
58
62
  return -1;
59
- if (typeof a === 'number' && typeof b === 'number')
60
- return (a - b) * direction;
61
- return String(a).localeCompare(String(b), undefined, { numeric: true }) * direction;
62
- });
63
+ const order = PsTableQueryService.compare(left.value, right.value);
64
+ return order === 0 ? left.index - right.index : order * direction;
65
+ })
66
+ .map((entry) => entry.row);
67
+ }
68
+ static compare(a, b) {
69
+ if (a === b || a === null || b === null)
70
+ return 0;
71
+ if (typeof a === 'number' && typeof b === 'number')
72
+ return a - b;
73
+ return PsTableQueryService.collator.compare(String(a), String(b));
63
74
  }
64
75
  static options(rows, column) {
65
76
  if (!column.filter)
@@ -95,3 +106,4 @@ class PsTableQueryService {
95
106
  }
96
107
  }
97
108
  exports.PsTableQueryService = PsTableQueryService;
109
+ PsTableQueryService.collator = new Intl.Collator(undefined, { numeric: true });
@@ -0,0 +1,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;
@@ -0,0 +1,5 @@
1
+ export declare class PsTableScrollService {
2
+ private static readonly scrollable;
3
+ static parent(element: HTMLElement): HTMLElement | null;
4
+ static offset(element: HTMLElement, parent: HTMLElement | null): number;
5
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PsTableScrollService = void 0;
4
+ class PsTableScrollService {
5
+ static parent(element) {
6
+ for (let node = element.parentElement; node !== null; node = node.parentElement) {
7
+ if (node === document.body || node === document.documentElement)
8
+ return null;
9
+ if (PsTableScrollService.scrollable.test(getComputedStyle(node).overflowY))
10
+ return node;
11
+ }
12
+ return null;
13
+ }
14
+ static offset(element, parent) {
15
+ const top = element.getBoundingClientRect().top;
16
+ return parent === null
17
+ ? top + window.scrollY
18
+ : top - parent.getBoundingClientRect().top + parent.scrollTop;
19
+ }
20
+ }
21
+ exports.PsTableScrollService = PsTableScrollService;
22
+ PsTableScrollService.scrollable = /(auto|scroll|overlay)/;