@humanspeak/svelte-headless-table 6.0.1 → 6.0.3

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 (78) hide show
  1. package/README.md +2 -2
  2. package/dist/bodyCells.d.ts +116 -0
  3. package/dist/bodyCells.js +80 -0
  4. package/dist/bodyRows.d.ts +92 -0
  5. package/dist/bodyRows.js +55 -0
  6. package/dist/columns.d.ts +204 -0
  7. package/dist/columns.js +120 -0
  8. package/dist/constants.d.ts +4 -0
  9. package/dist/constants.js +4 -0
  10. package/dist/createTable.d.ts +107 -0
  11. package/dist/createTable.js +92 -0
  12. package/dist/createViewModel.d.ts +105 -2
  13. package/dist/createViewModel.js +55 -1
  14. package/dist/headerCells.d.ts +210 -0
  15. package/dist/headerCells.js +151 -0
  16. package/dist/headerRows.d.ts +72 -0
  17. package/dist/headerRows.js +60 -0
  18. package/dist/plugins/addColumnFilters.d.ts +101 -0
  19. package/dist/plugins/addColumnFilters.js +55 -0
  20. package/dist/plugins/addColumnOrder.d.ts +30 -0
  21. package/dist/plugins/addColumnOrder.js +21 -0
  22. package/dist/plugins/addDataExport.d.ts +51 -0
  23. package/dist/plugins/addDataExport.js +30 -0
  24. package/dist/plugins/addExpandedRows.d.ts +43 -2
  25. package/dist/plugins/addExpandedRows.js +48 -2
  26. package/dist/plugins/addFlatten.d.ts +48 -3
  27. package/dist/plugins/addFlatten.js +28 -0
  28. package/dist/plugins/addGridLayout.d.ts +13 -0
  29. package/dist/plugins/addGridLayout.js +13 -0
  30. package/dist/plugins/addGroupBy.d.ts +80 -4
  31. package/dist/plugins/addGroupBy.js +48 -4
  32. package/dist/plugins/addHiddenColumns.d.ts +27 -0
  33. package/dist/plugins/addHiddenColumns.js +19 -0
  34. package/dist/plugins/addPagination.d.ts +54 -0
  35. package/dist/plugins/addPagination.js +29 -0
  36. package/dist/plugins/addResizedColumns.d.ts +58 -4
  37. package/dist/plugins/addResizedColumns.js +33 -0
  38. package/dist/plugins/addSelectedRows.d.ts +60 -2
  39. package/dist/plugins/addSelectedRows.js +67 -2
  40. package/dist/plugins/addSortBy.d.ts +83 -6
  41. package/dist/plugins/addSortBy.js +65 -24
  42. package/dist/plugins/addSubRows.d.ts +39 -1
  43. package/dist/plugins/addSubRows.js +25 -0
  44. package/dist/plugins/addTableFilter.d.ts +87 -3
  45. package/dist/plugins/addTableFilter.js +90 -40
  46. package/dist/plugins/cacheConfig.d.ts +7 -0
  47. package/dist/plugins/cacheConfig.js +11 -0
  48. package/dist/tableComponent.d.ts +41 -0
  49. package/dist/tableComponent.js +37 -0
  50. package/dist/types/Action.d.ts +16 -2
  51. package/dist/types/Entries.d.ts +6 -0
  52. package/dist/types/KeyPath.d.ts +20 -0
  53. package/dist/types/Label.d.ts +26 -3
  54. package/dist/types/Matrix.d.ts +6 -0
  55. package/dist/types/TablePlugin.d.ts +140 -10
  56. package/dist/utils/array.d.ts +24 -0
  57. package/dist/utils/array.js +24 -0
  58. package/dist/utils/attributes.d.ts +31 -0
  59. package/dist/utils/attributes.js +31 -0
  60. package/dist/utils/clone.d.ts +19 -0
  61. package/dist/utils/clone.js +13 -0
  62. package/dist/utils/compare.d.ts +29 -0
  63. package/dist/utils/compare.js +29 -0
  64. package/dist/utils/counter.d.ts +12 -0
  65. package/dist/utils/counter.js +12 -0
  66. package/dist/utils/css.d.ts +11 -0
  67. package/dist/utils/css.js +11 -0
  68. package/dist/utils/event.d.ts +14 -0
  69. package/dist/utils/event.js +14 -0
  70. package/dist/utils/filter.d.ts +47 -0
  71. package/dist/utils/filter.js +47 -0
  72. package/dist/utils/math.d.ts +22 -0
  73. package/dist/utils/math.js +22 -0
  74. package/dist/utils/matrix.d.ts +24 -0
  75. package/dist/utils/matrix.js +24 -0
  76. package/dist/utils/store.d.ts +116 -9
  77. package/dist/utils/store.js +63 -0
  78. package/package.json +31 -30
