@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.
- package/README.md +2 -2
- package/dist/bodyCells.d.ts +116 -0
- package/dist/bodyCells.js +80 -0
- package/dist/bodyRows.d.ts +92 -0
- package/dist/bodyRows.js +55 -0
- package/dist/columns.d.ts +204 -0
- package/dist/columns.js +120 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +4 -0
- package/dist/createTable.d.ts +107 -0
- package/dist/createTable.js +92 -0
- package/dist/createViewModel.d.ts +105 -2
- package/dist/createViewModel.js +55 -1
- package/dist/headerCells.d.ts +210 -0
- package/dist/headerCells.js +151 -0
- package/dist/headerRows.d.ts +72 -0
- package/dist/headerRows.js +60 -0
- package/dist/plugins/addColumnFilters.d.ts +101 -0
- package/dist/plugins/addColumnFilters.js +55 -0
- package/dist/plugins/addColumnOrder.d.ts +30 -0
- package/dist/plugins/addColumnOrder.js +21 -0
- package/dist/plugins/addDataExport.d.ts +51 -0
- package/dist/plugins/addDataExport.js +30 -0
- package/dist/plugins/addExpandedRows.d.ts +43 -2
- package/dist/plugins/addExpandedRows.js +48 -2
- package/dist/plugins/addFlatten.d.ts +48 -3
- package/dist/plugins/addFlatten.js +28 -0
- package/dist/plugins/addGridLayout.d.ts +13 -0
- package/dist/plugins/addGridLayout.js +13 -0
- package/dist/plugins/addGroupBy.d.ts +80 -4
- package/dist/plugins/addGroupBy.js +48 -4
- package/dist/plugins/addHiddenColumns.d.ts +27 -0
- package/dist/plugins/addHiddenColumns.js +19 -0
- package/dist/plugins/addPagination.d.ts +54 -0
- package/dist/plugins/addPagination.js +29 -0
- package/dist/plugins/addResizedColumns.d.ts +58 -4
- package/dist/plugins/addResizedColumns.js +33 -0
- package/dist/plugins/addSelectedRows.d.ts +60 -2
- package/dist/plugins/addSelectedRows.js +67 -2
- package/dist/plugins/addSortBy.d.ts +83 -6
- package/dist/plugins/addSortBy.js +65 -24
- package/dist/plugins/addSubRows.d.ts +39 -1
- package/dist/plugins/addSubRows.js +25 -0
- package/dist/plugins/addTableFilter.d.ts +87 -3
- package/dist/plugins/addTableFilter.js +90 -40
- package/dist/plugins/cacheConfig.d.ts +7 -0
- package/dist/plugins/cacheConfig.js +11 -0
- package/dist/tableComponent.d.ts +41 -0
- package/dist/tableComponent.js +37 -0
- package/dist/types/Action.d.ts +16 -2
- package/dist/types/Entries.d.ts +6 -0
- package/dist/types/KeyPath.d.ts +20 -0
- package/dist/types/Label.d.ts +26 -3
- package/dist/types/Matrix.d.ts +6 -0
- package/dist/types/TablePlugin.d.ts +140 -10
- package/dist/utils/array.d.ts +24 -0
- package/dist/utils/array.js +24 -0
- package/dist/utils/attributes.d.ts +31 -0
- package/dist/utils/attributes.js +31 -0
- package/dist/utils/clone.d.ts +19 -0
- package/dist/utils/clone.js +13 -0
- package/dist/utils/compare.d.ts +29 -0
- package/dist/utils/compare.js +29 -0
- package/dist/utils/counter.d.ts +12 -0
- package/dist/utils/counter.js +12 -0
- package/dist/utils/css.d.ts +11 -0
- package/dist/utils/css.js +11 -0
- package/dist/utils/event.d.ts +14 -0
- package/dist/utils/event.js +14 -0
- package/dist/utils/filter.d.ts +47 -0
- package/dist/utils/filter.js +47 -0
- package/dist/utils/math.d.ts +22 -0
- package/dist/utils/math.js +22 -0
- package/dist/utils/matrix.d.ts +24 -0
- package/dist/utils/matrix.js +24 -0
- package/dist/utils/store.d.ts +116 -9
- package/dist/utils/store.js +63 -0
- 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
|
-
|
|
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
|
-
|
|
18
|
-
|
|
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
|
|
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
|
-
|
|
43
|
-
|
|
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
|
|
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[
|
|
59
|
-
const cellB = b.cellForId[
|
|
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
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
+
/** Custom function to extract the filter value from the cell value. */
|
|
37
|
+
getFilterValue?: (_value: any) => string;
|
|
17
38
|
}
|
|
18
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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,
|
|
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
|
+
};
|