@open-dcp/grid 0.0.1 → 0.2.17

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 (46) hide show
  1. package/.github/workflows/main.yml +42 -0
  2. package/CLA.md +22 -0
  3. package/LICENSE +190 -0
  4. package/NOTICE.md +31 -0
  5. package/README.md +39 -39
  6. package/SECURITY.md +27 -0
  7. package/dist/GridService.d.ts +83 -0
  8. package/dist/GridService.js +410 -0
  9. package/dist/__tests__/GridServices.test.d.ts +1 -0
  10. package/dist/__tests__/GridServices.test.js +288 -0
  11. package/dist/index.d.ts +4 -0
  12. package/dist/index.js +1 -0
  13. package/dist/services/FilteringService.d.ts +34 -0
  14. package/dist/services/FilteringService.js +278 -0
  15. package/dist/services/FormattingService.d.ts +17 -0
  16. package/dist/services/FormattingService.js +179 -0
  17. package/dist/services/RowHeightService.d.ts +15 -0
  18. package/dist/services/RowHeightService.js +63 -0
  19. package/dist/services/SelectionService.d.ts +14 -0
  20. package/dist/services/SelectionService.js +55 -0
  21. package/dist/services/SortingService.d.ts +15 -0
  22. package/dist/services/SortingService.js +82 -0
  23. package/dist/services/__tests__/FilteringService.test.d.ts +1 -0
  24. package/dist/services/__tests__/FilteringService.test.js +140 -0
  25. package/dist/services/__tests__/FormattingService.test.d.ts +1 -0
  26. package/dist/services/__tests__/FormattingService.test.js +122 -0
  27. package/dist/types/grid.d.ts +71 -0
  28. package/dist/types/grid.js +1 -0
  29. package/dist/types/sorting.d.ts +8 -0
  30. package/dist/types/sorting.js +1 -0
  31. package/jest.config.js +16 -0
  32. package/package.json +40 -9
  33. package/reports/junit.xml +3 -0
  34. package/src/GridService.ts +561 -0
  35. package/src/__tests__/GridServices.test.ts +348 -0
  36. package/src/index.ts +17 -0
  37. package/src/services/FilteringService.ts +347 -0
  38. package/src/services/FormattingService.ts +205 -0
  39. package/src/services/RowHeightService.ts +81 -0
  40. package/src/services/SelectionService.ts +71 -0
  41. package/src/services/SortingService.ts +105 -0
  42. package/src/services/__tests__/FilteringService.test.ts +173 -0
  43. package/src/services/__tests__/FormattingService.test.ts +157 -0
  44. package/src/types/grid.ts +84 -0
  45. package/src/types/sorting.ts +9 -0
  46. package/tsconfig.json +19 -0