@@ -1,46 +1,123 @@
1
1
  import { type Readable, type Writable } from 'svelte/store';
2
2
  import type { BodyRow } from '../bodyRows.js';
3
3
  import type { NewTablePropSet, TablePlugin } from '../types/TablePlugin.js';
4
+ /**
5
+ * Configuration options for the addSortBy plugin.
6
+ */
4
7
  export interface SortByConfig {
8
+ /** Initial sort keys to apply on mount. */
5
9
  initialSortKeys?: SortKey[];
10
+ /** If true, prevents sorting by multiple columns. Defaults to false. */
6
11
  disableMultiSort?: boolean;
7
- isMultiSortEvent?: (event: Event) => boolean;
12
+ /** Function to detect multi-sort events (e.g., shift+click). Defaults to isShiftClick. */
13
+ isMultiSortEvent?: (_event: Event) => boolean;
14
+ /** Custom toggle order cycle. Defaults to ['asc', 'desc', undefined]. */
8
15
  toggleOrder?: ('asc' | 'desc' | undefined)[];
16
+ /** If true, sorting is handled server-side and rows are returned as-is. */
9
17
  serverSide?: boolean;
10
18
  }
19
+ /**
20
+ * State exposed by the addSortBy plugin.
21
+ *
22
+ * @template Item - The type of data items in the table.
23
+ */
11
24
  export interface SortByState<Item> {
25
+ /** Writable store containing the current sort keys. */
12
26
  sortKeys: WritableSortKeys;
27
+ /** Readable store containing the rows before sorting was applied. */
13
28
  preSortedRows: Readable<BodyRow<Item>[]>;
14
29
  }
30
+ /**
31
+ * Per-column configuration options for sorting.
32
+ */
15
33
  export interface SortByColumnOptions {
34
+ /** If true, this column cannot be sorted. */
16
35
  disable?: boolean;
17
- getSortValue?: (value: any) => string | number | (string | number)[];
18
- compareFn?: (left: any, right: any) => number;
36
+ /** Custom function to extract the sortable value from the cell value. */
37
+ getSortValue?: (_value: any) => string | number | (string | number)[];
38
+ /** Custom comparison function for sorting. */
39
+ compareFn?: (_left: any, _right: any) => number;
40
+ /** If true, inverts the sort order for this column. */
19
41
  invert?: boolean;
20
42
  }
43
+ /**
44
+ * Props added to table elements by the sort plugin.
45
+ */
21
46
  export type SortByPropSet = NewTablePropSet<{
22
47
  'thead.tr.th': {
48
+ /** Current sort order for this column. */
23
49
  order: 'asc' | 'desc' | undefined;
24
- toggle: (event: Event) => void;
50
+ /** Function to toggle sorting on this column. */
51
+ toggle: (_event: Event) => void;
52
+ /** Function to clear sorting on this column. */
25
53
  clear: () => void;
54
+ /** Whether sorting is disabled for this column. */
26
55
  disabled: boolean;
27
56
  };
28
57
  'tbody.tr.td': {
58
+ /** Current sort order for this column. */
29
59
  order: 'asc' | 'desc' | undefined;
30
60
  };
31
61
  }>;
62
+ /**
63
+ * Represents a single sort key with column ID and direction.
64
+ */
32
65
  export interface SortKey {
66
+ /** The column ID to sort by. */
33
67
  id: string;
68
+ /** The sort direction. */
34
69
  order: 'asc' | 'desc';
35
70
  }
71
+ /**
72
+ * Creates a writable store for managing sort keys with toggle and clear methods.
73
+ *
74
+ * @param initKeys - Initial sort keys.
75
+ * @returns A WritableSortKeys store with toggle and clear functionality.
76
+ * @example
77
+ * ```typescript
78
+ * const sortKeys = createSortKeysStore([{ id: 'name', order: 'asc' }])
79
+ * sortKeys.toggleId('age') // Adds ascending sort by age
80
+ * sortKeys.clearId('name') // Removes sort by name
81
+ * ```
82
+ */
36
83
  export declare const createSortKeysStore: (initKeys: SortKey[]) => WritableSortKeys;
