@alaarab/ogrid-js 2.1.2 → 2.1.4
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/dist/esm/index.js +6343 -32
- package/package.json +7 -5
- package/dist/esm/OGrid.js +0 -578
- package/dist/esm/OGridEventWiring.js +0 -178
- package/dist/esm/OGridRendering.js +0 -269
- package/dist/esm/components/ColumnChooser.js +0 -91
- package/dist/esm/components/ContextMenu.js +0 -125
- package/dist/esm/components/HeaderFilter.js +0 -281
- package/dist/esm/components/InlineCellEditor.js +0 -434
- package/dist/esm/components/MarchingAntsOverlay.js +0 -156
- package/dist/esm/components/PaginationControls.js +0 -85
- package/dist/esm/components/SideBar.js +0 -353
- package/dist/esm/components/StatusBar.js +0 -34
- package/dist/esm/renderer/TableRenderer.js +0 -846
- package/dist/esm/state/ClipboardState.js +0 -111
- package/dist/esm/state/ColumnPinningState.js +0 -82
- package/dist/esm/state/ColumnReorderState.js +0 -135
- package/dist/esm/state/ColumnResizeState.js +0 -55
- package/dist/esm/state/EventEmitter.js +0 -28
- package/dist/esm/state/FillHandleState.js +0 -206
- package/dist/esm/state/GridState.js +0 -324
- package/dist/esm/state/HeaderFilterState.js +0 -213
- package/dist/esm/state/KeyboardNavState.js +0 -216
- package/dist/esm/state/RowSelectionState.js +0 -72
- package/dist/esm/state/SelectionState.js +0 -109
- package/dist/esm/state/SideBarState.js +0 -41
- package/dist/esm/state/TableLayoutState.js +0 -97
- package/dist/esm/state/UndoRedoState.js +0 -71
- package/dist/esm/state/VirtualScrollState.js +0 -128
- package/dist/esm/types/columnTypes.js +0 -1
- package/dist/esm/types/gridTypes.js +0 -1
- package/dist/esm/types/index.js +0 -2
- package/dist/esm/utils/debounce.js +0 -2
- package/dist/esm/utils/getCellCoordinates.js +0 -15
- package/dist/esm/utils/index.js +0 -2
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
import { flattenColumns, processClientSideData, exportToCsv as coreExportToCsv, getCellValue, deriveFilterOptionsFromData, mergeFilter, validateColumns, validateRowIds, } from '@alaarab/ogrid-core';
|
|
2
|
-
import { EventEmitter } from './EventEmitter';
|
|
3
|
-
export class GridState {
|
|
4
|
-
constructor(options) {
|
|
5
|
-
this.emitter = new EventEmitter();
|
|
6
|
-
this._data = [];
|
|
7
|
-
this._filters = {};
|
|
8
|
-
this._isLoading = false;
|
|
9
|
-
this._serverItems = [];
|
|
10
|
-
this._serverTotalCount = 0;
|
|
11
|
-
this._fetchId = 0; // Guards against stale fetch responses
|
|
12
|
-
this._abortController = null; // Cancels in-flight fetch requests
|
|
13
|
-
this._firstDataRendered = false;
|
|
14
|
-
// Filter options for client-side data (used by sidebar filters panel & header filter popovers)
|
|
15
|
-
this._filterOptions = {};
|
|
16
|
-
// Column display order (array of columnIds)
|
|
17
|
-
this._columnOrder = [];
|
|
18
|
-
// Dirty-flag memoization for visibleColumnDefs getter
|
|
19
|
-
this._visibleColsCache = null;
|
|
20
|
-
this._visibleColsDirty = true;
|
|
21
|
-
this._allColumns = options.columns;
|
|
22
|
-
this._columns = flattenColumns(options.columns);
|
|
23
|
-
this._getRowId = options.getRowId;
|
|
24
|
-
this._data = options.data ?? [];
|
|
25
|
-
this._dataSource = options.dataSource;
|
|
26
|
-
this._page = options.page ?? 1;
|
|
27
|
-
this._pageSize = options.pageSize ?? 20;
|
|
28
|
-
this._sort = options.sort;
|
|
29
|
-
this._filters = options.filters ?? {};
|
|
30
|
-
this._visibleColumns = options.visibleColumns ?? new Set(this._columns.map(c => c.columnId));
|
|
31
|
-
this._columnOrder = this._columns.map(c => c.columnId);
|
|
32
|
-
this._onError = options.onError;
|
|
33
|
-
this._onFirstDataRendered = options.onFirstDataRendered;
|
|
34
|
-
this._rowHeight = options.rowHeight;
|
|
35
|
-
this._ariaLabel = options.ariaLabel;
|
|
36
|
-
this._stickyHeader = options.stickyHeader ?? true;
|
|
37
|
-
this._fullScreen = options.fullScreen ?? false;
|
|
38
|
-
// Derive initial filter options for client-side data
|
|
39
|
-
if (!this._dataSource) {
|
|
40
|
-
this._filterOptions = deriveFilterOptionsFromData(this._data, this._columns);
|
|
41
|
-
}
|
|
42
|
-
// Runtime validation — runs once at construction
|
|
43
|
-
validateColumns(this._columns);
|
|
44
|
-
if (!this._dataSource && this._data.length > 0) {
|
|
45
|
-
validateRowIds(this._data, this._getRowId);
|
|
46
|
-
this._firstDataRendered = true;
|
|
47
|
-
}
|
|
48
|
-
// If server-side, trigger initial fetch
|
|
49
|
-
if (this._dataSource) {
|
|
50
|
-
this._isLoading = true;
|
|
51
|
-
this.fetchServerData();
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
// --- Getters ---
|
|
55
|
-
get data() { return this._data; }
|
|
56
|
-
get page() { return this._page; }
|
|
57
|
-
get pageSize() { return this._pageSize; }
|
|
58
|
-
get sort() { return this._sort; }
|
|
59
|
-
get filters() { return this._filters; }
|
|
60
|
-
get visibleColumns() { return this._visibleColumns; }
|
|
61
|
-
get isLoading() { return this._isLoading; }
|
|
62
|
-
get columns() { return this._columns; }
|
|
63
|
-
get allColumns() { return this._allColumns; }
|
|
64
|
-
get getRowId() { return this._getRowId; }
|
|
65
|
-
get isServerSide() { return this._dataSource != null; }
|
|
66
|
-
get stickyHeader() { return this._stickyHeader; }
|
|
67
|
-
get fullScreen() { return this._fullScreen; }
|
|
68
|
-
get filterOptions() { return this._filterOptions; }
|
|
69
|
-
get columnOrder() { return this._columnOrder; }
|
|
70
|
-
get rowHeight() { return this._rowHeight; }
|
|
71
|
-
get ariaLabel() { return this._ariaLabel; }
|
|
72
|
-
/** Get the visible columns in display order (respects column reorder). Memoized via dirty flag. */
|
|
73
|
-
get visibleColumnDefs() {
|
|
74
|
-
if (!this._visibleColsDirty && this._visibleColsCache)
|
|
75
|
-
return this._visibleColsCache;
|
|
76
|
-
const visible = this._columns.filter(c => this._visibleColumns.has(c.columnId));
|
|
77
|
-
if (this._columnOrder.length === 0) {
|
|
78
|
-
this._visibleColsCache = visible;
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
const orderMap = new Map(this._columnOrder.map((id, idx) => [id, idx]));
|
|
82
|
-
this._visibleColsCache = [...visible].sort((a, b) => {
|
|
83
|
-
const ai = orderMap.get(a.columnId) ?? Infinity;
|
|
84
|
-
const bi = orderMap.get(b.columnId) ?? Infinity;
|
|
85
|
-
return ai - bi;
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
this._visibleColsDirty = false;
|
|
89
|
-
return this._visibleColsCache;
|
|
90
|
-
}
|
|
91
|
-
/** Get processed (sorted, filtered, paginated) items for current page. */
|
|
92
|
-
getProcessedItems() {
|
|
93
|
-
if (this.isServerSide) {
|
|
94
|
-
return { items: this._serverItems, totalCount: this._serverTotalCount };
|
|
95
|
-
}
|
|
96
|
-
const filtered = processClientSideData(this._data, this._columns, this._filters, this._sort?.field, this._sort?.direction);
|
|
97
|
-
const totalCount = filtered.length;
|
|
98
|
-
const startIdx = (this._page - 1) * this._pageSize;
|
|
99
|
-
const endIdx = startIdx + this._pageSize;
|
|
100
|
-
const items = filtered.slice(startIdx, endIdx);
|
|
101
|
-
return { items, totalCount };
|
|
102
|
-
}
|
|
103
|
-
// --- Server-side fetch ---
|
|
104
|
-
fetchServerData() {
|
|
105
|
-
if (!this._dataSource)
|
|
106
|
-
return;
|
|
107
|
-
// Cancel any in-flight request before starting a new one
|
|
108
|
-
if (this._abortController) {
|
|
109
|
-
this._abortController.abort();
|
|
110
|
-
}
|
|
111
|
-
const id = ++this._fetchId;
|
|
112
|
-
this._abortController = new AbortController();
|
|
113
|
-
const currentController = this._abortController;
|
|
114
|
-
this._isLoading = true;
|
|
115
|
-
this.emitter.emit('stateChange', { type: 'loading' });
|
|
116
|
-
this._dataSource
|
|
117
|
-
.fetchPage({
|
|
118
|
-
page: this._page,
|
|
119
|
-
pageSize: this._pageSize,
|
|
120
|
-
sort: this._sort ? { field: this._sort.field, direction: this._sort.direction } : undefined,
|
|
121
|
-
filters: this._filters,
|
|
122
|
-
})
|
|
123
|
-
.then((res) => {
|
|
124
|
-
// Ignore if this request was superseded by a newer one
|
|
125
|
-
if (id !== this._fetchId || currentController.signal.aborted)
|
|
126
|
-
return;
|
|
127
|
-
this._serverItems = res.items;
|
|
128
|
-
this._serverTotalCount = res.totalCount;
|
|
129
|
-
this._isLoading = false;
|
|
130
|
-
if (!this._firstDataRendered && res.items.length > 0) {
|
|
131
|
-
this._firstDataRendered = true;
|
|
132
|
-
validateRowIds(res.items, this._getRowId);
|
|
133
|
-
this._onFirstDataRendered?.();
|
|
134
|
-
}
|
|
135
|
-
this.emitter.emit('stateChange', { type: 'data' });
|
|
136
|
-
})
|
|
137
|
-
.catch((err) => {
|
|
138
|
-
// Ignore if this request was superseded or aborted
|
|
139
|
-
if (id !== this._fetchId || currentController.signal.aborted)
|
|
140
|
-
return;
|
|
141
|
-
this._onError?.(err);
|
|
142
|
-
this._serverItems = [];
|
|
143
|
-
this._serverTotalCount = 0;
|
|
144
|
-
this._isLoading = false;
|
|
145
|
-
this.emitter.emit('stateChange', { type: 'data' });
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
// --- Setters ---
|
|
149
|
-
setData(data) {
|
|
150
|
-
this._data = data;
|
|
151
|
-
if (!this.isServerSide) {
|
|
152
|
-
this._filterOptions = deriveFilterOptionsFromData(data, this._columns);
|
|
153
|
-
}
|
|
154
|
-
this.emitter.emit('stateChange', { type: 'data' });
|
|
155
|
-
}
|
|
156
|
-
setPage(page) {
|
|
157
|
-
this._page = page;
|
|
158
|
-
if (this.isServerSide) {
|
|
159
|
-
this.fetchServerData();
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
this.emitter.emit('stateChange', { type: 'page' });
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
setPageSize(pageSize) {
|
|
166
|
-
this._pageSize = pageSize;
|
|
167
|
-
this._page = 1;
|
|
168
|
-
if (this.isServerSide) {
|
|
169
|
-
this.fetchServerData();
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
this.emitter.emit('stateChange', { type: 'page' });
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
setSort(sort) {
|
|
176
|
-
this._sort = sort;
|
|
177
|
-
this._page = 1;
|
|
178
|
-
if (this.isServerSide) {
|
|
179
|
-
this.fetchServerData();
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
this.emitter.emit('stateChange', { type: 'sort' });
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
toggleSort(field) {
|
|
186
|
-
if (this._sort?.field === field) {
|
|
187
|
-
this._sort = this._sort.direction === 'asc'
|
|
188
|
-
? { field, direction: 'desc' }
|
|
189
|
-
: undefined;
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
this._sort = { field, direction: 'asc' };
|
|
193
|
-
}
|
|
194
|
-
this._page = 1;
|
|
195
|
-
if (this.isServerSide) {
|
|
196
|
-
this.fetchServerData();
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
this.emitter.emit('stateChange', { type: 'sort' });
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
setFilter(key, value) {
|
|
203
|
-
this._filters = mergeFilter(this._filters, key, value);
|
|
204
|
-
this._page = 1;
|
|
205
|
-
if (this.isServerSide) {
|
|
206
|
-
this.fetchServerData();
|
|
207
|
-
}
|
|
208
|
-
else {
|
|
209
|
-
this.emitter.emit('stateChange', { type: 'filter' });
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
clearFilters() {
|
|
213
|
-
this._filters = {};
|
|
214
|
-
this._page = 1;
|
|
215
|
-
if (this.isServerSide) {
|
|
216
|
-
this.fetchServerData();
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
this.emitter.emit('stateChange', { type: 'filter' });
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
setVisibleColumns(columns) {
|
|
223
|
-
this._visibleColumns = columns;
|
|
224
|
-
this._visibleColsDirty = true;
|
|
225
|
-
this.emitter.emit('stateChange', { type: 'columns' });
|
|
226
|
-
}
|
|
227
|
-
setColumnOrder(order) {
|
|
228
|
-
this._columnOrder = order;
|
|
229
|
-
this._visibleColsDirty = true;
|
|
230
|
-
this.emitter.emit('stateChange', { type: 'columns' });
|
|
231
|
-
}
|
|
232
|
-
setLoading(loading) {
|
|
233
|
-
this._isLoading = loading;
|
|
234
|
-
this.emitter.emit('stateChange', { type: 'loading' });
|
|
235
|
-
}
|
|
236
|
-
refreshData() {
|
|
237
|
-
if (this.isServerSide) {
|
|
238
|
-
this.fetchServerData();
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
// --- Event subscription ---
|
|
242
|
-
onStateChange(handler) {
|
|
243
|
-
this.emitter.on('stateChange', handler);
|
|
244
|
-
return () => this.emitter.off('stateChange', handler);
|
|
245
|
-
}
|
|
246
|
-
// --- API ---
|
|
247
|
-
getApi() {
|
|
248
|
-
return {
|
|
249
|
-
setRowData: (data) => {
|
|
250
|
-
if (!this.isServerSide)
|
|
251
|
-
this.setData(data);
|
|
252
|
-
},
|
|
253
|
-
setLoading: (loading) => this.setLoading(loading),
|
|
254
|
-
getColumnState: () => ({
|
|
255
|
-
visibleColumns: Array.from(this._visibleColumns),
|
|
256
|
-
sort: this._sort,
|
|
257
|
-
filters: Object.keys(this._filters).length > 0 ? this._filters : undefined,
|
|
258
|
-
}),
|
|
259
|
-
applyColumnState: (state) => {
|
|
260
|
-
if (state.visibleColumns)
|
|
261
|
-
this._visibleColumns = new Set(state.visibleColumns);
|
|
262
|
-
if (state.sort !== undefined)
|
|
263
|
-
this._sort = state.sort;
|
|
264
|
-
if (state.filters !== undefined)
|
|
265
|
-
this._filters = state.filters ?? {};
|
|
266
|
-
if (this.isServerSide) {
|
|
267
|
-
this.fetchServerData();
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
this.emitter.emit('stateChange', { type: 'columns' });
|
|
271
|
-
}
|
|
272
|
-
},
|
|
273
|
-
setFilterModel: (filters) => {
|
|
274
|
-
this._filters = filters;
|
|
275
|
-
this._page = 1;
|
|
276
|
-
if (this.isServerSide) {
|
|
277
|
-
this.fetchServerData();
|
|
278
|
-
}
|
|
279
|
-
else {
|
|
280
|
-
this.emitter.emit('stateChange', { type: 'filter' });
|
|
281
|
-
}
|
|
282
|
-
},
|
|
283
|
-
getSelectedRows: () => [],
|
|
284
|
-
setSelectedRows: () => { },
|
|
285
|
-
selectAll: () => { },
|
|
286
|
-
deselectAll: () => { },
|
|
287
|
-
clearFilters: () => this.clearFilters(),
|
|
288
|
-
clearSort: () => this.setSort(undefined),
|
|
289
|
-
resetGridState: () => {
|
|
290
|
-
this.clearFilters();
|
|
291
|
-
this.setSort(undefined);
|
|
292
|
-
},
|
|
293
|
-
getDisplayedRows: () => this.getProcessedItems().items,
|
|
294
|
-
refreshData: () => this.refreshData(),
|
|
295
|
-
// scrollToRow is wired by OGrid after construction when virtualScrollState is present.
|
|
296
|
-
// This stub is replaced by OGrid.ts (see "Wire scrollToRow API method") for virtual scroll.
|
|
297
|
-
// For non-virtual grids it remains a no-op (native browser scroll handles row visibility).
|
|
298
|
-
scrollToRow: () => { },
|
|
299
|
-
getColumnOrder: () => [...this._columnOrder],
|
|
300
|
-
setColumnOrder: (order) => this.setColumnOrder(order),
|
|
301
|
-
exportToCsv: (filename) => {
|
|
302
|
-
const { items } = this.getProcessedItems();
|
|
303
|
-
const cols = this.visibleColumnDefs.map(c => ({ columnId: c.columnId, name: c.name }));
|
|
304
|
-
coreExportToCsv(items, cols, (item, colId) => {
|
|
305
|
-
const col = this._columns.find(c => c.columnId === colId);
|
|
306
|
-
if (!col)
|
|
307
|
-
return '';
|
|
308
|
-
const val = getCellValue(item, col);
|
|
309
|
-
if (col.valueFormatter)
|
|
310
|
-
return col.valueFormatter(val, item);
|
|
311
|
-
return val != null ? String(val) : '';
|
|
312
|
-
}, filename);
|
|
313
|
-
},
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
destroy() {
|
|
317
|
-
// Cancel any in-flight fetch request
|
|
318
|
-
if (this._abortController) {
|
|
319
|
-
this._abortController.abort();
|
|
320
|
-
this._abortController = null;
|
|
321
|
-
}
|
|
322
|
-
this.emitter.removeAllListeners();
|
|
323
|
-
}
|
|
324
|
-
}
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from './EventEmitter';
|
|
2
|
-
/**
|
|
3
|
-
* Manages header filter popover state for all columns.
|
|
4
|
-
* Equivalent of React's useColumnHeaderFilterState, but class-based.
|
|
5
|
-
*/
|
|
6
|
-
export class HeaderFilterState {
|
|
7
|
-
constructor(onFilterChange) {
|
|
8
|
-
this.emitter = new EventEmitter();
|
|
9
|
-
// Which column's filter is currently open (null = none)
|
|
10
|
-
this._openColumnId = null;
|
|
11
|
-
// Temporary state for the currently open filter popover
|
|
12
|
-
this._tempTextValue = '';
|
|
13
|
-
this._tempSelected = new Set();
|
|
14
|
-
this._tempDateFrom = '';
|
|
15
|
-
this._tempDateTo = '';
|
|
16
|
-
this._searchText = '';
|
|
17
|
-
// Popover position
|
|
18
|
-
this._popoverPosition = null;
|
|
19
|
-
// External references
|
|
20
|
-
this._filters = {};
|
|
21
|
-
this._filterOptions = {};
|
|
22
|
-
// Click-outside handler
|
|
23
|
-
this._clickOutsideHandler = null;
|
|
24
|
-
this._escapeHandler = null;
|
|
25
|
-
this._popoverEl = null;
|
|
26
|
-
this._headerEl = null;
|
|
27
|
-
this._onFilterChange = onFilterChange;
|
|
28
|
-
}
|
|
29
|
-
get openColumnId() { return this._openColumnId; }
|
|
30
|
-
get tempTextValue() { return this._tempTextValue; }
|
|
31
|
-
get tempSelected() { return this._tempSelected; }
|
|
32
|
-
get tempDateFrom() { return this._tempDateFrom; }
|
|
33
|
-
get tempDateTo() { return this._tempDateTo; }
|
|
34
|
-
get searchText() { return this._searchText; }
|
|
35
|
-
get popoverPosition() { return this._popoverPosition; }
|
|
36
|
-
setFilters(filters) {
|
|
37
|
-
this._filters = filters;
|
|
38
|
-
}
|
|
39
|
-
setFilterOptions(options) {
|
|
40
|
-
this._filterOptions = options;
|
|
41
|
-
}
|
|
42
|
-
/** Allow OGrid to update the popover element reference after rendering (for click-outside detection). */
|
|
43
|
-
setPopoverEl(el) {
|
|
44
|
-
this._popoverEl = el;
|
|
45
|
-
}
|
|
46
|
-
getFilterOptions(filterField) {
|
|
47
|
-
return this._filterOptions[filterField] ?? [];
|
|
48
|
-
}
|
|
49
|
-
getFilteredOptions(filterField) {
|
|
50
|
-
const options = this.getFilterOptions(filterField);
|
|
51
|
-
if (!this._searchText)
|
|
52
|
-
return options;
|
|
53
|
-
const lower = this._searchText.toLowerCase();
|
|
54
|
-
return options.filter(opt => opt.toLowerCase().includes(lower));
|
|
55
|
-
}
|
|
56
|
-
hasActiveFilter(config) {
|
|
57
|
-
const fv = this._filters[config.filterField];
|
|
58
|
-
if (!fv)
|
|
59
|
-
return false;
|
|
60
|
-
if (fv.type === 'text')
|
|
61
|
-
return fv.value.trim().length > 0;
|
|
62
|
-
if (fv.type === 'multiSelect')
|
|
63
|
-
return fv.value.length > 0;
|
|
64
|
-
if (fv.type === 'date')
|
|
65
|
-
return !!(fv.value.from || fv.value.to);
|
|
66
|
-
if (fv.type === 'people')
|
|
67
|
-
return !!fv.value;
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Open a filter popover for a specific column.
|
|
72
|
-
*/
|
|
73
|
-
open(columnId, config, headerEl, popoverEl) {
|
|
74
|
-
// Close any existing popover first
|
|
75
|
-
if (this._openColumnId) {
|
|
76
|
-
this.close();
|
|
77
|
-
}
|
|
78
|
-
this._openColumnId = columnId;
|
|
79
|
-
this._headerEl = headerEl;
|
|
80
|
-
this._popoverEl = popoverEl;
|
|
81
|
-
// Initialize temp state from current filter values
|
|
82
|
-
const fv = this._filters[config.filterField];
|
|
83
|
-
if (config.filterType === 'text') {
|
|
84
|
-
this._tempTextValue = fv?.type === 'text' ? fv.value : '';
|
|
85
|
-
}
|
|
86
|
-
else if (config.filterType === 'multiSelect') {
|
|
87
|
-
this._tempSelected = new Set(fv?.type === 'multiSelect' ? fv.value : []);
|
|
88
|
-
}
|
|
89
|
-
else if (config.filterType === 'date') {
|
|
90
|
-
const dv = fv?.type === 'date' ? fv.value : {};
|
|
91
|
-
this._tempDateFrom = dv.from ?? '';
|
|
92
|
-
this._tempDateTo = dv.to ?? '';
|
|
93
|
-
}
|
|
94
|
-
this._searchText = '';
|
|
95
|
-
// Compute position
|
|
96
|
-
const rect = headerEl.getBoundingClientRect();
|
|
97
|
-
this._popoverPosition = { top: rect.bottom + 4, left: rect.left };
|
|
98
|
-
// Set up click-outside listener
|
|
99
|
-
this._clickOutsideHandler = (e) => {
|
|
100
|
-
const target = e.target;
|
|
101
|
-
if (this._popoverEl && !this._popoverEl.contains(target) &&
|
|
102
|
-
this._headerEl && !this._headerEl.contains(target)) {
|
|
103
|
-
this.close();
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
this._escapeHandler = (e) => {
|
|
107
|
-
if (e.key === 'Escape') {
|
|
108
|
-
e.preventDefault();
|
|
109
|
-
e.stopPropagation();
|
|
110
|
-
this.close();
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
setTimeout(() => {
|
|
114
|
-
if (this._clickOutsideHandler) {
|
|
115
|
-
document.addEventListener('mousedown', this._clickOutsideHandler, { passive: true });
|
|
116
|
-
}
|
|
117
|
-
}, 0);
|
|
118
|
-
if (this._escapeHandler) {
|
|
119
|
-
document.addEventListener('keydown', this._escapeHandler, true);
|
|
120
|
-
}
|
|
121
|
-
this.emitter.emit('change');
|
|
122
|
-
}
|
|
123
|
-
close() {
|
|
124
|
-
this._openColumnId = null;
|
|
125
|
-
this._popoverPosition = null;
|
|
126
|
-
this._popoverEl = null;
|
|
127
|
-
this._headerEl = null;
|
|
128
|
-
if (this._clickOutsideHandler) {
|
|
129
|
-
document.removeEventListener('mousedown', this._clickOutsideHandler);
|
|
130
|
-
this._clickOutsideHandler = null;
|
|
131
|
-
}
|
|
132
|
-
if (this._escapeHandler) {
|
|
133
|
-
document.removeEventListener('keydown', this._escapeHandler, true);
|
|
134
|
-
this._escapeHandler = null;
|
|
135
|
-
}
|
|
136
|
-
this.emitter.emit('change');
|
|
137
|
-
}
|
|
138
|
-
// --- Temp state setters ---
|
|
139
|
-
setTempTextValue(v) {
|
|
140
|
-
this._tempTextValue = v;
|
|
141
|
-
this.emitter.emit('change');
|
|
142
|
-
}
|
|
143
|
-
setSearchText(v) {
|
|
144
|
-
this._searchText = v;
|
|
145
|
-
this.emitter.emit('change');
|
|
146
|
-
}
|
|
147
|
-
setTempDateFrom(v) {
|
|
148
|
-
this._tempDateFrom = v;
|
|
149
|
-
this.emitter.emit('change');
|
|
150
|
-
}
|
|
151
|
-
setTempDateTo(v) {
|
|
152
|
-
this._tempDateTo = v;
|
|
153
|
-
this.emitter.emit('change');
|
|
154
|
-
}
|
|
155
|
-
// --- Checkbox handlers ---
|
|
156
|
-
handleCheckboxChange(option, checked) {
|
|
157
|
-
const next = new Set(this._tempSelected);
|
|
158
|
-
if (checked)
|
|
159
|
-
next.add(option);
|
|
160
|
-
else
|
|
161
|
-
next.delete(option);
|
|
162
|
-
this._tempSelected = next;
|
|
163
|
-
this.emitter.emit('change');
|
|
164
|
-
}
|
|
165
|
-
handleSelectAll(filterField) {
|
|
166
|
-
this._tempSelected = new Set(this.getFilterOptions(filterField));
|
|
167
|
-
this.emitter.emit('change');
|
|
168
|
-
}
|
|
169
|
-
handleClearSelection() {
|
|
170
|
-
this._tempSelected = new Set();
|
|
171
|
-
this.emitter.emit('change');
|
|
172
|
-
}
|
|
173
|
-
// --- Apply/Clear ---
|
|
174
|
-
applyTextFilter(filterField) {
|
|
175
|
-
const value = this._tempTextValue.trim();
|
|
176
|
-
this._onFilterChange(filterField, value ? { type: 'text', value } : undefined);
|
|
177
|
-
this.close();
|
|
178
|
-
}
|
|
179
|
-
clearTextFilter(filterField) {
|
|
180
|
-
this._tempTextValue = '';
|
|
181
|
-
this._onFilterChange(filterField, undefined);
|
|
182
|
-
this.close();
|
|
183
|
-
}
|
|
184
|
-
applyMultiSelectFilter(filterField) {
|
|
185
|
-
const arr = Array.from(this._tempSelected);
|
|
186
|
-
this._onFilterChange(filterField, arr.length > 0 ? { type: 'multiSelect', value: arr } : undefined);
|
|
187
|
-
this.close();
|
|
188
|
-
}
|
|
189
|
-
applyDateFilter(filterField) {
|
|
190
|
-
const from = this._tempDateFrom || undefined;
|
|
191
|
-
const to = this._tempDateTo || undefined;
|
|
192
|
-
this._onFilterChange(filterField, from || to ? { type: 'date', value: { from, to } } : undefined);
|
|
193
|
-
this.close();
|
|
194
|
-
}
|
|
195
|
-
clearDateFilter(filterField) {
|
|
196
|
-
this._tempDateFrom = '';
|
|
197
|
-
this._tempDateTo = '';
|
|
198
|
-
this._onFilterChange(filterField, undefined);
|
|
199
|
-
this.close();
|
|
200
|
-
}
|
|
201
|
-
clearFilter(filterField) {
|
|
202
|
-
this._onFilterChange(filterField, undefined);
|
|
203
|
-
this.close();
|
|
204
|
-
}
|
|
205
|
-
onChange(handler) {
|
|
206
|
-
this.emitter.on('change', handler);
|
|
207
|
-
return () => this.emitter.off('change', handler);
|
|
208
|
-
}
|
|
209
|
-
destroy() {
|
|
210
|
-
this.close();
|
|
211
|
-
this.emitter.removeAllListeners();
|
|
212
|
-
}
|
|
213
|
-
}
|