@@ -0,0 +1,288 @@
1
+ import { GridService } from '../GridService';
2
+ describe('GridService', () => {
3
+ let gridService;
4
+ let mockRows;
5
+ let mockColumns;
6
+ beforeEach(() => {
7
+ // Setup mock columns
8
+ mockColumns = [
9
+ { key: 'id', label: 'ID', type: 'integer' },
10
+ { key: 'name', label: 'Name', type: 'string' },
11
+ { key: 'age', label: 'Age', type: 'number' },
12
+ ];
13
+ // Setup mock rows
14
+ mockRows = [
15
+ { id: { raw: 1, value: 1 }, name: { raw: 'Alice', value: 'Alice' }, age: { raw: 30, value: 30 } },
16
+ { id: { raw: 2, value: 2 }, name: { raw: 'Bob', value: 'Bob' }, age: { raw: 25, value: 25 } },
17
+ { id: { raw: 3, value: 3 }, name: { raw: 'Charlie', value: 'Charlie' }, age: { raw: 35, value: 35 } },
18
+ ];
19
+ gridService = new GridService(mockRows, mockColumns);
20
+ });
21
+ it('should initialize with correct row count', () => {
22
+ expect(gridService.rowCount()).toBe(3);
23
+ });
24
+ it('should return all rows', () => {
25
+ const rows = gridService.getRows();
26
+ expect(rows.length).toBe(3);
27
+ });
28
+ it('should return all columns', () => {
29
+ const columns = gridService.getColumns();
30
+ expect(columns.length).toBe(3);
31
+ });
32
+ it('should update rows', () => {
33
+ const newRows = [
34
+ { id: { raw: 4, value: 4 }, name: { raw: 'David', value: 'David' }, age: { raw: 40, value: 40 } },
35
+ ];
36
+ gridService.setRows(newRows);
37
+ expect(gridService.rowCount()).toBe(1);
38
+ });
39
+ it('should toggle sort on a column', () => {
40
+ gridService.toggleSort('name');
41
+ const sortCondition = gridService.getSortCondition('name');
42
+ expect(sortCondition).not.toBeNull();
43
+ expect(sortCondition === null || sortCondition === void 0 ? void 0 : sortCondition.direction).toBe('asc');
44
+ });
45
+ it('should toggle sort direction from asc to desc', () => {
46
+ gridService.toggleSort('name');
47
+ gridService.toggleSort('name');
48
+ const sortCondition = gridService.getSortCondition('name');
49
+ expect(sortCondition === null || sortCondition === void 0 ? void 0 : sortCondition.direction).toBe('desc');
50
+ });
51
+ it('should remove sort after third toggle', () => {
52
+ gridService.toggleSort('name');
53
+ gridService.toggleSort('name');
54
+ gridService.toggleSort('name');
55
+ const sortCondition = gridService.getSortCondition('name');
56
+ expect(sortCondition).toBeNull();
57
+ });
58
+ it('should sort rows by age ascending', () => {
59
+ gridService.toggleSort('age');
60
+ const sortedRows = gridService.getFilteredAndSortRows();
61
+ expect(sortedRows[0].age.value).toBe(25);
62
+ expect(sortedRows[1].age.value).toBe(30);
63
+ expect(sortedRows[2].age.value).toBe(35);
64
+ });
65
+ it('should enable API mode', () => {
66
+ gridService.enableApiMode({ clientSideSort: false, clientSideFilter: false });
67
+ expect(gridService['apiMode']).toBe(true);
68
+ });
69
+ it('should set rows from API', () => {
70
+ gridService.enableApiMode({ clientSideSort: false, clientSideFilter: false });
71
+ const apiRows = [
72
+ { id: { raw: 5, value: 5 }, name: { raw: 'Eve', value: 'Eve' }, age: { raw: 28, value: 28 } },
73
+ ];
74
+ gridService.setRowsFromApi(apiRows);
75
+ expect(gridService.rowCount()).toBe(1);
76
+ });
77
+ it('should emit filterSort$ observable when sort is toggled in API mode', (done) => {
78
+ gridService.enableApiMode({ clientSideSort: false, clientSideFilter: false });
79
+ gridService.filterSort$.subscribe(({ filterTree, sorts }) => {
80
+ if (sorts.length > 0) {
81
+ expect(sorts[0].columnKey).toBe('name');
82
+ expect(sorts[0].direction).toBe('asc');
83
+ done();
84
+ }
85
+ });
86
+ gridService.toggleSort('name');
87
+ });
88
+ it('should use client-side sorting when enabled in API mode', () => {
89
+ gridService.enableApiMode({ clientSideSort: true, clientSideFilter: false });
90
+ gridService.setRowsFromApi(mockRows);
91
+ gridService.toggleSort('age');
92
+ const sortedRows = gridService.getFilteredAndSortRows();
93
+ expect(sortedRows[0].age.value).toBe(25);
94
+ expect(sortedRows[1].age.value).toBe(30);
95
+ expect(sortedRows[2].age.value).toBe(35);
96
+ });
97
+ it('should select a row', (done) => {
98
+ gridService.selectRow(0);
99
+ gridService.getSelected().subscribe(selected => {
100
+ expect(selected.has(0)).toBe(true);
101
+ done();
102
+ });
103
+ });
104
+ it('should deselect a row', (done) => {
105
+ gridService.selectRow(0);
106
+ gridService.deselectRow(0);
107
+ gridService.getSelected().subscribe(selected => {
108
+ expect(selected.has(0)).toBe(false);
109
+ done();
110
+ });
111
+ });
112
+ it('should toggle row selection', (done) => {
113
+ gridService.toggleRowSelection(0);
114
+ gridService.getSelected().subscribe(selected => {
115
+ expect(selected.has(0)).toBe(true);
116
+ done();
117
+ });
118
+ });
119
+ it('should clear all selections', (done) => {
120
+ gridService.selectRow(0);
121
+ gridService.selectRow(1);
122
+ gridService.clearSelection();
123
+ gridService.getSelected().subscribe(selected => {
124
+ expect(selected.size).toBe(0);
125
+ done();
126
+ });
127
+ });
128
+ it('should update display format', (done) => {
129
+ gridService.updateDisplayFormat({ dateFormat: 'dd/MM/yyyy' });
130
+ gridService.displayFormat$.subscribe(format => {
131
+ expect(format.dateFormat).toBe('dd/MM/yyyy');
132
+ done();
133
+ });
134
+ });
135
+ it('should set and clear global search', () => {
136
+ gridService.setGlobalSearch('Alice');
137
+ expect(gridService.getGlobalSearch()).toEqual({ query: 'Alice', options: undefined });
138
+ const searchedRows = gridService.getFilteredRows();
139
+ expect(searchedRows).toHaveLength(1);
140
+ expect(searchedRows[0].name.value).toBe('Alice');
141
+ gridService.clearGlobalSearch();
142
+ expect(gridService.getGlobalSearch()).toBeNull();
143
+ expect(gridService.getFilteredRows()).toHaveLength(3);
144
+ });
145
+ it('should apply global search only to restricted columns', () => {
146
+ gridService.setGlobalSearch('25', { columns: ['name'] });
147
+ expect(gridService.getFilteredRows()).toHaveLength(0);
148
+ gridService.setGlobalSearch('25', { columns: ['age'] });
149
+ const searchedRows = gridService.getFilteredRows();
150
+ expect(searchedRows).toHaveLength(1);
151
+ expect(searchedRows[0].name.value).toBe('Bob');
152
+ });
153
+ it('should apply case-insensitive global search using options', () => {
154
+ gridService.setGlobalSearch('ALICE', { caseInsensitive: true });
155
+ const searchedRows = gridService.getFilteredRows();
156
+ expect(searchedRows).toHaveLength(1);
157
+ expect(searchedRows[0].name.value).toBe('Alice');
158
+ });
159
+ it('should update global search matches after setRows', () => {
160
+ gridService.setGlobalSearch('zara', { caseInsensitive: true });
161
+ expect(gridService.getFilteredRows()).toHaveLength(0);
162
+ const newRows = [
163
+ { id: { raw: 10, value: 10 }, name: { raw: 'Zara', value: 'Zara' }, age: { raw: 22, value: 22 } },
164
+ ];
165
+ gridService.setRows(newRows);
166
+ const searchedRows = gridService.getFilteredRows();
167
+ expect(searchedRows).toHaveLength(1);
168
+ expect(searchedRows[0].name.value).toBe('Zara');
169
+ });
170
+ it('should update global search matches after setColumns', () => {
171
+ gridService.setGlobalSearch('30', { columns: ['age'] });
172
+ expect(gridService.getFilteredRows()).toHaveLength(1);
173
+ gridService.setColumns([
174
+ { key: 'id', label: 'ID', type: 'integer' },
175
+ { key: 'name', label: 'Name', type: 'string' },
176
+ ]);
177
+ gridService.setGlobalSearch('30');
178
+ expect(gridService.getFilteredRows()).toHaveLength(0);
179
+ });
180
+ it('should emit global search in filterSort$ payload in API mode', (done) => {
181
+ gridService.enableApiMode({ clientSideSort: false, clientSideFilter: false });
182
+ gridService.filterSort$.subscribe(({ globalSearch }) => {
183
+ var _a, _b;
184
+ if ((globalSearch === null || globalSearch === void 0 ? void 0 : globalSearch.query) === 'alice') {
185
+ expect((_a = globalSearch.options) === null || _a === void 0 ? void 0 : _a.columns).toEqual(['name']);
186
+ expect((_b = globalSearch.options) === null || _b === void 0 ? void 0 : _b.caseInsensitive).toBe(true);
187
+ done();
188
+ }
189
+ });
190
+ gridService.setGlobalSearch('alice', { columns: ['name'], caseInsensitive: true });
191
+ });
192
+ it('should support display-mode global search', () => {
193
+ const durationColumns = [
194
+ { key: 'name', label: 'Name', type: 'string' },
195
+ { key: 'sessionDuration', label: 'Session Duration', type: 'duration' },
196
+ ];
197
+ const durationRows = [
198
+ { name: { raw: 'Alice', value: 'Alice' }, sessionDuration: { raw: 3661, value: 3661 } },
199
+ { name: { raw: 'Bob', value: 'Bob' }, sessionDuration: { raw: 125, value: 125 } },
200
+ ];
201
+ const durationGrid = new GridService(durationRows, durationColumns);
202
+ durationGrid.setGlobalSearch('01:01', { columns: ['sessionDuration'], searchMode: 'display' });
203
+ const displayMatches = durationGrid.getFilteredRows();
204
+ expect(displayMatches).toHaveLength(1);
205
+ expect(displayMatches[0].name.value).toBe('Alice');
206
+ durationGrid.setGlobalSearch('01:01', { columns: ['sessionDuration'], searchMode: 'raw' });
207
+ expect(durationGrid.getFilteredRows()).toHaveLength(0);
208
+ });
209
+ it('should refresh display-mode global search after display format updates', () => {
210
+ const dateColumns = [
211
+ { key: 'name', label: 'Name', type: 'string' },
212
+ { key: 'created', label: 'Created', type: 'date' },
213
+ ];
214
+ const dateRows = [
215
+ { name: { raw: 'Alice', value: 'Alice' }, created: { raw: '2024-01-15T00:00:00Z', value: new Date('2024-01-15T00:00:00Z').getTime() } },
216
+ { name: { raw: 'Bob', value: 'Bob' }, created: { raw: '2024-12-31T00:00:00Z', value: new Date('2024-12-31T00:00:00Z').getTime() } },
217
+ ];
218
+ const dateGrid = new GridService(dateRows, dateColumns);
219
+ dateGrid.setGlobalSearch('/', { columns: ['created'], searchMode: 'display' });
220
+ expect(dateGrid.getFilteredRows()).toHaveLength(0);
221
+ dateGrid.updateDisplayFormat({ dateFormat: 'dd/MM/yyyy' });
222
+ expect(dateGrid.getFilteredRows()).toHaveLength(2);
223
+ });
224
+ it('should use column globalSearchText callback for component columns', () => {
225
+ const componentColumns = [
226
+ { key: 'id', label: 'ID', type: 'integer' },
227
+ {
228
+ key: 'status',
229
+ label: 'Status',
230
+ type: 'component',
231
+ globalSearchText: (cell) => {
232
+ var _a;
233
+ const code = String((_a = cell === null || cell === void 0 ? void 0 : cell.raw) !== null && _a !== void 0 ? _a : '');
234
+ if (code === 'A')
235
+ return 'Active';
236
+ if (code === 'I')
237
+ return 'Inactive';
238
+ return 'Pending';
239
+ },
240
+ },
241
+ ];
242
+ const componentRows = [
243
+ { id: { raw: 1, value: 1 }, status: { raw: 'A', value: 'A' } },
244
+ { id: { raw: 2, value: 2 }, status: { raw: 'I', value: 'I' } },
245
+ ];
246
+ const componentGrid = new GridService(componentRows, componentColumns);
247
+ componentGrid.setGlobalSearch('inactive', { searchMode: 'display', caseInsensitive: true });
248
+ const searchedRows = componentGrid.getFilteredRows();
249
+ expect(searchedRows).toHaveLength(1);
250
+ expect(searchedRows[0].id.value).toBe(2);
251
+ });
252
+ it('should keep sort consistent when global search query changes', () => {
253
+ var _a, _b, _c;
254
+ gridService.toggleSort('age');
255
+ expect((_a = gridService.getSortCondition('age')) === null || _a === void 0 ? void 0 : _a.direction).toBe('asc');
256
+ gridService.setGlobalSearch('a', { caseInsensitive: true });
257
+ expect((_b = gridService.getSortCondition('age')) === null || _b === void 0 ? void 0 : _b.direction).toBe('asc');
258
+ const firstSearch = gridService.getFilteredAndSortRows();
259
+ expect(firstSearch).toHaveLength(2);
260
+ expect(firstSearch[0].name.value).toBe('Alice');
261
+ expect(firstSearch[1].name.value).toBe('Charlie');
262
+ gridService.setGlobalSearch('bo', { caseInsensitive: true });
263
+ expect((_c = gridService.getSortCondition('age')) === null || _c === void 0 ? void 0 : _c.direction).toBe('asc');
264
+ const secondSearch = gridService.getFilteredAndSortRows();
265
+ expect(secondSearch).toHaveLength(1);
266
+ expect(secondSearch[0].name.value).toBe('Bob');
267
+ });
268
+ it('should combine global search and per-column filter with sorting', () => {
269
+ gridService.toggleSort('age');
270
+ gridService.addFilter({ type: 'condition', columnKey: 'age', operator: '>=', value: 30 });
271
+ gridService.setGlobalSearch('a', { caseInsensitive: true });
272
+ const rows = gridService.getFilteredAndSortRows();
273
+ expect(rows).toHaveLength(2);
274
+ expect(rows[0].name.value).toBe('Alice');
275
+ expect(rows[1].name.value).toBe('Charlie');
276
+ });
277
+ it('should not apply global search locally in API mode when client-side filtering is disabled', () => {
278
+ gridService.enableApiMode({ clientSideSort: true, clientSideFilter: false });
279
+ gridService.setRowsFromApi(mockRows);
280
+ gridService.setGlobalSearch('Alice');
281
+ gridService.toggleSort('age');
282
+ const rows = gridService.getFilteredAndSortRows();
283
+ expect(rows).toHaveLength(3);
284
+ expect(rows[0].name.value).toBe('Bob');
285
+ expect(rows[1].name.value).toBe('Alice');
286
+ expect(rows[2].name.value).toBe('Charlie');
287
+ });
288
+ });
@@ -0,0 +1,4 @@
1
+ export { GridService } from './GridService';
2
+ export { GridState, Row, RenderRow, Column, ColumnType, DisplayFormat, GridOptions, FilterOption, GlobalSearchMode, GlobalSearchOptions, GlobalSearchState, FilterSortUpdatePayload, } from './types/grid';
3
+ export { SortingCondition } from './types/sorting';
4
+ export { FilterCondition, OperatorType, FilterGroup, FilterNode } from '@open-dcp/filter';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { GridService } from './GridService';
@@ -0,0 +1,34 @@
1
+ import type { GridService } from "../GridService";
2
+ import type { GlobalSearchOptions, GlobalSearchState, Row } from "../types/grid";
3
+ import { FilterCondition, FilterNode } from "@open-dcp/filter";
4
+ export declare class FilteringService {
5
+ private readonly gridService;
6
+ private readonly filteredIndexes$;
7
+ private filterTree;
8
+ private globalSearch;
9
+ private searchableTextCache;
10
+ private cacheSignature;
11
+ constructor(gridService: GridService, filterTree?: FilterNode | null);
12
+ private getColumnKeys;
13
+ setFilterTree(tree: FilterNode | null): void;
14
+ getFilterTree(): FilterNode | null;
15
+ setGlobalSearch(query: string, options?: GlobalSearchOptions): void;
16
+ clearGlobalSearch(): void;
17
+ getGlobalSearch(): GlobalSearchState | null;
18
+ updateFilterIndexesForUpdatedData(): void;
19
+ private invalidateSearchCache;
20
+ private getSearchColumns;
21
+ private toSearchValue;
22
+ private getCellSearchValue;
23
+ private getCacheSignature;
24
+ private ensureSearchCache;
25
+ private matchesFilterTree;
26
+ hasFilter(columnKey: string | number): boolean;
27
+ addFilter(filterNode: FilterNode): void;
28
+ resetFilters(): void;
29
+ resetFilter(columnKey: string | number): void;
30
+ getFilterIndexes(): number[] | null;
31
+ getFilters(): FilterCondition[];
32
+ private updateFilterIndexes;
33
+ applyFilter(rows: Row[]): Row[];
34
+ }
@@ -0,0 +1,278 @@
1
+ import { BehaviorSubject } from "rxjs";
2
+ import { evaluateFilterTree } from "@open-dcp/filter";
3
+ import { logger } from '@open-dcp/utilities';
4
+ export class FilteringService {
5
+ constructor(gridService, filterTree = null) {
6
+ this.filteredIndexes$ = new BehaviorSubject(null);
7
+ this.filterTree = null;
8
+ this.globalSearch = null;
9
+ this.searchableTextCache = null;
10
+ this.cacheSignature = null;
11
+ this.gridService = gridService;
12
+ this.filterTree = filterTree;
13
+ }
14
+ getColumnKeys(node) {
15
+ if (node.type === 'condition')
16
+ return [node.columnKey];
17
+ if (node.type === 'group') {
18
+ return node.filters.reduce((acc, filter) => {
19
+ return acc.concat(this.getColumnKeys(filter));
20
+ }, []);
21
+ }
22
+ return [];
23
+ }
24
+ setFilterTree(tree) {
25
+ this.filterTree = tree;
26
+ this.updateFilterIndexes();
27
+ }
28
+ getFilterTree() {
29
+ return this.filterTree;
30
+ }
31
+ setGlobalSearch(query, options) {
32
+ const normalizedQuery = query.trim();
33
+ if (normalizedQuery.length === 0) {
34
+ this.globalSearch = null;
35
+ this.updateFilterIndexes();
36
+ return;
37
+ }
38
+ this.globalSearch = {
39
+ query: normalizedQuery,
40
+ options: options ? Object.assign({}, options) : undefined,
41
+ };
42
+ this.updateFilterIndexes();
43
+ }
44
+ clearGlobalSearch() {
45
+ this.globalSearch = null;
46
+ this.updateFilterIndexes();
47
+ }
48
+ getGlobalSearch() {
49
+ return this.globalSearch;
50
+ }
51
+ updateFilterIndexesForUpdatedData() {
52
+ this.invalidateSearchCache();
53
+ this.updateFilterIndexes();
54
+ }
55
+ invalidateSearchCache() {
56
+ this.searchableTextCache = null;
57
+ this.cacheSignature = null;
58
+ }
59
+ getSearchColumns(options) {
60
+ const allColumns = this.gridService.getColumns();
61
+ if ((options === null || options === void 0 ? void 0 : options.columns) && options.columns.length > 0) {
62
+ const keySet = new Set(options.columns);
63
+ const filteredColumns = allColumns.filter((column) => keySet.has(column.key));
64
+ if (filteredColumns.length === 0) {
65
+ logger.warn('DcpGrid: Global search columns do not match any configured columns');
66
+ }
67
+ return filteredColumns;
68
+ }
69
+ return allColumns;
70
+ }
71
+ toSearchValue(value) {
72
+ if (value === null || value === undefined) {
73
+ return '';
74
+ }
75
+ if (value instanceof Date) {
76
+ return value.toISOString();
77
+ }
78
+ return String(value);
79
+ }
80
+ getCellSearchValue(row, column, searchMode) {
81
+ const columnKey = column.key;
82
+ const cell = row[columnKey];
83
+ const mappedCell = (cell && typeof cell === 'object') ? cell : null;
84
+ if (typeof column.globalSearchText === 'function') {
85
+ const customValue = column.globalSearchText(mappedCell, row, searchMode);
86
+ const customText = this.toSearchValue(customValue);
87
+ if (customText.length > 0) {
88
+ return customText;
89
+ }
90
+ }
91
+ if (mappedCell) {
92
+ if (searchMode === 'raw' && 'raw' in mappedCell) {
93
+ return this.toSearchValue(mappedCell.raw);
94
+ }
95
+ if (searchMode === 'display') {
96
+ if ('display' in mappedCell) {
97
+ const displayText = this.toSearchValue(mappedCell.display);
98
+ if (displayText.length > 0) {
99
+ return displayText;
100
+ }
101
+ }
102
+ const formattedDisplay = this.gridService.getCellDisplayValue(row, columnKey);
103
+ if (formattedDisplay.length > 0) {
104
+ return formattedDisplay;
105
+ }
106
+ if ('value' in mappedCell) {
107
+ return this.toSearchValue(mappedCell.value);
108
+ }
109
+ if ('raw' in mappedCell) {
110
+ return this.toSearchValue(mappedCell.raw);
111
+ }
112
+ }
113
+ if ('value' in mappedCell) {
114
+ return this.toSearchValue(mappedCell.value);
115
+ }
116
+ if ('raw' in mappedCell) {
117
+ return this.toSearchValue(mappedCell.raw);
118
+ }
119
+ }
120
+ return this.toSearchValue(cell);
121
+ }
122
+ getCacheSignature(columns, caseInsensitive, searchMode) {
123
+ const columnKeys = columns.map((column) => String(column.key));
124
+ return `${columnKeys.join('|')}::${caseInsensitive ? 'i' : 's'}::${searchMode}`;
125
+ }
126
+ ensureSearchCache(rows, columns, caseInsensitive, searchMode) {
127
+ const signature = this.getCacheSignature(columns, caseInsensitive, searchMode);
128
+ if (this.searchableTextCache !== null &&
129
+ this.cacheSignature === signature &&
130
+ this.searchableTextCache.length === rows.length) {
131
+ return;
132
+ }
133
+ this.searchableTextCache = rows.map(row => {
134
+ const baseText = columns
135
+ .map(column => this.getCellSearchValue(row, column, searchMode))
136
+ .join(' ');
137
+ return caseInsensitive ? baseText.toLowerCase() : baseText;
138
+ });
139
+ this.cacheSignature = signature;
140
+ }
141
+ matchesFilterTree(row) {
142
+ var _a;
143
+ if (!this.filterTree) {
144
+ return true;
145
+ }
146
+ return evaluateFilterTree(this.filterTree, row, {
147
+ caseInsensitive: (_a = this.gridService.gridOptions.caseInsensitiveFiltering) !== null && _a !== void 0 ? _a : false,
148
+ });
149
+ }
150
+ hasFilter(columnKey) {
151
+ const checkNode = (node) => {
152
+ if (node.type === 'condition') {
153
+ return node.columnKey === columnKey;
154
+ }
155
+ if (node.type === 'group') {
156
+ return node.filters.some(checkNode);
157
+ }
158
+ return false;
159
+ };
160
+ if (!this.filterTree)
161
+ return false;
162
+ return checkNode(this.filterTree);
163
+ }
164
+ addFilter(filterNode) {
165
+ const columnKeys = this.getColumnKeys(filterNode);
166
+ // Start with existing filters (if any)
167
+ let newFilters = [];
168
+ if (this.filterTree && this.filterTree.type === 'group') {
169
+ // Remove all filters for the affected column(s)
170
+ newFilters = this.filterTree.filters.filter(f => {
171
+ let keys;
172
+ if (f.type === 'condition') {
173
+ keys = [f.columnKey];
174
+ }
175
+ else if (f.type === 'group') {
176
+ keys = f.filters.reduce((acc, ff) => {
177
+ if (ff.type === 'condition') {
178
+ acc.push(ff.columnKey);
179
+ }
180
+ return acc;
181
+ }, []);
182
+ }
183
+ else {
184
+ keys = [];
185
+ }
186
+ return !keys.some(k => columnKeys.includes(k));
187
+ });
188
+ }
189
+ // Add the new filter node
190
+ newFilters.push(filterNode);
191
+ // Set the new filter tree
192
+ this.setFilterTree({
193
+ type: 'group',
194
+ operator: 'and',
195
+ filters: newFilters
196
+ });
197
+ }
198
+ resetFilters() {
199
+ this.setFilterTree(null);
200
+ }
201
+ resetFilter(columnKey) {
202
+ if (!this.filterTree || this.filterTree.type !== 'group')
203
+ return;
204
+ // Remove any filter node (condition or group) that applies to this column
205
+ const newFilters = this.filterTree.filters.filter(f => !this.getColumnKeys(f).includes(columnKey));
206
+ // If no filters left, set to null
207
+ if (newFilters.length === 0) {
208
+ this.setFilterTree(null);
209
+ }
210
+ else {
211
+ this.setFilterTree(Object.assign(Object.assign({}, this.filterTree), { filters: newFilters }));
212
+ }
213
+ }
214
+ getFilterIndexes() {
215
+ return this.filteredIndexes$.getValue();
216
+ }
217
+ getFilters() {
218
+ if (!this.filterTree || this.filterTree.type !== 'group')
219
+ return [];
220
+ return this.filterTree.filters.filter(f => f.type === 'condition');
221
+ }
222
+ updateFilterIndexes() {
223
+ var _a, _b, _c, _d, _e;
224
+ const rows = this.gridService.getRows();
225
+ if (!this.gridService.shouldApplyClientSideFiltering()) {
226
+ this.filteredIndexes$.next(null);
227
+ this.gridService.setIndexMapping(new Map([...Array(rows.length).keys()].map(i => [i, i])));
228
+ this.gridService.updateRenderedRows();
229
+ return;
230
+ }
231
+ if (!this.filterTree && !this.globalSearch) {
232
+ // No filters: show all rows, identity mapping
233
+ this.filteredIndexes$.next(null);
234
+ this.gridService.setIndexMapping(new Map([...Array(this.gridService.rowCount()).keys()].map(i => [i, i])));
235
+ return;
236
+ }
237
+ let globalSearchQuery = null;
238
+ if (this.globalSearch) {
239
+ const columns = this.getSearchColumns(this.globalSearch.options);
240
+ const caseInsensitive = (_c = (_b = (_a = this.globalSearch.options) === null || _a === void 0 ? void 0 : _a.caseInsensitive) !== null && _b !== void 0 ? _b : this.gridService.gridOptions.caseInsensitiveFiltering) !== null && _c !== void 0 ? _c : false;
241
+ const searchMode = (_e = (_d = this.globalSearch.options) === null || _d === void 0 ? void 0 : _d.searchMode) !== null && _e !== void 0 ? _e : 'value';
242
+ globalSearchQuery = caseInsensitive ? this.globalSearch.query.toLowerCase() : this.globalSearch.query;
243
+ this.ensureSearchCache(rows, columns, caseInsensitive, searchMode);
244
+ }
245
+ // Find all matching row indexes using both filter tree and global search.
246
+ const matchingIndexes = rows
247
+ .map((row, idx) => {
248
+ var _a, _b;
249
+ const matchesTree = this.matchesFilterTree(row);
250
+ if (!matchesTree) {
251
+ return -1;
252
+ }
253
+ if (!globalSearchQuery) {
254
+ return idx;
255
+ }
256
+ const searchableText = (_b = (_a = this.searchableTextCache) === null || _a === void 0 ? void 0 : _a[idx]) !== null && _b !== void 0 ? _b : '';
257
+ return searchableText.includes(globalSearchQuery) ? idx : -1;
258
+ })
259
+ .filter(idx => idx !== -1);
260
+ // Update index mapping for performance and downstream features
261
+ this.gridService.clearIndexMap();
262
+ matchingIndexes.forEach((originalIdx, filteredIdx) => {
263
+ this.gridService.setIndexMappingValue(filteredIdx, originalIdx);
264
+ });
265
+ this.filteredIndexes$.next(matchingIndexes);
266
+ }
267
+ applyFilter(rows) {
268
+ const filteredIndexes = this.filteredIndexes$.getValue();
269
+ if (filteredIndexes === null) {
270
+ return rows;
271
+ }
272
+ this.gridService.clearIndexMap();
273
+ filteredIndexes.forEach((filteredIndex, i) => {
274
+ this.gridService.setIndexMappingValue(i, filteredIndex);
275
+ });
276
+ return filteredIndexes.map(index => rows[index]);
277
+ }
278
+ }
@@ -0,0 +1,17 @@
1
+ import type { GridService } from "../GridService";
2
+ import { Column, DisplayValue, Row } from "../types/grid";
3
+ export declare class FormattingService {
4
+ private readonly gridService;
5
+ constructor(gridService: GridService);
6
+ applyFormattingToRow(row: Row): Row;
7
+ private formatCustomCell;
8
+ private formatDateCell;
9
+ formatCellDisplay(row: Row, column: Column): DisplayValue;
10
+ private transformCustomOrComponent;
11
+ private transformInteger;
12
+ private transformNumber;
13
+ private transformDate;
14
+ private transformDuration;
15
+ private transformString;
16
+ formatCellValue(row: Row, column: Column): any;
17
+ }