84
+ /**
85
+ * Options for the toggleId method.
86
+ */
37
87
  interface ToggleOptions {
88
+ /** Whether to allow multiple sort keys. */
38
89
  multiSort?: boolean;
90
+ /** Custom toggle order cycle. */
39
91
  toggleOrder?: ('asc' | 'desc' | undefined)[];
40
92
  }
93
+ /**
94
+ * A writable store for sort keys with additional toggle and clear methods.
95
+ */
41
96
  export type WritableSortKeys = Writable<SortKey[]> & {
42
- toggleId: (id: string, options: ToggleOptions) => void;
43
- clearId: (id: string) => void;
97
+ /** Toggles the sort state for a column ID. */
98
+ toggleId: (_id: string, _options: ToggleOptions) => void;
99
+ /** Clears the sort state for a column ID. */
100
+ clearId: (_id: string) => void;
44
101
  };
102
+ /**
103
+ * Creates a sort plugin that enables sorting table rows by one or more columns.
104
+ * Supports ascending, descending, and unsorted states with customizable toggle order.
105
+ *
106
+ * @template Item - The type of data items in the table.
107
+ * @param config - Configuration options for sorting behavior.
108
+ * @returns A TablePlugin that provides sorting functionality.
109
+ * @example
110
+ * ```typescript
111
+ * const table = createTable(data, {
112
+ * sort: addSortBy({
113
+ * initialSortKeys: [{ id: 'name', order: 'asc' }],
114
+ * disableMultiSort: false
115
+ * })
116
+ * })
117
+ *
118
+ * // Access sort state in your component
119
+ * const { sortKeys } = table.pluginStates.sort
120
+ * ```
121
+ */
45
122
  export declare const addSortBy: <Item>({ initialSortKeys, disableMultiSort, isMultiSortEvent, toggleOrder, serverSide }?: SortByConfig) => TablePlugin<Item, SortByState<Item>, SortByColumnOptions, SortByPropSet>;
46
123
  export {};
@@ -2,6 +2,18 @@ import { derived, writable } from 'svelte/store';
2
2
  import { compare } from '../utils/compare.js';
3
3
  import { isShiftClick } from '../utils/event.js';
4
4
  const DEFAULT_TOGGLE_ORDER = ['asc', 'desc', undefined];
5
+ /**
6
+ * Creates a writable store for managing sort keys with toggle and clear methods.
7
+ *
8
+ * @param initKeys - Initial sort keys.
9
+ * @returns A WritableSortKeys store with toggle and clear functionality.
10
+ * @example
11
+ * ```typescript
12
+ * const sortKeys = createSortKeysStore([{ id: 'name', order: 'asc' }])
13
+ * sortKeys.toggleId('age') // Adds ascending sort by age
14
+ * sortKeys.clearId('name') // Removes sort by name
15
+ * ```
16
+ */
5
17
  export const createSortKeysStore = (initKeys) => {
6
18
  const { subscribe, update, set } = writable(initKeys);
7
19
  const toggleId = (id, { multiSort = true, toggleOrder = DEFAULT_TOGGLE_ORDER } = {}) => {
@@ -48,18 +60,35 @@ export const createSortKeysStore = (initKeys) => {
48
60
  clearId
49
61
  };
50
62
  };
63
+ /**
64
+ * Sorts rows based on the provided sort keys and column options.
65
+ * Recursively sorts subRows as well.
66
+ *
67
+ * @template Item - The type of data items.
68
+ * @template Row - The row type.
69
+ * @param rows - The rows to sort.
70
+ * @param sortKeys - The sort keys to apply.
71
+ * @param columnOptions - Per-column sort configuration.
72
+ * @returns A new array of sorted rows.
73
+ * @internal
74
+ */
51
75
  const getSortedRows = (rows, sortKeys, columnOptions) => {
76
+ // Pre-compute sort config for each key to avoid repeated lookups during comparison
77
+ const sortConfig = sortKeys.map((key) => ({
78
+ id: key.id,
79
+ order: key.order,
80
+ invert: columnOptions[key.id]?.invert ?? false,
81
+ compareFn: columnOptions[key.id]?.compareFn,
82
+ getSortValue: columnOptions[key.id]?.getSortValue,
83
+ orderFactor: (key.order === 'desc' ? -1 : 1) * (columnOptions[key.id]?.invert ? -1 : 1)
84
+ }));
52
85
  // Shallow clone to prevent sort affecting `preSortedRows`.
53
86
  const $sortedRows = [...rows];
54
87
  $sortedRows.sort((a, b) => {
55
- for (const key of sortKeys) {
56
- const invert = columnOptions[key.id]?.invert ?? false;
88
+ for (const config of sortConfig) {
57
89
  // TODO check why cellForId returns `undefined`.
58
- const cellA = a.cellForId[key.id];
59
- const cellB = b.cellForId[key.id];
60
- let order = 0;
61
- const compareFn = columnOptions[key.id]?.compareFn;
62
- const getSortValue = columnOptions[key.id]?.getSortValue;
90
+ const cellA = a.cellForId[config.id];
91
+ const cellB = b.cellForId[config.id];
63
92
  // Only need to check properties of `cellA` as both should have the same
64
93
  // properties.
65
94
  if (!cellA.isData()) {
@@ -67,12 +96,13 @@ const getSortedRows = (rows, sortKeys, columnOptions) => {
67
96
  }
68
97
  const valueA = cellA.value;
69
98
  const valueB = cellB.value;
70
- if (compareFn !== undefined) {
71
- order = compareFn(valueA, valueB);
99
+ let order = 0;
100
+ if (config.compareFn !== undefined) {
101
+ order = config.compareFn(valueA, valueB);
72
102
  }
73
- else if (getSortValue !== undefined) {
74
- const sortValueA = getSortValue(valueA);
75
- const sortValueB = getSortValue(valueB);
103
+ else if (config.getSortValue !== undefined) {
104
+ const sortValueA = config.getSortValue(valueA);
105
+ const sortValueB = config.getSortValue(valueB);
76
106
  order = compare(sortValueA, sortValueB);
77
107
  }
78
108
  else if (typeof valueA === 'string' || typeof valueA === 'number') {
@@ -85,17 +115,7 @@ const getSortedRows = (rows, sortKeys, columnOptions) => {
85
115
  order = compare(sortValueA, sortValueB);
86
116
  }
87
117
  if (order !== 0) {
88
- let orderFactor = 1;
89
- // If the current key order is `'desc'`, reverse the order.
90
- if (key.order === 'desc') {
91
- orderFactor *= -1;
92
- }
93
- // If `invert` is `true`, we want to invert the sort without
94
- // affecting the view model's indication.
95
- if (invert) {
96
- orderFactor *= -1;
97
- }
98
- return order * orderFactor;
118
+ return order * config.orderFactor;
99
119
  }
100
120
  }
101
121
  return 0;
@@ -112,6 +132,26 @@ const getSortedRows = (rows, sortKeys, columnOptions) => {
112
132
  }
113
133
  return $sortedRows;
114
134
  };
135
+ /**
136
+ * Creates a sort plugin that enables sorting table rows by one or more columns.
137
+ * Supports ascending, descending, and unsorted states with customizable toggle order.
138
+ *
139
+ * @template Item - The type of data items in the table.
140
+ * @param config - Configuration options for sorting behavior.
141
+ * @returns A TablePlugin that provides sorting functionality.
142
+ * @example
143
+ * ```typescript
144
+ * const table = createTable(data, {
145
+ * sort: addSortBy({
146
+ * initialSortKeys: [{ id: 'name', order: 'asc' }],
147
+ * disableMultiSort: false
148
+ * })
149
+ * })
150
+ *
151
+ * // Access sort state in your component
152
+ * const { sortKeys } = table.pluginStates.sort
153
+ * ```
154
+ */
115
155
  export const addSortBy = ({ initialSortKeys = [], disableMultiSort = false, isMultiSortEvent = isShiftClick, toggleOrder, serverSide = false } = {}) => ({ columnOptions }) => {
116
156
  const disabledSortIds = Object.entries(columnOptions)
117
157
  .filter(([, option]) => option.disable === true)
@@ -121,7 +161,8 @@ export const addSortBy = ({ initialSortKeys = [], disableMultiSort = false, isMu
121
161
  const deriveRows = (rows) => {
122
162
  return derived([rows, sortKeys], ([$rows, $sortKeys]) => {
123
163
  preSortedRows.set($rows);
124
- if (serverSide) {
164
+ // Early return if no sorting needed
165
+ if (serverSide || $sortKeys.length === 0) {
125
166
  return $rows;
126
167
  }
127
168
  return getSortedRows($rows, $sortKeys, columnOptions);
@@ -1,9 +1,47 @@
1
1
  import type { NewTablePropSet, TablePlugin } from '../types/TablePlugin.js';
2
+ /**
3
+ * Utility type that extracts keys from Item whose values are arrays of Item.
4
+ * Used to infer valid children property keys.
5
+ *
6
+ * @template Item - The type of data items.
7
+ */
2
8
  export type ValidChildrenKey<Item> = {
3
9
  [Key in keyof Item]: Item[Key] extends Item[] ? Key : never;
4
10
  }[keyof Item];
5
- export type ValidChildrenFn<Item> = (item: Item) => Item[] | undefined;
11
+ /**
12
+ * Function type for extracting child items from a parent item.
13
+ *
14
+ * @template Item - The type of data items.
15
+ */
16
+ export type ValidChildrenFn<Item> = (_item: Item) => Item[] | undefined;
17
+ /**
18
+ * Configuration options for the addSubRows plugin.
19
+ *
20
+ * @template Item - The type of data items.
21
+ */
6
22
  export interface SubRowsConfig<Item> {
23
+ /** Property key or function to extract child items from each item. */
7
24
  children: ValidChildrenKey<Item> | ValidChildrenFn<Item>;
8
25
  }
26
+ /**
27
+ * Creates a sub-rows plugin that enables hierarchical data display.
28
+ * Extracts child items from each row using the specified children accessor.
29
+ *
30
+ * @template Item - The type of data items in the table.
31
+ * @param config - Configuration with the children accessor.
32
+ * @returns A TablePlugin that creates hierarchical row structure.
33
+ * @example
34
+ * ```typescript
35
+ * interface Employee {
36
+ * name: string
37
+ * directReports?: Employee[]
38
+ * }
39
+ *
40
+ * const table = createTable(data, {
41
+ * subRows: addSubRows({
42
+ * children: 'directReports' // or: (item) => item.directReports
43
+ * })
44
+ * })
45
+ * ```
46
+ */
9
47
  export declare const addSubRows: <Item>({ children }: SubRowsConfig<Item>) => TablePlugin<Item, Record<string, never>, Record<string, never>, NewTablePropSet<never>>;
@@ -1,5 +1,9 @@
1
1
  import { derived } from 'svelte/store';
2
2
  import { DataBodyRow, getSubRows } from '../bodyRows.js';
3
+ /**
4
+ * Recursively adds sub-rows to a row based on the children accessor.
5
+ * @internal
6
+ */
3
7
  const withSubRows = (row, getChildren) => {
4
8
  const subItems = getChildren(row.original);
5
9
  if (subItems === undefined) {
@@ -9,6 +13,27 @@ const withSubRows = (row, getChildren) => {
9
13
  row.subRows = subRows.map((row) => withSubRows(row, getChildren));
10
14
  return row;
11
15
  };
16
+ /**
17
+ * Creates a sub-rows plugin that enables hierarchical data display.
18
+ * Extracts child items from each row using the specified children accessor.
19
+ *
20
+ * @template Item - The type of data items in the table.
21
+ * @param config - Configuration with the children accessor.
22
+ * @returns A TablePlugin that creates hierarchical row structure.
23
+ * @example
24
+ * ```typescript
25
+ * interface Employee {
26
+ * name: string
27
+ * directReports?: Employee[]
28
+ * }
29
+ *
30
+ * const table = createTable(data, {
31
+ * subRows: addSubRows({
32
+ * children: 'directReports' // or: (item) => item.directReports
33
+ * })
34
+ * })
35
+ * ```
36
+ */
12
37
  export const addSubRows = ({ children }) => () => {
13
38
  const getChildren = children instanceof Function ? children : (item) => item[children];
14
39
  const deriveRows = (rows) => {
@@ -1,28 +1,112 @@
1
1
  import { type Readable, type Writable } from 'svelte/store';
2
2
  import type { BodyRow } from '../bodyRows.js';
3
3
  import type { NewTablePropSet, TablePlugin } from '../types/TablePlugin.js';
4
+ /**
5
+ * Configuration options for the addTableFilter plugin.
6
+ */
4
7
  export interface TableFilterConfig {
8
+ /** Custom filter function. Defaults to textPrefixFilter. */
5
9
  fn?: TableFilterFn;
10
+ /** Initial filter value. Defaults to empty string. */
6
11
  initialFilterValue?: string;
12
+ /** Whether to include hidden columns in the filter. Defaults to false. */
7
13
  includeHiddenColumns?: boolean;
14
+ /** If true, filtering is handled server-side and all rows are returned. Defaults to false. */
8
15
  serverSide?: boolean;
9
16
  }
17
+ /**
18
+ * State exposed by the addTableFilter plugin.
19
+ *
20
+ * @template Item - The type of data items in the table.
21
+ */
10
22
  export interface TableFilterState<Item> {
23
+ /** Writable store containing the current filter value. */
11
24
  filterValue: Writable<string>;
25
+ /** Readable store containing the rows before filtering was applied. */
12
26
  preFilteredRows: Readable<BodyRow<Item>[]>;
13
27
  }
14
- export interface TableFilterColumnOptions<Item> {
28
+ /**
29
+ * Per-column configuration options for the table filter.
30
+ *
31
+ * @template _Item - The type of data items (unused but required for type inference).
32
+ */
33
+ export interface TableFilterColumnOptions<_Item> {
34
+ /** If true, this column is excluded from filtering. */
15
35
  exclude?: boolean;
16
- getFilterValue?: (value: any) => string;
36
+ /** Custom function to extract the filter value from the cell value. */
37
+ getFilterValue?: (_value: any) => string;
17
38
  }
18
- export type TableFilterFn = (props: TableFilterFnProps) => boolean;
39
+ /**
40
+ * Function type for custom filter implementations.
41
+ *
42
+ * @param props - The filter function props containing the filter value and cell value.
43
+ * @returns True if the value matches the filter.
44
+ */
45
+ export type TableFilterFn = (_props: TableFilterFnProps) => boolean;
46
+ /**
47
+ * Props passed to a TableFilterFn.
48
+ */
19
49
  export type TableFilterFnProps = {
50
+ /** The current filter value entered by the user. */
20
51
  filterValue: string;
52
+ /** The string value of the cell being tested. */
21
53
  value: string;
22
54
  };
55
+ /**
56
+ * Props added to tbody.tr.td elements by the table filter plugin.
57
+ */
23
58
  export type TableFilterPropSet = NewTablePropSet<{
24
59
  'tbody.tr.td': {
60
+ /** True if this cell matches the current filter. */
25
61
  matches: boolean;
26
62
  };
27
63
  }>;
64
+ /**
65
+ * Options for the rowMatchesFilter function.
66
+ *
67
+ * @template Item - The type of data items in the table.
68
+ */
69
+ export interface RowMatchesFilterOptions<Item> {
70
+ /** Per-column filter configuration. */
71
+ columnOptions: Record<string, TableFilterColumnOptions<Item>>;
72
+ /** The current filter value. */
73
+ filterValue: string;
74
+ /** The filter function to use. */
75
+ fn: TableFilterFn;
76
+ /** Whether to include hidden columns in filtering. */
77
+ includeHiddenColumns: boolean;
78
+ /** Record to track which cells matched the filter. */
79
+ tableCellMatches: Record<string, boolean>;
80
+ }
81
+ /**
82
+ * Checks if a row matches the filter criteria.
83
+ * Uses O(1) Set-based lookups for visibility checks instead of O(m) array searches.
84
+ *
85
+ * @template Item - The type of data items in the table.
86
+ * @param row - The row to check.
87
+ * @param options - The filter options.
88
+ * @returns True if any cell in the row matches the filter, or if the row has subRows.
89
+ */
90
+ export declare const rowMatchesFilter: <Item>(row: BodyRow<Item>, options: RowMatchesFilterOptions<Item>) => boolean;
91
+ /**
92
+ * Creates a table filter plugin that enables filtering rows by text search across columns.
93
+ *
94
+ * @template Item - The type of data items in the table.
95
+ * @param config - Configuration options for the filter.
96
+ * @returns A TablePlugin that provides filtering functionality.
97
+ * @example
98
+ * ```typescript
99
+ * const table = createTable(data, {
100
+ * filter: addTableFilter({
101
+ * fn: ({ filterValue, value }) => value.toLowerCase().includes(filterValue.toLowerCase()),
102
+ * initialFilterValue: '',
103
+ * includeHiddenColumns: false
104
+ * })
105
+ * })
106
+ *
107
+ * // Access the filter state
108
+ * const { filterValue } = table.pluginStates.filter
109
+ * filterValue.set('search term')
110
+ * ```
111
+ */
28
112
  export declare const addTableFilter: <Item>({ fn, initialFilterValue, includeHiddenColumns, serverSide }?: TableFilterConfig) => TablePlugin<Item, TableFilterState<Item>, TableFilterColumnOptions<Item>, TableFilterPropSet>;
@@ -1,7 +1,65 @@
1
1
  import { derived, writable } from 'svelte/store';
2
2
  import { recordSetStore } from '../utils/store.js';
3
3
  import { textPrefixFilter } from './addColumnFilters.js';
4
- const getFilteredRows = (rows, filterValue, columnOptions, { tableCellMatches, fn, includeHiddenColumns }) => {
4
+ /**
5
+ * Checks if a row matches the filter criteria.
6
+ * Uses O(1) Set-based lookups for visibility checks instead of O(m) array searches.
7
+ *
8
+ * @template Item - The type of data items in the table.
9
+ * @param row - The row to check.
10
+ * @param options - The filter options.
11
+ * @returns True if any cell in the row matches the filter, or if the row has subRows.
12
+ */
13
+ export const rowMatchesFilter = (row, options) => {
14
+ const { columnOptions, filterValue, fn, includeHiddenColumns, tableCellMatches } = options;
15
+ // Parent rows with children are always included
16
+ if ((row.subRows?.length ?? 0) !== 0) {
17
+ return true;
18
+ }
19
+ // Pre-compute visible cell IDs once per row - O(m)
20
+ // This is the optimization: uses Set.has() which is O(1) instead of .find() which is O(m)
21
+ const visibleCellIds = new Set(row.cells.map((c) => c.id));
22
+ const rowCellMatches = Object.values(row.cellForId).map((cell) => {
23
+ const cellOptions = columnOptions[cell.id];
24
+ if (cellOptions?.exclude === true) {
25
+ return false;
26
+ }
27
+ // O(1) lookup instead of O(m) .find()
28
+ const isHidden = !visibleCellIds.has(cell.id);
29
+ if (isHidden && !includeHiddenColumns) {
30
+ return false;
31
+ }
32
+ if (!cell.isData()) {
33
+ return false;
34
+ }
35
+ let value = cell.value;
36
+ if (cellOptions?.getFilterValue !== undefined) {
37
+ value = cellOptions.getFilterValue(value);
38
+ }
39
+ const matches = fn({ value: String(value), filterValue });
40
+ if (matches) {
41
+ const dataRowColId = cell.dataRowColId();
42
+ if (dataRowColId !== undefined) {
43
+ tableCellMatches[dataRowColId] = matches;
44
+ }
45
+ }
46
+ return matches;
47
+ });
48
+ return rowCellMatches.includes(true);
49
+ };
50
+ /**
51
+ * Recursively filters rows and their subRows based on the filter criteria.
52
+ *
53
+ * @template Item - The type of data items in the table.
54
+ * @template Row - The row type.
55
+ * @param rows - The rows to filter.
56
+ * @param filterValue - The current filter value.
57
+ * @param options - The filter options.
58
+ * @returns The filtered rows array.
59
+ * @internal
60
+ */
61
+ const getFilteredRows = (rows, filterValue, options) => {
62
+ const { columnOptions, tableCellMatches, fn, includeHiddenColumns } = options;
5
63
  const $filteredRows = rows
6
64
  // Filter `subRows`
7
65
  .map((row) => {
@@ -9,50 +67,41 @@ const getFilteredRows = (rows, filterValue, columnOptions, { tableCellMatches, f
9
67
  if (subRows === undefined) {
10
68
  return row;
11
69
  }
12
- const filteredSubRows = getFilteredRows(subRows, filterValue, columnOptions, {
13
- tableCellMatches,
14
- fn,
15
- includeHiddenColumns
16
- });
70
+ const filteredSubRows = getFilteredRows(subRows, filterValue, options);
17
71
  const clonedRow = row.clone();
18
72
  clonedRow.subRows = filteredSubRows;
19
73
  return clonedRow;
20
74
  })
21
- .filter((row) => {
22
- if ((row.subRows?.length ?? 0) !== 0) {
23
- return true;
24
- }
25
- // An array of booleans, true if the cell matches the filter.
26
- const rowCellMatches = Object.values(row.cellForId).map((cell) => {
27
- const options = columnOptions[cell.id];
28
- if (options?.exclude === true) {
29
- return false;
30
- }
31
- const isHidden = row.cells.find((c) => c.id === cell.id) === undefined;
32
- if (isHidden && !includeHiddenColumns) {
33
- return false;
34
- }
35
- if (!cell.isData()) {
36
- return false;
37
- }
38
- let value = cell.value;
39
- if (options?.getFilterValue !== undefined) {
40
- value = options?.getFilterValue(value);
41
- }
42
- const matches = fn({ value: String(value), filterValue });
43
- if (matches) {
44
- const dataRowColId = cell.dataRowColId();
45
- if (dataRowColId !== undefined) {
46
- tableCellMatches[dataRowColId] = matches;
47
- }
48
- }
49
- return matches;
50
- });
51
- // If any cell matches, include in the filtered results.
52
- return rowCellMatches.includes(true);
53
- });
75
+ .filter((row) => rowMatchesFilter(row, {
76
+ columnOptions,
77
+ filterValue,
78
+ fn,
79
+ includeHiddenColumns,
80
+ tableCellMatches
81
+ }));
54
82
  return $filteredRows;
55
83
  };
84
+ /**
85
+ * Creates a table filter plugin that enables filtering rows by text search across columns.
86
+ *
87
+ * @template Item - The type of data items in the table.
88
+ * @param config - Configuration options for the filter.
89
+ * @returns A TablePlugin that provides filtering functionality.
90
+ * @example
91
+ * ```typescript
92
+ * const table = createTable(data, {
93
+ * filter: addTableFilter({
94
+ * fn: ({ filterValue, value }) => value.toLowerCase().includes(filterValue.toLowerCase()),
95
+ * initialFilterValue: '',
96
+ * includeHiddenColumns: false
97
+ * })
98
+ * })
99
+ *
100
+ * // Access the filter state
101
+ * const { filterValue } = table.pluginStates.filter
102
+ * filterValue.set('search term')
103
+ * ```
104
+ */
56
105
  export const addTableFilter = ({ fn = textPrefixFilter, initialFilterValue = '', includeHiddenColumns = false, serverSide = false } = {}) => ({ columnOptions }) => {
57
106
  const filterValue = writable(initialFilterValue);
58
107
  const preFilteredRows = writable([]);
@@ -63,7 +112,8 @@ export const addTableFilter = ({ fn = textPrefixFilter, initialFilterValue = '',
63
112
  preFilteredRows.set($rows);
64
113
  tableCellMatches.clear();
65
114
  const $tableCellMatches = {};
66
- const $filteredRows = getFilteredRows($rows, $filterValue, columnOptions, {
115
+ const $filteredRows = getFilteredRows($rows, $filterValue, {
116
+ columnOptions,
67
117
  tableCellMatches: $tableCellMatches,
68
118
  fn,
69
119
  includeHiddenColumns
@@ -0,0 +1,7 @@
1
+ import type { CacheOptions } from '@humanspeak/memory-cache';
2
+ /**
3
+ * Default configuration for row state LRU caches used by plugins.
4
+ * Provides automatic eviction to prevent unbounded memory growth
5
+ * when row identities change.
6
+ */
7
+ export declare const DEFAULT_ROW_STATE_CACHE_CONFIG: CacheOptions;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Default configuration for row state LRU caches used by plugins.
3
+ * Provides automatic eviction to prevent unbounded memory growth
4
+ * when row identities change.
5
+ */
6
+ export const DEFAULT_ROW_STATE_CACHE_CONFIG = {
7
+ /** Maximum number of row state entries before LRU eviction */
8
+ maxSize: 1000,
9
+ /** Time-to-live in milliseconds (5 minutes) */
10
+ ttl: 5 * 60 * 1000
11
+ };