@alaarab/ogrid-angular 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.
Files changed (32) hide show
  1. package/dist/esm/index.js +4606 -30
  2. package/dist/types/components/base-datagrid-table.component.d.ts +8 -8
  3. package/package.json +4 -4
  4. package/dist/esm/components/base-column-chooser.component.js +0 -78
  5. package/dist/esm/components/base-column-header-filter.component.js +0 -281
  6. package/dist/esm/components/base-datagrid-table.component.js +0 -648
  7. package/dist/esm/components/base-inline-cell-editor.component.js +0 -253
  8. package/dist/esm/components/base-ogrid.component.js +0 -36
  9. package/dist/esm/components/base-pagination-controls.component.js +0 -72
  10. package/dist/esm/components/base-popover-cell-editor.component.js +0 -114
  11. package/dist/esm/components/empty-state.component.js +0 -58
  12. package/dist/esm/components/grid-context-menu.component.js +0 -153
  13. package/dist/esm/components/inline-cell-editor-template.js +0 -107
  14. package/dist/esm/components/marching-ants-overlay.component.js +0 -164
  15. package/dist/esm/components/ogrid-layout.component.js +0 -188
  16. package/dist/esm/components/sidebar.component.js +0 -274
  17. package/dist/esm/components/status-bar.component.js +0 -71
  18. package/dist/esm/services/column-reorder.service.js +0 -180
  19. package/dist/esm/services/datagrid-editing.service.js +0 -52
  20. package/dist/esm/services/datagrid-interaction.service.js +0 -667
  21. package/dist/esm/services/datagrid-layout.service.js +0 -151
  22. package/dist/esm/services/datagrid-state.service.js +0 -591
  23. package/dist/esm/services/ogrid.service.js +0 -746
  24. package/dist/esm/services/virtual-scroll.service.js +0 -91
  25. package/dist/esm/styles/ogrid-theme-vars.js +0 -53
  26. package/dist/esm/types/columnTypes.js +0 -1
  27. package/dist/esm/types/dataGridTypes.js +0 -1
  28. package/dist/esm/types/index.js +0 -1
  29. package/dist/esm/utils/dataGridViewModel.js +0 -6
  30. package/dist/esm/utils/debounce.js +0 -68
  31. package/dist/esm/utils/index.js +0 -8
  32. package/dist/esm/utils/latestRef.js +0 -41
package/dist/esm/index.js CHANGED
@@ -1,31 +1,4607 @@
1
- // Re-export core types + utils
1
+ import { flattenColumns, getMultiSelectFilterFields, deriveFilterOptionsFromData, processClientSideData, validateColumns, validateRowIds, computeNextSortState, mergeFilter, CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, parseValue, UndoRedoStack, rangesEqual, normalizeSelectionRange, formatSelectionAsTsv, parseTsvClipboard, getCellValue, computeTabNavigation, computeAggregations, getDataGridStatusBarConfig, computeVisibleRange, computeTotalHeight, getScrollTopForRow, GRID_BORDER_RADIUS, getStatusBarParts, GRID_CONTEXT_MENU_ITEMS, formatShortcut, injectGlobalStyles, buildHeaderRows, getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildPopoverEditorProps, measureColumnContentWidth, getPaginationViewModel, ROW_NUMBER_COLUMN_WIDTH, reorderColumnArray, findCtrlArrowTarget, measureRange } from '@alaarab/ogrid-core';
2
2
  export * from '@alaarab/ogrid-core';
3
- // Explicitly re-export constants for test resolution
4
- export { CHECKBOX_COLUMN_WIDTH, ROW_NUMBER_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, GRID_BORDER_RADIUS, PEOPLE_SEARCH_DEBOUNCE_MS, DEFAULT_DEBOUNCE_MS, SIDEBAR_TRANSITION_MS, Z_INDEX, } from '@alaarab/ogrid-core';
5
- export { toUserLike, isInSelectionRange, normalizeSelectionRange } from './types';
6
- // Services
7
- export { OGridService } from './services/ogrid.service';
8
- export { DataGridStateService } from './services/datagrid-state.service';
9
- export { DataGridLayoutHelper } from './services/datagrid-layout.service';
10
- export { DataGridEditingHelper } from './services/datagrid-editing.service';
11
- export { DataGridInteractionHelper } from './services/datagrid-interaction.service';
12
- export { ColumnReorderService } from './services/column-reorder.service';
13
- export { VirtualScrollService } from './services/virtual-scroll.service';
14
- // Components
15
- export { OGridLayoutComponent } from './components/ogrid-layout.component';
16
- export { StatusBarComponent } from './components/status-bar.component';
17
- export { GridContextMenuComponent } from './components/grid-context-menu.component';
18
- export { SideBarComponent } from './components/sidebar.component';
19
- export { MarchingAntsOverlayComponent } from './components/marching-ants-overlay.component';
20
- export { EmptyStateComponent } from './components/empty-state.component';
21
- export { BaseOGridComponent } from './components/base-ogrid.component';
22
- export { BaseDataGridTableComponent } from './components/base-datagrid-table.component';
23
- export { BaseColumnHeaderFilterComponent } from './components/base-column-header-filter.component';
24
- export { BaseColumnChooserComponent } from './components/base-column-chooser.component';
25
- export { BasePaginationControlsComponent } from './components/base-pagination-controls.component';
26
- export { BaseInlineCellEditorComponent } from './components/base-inline-cell-editor.component';
27
- export { INLINE_CELL_EDITOR_TEMPLATE, INLINE_CELL_EDITOR_STYLES } from './components/inline-cell-editor-template';
28
- export { BasePopoverCellEditorComponent, POPOVER_CELL_EDITOR_TEMPLATE, POPOVER_CELL_EDITOR_OVERLAY_STYLES } from './components/base-popover-cell-editor.component';
29
- // Shared styles
30
- export { OGRID_THEME_VARS_CSS } from './styles/ogrid-theme-vars';
31
- export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, createDebouncedSignal, createDebouncedCallback, debounce, createLatestCallback, } from './utils';
3
+ export { CELL_PADDING, CHECKBOX_COLUMN_WIDTH, DEFAULT_DEBOUNCE_MS, DEFAULT_MIN_COLUMN_WIDTH, GRID_BORDER_RADIUS, PEOPLE_SEARCH_DEBOUNCE_MS, ROW_NUMBER_COLUMN_WIDTH, SIDEBAR_TRANSITION_MS, Z_INDEX, debounce, getCellRenderDescriptor, getHeaderFilterConfig, isInSelectionRange, normalizeSelectionRange, resolveCellDisplayContent, resolveCellStyle, toUserLike } from '@alaarab/ogrid-core';
4
+ import { Injectable, Input, Component, ChangeDetectionStrategy, ViewEncapsulation, Output, ViewChild, inject, DestroyRef, signal, computed, effect, NgZone, EventEmitter, Injector, EnvironmentInjector, createComponent } from '@angular/core';
5
+ import { NgTemplateOutlet } from '@angular/common';
6
+
7
+ var __defProp = Object.defineProperty;
8
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
+ var __decorateClass = (decorators, target, key, kind) => {
10
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
11
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
12
+ if (decorator = decorators[i])
13
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
14
+ if (kind && result) __defProp(target, key, result);
15
+ return result;
16
+ };
17
+ var DEFAULT_PAGE_SIZE = 25;
18
+ var EMPTY_LOADING_OPTIONS = {};
19
+ var DEFAULT_PANELS = ["columns", "filters"];
20
+ var OGridService = class {
21
+ constructor() {
22
+ this.destroyRef = inject(DestroyRef);
23
+ // --- Input signals (set by the component consuming this service) ---
24
+ this.columnsProp = signal([]);
25
+ this.getRowId = signal((item) => item["id"]);
26
+ this.data = signal(void 0);
27
+ this.dataSource = signal(void 0);
28
+ this.controlledPage = signal(void 0);
29
+ this.controlledPageSize = signal(void 0);
30
+ this.controlledSort = signal(void 0);
31
+ this.controlledFilters = signal(void 0);
32
+ this.controlledVisibleColumns = signal(void 0);
33
+ this.controlledLoading = signal(void 0);
34
+ this.onPageChange = signal(void 0);
35
+ this.onPageSizeChange = signal(void 0);
36
+ this.onSortChange = signal(void 0);
37
+ this.onFiltersChange = signal(void 0);
38
+ this.onVisibleColumnsChange = signal(void 0);
39
+ this.columnOrder = signal(void 0);
40
+ this.onColumnOrderChange = signal(void 0);
41
+ this.onColumnResized = signal(void 0);
42
+ this.onAutosizeColumn = signal(void 0);
43
+ this.onColumnPinned = signal(void 0);
44
+ this.defaultPageSize = signal(DEFAULT_PAGE_SIZE);
45
+ this.defaultSortBy = signal(void 0);
46
+ this.defaultSortDirection = signal("asc");
47
+ this.toolbar = signal(void 0);
48
+ this.toolbarBelow = signal(void 0);
49
+ this.emptyState = signal(void 0);
50
+ this.entityLabelPlural = signal("items");
51
+ this.className = signal(void 0);
52
+ this.layoutMode = signal("fill");
53
+ this.suppressHorizontalScroll = signal(void 0);
54
+ this.stickyHeader = signal(true);
55
+ this.fullScreen = signal(false);
56
+ this.editable = signal(void 0);
57
+ this.cellSelection = signal(void 0);
58
+ this.density = signal("normal");
59
+ this.rowHeight = signal(void 0);
60
+ this.onCellValueChanged = signal(void 0);
61
+ this.onUndo = signal(void 0);
62
+ this.onRedo = signal(void 0);
63
+ this.canUndo = signal(void 0);
64
+ this.canRedo = signal(void 0);
65
+ this.rowSelection = signal("none");
66
+ this.selectedRows = signal(void 0);
67
+ this.onSelectionChange = signal(void 0);
68
+ this.statusBar = signal(void 0);
69
+ this.pageSizeOptions = signal(void 0);
70
+ this.sideBarConfig = signal(void 0);
71
+ this.onFirstDataRendered = signal(void 0);
72
+ this.onError = signal(void 0);
73
+ this.columnChooserProp = signal(void 0);
74
+ this.columnReorder = signal(void 0);
75
+ this.virtualScroll = signal(void 0);
76
+ this.ariaLabel = signal(void 0);
77
+ this.ariaLabelledBy = signal(void 0);
78
+ // --- Internal state signals ---
79
+ this.internalData = signal([]);
80
+ this.internalLoading = signal(false);
81
+ this.internalPage = signal(1);
82
+ this.internalPageSizeOverride = signal(null);
83
+ this.internalSortOverride = signal(null);
84
+ this.internalFilters = signal({});
85
+ this.internalVisibleColumnsOverride = signal(null);
86
+ this.internalSelectedRows = signal(/* @__PURE__ */ new Set());
87
+ this.columnWidthOverrides = signal({});
88
+ this.pinnedOverrides = signal({});
89
+ // Server-side state
90
+ this.serverItems = signal([]);
91
+ this.serverTotalCount = signal(0);
92
+ this.serverLoading = signal(true);
93
+ this.fetchAbortController = null;
94
+ this.filterAbortController = null;
95
+ this.refreshCounter = signal(0);
96
+ this.firstDataRendered = signal(false);
97
+ // Side bar state
98
+ this.sideBarActivePanel = signal(null);
99
+ // Filter options state
100
+ this.serverFilterOptions = signal({});
101
+ this.loadingFilterOptions = signal({});
102
+ // --- Derived computed signals ---
103
+ this.columns = computed(() => flattenColumns(this.columnsProp()));
104
+ this.isServerSide = computed(() => this.dataSource() != null);
105
+ this.isClientSide = computed(() => !this.isServerSide());
106
+ this.displayData = computed(() => this.data() ?? this.internalData());
107
+ this.displayLoading = computed(() => this.controlledLoading() ?? this.internalLoading());
108
+ this.defaultSortField = computed(() => this.defaultSortBy() ?? this.columns()[0]?.columnId ?? "");
109
+ this.page = computed(() => this.controlledPage() ?? this.internalPage());
110
+ this.pageSize = computed(() => this.controlledPageSize() ?? this.internalPageSizeOverride() ?? this.defaultPageSize());
111
+ this.sort = computed(() => this.controlledSort() ?? this.internalSortOverride() ?? {
112
+ field: this.defaultSortField(),
113
+ direction: this.defaultSortDirection()
114
+ });
115
+ this.filters = computed(() => this.controlledFilters() ?? this.internalFilters());
116
+ this.visibleColumns = computed(() => {
117
+ const controlled = this.controlledVisibleColumns();
118
+ if (controlled) return controlled;
119
+ const override = this.internalVisibleColumnsOverride();
120
+ if (override) return override;
121
+ const cols = this.columns();
122
+ if (cols.length === 0) return /* @__PURE__ */ new Set();
123
+ const visible = cols.filter((c) => c.defaultVisible !== false).map((c) => c.columnId);
124
+ return new Set(visible.length > 0 ? visible : cols.map((c) => c.columnId));
125
+ });
126
+ this.effectiveSelectedRows = computed(() => this.selectedRows() ?? this.internalSelectedRows());
127
+ this.columnChooserPlacement = computed(() => {
128
+ const prop = this.columnChooserProp();
129
+ return prop === false ? "none" : prop === "sidebar" ? "sidebar" : "toolbar";
130
+ });
131
+ this.multiSelectFilterFields = computed(() => getMultiSelectFilterFields(this.columns()));
132
+ this.hasServerFilterOptions = computed(() => this.dataSource()?.fetchFilterOptions != null);
133
+ this.clientFilterOptions = computed(() => {
134
+ if (this.hasServerFilterOptions()) return this.serverFilterOptions();
135
+ return deriveFilterOptionsFromData(this.displayData(), this.columns());
136
+ });
137
+ this.clientItemsAndTotal = computed(() => {
138
+ if (!this.isClientSide()) return null;
139
+ const rows = processClientSideData(
140
+ this.displayData(),
141
+ this.columns(),
142
+ this.filters(),
143
+ this.sort().field,
144
+ this.sort().direction
145
+ );
146
+ const total = rows.length;
147
+ const start = (this.page() - 1) * this.pageSize();
148
+ const paged = rows.slice(start, start + this.pageSize());
149
+ return { items: paged, totalCount: total };
150
+ });
151
+ this.displayItems = computed(() => {
152
+ const cit = this.clientItemsAndTotal();
153
+ return this.isClientSide() && cit ? cit.items : this.serverItems();
154
+ });
155
+ this.displayTotalCount = computed(() => {
156
+ const cit = this.clientItemsAndTotal();
157
+ return this.isClientSide() && cit ? cit.totalCount : this.serverTotalCount();
158
+ });
159
+ this.hasActiveFilters = computed(() => {
160
+ return Object.values(this.filters()).some((v) => v !== void 0);
161
+ });
162
+ this.columnChooserColumns = computed(
163
+ () => this.columns().map((c) => ({
164
+ columnId: c.columnId,
165
+ name: c.name,
166
+ required: c.required === true
167
+ }))
168
+ );
169
+ this.statusBarConfig = computed(() => {
170
+ const sb = this.statusBar();
171
+ if (!sb) return void 0;
172
+ if (typeof sb === "object") return sb;
173
+ const totalData = this.isClientSide() ? this.data()?.length ?? 0 : this.serverTotalCount();
174
+ const filteredData = this.displayTotalCount();
175
+ return {
176
+ totalCount: totalData,
177
+ filteredCount: this.hasActiveFilters() ? filteredData : void 0,
178
+ selectedCount: this.effectiveSelectedRows().size,
179
+ suppressRowCount: true
180
+ };
181
+ });
182
+ this.isLoadingResolved = computed(() => {
183
+ return this.isServerSide() && this.serverLoading() || this.displayLoading();
184
+ });
185
+ // Side bar
186
+ this.sideBarEnabled = computed(() => {
187
+ const config = this.sideBarConfig();
188
+ return config != null && config !== false;
189
+ });
190
+ this.sideBarParsed = computed(() => {
191
+ const config = this.sideBarConfig();
192
+ if (!this.sideBarEnabled() || config === true) {
193
+ return { panels: DEFAULT_PANELS, position: "right", defaultPanel: null };
194
+ }
195
+ const def = config;
196
+ return {
197
+ panels: def.panels ?? DEFAULT_PANELS,
198
+ position: def.position ?? "right",
199
+ defaultPanel: def.defaultPanel ?? null
200
+ };
201
+ });
202
+ this.filterableColumns = computed(
203
+ () => this.columns().filter((c) => c.filterable && c.filterable.type).map((c) => ({
204
+ columnId: c.columnId,
205
+ name: c.name,
206
+ filterField: c.filterable?.filterField ?? c.columnId,
207
+ filterType: c.filterable?.type
208
+ }))
209
+ );
210
+ this.sideBarState = computed(() => ({
211
+ isEnabled: this.sideBarEnabled(),
212
+ activePanel: this.sideBarActivePanel(),
213
+ setActivePanel: (panel) => this.sideBarActivePanel.set(panel),
214
+ panels: this.sideBarParsed().panels,
215
+ position: this.sideBarParsed().position,
216
+ isOpen: this.sideBarActivePanel() !== null,
217
+ toggle: (panel) => this.sideBarActivePanel.update((p) => p === panel ? null : panel),
218
+ close: () => this.sideBarActivePanel.set(null)
219
+ }));
220
+ // --- Pre-computed stable callback references for dataGridProps ---
221
+ // These avoid recreating arrow functions on every dataGridProps recomputation.
222
+ this.handleSortFn = (columnKey, direction) => this.handleSort(columnKey, direction);
223
+ this.handleColumnResizedFn = (columnId, width) => this.handleColumnResized(columnId, width);
224
+ this.handleColumnPinnedFn = (columnId, pinned) => this.handleColumnPinned(columnId, pinned);
225
+ this.handleSelectionChangeFn = (event) => this.handleSelectionChange(event);
226
+ this.handleFilterChangeFn = (key, value) => this.handleFilterChange(key, value);
227
+ this.clearAllFiltersFn = () => this.setFilters({});
228
+ this.setPageFn = (p) => this.setPage(p);
229
+ this.setPageSizeFn = (size) => this.setPageSize(size);
230
+ this.handleVisibilityChangeFn = (columnKey, isVisible) => this.handleVisibilityChange(columnKey, isVisible);
231
+ // --- Data grid props computed ---
232
+ this.dataGridProps = computed(() => ({
233
+ items: this.displayItems(),
234
+ columns: this.columnsProp(),
235
+ getRowId: this.getRowId(),
236
+ sortBy: this.sort().field,
237
+ sortDirection: this.sort().direction,
238
+ onColumnSort: this.handleSortFn,
239
+ visibleColumns: this.visibleColumns(),
240
+ columnOrder: this.columnOrder(),
241
+ onColumnOrderChange: this.onColumnOrderChange(),
242
+ onColumnResized: this.handleColumnResizedFn,
243
+ onAutosizeColumn: this.onAutosizeColumn(),
244
+ onColumnPinned: this.handleColumnPinnedFn,
245
+ pinnedColumns: this.pinnedOverrides(),
246
+ initialColumnWidths: this.columnWidthOverrides(),
247
+ editable: this.editable(),
248
+ cellSelection: this.cellSelection(),
249
+ density: this.density(),
250
+ rowHeight: this.rowHeight(),
251
+ onCellValueChanged: this.onCellValueChanged(),
252
+ onUndo: this.onUndo(),
253
+ onRedo: this.onRedo(),
254
+ canUndo: this.canUndo(),
255
+ canRedo: this.canRedo(),
256
+ rowSelection: this.rowSelection(),
257
+ selectedRows: this.effectiveSelectedRows(),
258
+ onSelectionChange: this.handleSelectionChangeFn,
259
+ statusBar: this.statusBarConfig(),
260
+ isLoading: this.isLoadingResolved(),
261
+ filters: this.filters(),
262
+ onFilterChange: this.handleFilterChangeFn,
263
+ filterOptions: this.clientFilterOptions(),
264
+ loadingFilterOptions: this.dataSource()?.fetchFilterOptions ? this.loadingFilterOptions() : EMPTY_LOADING_OPTIONS,
265
+ peopleSearch: this.dataSource()?.searchPeople?.bind(this.dataSource()),
266
+ getUserByEmail: this.dataSource()?.getUserByEmail?.bind(this.dataSource()),
267
+ layoutMode: this.layoutMode(),
268
+ suppressHorizontalScroll: this.suppressHorizontalScroll(),
269
+ stickyHeader: this.stickyHeader(),
270
+ columnReorder: this.columnReorder(),
271
+ virtualScroll: this.virtualScroll(),
272
+ "aria-label": this.ariaLabel(),
273
+ "aria-labelledby": this.ariaLabelledBy(),
274
+ emptyState: {
275
+ hasActiveFilters: this.hasActiveFilters(),
276
+ onClearAll: this.clearAllFiltersFn,
277
+ message: this.emptyState()?.message,
278
+ render: this.emptyState()?.render
279
+ }
280
+ }));
281
+ this.pagination = computed(() => ({
282
+ page: this.page(),
283
+ pageSize: this.pageSize(),
284
+ displayTotalCount: this.displayTotalCount(),
285
+ setPage: this.setPageFn,
286
+ setPageSize: this.setPageSizeFn,
287
+ pageSizeOptions: this.pageSizeOptions(),
288
+ entityLabelPlural: this.entityLabelPlural()
289
+ }));
290
+ this.columnChooser = computed(() => ({
291
+ columns: this.columnChooserColumns(),
292
+ visibleColumns: this.visibleColumns(),
293
+ onVisibilityChange: this.handleVisibilityChangeFn,
294
+ placement: this.columnChooserPlacement()
295
+ }));
296
+ this.filtersResult = computed(() => ({
297
+ hasActiveFilters: this.hasActiveFilters(),
298
+ setFilters: (f) => this.setFilters(f)
299
+ }));
300
+ this.sideBarProps = computed(() => {
301
+ const state = this.sideBarState();
302
+ if (!state.isEnabled) return null;
303
+ return {
304
+ activePanel: state.activePanel,
305
+ onPanelChange: state.setActivePanel,
306
+ panels: state.panels,
307
+ position: state.position,
308
+ columns: this.columnChooserColumns(),
309
+ visibleColumns: this.visibleColumns(),
310
+ onVisibilityChange: (columnKey, visible) => this.handleVisibilityChange(columnKey, visible),
311
+ onSetVisibleColumns: (cols) => this.setVisibleColumns(cols),
312
+ filterableColumns: this.filterableColumns(),
313
+ filters: this.filters(),
314
+ onFilterChange: (key, value) => this.handleFilterChange(key, value),
315
+ filterOptions: this.clientFilterOptions()
316
+ };
317
+ });
318
+ let columnsValidated = false;
319
+ effect(() => {
320
+ const cols = this.columns();
321
+ if (!columnsValidated && cols.length > 0) {
322
+ columnsValidated = true;
323
+ validateColumns(cols);
324
+ }
325
+ });
326
+ effect((onCleanup) => {
327
+ const ds = this.dataSource();
328
+ if (!this.isServerSide() || !ds) {
329
+ if (!this.isServerSide()) this.serverLoading.set(false);
330
+ return;
331
+ }
332
+ const page = this.page();
333
+ const pageSize = this.pageSize();
334
+ const sort = this.sort();
335
+ const filters = this.filters();
336
+ this.refreshCounter();
337
+ const controller = new AbortController();
338
+ this.fetchAbortController = controller;
339
+ this.serverLoading.set(true);
340
+ ds.fetchPage({ page, pageSize, sort: { field: sort.field, direction: sort.direction }, filters }).then((res) => {
341
+ if (controller.signal.aborted) return;
342
+ this.serverItems.set(res.items);
343
+ this.serverTotalCount.set(res.totalCount);
344
+ }).catch((err) => {
345
+ if (controller.signal.aborted) return;
346
+ this.onError()?.(err);
347
+ this.serverItems.set([]);
348
+ this.serverTotalCount.set(0);
349
+ }).finally(() => {
350
+ if (!controller.signal.aborted) this.serverLoading.set(false);
351
+ });
352
+ onCleanup(() => {
353
+ controller.abort();
354
+ });
355
+ });
356
+ let rowIdsValidated = false;
357
+ effect(() => {
358
+ const items = this.displayItems();
359
+ if (!this.firstDataRendered() && items.length > 0) {
360
+ this.firstDataRendered.set(true);
361
+ this.onFirstDataRendered()?.();
362
+ }
363
+ if (!rowIdsValidated && items.length > 0) {
364
+ rowIdsValidated = true;
365
+ validateRowIds(items, this.getRowId());
366
+ }
367
+ });
368
+ effect((onCleanup) => {
369
+ const ds = this.dataSource();
370
+ const fields = this.multiSelectFilterFields();
371
+ const fetcher = ds && "fetchFilterOptions" in ds && typeof ds.fetchFilterOptions === "function" ? ds.fetchFilterOptions.bind(ds) : void 0;
372
+ if (!fetcher || fields.length === 0) {
373
+ this.serverFilterOptions.set({});
374
+ this.loadingFilterOptions.set({});
375
+ return;
376
+ }
377
+ const controller = new AbortController();
378
+ this.filterAbortController = controller;
379
+ const loading = {};
380
+ fields.forEach((f) => {
381
+ loading[f] = true;
382
+ });
383
+ this.loadingFilterOptions.set(loading);
384
+ const results = {};
385
+ Promise.all(
386
+ fields.map(async (field) => {
387
+ try {
388
+ results[field] = await fetcher(field);
389
+ } catch {
390
+ results[field] = [];
391
+ }
392
+ })
393
+ ).then(() => {
394
+ if (controller.signal.aborted) return;
395
+ this.serverFilterOptions.set(results);
396
+ this.loadingFilterOptions.set({});
397
+ });
398
+ onCleanup(() => {
399
+ controller.abort();
400
+ });
401
+ });
402
+ effect(() => {
403
+ const parsed = this.sideBarParsed();
404
+ if (parsed.defaultPanel) {
405
+ this.sideBarActivePanel.set(parsed.defaultPanel);
406
+ }
407
+ });
408
+ this.destroyRef.onDestroy(() => {
409
+ this.fetchAbortController?.abort();
410
+ this.filterAbortController?.abort();
411
+ this.fetchAbortController = null;
412
+ this.filterAbortController = null;
413
+ this.onPageChange.set(void 0);
414
+ this.onPageSizeChange.set(void 0);
415
+ this.onSortChange.set(void 0);
416
+ this.onFiltersChange.set(void 0);
417
+ this.onVisibleColumnsChange.set(void 0);
418
+ this.onColumnOrderChange.set(void 0);
419
+ this.onColumnResized.set(void 0);
420
+ this.onAutosizeColumn.set(void 0);
421
+ this.onColumnPinned.set(void 0);
422
+ this.onCellValueChanged.set(void 0);
423
+ this.onSelectionChange.set(void 0);
424
+ this.onFirstDataRendered.set(void 0);
425
+ this.onError.set(void 0);
426
+ this.onUndo.set(void 0);
427
+ this.onRedo.set(void 0);
428
+ });
429
+ }
430
+ // --- Setters ---
431
+ setPage(p) {
432
+ if (this.controlledPage() === void 0) this.internalPage.set(p);
433
+ this.onPageChange()?.(p);
434
+ }
435
+ setPageSize(size) {
436
+ if (this.controlledPageSize() === void 0) this.internalPageSizeOverride.set(size);
437
+ this.onPageSizeChange()?.(size);
438
+ this.setPage(1);
439
+ }
440
+ setSort(s) {
441
+ if (this.controlledSort() === void 0) this.internalSortOverride.set(s);
442
+ this.onSortChange()?.(s);
443
+ this.setPage(1);
444
+ }
445
+ setFilters(f) {
446
+ if (this.controlledFilters() === void 0) this.internalFilters.set(f);
447
+ this.onFiltersChange()?.(f);
448
+ this.setPage(1);
449
+ }
450
+ setVisibleColumns(cols) {
451
+ if (this.controlledVisibleColumns() === void 0) this.internalVisibleColumnsOverride.set(cols);
452
+ this.onVisibleColumnsChange()?.(cols);
453
+ }
454
+ handleSort(columnKey, direction) {
455
+ this.setSort(computeNextSortState(this.sort(), columnKey, direction));
456
+ }
457
+ handleFilterChange(key, value) {
458
+ this.setFilters(mergeFilter(this.filters(), key, value));
459
+ }
460
+ handleVisibilityChange(columnKey, isVisible) {
461
+ const next = new Set(this.visibleColumns());
462
+ if (isVisible) next.add(columnKey);
463
+ else next.delete(columnKey);
464
+ this.setVisibleColumns(next);
465
+ }
466
+ handleSelectionChange(event) {
467
+ if (this.selectedRows() === void 0) {
468
+ this.internalSelectedRows.set(new Set(event.selectedRowIds));
469
+ }
470
+ this.onSelectionChange()?.(event);
471
+ }
472
+ handleColumnResized(columnId, width) {
473
+ this.columnWidthOverrides.update((prev) => ({ ...prev, [columnId]: width }));
474
+ this.onColumnResized()?.(columnId, width);
475
+ }
476
+ handleColumnPinned(columnId, pinned) {
477
+ this.pinnedOverrides.update((prev) => {
478
+ if (pinned === null) {
479
+ const { [columnId]: _removed, ...rest } = prev;
480
+ return rest;
481
+ }
482
+ return { ...prev, [columnId]: pinned };
483
+ });
484
+ this.onColumnPinned()?.(columnId, pinned);
485
+ }
486
+ // --- Configure from props ---
487
+ configure(props) {
488
+ this.columnsProp.set(props.columns);
489
+ this.getRowId.set(props.getRowId);
490
+ if ("data" in props && props.data !== void 0) this.data.set(props.data);
491
+ if ("dataSource" in props && props.dataSource !== void 0) this.dataSource.set(props.dataSource);
492
+ if (props.page !== void 0) this.controlledPage.set(props.page);
493
+ if (props.pageSize !== void 0) this.controlledPageSize.set(props.pageSize);
494
+ if (props.sort !== void 0) this.controlledSort.set(props.sort);
495
+ if (props.filters !== void 0) this.controlledFilters.set(props.filters);
496
+ if (props.visibleColumns !== void 0) this.controlledVisibleColumns.set(props.visibleColumns);
497
+ if (props.isLoading !== void 0) this.controlledLoading.set(props.isLoading);
498
+ if (props.onPageChange) this.onPageChange.set(props.onPageChange);
499
+ if (props.onPageSizeChange) this.onPageSizeChange.set(props.onPageSizeChange);
500
+ if (props.onSortChange) this.onSortChange.set(props.onSortChange);
501
+ if (props.onFiltersChange) this.onFiltersChange.set(props.onFiltersChange);
502
+ if (props.onVisibleColumnsChange) this.onVisibleColumnsChange.set(props.onVisibleColumnsChange);
503
+ if (props.columnOrder !== void 0) this.columnOrder.set(props.columnOrder);
504
+ if (props.onColumnOrderChange) this.onColumnOrderChange.set(props.onColumnOrderChange);
505
+ if (props.onColumnResized) this.onColumnResized.set(props.onColumnResized);
506
+ if (props.onAutosizeColumn) this.onAutosizeColumn.set(props.onAutosizeColumn);
507
+ if (props.onColumnPinned) this.onColumnPinned.set(props.onColumnPinned);
508
+ if (props.defaultPageSize !== void 0) this.defaultPageSize.set(props.defaultPageSize);
509
+ if (props.defaultSortBy !== void 0) this.defaultSortBy.set(props.defaultSortBy);
510
+ if (props.defaultSortDirection !== void 0) this.defaultSortDirection.set(props.defaultSortDirection);
511
+ if (props.editable !== void 0) this.editable.set(props.editable);
512
+ if (props.cellSelection !== void 0) this.cellSelection.set(props.cellSelection);
513
+ if (props.density !== void 0) this.density.set(props.density);
514
+ if (props.rowHeight !== void 0) this.rowHeight.set(props.rowHeight);
515
+ if (props.onCellValueChanged) this.onCellValueChanged.set(props.onCellValueChanged);
516
+ if (props.onUndo) this.onUndo.set(props.onUndo);
517
+ if (props.onRedo) this.onRedo.set(props.onRedo);
518
+ if (props.canUndo !== void 0) this.canUndo.set(props.canUndo);
519
+ if (props.canRedo !== void 0) this.canRedo.set(props.canRedo);
520
+ if (props.rowSelection !== void 0) this.rowSelection.set(props.rowSelection);
521
+ if (props.selectedRows !== void 0) this.selectedRows.set(props.selectedRows);
522
+ if (props.onSelectionChange) this.onSelectionChange.set(props.onSelectionChange);
523
+ if (props.statusBar !== void 0) this.statusBar.set(props.statusBar);
524
+ if (props.pageSizeOptions !== void 0) this.pageSizeOptions.set(props.pageSizeOptions);
525
+ if (props.sideBar !== void 0) this.sideBarConfig.set(props.sideBar);
526
+ if (props.onFirstDataRendered) this.onFirstDataRendered.set(props.onFirstDataRendered);
527
+ if (props.onError) this.onError.set(props.onError);
528
+ if (props.columnChooser !== void 0) this.columnChooserProp.set(props.columnChooser);
529
+ if (props.columnReorder !== void 0) this.columnReorder.set(props.columnReorder);
530
+ if (props.virtualScroll !== void 0) this.virtualScroll.set(props.virtualScroll);
531
+ if (props.entityLabelPlural !== void 0) this.entityLabelPlural.set(props.entityLabelPlural);
532
+ if (props.className !== void 0) this.className.set(props.className);
533
+ if (props.layoutMode !== void 0) this.layoutMode.set(props.layoutMode);
534
+ if (props.suppressHorizontalScroll !== void 0) this.suppressHorizontalScroll.set(props.suppressHorizontalScroll);
535
+ if (props.stickyHeader !== void 0) this.stickyHeader.set(props.stickyHeader);
536
+ if (props.fullScreen !== void 0) this.fullScreen.set(props.fullScreen);
537
+ if (props["aria-label"] !== void 0) this.ariaLabel.set(props["aria-label"]);
538
+ if (props["aria-labelledby"] !== void 0) this.ariaLabelledBy.set(props["aria-labelledby"]);
539
+ }
540
+ // --- API ---
541
+ // --- Column Pinning Methods ---
542
+ /**
543
+ * Pin a column to the left or right edge.
544
+ */
545
+ pinColumn(columnId, side) {
546
+ this.pinnedOverrides.update((prev) => ({ ...prev, [columnId]: side }));
547
+ this.onColumnPinned()?.(columnId, side);
548
+ }
549
+ /**
550
+ * Unpin a column (remove sticky positioning).
551
+ */
552
+ unpinColumn(columnId) {
553
+ this.pinnedOverrides.update((prev) => {
554
+ const { [columnId]: _, ...next } = prev;
555
+ return next;
556
+ });
557
+ this.onColumnPinned()?.(columnId, null);
558
+ }
559
+ /**
560
+ * Check if a column is pinned and which side.
561
+ */
562
+ isPinned(columnId) {
563
+ return this.pinnedOverrides()[columnId];
564
+ }
565
+ /**
566
+ * Compute sticky left offsets for left-pinned columns.
567
+ * Returns a map of columnId -> left offset in pixels.
568
+ */
569
+ computeLeftOffsets(visibleCols, columnWidths, defaultWidth, hasCheckboxColumn, checkboxColumnWidth) {
570
+ const offsets = {};
571
+ const pinned = this.pinnedOverrides();
572
+ let left = hasCheckboxColumn ? checkboxColumnWidth : 0;
573
+ for (const col of visibleCols) {
574
+ if (pinned[col.columnId] === "left") {
575
+ offsets[col.columnId] = left;
576
+ left += columnWidths[col.columnId] ?? defaultWidth;
577
+ }
578
+ }
579
+ return offsets;
580
+ }
581
+ /**
582
+ * Compute sticky right offsets for right-pinned columns.
583
+ * Returns a map of columnId -> right offset in pixels.
584
+ */
585
+ computeRightOffsets(visibleCols, columnWidths, defaultWidth) {
586
+ const offsets = {};
587
+ const pinned = this.pinnedOverrides();
588
+ let right = 0;
589
+ for (let i = visibleCols.length - 1; i >= 0; i--) {
590
+ const col = visibleCols[i];
591
+ if (pinned[col.columnId] === "right") {
592
+ offsets[col.columnId] = right;
593
+ right += columnWidths[col.columnId] ?? defaultWidth;
594
+ }
595
+ }
596
+ return offsets;
597
+ }
598
+ getApi() {
599
+ return {
600
+ setRowData: (d) => {
601
+ if (!this.isServerSide()) this.internalData.set(d);
602
+ },
603
+ setLoading: (loading) => this.internalLoading.set(loading),
604
+ getColumnState: () => ({
605
+ visibleColumns: Array.from(this.visibleColumns()),
606
+ sort: this.sort(),
607
+ columnOrder: this.columnOrder() ?? void 0,
608
+ columnWidths: Object.keys(this.columnWidthOverrides()).length > 0 ? this.columnWidthOverrides() : void 0,
609
+ filters: Object.keys(this.filters()).length > 0 ? this.filters() : void 0,
610
+ pinnedColumns: Object.keys(this.pinnedOverrides()).length > 0 ? this.pinnedOverrides() : void 0
611
+ }),
612
+ applyColumnState: (state) => {
613
+ if (state.visibleColumns) this.setVisibleColumns(new Set(state.visibleColumns));
614
+ if (state.sort) this.setSort(state.sort);
615
+ if (state.columnOrder) this.onColumnOrderChange()?.(state.columnOrder);
616
+ if (state.columnWidths) this.columnWidthOverrides.set(state.columnWidths);
617
+ if (state.filters) this.setFilters(state.filters);
618
+ if (state.pinnedColumns) this.pinnedOverrides.set(state.pinnedColumns);
619
+ },
620
+ setFilterModel: (filters) => this.setFilters(filters),
621
+ getSelectedRows: () => Array.from(this.effectiveSelectedRows()),
622
+ setSelectedRows: (rowIds) => {
623
+ if (this.selectedRows() === void 0) this.internalSelectedRows.set(new Set(rowIds));
624
+ },
625
+ selectAll: () => {
626
+ const allIds = new Set(this.displayItems().map((item) => this.getRowId()(item)));
627
+ if (this.selectedRows() === void 0) this.internalSelectedRows.set(allIds);
628
+ this.onSelectionChange()?.({
629
+ selectedRowIds: Array.from(allIds),
630
+ selectedItems: this.displayItems()
631
+ });
632
+ },
633
+ deselectAll: () => {
634
+ if (this.selectedRows() === void 0) this.internalSelectedRows.set(/* @__PURE__ */ new Set());
635
+ this.onSelectionChange()?.({ selectedRowIds: [], selectedItems: [] });
636
+ },
637
+ clearFilters: () => this.setFilters({}),
638
+ clearSort: () => this.setSort({ field: this.defaultSortField(), direction: this.defaultSortDirection() }),
639
+ resetGridState: (options) => {
640
+ this.setFilters({});
641
+ this.setSort({ field: this.defaultSortField(), direction: this.defaultSortDirection() });
642
+ if (!options?.keepSelection) {
643
+ if (this.selectedRows() === void 0) this.internalSelectedRows.set(/* @__PURE__ */ new Set());
644
+ this.onSelectionChange()?.({ selectedRowIds: [], selectedItems: [] });
645
+ }
646
+ },
647
+ getDisplayedRows: () => this.displayItems(),
648
+ refreshData: () => {
649
+ if (this.isServerSide()) {
650
+ this.refreshCounter.update((c) => c + 1);
651
+ }
652
+ },
653
+ getColumnOrder: () => this.columnOrder() ?? this.columns().map((c) => c.columnId),
654
+ setColumnOrder: (order) => {
655
+ this.onColumnOrderChange()?.(order);
656
+ },
657
+ scrollToRow: (_index, _options) => {
658
+ }
659
+ };
660
+ }
661
+ };
662
+ OGridService = __decorateClass([
663
+ Injectable()
664
+ ], OGridService);
665
+ var DataGridLayoutHelper = class {
666
+ constructor(props, wrapperEl, ngZone) {
667
+ // --- Internal state ---
668
+ this.containerWidthSig = signal(0);
669
+ this.columnSizingOverridesSig = signal({});
670
+ // ResizeObserver
671
+ this.resizeObserver = null;
672
+ this.props = props;
673
+ this.wrapperEl = wrapperEl;
674
+ this.initialColumnWidthsSig = computed(() => this.props()?.initialColumnWidths);
675
+ this.flatColumnsRaw = computed(() => {
676
+ const p = this.props();
677
+ if (!p) return [];
678
+ return flattenColumns(p.columns);
679
+ });
680
+ this.flatColumns = computed(() => {
681
+ const raw = this.flatColumnsRaw();
682
+ const p = this.props();
683
+ const pinnedColumns = p?.pinnedColumns;
684
+ if (!pinnedColumns || Object.keys(pinnedColumns).length === 0) return raw;
685
+ return raw.map((col) => {
686
+ const override = pinnedColumns[col.columnId];
687
+ if (override && col.pinned !== override) return { ...col, pinned: override };
688
+ return col;
689
+ });
690
+ });
691
+ this.visibleCols = computed(() => {
692
+ const p = this.props();
693
+ if (!p) return [];
694
+ const flatCols = this.flatColumns();
695
+ const filtered = p.visibleColumns ? flatCols.filter((c) => p.visibleColumns.has(c.columnId)) : flatCols;
696
+ const order = p.columnOrder;
697
+ if (!order?.length) return filtered;
698
+ const orderMap = /* @__PURE__ */ new Map();
699
+ for (let i = 0; i < order.length; i++) {
700
+ orderMap.set(order[i], i);
701
+ }
702
+ return [...filtered].sort((a, b) => {
703
+ const ia = orderMap.get(a.columnId) ?? -1;
704
+ const ib = orderMap.get(b.columnId) ?? -1;
705
+ if (ia === -1 && ib === -1) return 0;
706
+ if (ia === -1) return 1;
707
+ if (ib === -1) return -1;
708
+ return ia - ib;
709
+ });
710
+ });
711
+ this.visibleColumnCount = computed(() => this.visibleCols().length);
712
+ this.hasCheckboxCol = computed(() => (this.props()?.rowSelection ?? "none") === "multiple");
713
+ this.hasRowNumbersCol = computed(() => !!this.props()?.showRowNumbers);
714
+ this.specialColsCount = computed(() => (this.hasCheckboxCol() ? 1 : 0) + (this.hasRowNumbersCol() ? 1 : 0));
715
+ this.totalColCount = computed(() => this.visibleColumnCount() + this.specialColsCount());
716
+ this.colOffset = computed(() => this.specialColsCount());
717
+ this.rowIndexByRowId = computed(() => {
718
+ const p = this.props();
719
+ if (!p) return /* @__PURE__ */ new Map();
720
+ const m = /* @__PURE__ */ new Map();
721
+ p.items.forEach((item, idx) => m.set(p.getRowId(item), idx));
722
+ return m;
723
+ });
724
+ this.minTableWidth = computed(() => {
725
+ const checkboxW = this.hasCheckboxCol() ? CHECKBOX_COLUMN_WIDTH : 0;
726
+ return this.visibleCols().reduce(
727
+ (sum, c) => sum + (c.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH) + CELL_PADDING,
728
+ checkboxW
729
+ );
730
+ });
731
+ this.desiredTableWidth = computed(() => {
732
+ const checkboxW = this.hasCheckboxCol() ? CHECKBOX_COLUMN_WIDTH : 0;
733
+ const overrides = this.columnSizingOverridesSig();
734
+ return this.visibleCols().reduce((sum, c) => {
735
+ const override = overrides[c.columnId];
736
+ const w = override ? override.widthPx : c.idealWidth ?? c.defaultWidth ?? c.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
737
+ return sum + Math.max(c.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH, w) + CELL_PADDING;
738
+ }, checkboxW);
739
+ });
740
+ effect(() => {
741
+ const widths = this.initialColumnWidthsSig();
742
+ if (widths) {
743
+ const result = {};
744
+ for (const [id, width] of Object.entries(widths)) {
745
+ result[id] = { widthPx: width };
746
+ }
747
+ this.columnSizingOverridesSig.set(result);
748
+ }
749
+ });
750
+ effect(() => {
751
+ const el = this.wrapperEl();
752
+ if (this.resizeObserver) {
753
+ this.resizeObserver.disconnect();
754
+ this.resizeObserver = null;
755
+ }
756
+ if (!el) return;
757
+ const measure = () => {
758
+ const rect = el.getBoundingClientRect();
759
+ const cs = window.getComputedStyle(el);
760
+ const borderX = (parseFloat(cs.borderLeftWidth || "0") || 0) + (parseFloat(cs.borderRightWidth || "0") || 0);
761
+ this.containerWidthSig.set(Math.max(0, rect.width - borderX));
762
+ };
763
+ ngZone.runOutsideAngular(() => {
764
+ this.resizeObserver = new ResizeObserver(measure);
765
+ this.resizeObserver.observe(el);
766
+ });
767
+ measure();
768
+ });
769
+ effect(() => {
770
+ const colIds = new Set(this.flatColumns().map((c) => c.columnId));
771
+ this.columnSizingOverridesSig.update((prev) => {
772
+ const next = {};
773
+ let changed = false;
774
+ for (const [id, value] of Object.entries(prev)) {
775
+ if (colIds.has(id)) {
776
+ next[id] = value;
777
+ } else {
778
+ changed = true;
779
+ }
780
+ }
781
+ return changed ? next : prev;
782
+ });
783
+ });
784
+ }
785
+ destroy() {
786
+ if (this.resizeObserver) {
787
+ this.resizeObserver.disconnect();
788
+ this.resizeObserver = null;
789
+ }
790
+ }
791
+ };
792
+ var DataGridEditingHelper = class {
793
+ constructor(getVisibleCols, getItems, getWrappedOnCellValueChanged, setActiveCellFn) {
794
+ this.editingCellSig = signal(null);
795
+ this.pendingEditorValueSig = signal(void 0);
796
+ this.popoverAnchorElSig = signal(null);
797
+ this.getVisibleCols = getVisibleCols;
798
+ this.getItems = getItems;
799
+ this.getWrappedOnCellValueChanged = getWrappedOnCellValueChanged;
800
+ this.setActiveCellFn = setActiveCellFn;
801
+ }
802
+ setEditingCell(cell) {
803
+ this.editingCellSig.set(cell);
804
+ }
805
+ setPendingEditorValue(value) {
806
+ this.pendingEditorValueSig.set(value);
807
+ }
808
+ commitCellEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex) {
809
+ const col = this.getVisibleCols().find((c) => c.columnId === columnId);
810
+ if (col) {
811
+ const result = parseValue(newValue, oldValue, item, col);
812
+ if (!result.valid) {
813
+ this.editingCellSig.set(null);
814
+ this.popoverAnchorElSig.set(null);
815
+ this.pendingEditorValueSig.set(void 0);
816
+ return;
817
+ }
818
+ newValue = result.value;
819
+ }
820
+ const onCellValueChanged = this.getWrappedOnCellValueChanged();
821
+ onCellValueChanged?.({ item, columnId, oldValue, newValue, rowIndex });
822
+ this.editingCellSig.set(null);
823
+ this.popoverAnchorElSig.set(null);
824
+ this.pendingEditorValueSig.set(void 0);
825
+ const items = this.getItems();
826
+ if (rowIndex < items.length - 1) {
827
+ this.setActiveCellFn({ rowIndex: rowIndex + 1, columnIndex: globalColIndex });
828
+ }
829
+ }
830
+ cancelPopoverEdit() {
831
+ this.editingCellSig.set(null);
832
+ this.popoverAnchorElSig.set(null);
833
+ this.pendingEditorValueSig.set(void 0);
834
+ }
835
+ };
836
+ var DataGridInteractionHelper = class {
837
+ constructor() {
838
+ // --- Signals ---
839
+ this.activeCellSig = signal(null);
840
+ this.selectionRangeSig = signal(null);
841
+ this.isDraggingSig = signal(false);
842
+ this.contextMenuPositionSig = signal(null);
843
+ this.cutRangeSig = signal(null);
844
+ this.copyRangeSig = signal(null);
845
+ this.internalClipboard = null;
846
+ // Undo/redo
847
+ this.undoRedoStack = new UndoRedoStack(100);
848
+ this.undoLengthSig = signal(0);
849
+ this.redoLengthSig = signal(0);
850
+ this.canUndo = computed(() => this.undoLengthSig() > 0);
851
+ this.canRedo = computed(() => this.redoLengthSig() > 0);
852
+ this.hasCellSelection = computed(() => this.selectionRangeSig() != null || this.activeCellSig() != null);
853
+ // Fill handle state
854
+ this.fillDragStart = null;
855
+ this.fillRafId = 0;
856
+ this.fillMoveHandler = null;
857
+ this.fillUpHandler = null;
858
+ // Drag selection refs
859
+ this.dragStartPos = null;
860
+ this.dragMoved = false;
861
+ this.isDraggingRef = false;
862
+ this.liveDragRange = null;
863
+ this.rafId = 0;
864
+ this.lastMousePos = null;
865
+ this.autoScrollInterval = null;
866
+ }
867
+ setActiveCell(cell) {
868
+ const prev = this.activeCellSig();
869
+ if (prev === cell) return;
870
+ if (prev && cell && prev.rowIndex === cell.rowIndex && prev.columnIndex === cell.columnIndex) return;
871
+ this.activeCellSig.set(cell);
872
+ }
873
+ setSelectionRange(range) {
874
+ const prev = this.selectionRangeSig();
875
+ if (rangesEqual(prev, range)) return;
876
+ this.selectionRangeSig.set(range);
877
+ }
878
+ // --- Context menu ---
879
+ setContextMenuPosition(pos) {
880
+ this.contextMenuPositionSig.set(pos);
881
+ }
882
+ handleCellContextMenu(e) {
883
+ e.preventDefault?.();
884
+ this.contextMenuPositionSig.set({ x: e.clientX, y: e.clientY });
885
+ }
886
+ closeContextMenu() {
887
+ this.contextMenuPositionSig.set(null);
888
+ }
889
+ // --- Clipboard ---
890
+ handleCopy(items, visibleCols, colOffset) {
891
+ const range = this.getEffectiveRange(colOffset);
892
+ if (range == null) return;
893
+ const norm = normalizeSelectionRange(range);
894
+ const tsv = formatSelectionAsTsv(items, visibleCols, norm);
895
+ this.internalClipboard = tsv;
896
+ this.copyRangeSig.set(norm);
897
+ void navigator.clipboard.writeText(tsv).catch(() => {
898
+ });
899
+ }
900
+ handleCut(items, visibleCols, colOffset, editable, wrappedOnCellValueChanged) {
901
+ if (editable === false) return;
902
+ const range = this.getEffectiveRange(colOffset);
903
+ if (range == null || !wrappedOnCellValueChanged) return;
904
+ const norm = normalizeSelectionRange(range);
905
+ this.cutRangeSig.set(norm);
906
+ this.copyRangeSig.set(null);
907
+ this.handleCopy(items, visibleCols, colOffset);
908
+ this.copyRangeSig.set(null);
909
+ }
910
+ async handlePaste(items, visibleCols, colOffset, editable, wrappedOnCellValueChanged) {
911
+ if (editable === false) return;
912
+ if (!wrappedOnCellValueChanged) return;
913
+ let text;
914
+ try {
915
+ text = await navigator.clipboard.readText();
916
+ } catch {
917
+ text = "";
918
+ }
919
+ if (!text.trim() && this.internalClipboard != null) {
920
+ text = this.internalClipboard;
921
+ }
922
+ if (!text.trim()) return;
923
+ const norm = this.getEffectiveRange(colOffset);
924
+ const anchorRow = norm ? norm.startRow : 0;
925
+ const anchorCol = norm ? norm.startCol : 0;
926
+ const parsedRows = parseTsvClipboard(text);
927
+ this.beginBatch();
928
+ for (let r = 0; r < parsedRows.length; r++) {
929
+ const cells = parsedRows[r];
930
+ for (let c = 0; c < cells.length; c++) {
931
+ const targetRow = anchorRow + r;
932
+ const targetCol = anchorCol + c;
933
+ if (targetRow >= items.length || targetCol >= visibleCols.length) continue;
934
+ const item = items[targetRow];
935
+ const col = visibleCols[targetCol];
936
+ const colEditable = col.editable === true || typeof col.editable === "function" && col.editable(item);
937
+ if (!colEditable) continue;
938
+ const rawValue = cells[c] ?? "";
939
+ const oldValue = getCellValue(item, col);
940
+ const result = parseValue(rawValue, oldValue, item, col);
941
+ if (!result.valid) continue;
942
+ wrappedOnCellValueChanged({ item, columnId: col.columnId, oldValue, newValue: result.value, rowIndex: targetRow });
943
+ }
944
+ }
945
+ const cutRange = this.cutRangeSig();
946
+ if (cutRange) {
947
+ for (let r = cutRange.startRow; r <= cutRange.endRow; r++) {
948
+ for (let c = cutRange.startCol; c <= cutRange.endCol; c++) {
949
+ if (r >= items.length || c >= visibleCols.length) continue;
950
+ const item = items[r];
951
+ const col = visibleCols[c];
952
+ const colEditable = col.editable === true || typeof col.editable === "function" && col.editable(item);
953
+ if (!colEditable) continue;
954
+ const oldValue = getCellValue(item, col);
955
+ const result = parseValue("", oldValue, item, col);
956
+ if (!result.valid) continue;
957
+ wrappedOnCellValueChanged({ item, columnId: col.columnId, oldValue, newValue: result.value, rowIndex: r });
958
+ }
959
+ }
960
+ this.cutRangeSig.set(null);
961
+ }
962
+ this.endBatch();
963
+ this.copyRangeSig.set(null);
964
+ }
965
+ clearClipboardRanges() {
966
+ this.copyRangeSig.set(null);
967
+ this.cutRangeSig.set(null);
968
+ }
969
+ // --- Undo/Redo ---
970
+ beginBatch() {
971
+ this.undoRedoStack.beginBatch();
972
+ }
973
+ endBatch() {
974
+ this.undoRedoStack.endBatch();
975
+ this.undoLengthSig.set(this.undoRedoStack.historyLength);
976
+ this.redoLengthSig.set(this.undoRedoStack.redoLength);
977
+ }
978
+ undo(originalOnCellValueChanged) {
979
+ if (!originalOnCellValueChanged) return;
980
+ const lastBatch = this.undoRedoStack.undo();
981
+ if (!lastBatch) return;
982
+ this.undoLengthSig.set(this.undoRedoStack.historyLength);
983
+ this.redoLengthSig.set(this.undoRedoStack.redoLength);
984
+ for (let i = lastBatch.length - 1; i >= 0; i--) {
985
+ const ev = lastBatch[i];
986
+ originalOnCellValueChanged({ ...ev, oldValue: ev.newValue, newValue: ev.oldValue });
987
+ }
988
+ }
989
+ redo(originalOnCellValueChanged) {
990
+ if (!originalOnCellValueChanged) return;
991
+ const nextBatch = this.undoRedoStack.redo();
992
+ if (!nextBatch) return;
993
+ this.undoLengthSig.set(this.undoRedoStack.historyLength);
994
+ this.redoLengthSig.set(this.undoRedoStack.redoLength);
995
+ for (const ev of nextBatch) {
996
+ originalOnCellValueChanged(ev);
997
+ }
998
+ }
999
+ // --- Cell selection / mouse handling ---
1000
+ handleCellMouseDown(e, rowIndex, globalColIndex, colOffset, wrapperEl) {
1001
+ if (e.button !== 0) return;
1002
+ wrapperEl?.focus({ preventScroll: true });
1003
+ this.clearClipboardRanges();
1004
+ if (globalColIndex < colOffset) return;
1005
+ e.preventDefault();
1006
+ const dataColIndex = globalColIndex - colOffset;
1007
+ const currentRange = this.selectionRangeSig();
1008
+ if (e.shiftKey && currentRange != null) {
1009
+ this.setSelectionRange(
1010
+ normalizeSelectionRange({
1011
+ startRow: currentRange.startRow,
1012
+ startCol: currentRange.startCol,
1013
+ endRow: rowIndex,
1014
+ endCol: dataColIndex
1015
+ })
1016
+ );
1017
+ this.setActiveCell({ rowIndex, columnIndex: globalColIndex });
1018
+ } else {
1019
+ this.dragStartPos = { row: rowIndex, col: dataColIndex };
1020
+ this.dragMoved = false;
1021
+ const initial = {
1022
+ startRow: rowIndex,
1023
+ startCol: dataColIndex,
1024
+ endRow: rowIndex,
1025
+ endCol: dataColIndex
1026
+ };
1027
+ this.setSelectionRange(initial);
1028
+ this.liveDragRange = initial;
1029
+ this.setActiveCell({ rowIndex, columnIndex: globalColIndex });
1030
+ this.isDraggingRef = true;
1031
+ }
1032
+ }
1033
+ handleSelectAllCells(rowCount, visibleColCount, colOffset) {
1034
+ if (rowCount === 0 || visibleColCount === 0) return;
1035
+ this.setSelectionRange({
1036
+ startRow: 0,
1037
+ startCol: 0,
1038
+ endRow: rowCount - 1,
1039
+ endCol: visibleColCount - 1
1040
+ });
1041
+ this.setActiveCell({ rowIndex: 0, columnIndex: colOffset });
1042
+ }
1043
+ // --- Fill handle ---
1044
+ handleFillHandleMouseDown(e) {
1045
+ e.preventDefault();
1046
+ e.stopPropagation();
1047
+ const range = this.selectionRangeSig();
1048
+ if (!range) return;
1049
+ this.fillDragStart = { startRow: range.startRow, startCol: range.startCol };
1050
+ }
1051
+ // --- Keyboard navigation ---
1052
+ handleGridKeyDown(e, items, getRowId, visibleCols, colOffset, hasCheckboxCol, visibleColumnCount, editable, wrappedOnCellValueChanged, originalOnCellValueChanged, rowSelection, selectedRowIds, wrapperEl, handleRowCheckboxChange, editingCell, setEditingCell) {
1053
+ const activeCell = this.activeCellSig();
1054
+ const selectionRange = this.selectionRangeSig();
1055
+ const maxRowIndex = items.length - 1;
1056
+ const maxColIndex = visibleColumnCount - 1 + colOffset;
1057
+ if (items.length === 0) return;
1058
+ if (activeCell === null) {
1059
+ if (["ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight", "Tab", "Enter", "Home", "End"].includes(e.key)) {
1060
+ this.setActiveCell({ rowIndex: 0, columnIndex: colOffset });
1061
+ e.preventDefault();
1062
+ }
1063
+ return;
1064
+ }
1065
+ const { rowIndex, columnIndex } = activeCell;
1066
+ const dataColIndex = columnIndex - colOffset;
1067
+ const shift = e.shiftKey;
1068
+ const ctrl = e.ctrlKey || e.metaKey;
1069
+ const isEmptyAt = (r, c) => {
1070
+ if (r < 0 || r >= items.length || c < 0 || c >= visibleCols.length) return true;
1071
+ const v = getCellValue(items[r], visibleCols[c]);
1072
+ return v == null || v === "";
1073
+ };
1074
+ const findCtrlTarget = findCtrlArrowTarget;
1075
+ switch (e.key) {
1076
+ case "c":
1077
+ if (ctrl) {
1078
+ if (editingCell != null) break;
1079
+ e.preventDefault();
1080
+ this.handleCopy(items, visibleCols, colOffset);
1081
+ }
1082
+ break;
1083
+ case "x":
1084
+ if (ctrl) {
1085
+ if (editingCell != null) break;
1086
+ e.preventDefault();
1087
+ this.handleCut(items, visibleCols, colOffset, editable, wrappedOnCellValueChanged);
1088
+ }
1089
+ break;
1090
+ case "v":
1091
+ if (ctrl) {
1092
+ if (editingCell != null) break;
1093
+ e.preventDefault();
1094
+ void this.handlePaste(items, visibleCols, colOffset, editable, wrappedOnCellValueChanged);
1095
+ }
1096
+ break;
1097
+ case "ArrowDown": {
1098
+ e.preventDefault();
1099
+ const newRow = ctrl ? findCtrlTarget(rowIndex, maxRowIndex, 1, (r) => isEmptyAt(r, Math.max(0, dataColIndex))) : Math.min(rowIndex + 1, maxRowIndex);
1100
+ if (shift) {
1101
+ this.setSelectionRange(normalizeSelectionRange({
1102
+ startRow: selectionRange?.startRow ?? rowIndex,
1103
+ startCol: selectionRange?.startCol ?? dataColIndex,
1104
+ endRow: newRow,
1105
+ endCol: selectionRange?.endCol ?? dataColIndex
1106
+ }));
1107
+ } else {
1108
+ this.setSelectionRange({ startRow: newRow, startCol: dataColIndex, endRow: newRow, endCol: dataColIndex });
1109
+ }
1110
+ this.setActiveCell({ rowIndex: newRow, columnIndex });
1111
+ break;
1112
+ }
1113
+ case "ArrowUp": {
1114
+ e.preventDefault();
1115
+ const newRowUp = ctrl ? findCtrlTarget(rowIndex, 0, -1, (r) => isEmptyAt(r, Math.max(0, dataColIndex))) : Math.max(rowIndex - 1, 0);
1116
+ if (shift) {
1117
+ this.setSelectionRange(normalizeSelectionRange({
1118
+ startRow: selectionRange?.startRow ?? rowIndex,
1119
+ startCol: selectionRange?.startCol ?? dataColIndex,
1120
+ endRow: newRowUp,
1121
+ endCol: selectionRange?.endCol ?? dataColIndex
1122
+ }));
1123
+ } else {
1124
+ this.setSelectionRange({ startRow: newRowUp, startCol: dataColIndex, endRow: newRowUp, endCol: dataColIndex });
1125
+ }
1126
+ this.setActiveCell({ rowIndex: newRowUp, columnIndex });
1127
+ break;
1128
+ }
1129
+ case "ArrowRight": {
1130
+ e.preventDefault();
1131
+ let newCol;
1132
+ if (ctrl && dataColIndex >= 0) {
1133
+ newCol = findCtrlTarget(dataColIndex, visibleCols.length - 1, 1, (c) => isEmptyAt(rowIndex, c)) + colOffset;
1134
+ } else {
1135
+ newCol = Math.min(columnIndex + 1, maxColIndex);
1136
+ }
1137
+ const newDataCol = newCol - colOffset;
1138
+ if (shift) {
1139
+ this.setSelectionRange(normalizeSelectionRange({
1140
+ startRow: selectionRange?.startRow ?? rowIndex,
1141
+ startCol: selectionRange?.startCol ?? dataColIndex,
1142
+ endRow: selectionRange?.endRow ?? rowIndex,
1143
+ endCol: newDataCol
1144
+ }));
1145
+ } else {
1146
+ this.setSelectionRange({ startRow: rowIndex, startCol: newDataCol, endRow: rowIndex, endCol: newDataCol });
1147
+ }
1148
+ this.setActiveCell({ rowIndex, columnIndex: newCol });
1149
+ break;
1150
+ }
1151
+ case "ArrowLeft": {
1152
+ e.preventDefault();
1153
+ let newColLeft;
1154
+ if (ctrl && dataColIndex >= 0) {
1155
+ newColLeft = findCtrlTarget(dataColIndex, 0, -1, (c) => isEmptyAt(rowIndex, c)) + colOffset;
1156
+ } else {
1157
+ newColLeft = Math.max(columnIndex - 1, colOffset);
1158
+ }
1159
+ const newDataColLeft = newColLeft - colOffset;
1160
+ if (shift) {
1161
+ this.setSelectionRange(normalizeSelectionRange({
1162
+ startRow: selectionRange?.startRow ?? rowIndex,
1163
+ startCol: selectionRange?.startCol ?? dataColIndex,
1164
+ endRow: selectionRange?.endRow ?? rowIndex,
1165
+ endCol: newDataColLeft
1166
+ }));
1167
+ } else {
1168
+ this.setSelectionRange({ startRow: rowIndex, startCol: newDataColLeft, endRow: rowIndex, endCol: newDataColLeft });
1169
+ }
1170
+ this.setActiveCell({ rowIndex, columnIndex: newColLeft });
1171
+ break;
1172
+ }
1173
+ case "Tab": {
1174
+ e.preventDefault();
1175
+ const tabResult = computeTabNavigation(rowIndex, columnIndex, maxRowIndex, maxColIndex, colOffset, e.shiftKey);
1176
+ const newDataColTab = tabResult.columnIndex - colOffset;
1177
+ this.setSelectionRange({ startRow: tabResult.rowIndex, startCol: newDataColTab, endRow: tabResult.rowIndex, endCol: newDataColTab });
1178
+ this.setActiveCell({ rowIndex: tabResult.rowIndex, columnIndex: tabResult.columnIndex });
1179
+ break;
1180
+ }
1181
+ case "Home": {
1182
+ e.preventDefault();
1183
+ const newRowHome = ctrl ? 0 : rowIndex;
1184
+ this.setSelectionRange({ startRow: newRowHome, startCol: 0, endRow: newRowHome, endCol: 0 });
1185
+ this.setActiveCell({ rowIndex: newRowHome, columnIndex: colOffset });
1186
+ break;
1187
+ }
1188
+ case "End": {
1189
+ e.preventDefault();
1190
+ const newRowEnd = ctrl ? maxRowIndex : rowIndex;
1191
+ this.setSelectionRange({ startRow: newRowEnd, startCol: visibleColumnCount - 1, endRow: newRowEnd, endCol: visibleColumnCount - 1 });
1192
+ this.setActiveCell({ rowIndex: newRowEnd, columnIndex: maxColIndex });
1193
+ break;
1194
+ }
1195
+ case "Enter":
1196
+ case "F2": {
1197
+ e.preventDefault();
1198
+ if (dataColIndex >= 0 && dataColIndex < visibleCols.length) {
1199
+ const col = visibleCols[dataColIndex];
1200
+ const item = items[rowIndex];
1201
+ if (item && col) {
1202
+ const colEditable = col.editable === true || typeof col.editable === "function" && col.editable(item);
1203
+ if (editable !== false && colEditable && wrappedOnCellValueChanged != null) {
1204
+ setEditingCell({ rowId: getRowId(item), columnId: col.columnId });
1205
+ }
1206
+ }
1207
+ }
1208
+ break;
1209
+ }
1210
+ case "Escape":
1211
+ e.preventDefault();
1212
+ if (editingCell != null) {
1213
+ setEditingCell(null);
1214
+ } else {
1215
+ this.clearClipboardRanges();
1216
+ this.setActiveCell(null);
1217
+ this.setSelectionRange(null);
1218
+ }
1219
+ break;
1220
+ case " ":
1221
+ if (rowSelection !== "none" && columnIndex === 0 && hasCheckboxCol) {
1222
+ e.preventDefault();
1223
+ const item = items[rowIndex];
1224
+ if (item) {
1225
+ const id = getRowId(item);
1226
+ const isSelected = selectedRowIds.has(id);
1227
+ handleRowCheckboxChange(id, !isSelected, rowIndex, e.shiftKey);
1228
+ }
1229
+ }
1230
+ break;
1231
+ case "z":
1232
+ if (ctrl) {
1233
+ if (editingCell == null) {
1234
+ if (e.shiftKey) {
1235
+ e.preventDefault();
1236
+ this.redo(originalOnCellValueChanged);
1237
+ } else {
1238
+ e.preventDefault();
1239
+ this.undo(originalOnCellValueChanged);
1240
+ }
1241
+ }
1242
+ }
1243
+ break;
1244
+ case "y":
1245
+ if (ctrl && editingCell == null) {
1246
+ e.preventDefault();
1247
+ this.redo(originalOnCellValueChanged);
1248
+ }
1249
+ break;
1250
+ case "a":
1251
+ if (ctrl) {
1252
+ if (editingCell != null) break;
1253
+ e.preventDefault();
1254
+ if (items.length > 0 && visibleColumnCount > 0) {
1255
+ this.setSelectionRange({ startRow: 0, startCol: 0, endRow: items.length - 1, endCol: visibleColumnCount - 1 });
1256
+ this.setActiveCell({ rowIndex: 0, columnIndex: colOffset });
1257
+ }
1258
+ }
1259
+ break;
1260
+ case "Delete":
1261
+ case "Backspace": {
1262
+ if (editingCell != null) break;
1263
+ if (editable === false) break;
1264
+ if (wrappedOnCellValueChanged == null) break;
1265
+ const range = selectionRange ?? (activeCell != null ? { startRow: activeCell.rowIndex, startCol: activeCell.columnIndex - colOffset, endRow: activeCell.rowIndex, endCol: activeCell.columnIndex - colOffset } : null);
1266
+ if (range == null) break;
1267
+ e.preventDefault();
1268
+ const norm = normalizeSelectionRange(range);
1269
+ for (let r = norm.startRow; r <= norm.endRow; r++) {
1270
+ for (let c = norm.startCol; c <= norm.endCol; c++) {
1271
+ if (r >= items.length || c >= visibleCols.length) continue;
1272
+ const item = items[r];
1273
+ const col = visibleCols[c];
1274
+ const colEditable = col.editable === true || typeof col.editable === "function" && col.editable(item);
1275
+ if (!colEditable) continue;
1276
+ const oldValue = getCellValue(item, col);
1277
+ const result = parseValue("", oldValue, item, col);
1278
+ if (!result.valid) continue;
1279
+ wrappedOnCellValueChanged({ item, columnId: col.columnId, oldValue, newValue: result.value, rowIndex: r });
1280
+ }
1281
+ }
1282
+ break;
1283
+ }
1284
+ case "F10":
1285
+ if (e.shiftKey) {
1286
+ e.preventDefault();
1287
+ if (activeCell != null && wrapperEl) {
1288
+ const sel = `[data-row-index="${activeCell.rowIndex}"][data-col-index="${activeCell.columnIndex}"]`;
1289
+ const cell = wrapperEl.querySelector(sel);
1290
+ if (cell) {
1291
+ const rect = cell.getBoundingClientRect();
1292
+ this.setContextMenuPosition({ x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 });
1293
+ } else {
1294
+ this.setContextMenuPosition({ x: 100, y: 100 });
1295
+ }
1296
+ } else {
1297
+ this.setContextMenuPosition({ x: 100, y: 100 });
1298
+ }
1299
+ }
1300
+ break;
1301
+ }
1302
+ }
1303
+ // --- Drag helpers ---
1304
+ onWindowMouseMove(e, colOffset, wrapperEl) {
1305
+ if (!this.isDraggingRef || !this.dragStartPos) return;
1306
+ if (!this.dragMoved) {
1307
+ this.dragMoved = true;
1308
+ this.isDraggingSig.set(true);
1309
+ }
1310
+ this.lastMousePos = { cx: e.clientX, cy: e.clientY };
1311
+ if (this.rafId) cancelAnimationFrame(this.rafId);
1312
+ this.rafId = requestAnimationFrame(() => {
1313
+ this.rafId = 0;
1314
+ const pos = this.lastMousePos;
1315
+ if (!pos) return;
1316
+ const newRange = this.resolveRangeFromMouse(pos.cx, pos.cy, colOffset);
1317
+ if (!newRange) return;
1318
+ const prev = this.liveDragRange;
1319
+ if (prev && prev.startRow === newRange.startRow && prev.startCol === newRange.startCol && prev.endRow === newRange.endRow && prev.endCol === newRange.endCol) return;
1320
+ this.liveDragRange = newRange;
1321
+ this.applyDragAttrs(newRange, colOffset, wrapperEl);
1322
+ });
1323
+ }
1324
+ onWindowMouseUp(colOffset, wrapperEl) {
1325
+ if (!this.isDraggingRef) return;
1326
+ if (this.autoScrollInterval) {
1327
+ clearInterval(this.autoScrollInterval);
1328
+ this.autoScrollInterval = null;
1329
+ }
1330
+ if (this.rafId) {
1331
+ cancelAnimationFrame(this.rafId);
1332
+ this.rafId = 0;
1333
+ }
1334
+ this.isDraggingRef = false;
1335
+ const wasDrag = this.dragMoved;
1336
+ if (wasDrag) {
1337
+ const pos = this.lastMousePos;
1338
+ if (pos) {
1339
+ const flushed = this.resolveRangeFromMouse(pos.cx, pos.cy, colOffset);
1340
+ if (flushed) this.liveDragRange = flushed;
1341
+ }
1342
+ const finalRange = this.liveDragRange;
1343
+ if (finalRange) {
1344
+ this.setSelectionRange(finalRange);
1345
+ this.setActiveCell({
1346
+ rowIndex: finalRange.endRow,
1347
+ columnIndex: finalRange.endCol + colOffset
1348
+ });
1349
+ }
1350
+ }
1351
+ this.clearDragAttrs(wrapperEl);
1352
+ this.liveDragRange = null;
1353
+ this.lastMousePos = null;
1354
+ this.dragStartPos = null;
1355
+ if (wasDrag) this.isDraggingSig.set(false);
1356
+ }
1357
+ resolveRangeFromMouse(cx, cy, colOffset) {
1358
+ if (!this.dragStartPos) return null;
1359
+ const target = document.elementFromPoint(cx, cy);
1360
+ const cell = target?.closest?.("[data-row-index][data-col-index]");
1361
+ if (!cell) return null;
1362
+ const r = parseInt(cell.getAttribute("data-row-index") ?? "", 10);
1363
+ const c = parseInt(cell.getAttribute("data-col-index") ?? "", 10);
1364
+ if (Number.isNaN(r) || Number.isNaN(c) || c < colOffset) return null;
1365
+ const dataCol = c - colOffset;
1366
+ const start = this.dragStartPos;
1367
+ return normalizeSelectionRange({
1368
+ startRow: start.row,
1369
+ startCol: start.col,
1370
+ endRow: r,
1371
+ endCol: dataCol
1372
+ });
1373
+ }
1374
+ applyDragAttrs(range, colOff, wrapper) {
1375
+ if (!wrapper) return;
1376
+ const minR = Math.min(range.startRow, range.endRow);
1377
+ const maxR = Math.max(range.startRow, range.endRow);
1378
+ const minC = Math.min(range.startCol, range.endCol);
1379
+ const maxC = Math.max(range.startCol, range.endCol);
1380
+ const cells = wrapper.querySelectorAll("[data-row-index][data-col-index]");
1381
+ for (let i = 0; i < cells.length; i++) {
1382
+ const el = cells[i];
1383
+ const r = parseInt(el.getAttribute("data-row-index") ?? "", 10);
1384
+ const c = parseInt(el.getAttribute("data-col-index") ?? "", 10) - colOff;
1385
+ const inRange = r >= minR && r <= maxR && c >= minC && c <= maxC;
1386
+ if (inRange) {
1387
+ if (!el.hasAttribute("data-drag-range")) el.setAttribute("data-drag-range", "");
1388
+ } else {
1389
+ if (el.hasAttribute("data-drag-range")) el.removeAttribute("data-drag-range");
1390
+ }
1391
+ }
1392
+ }
1393
+ clearDragAttrs(wrapper) {
1394
+ if (!wrapper) return;
1395
+ const marked = wrapper.querySelectorAll("[data-drag-range]");
1396
+ for (let i = 0; i < marked.length; i++) marked[i].removeAttribute("data-drag-range");
1397
+ }
1398
+ // --- Private helpers ---
1399
+ getEffectiveRange(colOffset) {
1400
+ const sel = this.selectionRangeSig();
1401
+ const ac = this.activeCellSig();
1402
+ return sel ?? (ac != null ? { startRow: ac.rowIndex, startCol: ac.columnIndex - colOffset, endRow: ac.rowIndex, endCol: ac.columnIndex - colOffset } : null);
1403
+ }
1404
+ destroy() {
1405
+ if (this.rafId) {
1406
+ cancelAnimationFrame(this.rafId);
1407
+ this.rafId = 0;
1408
+ }
1409
+ if (this.fillRafId) {
1410
+ cancelAnimationFrame(this.fillRafId);
1411
+ this.fillRafId = 0;
1412
+ }
1413
+ if (this.autoScrollInterval) {
1414
+ clearInterval(this.autoScrollInterval);
1415
+ this.autoScrollInterval = null;
1416
+ }
1417
+ if (this.fillMoveHandler) {
1418
+ window.removeEventListener("mousemove", this.fillMoveHandler, true);
1419
+ this.fillMoveHandler = null;
1420
+ }
1421
+ if (this.fillUpHandler) {
1422
+ window.removeEventListener("mouseup", this.fillUpHandler, true);
1423
+ this.fillUpHandler = null;
1424
+ }
1425
+ this.undoRedoStack.clear();
1426
+ }
1427
+ };
1428
+
1429
+ // src/services/datagrid-state.service.ts
1430
+ var NOOP = () => {
1431
+ };
1432
+ var NOOP_ASYNC = async () => {
1433
+ };
1434
+ var NOOP_MOUSE = (_e, _r, _c) => {
1435
+ };
1436
+ var NOOP_KEY = (_e) => {
1437
+ };
1438
+ var NOOP_CTX = (_e) => {
1439
+ };
1440
+ var DataGridStateService = class {
1441
+ constructor() {
1442
+ this.destroyRef = inject(DestroyRef);
1443
+ this.ngZone = inject(NgZone);
1444
+ // --- Input signals ---
1445
+ this.props = signal(null);
1446
+ this.wrapperEl = signal(null);
1447
+ // --- Internal state (still owned by main service for backward compat) ---
1448
+ this.internalSelectedRows = signal(/* @__PURE__ */ new Set());
1449
+ // Row selection
1450
+ this.lastClickedRow = -1;
1451
+ // Header menu state (for column pinning UI)
1452
+ this.headerMenuIsOpenSig = signal(false);
1453
+ this.headerMenuOpenForColumnSig = signal(null);
1454
+ this.headerMenuAnchorElementSig = signal(null);
1455
+ // --- Derived computed ---
1456
+ this.propsResolved = computed(() => {
1457
+ const p = this.props();
1458
+ if (!p) throw new Error("DataGridStateService: props must be set before use");
1459
+ return p;
1460
+ });
1461
+ this.cellSelection = computed(() => {
1462
+ const p = this.props();
1463
+ return p ? p.cellSelection !== false : true;
1464
+ });
1465
+ // Narrow signal extractors — prevent full props() dependency in effects/computed
1466
+ this.originalOnCellValueChanged = computed(() => this.props()?.onCellValueChanged);
1467
+ // Undo/redo wrapped callback — only recomputes when the actual callback reference changes
1468
+ this.wrappedOnCellValueChanged = computed(() => {
1469
+ const original = this.originalOnCellValueChanged();
1470
+ if (!original) return void 0;
1471
+ return (event) => {
1472
+ this.interactionHelper.undoRedoStack.record(event);
1473
+ if (!this.interactionHelper.undoRedoStack.isBatching) {
1474
+ this.interactionHelper.undoLengthSig.set(this.interactionHelper.undoRedoStack.historyLength);
1475
+ this.interactionHelper.redoLengthSig.set(this.interactionHelper.undoRedoStack.redoLength);
1476
+ }
1477
+ original(event);
1478
+ };
1479
+ });
1480
+ // --- Delegated computed signals from layoutHelper ---
1481
+ this.flatColumnsRaw = computed(() => this.layoutHelper.flatColumnsRaw());
1482
+ this.flatColumns = computed(() => this.layoutHelper.flatColumns());
1483
+ this.visibleCols = computed(() => this.layoutHelper.visibleCols());
1484
+ this.visibleColumnCount = computed(() => this.layoutHelper.visibleColumnCount());
1485
+ this.hasCheckboxCol = computed(() => this.layoutHelper.hasCheckboxCol());
1486
+ this.hasRowNumbersCol = computed(() => this.layoutHelper.hasRowNumbersCol());
1487
+ this.specialColsCount = computed(() => this.layoutHelper.specialColsCount());
1488
+ this.totalColCount = computed(() => this.layoutHelper.totalColCount());
1489
+ this.colOffset = computed(() => this.layoutHelper.colOffset());
1490
+ this.rowIndexByRowId = computed(() => this.layoutHelper.rowIndexByRowId());
1491
+ this.selectedRowIds = computed(() => {
1492
+ const p = this.props();
1493
+ if (!p) return /* @__PURE__ */ new Set();
1494
+ const controlled = p.selectedRows;
1495
+ if (controlled != null) {
1496
+ return controlled instanceof Set ? controlled : new Set(controlled);
1497
+ }
1498
+ return this.internalSelectedRows();
1499
+ });
1500
+ this.allSelected = computed(() => {
1501
+ const p = this.props();
1502
+ if (!p || p.items.length === 0) return false;
1503
+ const selected = this.selectedRowIds();
1504
+ return p.items.every((item) => selected.has(p.getRowId(item)));
1505
+ });
1506
+ this.someSelected = computed(() => {
1507
+ const p = this.props();
1508
+ if (!p) return false;
1509
+ const selected = this.selectedRowIds();
1510
+ return !this.allSelected() && p.items.some((item) => selected.has(p.getRowId(item)));
1511
+ });
1512
+ this.hasCellSelection = computed(() => this.interactionHelper.hasCellSelection());
1513
+ this.canUndo = computed(() => this.interactionHelper.canUndo());
1514
+ this.canRedo = computed(() => this.interactionHelper.canRedo());
1515
+ // Table layout (delegated to layoutHelper)
1516
+ this.minTableWidth = computed(() => this.layoutHelper.minTableWidth());
1517
+ this.desiredTableWidth = computed(() => this.layoutHelper.desiredTableWidth());
1518
+ this.aggregation = computed(() => {
1519
+ const p = this.props();
1520
+ if (!p) return null;
1521
+ return computeAggregations(
1522
+ p.items,
1523
+ this.visibleCols(),
1524
+ this.cellSelection() ? this.interactionHelper.selectionRangeSig() : null
1525
+ );
1526
+ });
1527
+ this.statusBarConfig = computed(() => {
1528
+ const p = this.props();
1529
+ if (!p) return null;
1530
+ const base = getDataGridStatusBarConfig(
1531
+ p.statusBar,
1532
+ p.items.length,
1533
+ this.selectedRowIds().size
1534
+ );
1535
+ if (!base) return null;
1536
+ return { ...base, aggregation: this.aggregation() ?? void 0 };
1537
+ });
1538
+ this.showEmptyInGrid = computed(() => {
1539
+ const p = this.props();
1540
+ if (!p) return false;
1541
+ return p.items.length === 0 && !!p.emptyState && !p.isLoading;
1542
+ });
1543
+ this.layoutHelper = new DataGridLayoutHelper(this.props, this.wrapperEl, this.ngZone);
1544
+ this.interactionHelper = new DataGridInteractionHelper();
1545
+ this.editingHelper = new DataGridEditingHelper(
1546
+ () => this.visibleCols(),
1547
+ () => this.props()?.items ?? [],
1548
+ () => this.wrappedOnCellValueChanged(),
1549
+ (cell) => this.setActiveCell(cell)
1550
+ );
1551
+ effect((onCleanup) => {
1552
+ const onMove = (e) => this.onWindowMouseMove(e);
1553
+ const onUp = () => this.onWindowMouseUp();
1554
+ this.ngZone.runOutsideAngular(() => {
1555
+ window.addEventListener("mousemove", onMove, true);
1556
+ window.addEventListener("mouseup", onUp, true);
1557
+ });
1558
+ onCleanup(() => {
1559
+ window.removeEventListener("mousemove", onMove, true);
1560
+ window.removeEventListener("mouseup", onUp, true);
1561
+ });
1562
+ });
1563
+ this.destroyRef.onDestroy(() => {
1564
+ this.interactionHelper.destroy();
1565
+ this.layoutHelper.destroy();
1566
+ });
1567
+ }
1568
+ // --- Row selection methods ---
1569
+ updateSelection(newSelectedIds) {
1570
+ const p = this.props();
1571
+ if (!p) return;
1572
+ if (p.selectedRows === void 0) {
1573
+ this.internalSelectedRows.set(newSelectedIds);
1574
+ }
1575
+ p.onSelectionChange?.({
1576
+ selectedRowIds: Array.from(newSelectedIds),
1577
+ selectedItems: p.items.filter((item) => newSelectedIds.has(p.getRowId(item)))
1578
+ });
1579
+ }
1580
+ handleRowCheckboxChange(rowId, checked, rowIndex, shiftKey) {
1581
+ const p = this.props();
1582
+ if (!p) return;
1583
+ const rowSelection = p.rowSelection ?? "none";
1584
+ if (rowSelection === "single") {
1585
+ this.updateSelection(checked ? /* @__PURE__ */ new Set([rowId]) : /* @__PURE__ */ new Set());
1586
+ this.lastClickedRow = rowIndex;
1587
+ return;
1588
+ }
1589
+ const next = new Set(this.selectedRowIds());
1590
+ if (shiftKey && this.lastClickedRow >= 0 && this.lastClickedRow !== rowIndex) {
1591
+ const start = Math.min(this.lastClickedRow, rowIndex);
1592
+ const end = Math.max(this.lastClickedRow, rowIndex);
1593
+ for (let i = start; i <= end; i++) {
1594
+ if (i < p.items.length) {
1595
+ const id = p.getRowId(p.items[i]);
1596
+ if (checked) next.add(id);
1597
+ else next.delete(id);
1598
+ }
1599
+ }
1600
+ } else {
1601
+ if (checked) next.add(rowId);
1602
+ else next.delete(rowId);
1603
+ }
1604
+ this.lastClickedRow = rowIndex;
1605
+ this.updateSelection(next);
1606
+ }
1607
+ handleSelectAll(checked) {
1608
+ const p = this.props();
1609
+ if (!p) return;
1610
+ if (checked) {
1611
+ this.updateSelection(new Set(p.items.map((item) => p.getRowId(item))));
1612
+ } else {
1613
+ this.updateSelection(/* @__PURE__ */ new Set());
1614
+ }
1615
+ }
1616
+ // --- Cell editing (delegated to editingHelper) ---
1617
+ setEditingCell(cell) {
1618
+ this.editingHelper.setEditingCell(cell);
1619
+ }
1620
+ setPendingEditorValue(value) {
1621
+ this.editingHelper.setPendingEditorValue(value);
1622
+ }
1623
+ setActiveCell(cell) {
1624
+ this.interactionHelper.setActiveCell(cell);
1625
+ }
1626
+ setSelectionRange(range) {
1627
+ this.interactionHelper.setSelectionRange(range);
1628
+ }
1629
+ commitCellEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex) {
1630
+ this.editingHelper.commitCellEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex);
1631
+ }
1632
+ cancelPopoverEdit() {
1633
+ this.editingHelper.cancelPopoverEdit();
1634
+ }
1635
+ // --- Cell selection / mouse handling (delegated to interactionHelper) ---
1636
+ handleCellMouseDown(e, rowIndex, globalColIndex) {
1637
+ this.interactionHelper.handleCellMouseDown(e, rowIndex, globalColIndex, this.colOffset(), this.wrapperEl());
1638
+ }
1639
+ handleSelectAllCells() {
1640
+ const p = this.props();
1641
+ if (!p) return;
1642
+ this.interactionHelper.handleSelectAllCells(p.items.length, this.visibleColumnCount(), this.colOffset());
1643
+ }
1644
+ // --- Context menu (delegated to interactionHelper) ---
1645
+ setContextMenuPosition(pos) {
1646
+ this.interactionHelper.setContextMenuPosition(pos);
1647
+ }
1648
+ handleCellContextMenu(e) {
1649
+ this.interactionHelper.handleCellContextMenu(e);
1650
+ }
1651
+ closeContextMenu() {
1652
+ this.interactionHelper.closeContextMenu();
1653
+ }
1654
+ // --- Clipboard (delegated to interactionHelper) ---
1655
+ handleCopy() {
1656
+ const p = this.props();
1657
+ if (!p) return;
1658
+ this.interactionHelper.handleCopy(p.items, this.visibleCols(), this.colOffset());
1659
+ }
1660
+ handleCut() {
1661
+ const p = this.props();
1662
+ if (!p) return;
1663
+ this.interactionHelper.handleCut(
1664
+ p.items,
1665
+ this.visibleCols(),
1666
+ this.colOffset(),
1667
+ p.editable,
1668
+ this.wrappedOnCellValueChanged()
1669
+ );
1670
+ }
1671
+ async handlePaste() {
1672
+ const p = this.props();
1673
+ if (!p) return;
1674
+ await this.interactionHelper.handlePaste(
1675
+ p.items,
1676
+ this.visibleCols(),
1677
+ this.colOffset(),
1678
+ p.editable,
1679
+ this.wrappedOnCellValueChanged()
1680
+ );
1681
+ }
1682
+ clearClipboardRanges() {
1683
+ this.interactionHelper.clearClipboardRanges();
1684
+ }
1685
+ // --- Undo/Redo (delegated to interactionHelper) ---
1686
+ beginBatch() {
1687
+ this.interactionHelper.beginBatch();
1688
+ }
1689
+ endBatch() {
1690
+ this.interactionHelper.endBatch();
1691
+ }
1692
+ undo() {
1693
+ const p = this.props();
1694
+ this.interactionHelper.undo(p?.onCellValueChanged);
1695
+ }
1696
+ redo() {
1697
+ const p = this.props();
1698
+ this.interactionHelper.redo(p?.onCellValueChanged);
1699
+ }
1700
+ // --- Keyboard navigation (delegated to interactionHelper) ---
1701
+ handleGridKeyDown(e) {
1702
+ const p = this.props();
1703
+ if (!p) return;
1704
+ this.interactionHelper.handleGridKeyDown(
1705
+ e,
1706
+ p.items,
1707
+ p.getRowId,
1708
+ this.visibleCols(),
1709
+ this.colOffset(),
1710
+ this.hasCheckboxCol(),
1711
+ this.visibleColumnCount(),
1712
+ p.editable,
1713
+ this.wrappedOnCellValueChanged(),
1714
+ p.onCellValueChanged,
1715
+ p.rowSelection ?? "none",
1716
+ this.selectedRowIds(),
1717
+ this.wrapperEl(),
1718
+ (rowId, checked, rowIndex, shiftKey) => this.handleRowCheckboxChange(rowId, checked, rowIndex, shiftKey),
1719
+ this.editingHelper.editingCellSig(),
1720
+ (cell) => this.setEditingCell(cell)
1721
+ );
1722
+ }
1723
+ // --- Fill handle (delegated to interactionHelper + setupFillHandleDrag) ---
1724
+ handleFillHandleMouseDown(e) {
1725
+ this.interactionHelper.handleFillHandleMouseDown(e);
1726
+ this.setupFillHandleDrag();
1727
+ }
1728
+ // --- Column pinning ---
1729
+ pinColumn(columnId, side) {
1730
+ const props = this.props();
1731
+ props?.onColumnPinned?.(columnId, side);
1732
+ }
1733
+ unpinColumn(columnId) {
1734
+ const props = this.props();
1735
+ props?.onColumnPinned?.(columnId, null);
1736
+ }
1737
+ isPinned(columnId) {
1738
+ const props = this.props();
1739
+ return props?.pinnedColumns?.[columnId];
1740
+ }
1741
+ getPinState(columnId) {
1742
+ const pinned = this.isPinned(columnId);
1743
+ return {
1744
+ canPinLeft: pinned !== "left",
1745
+ canPinRight: pinned !== "right",
1746
+ canUnpin: !!pinned
1747
+ };
1748
+ }
1749
+ // --- Header menu ---
1750
+ openHeaderMenu(columnId, anchorEl) {
1751
+ this.headerMenuOpenForColumnSig.set(columnId);
1752
+ this.headerMenuAnchorElementSig.set(anchorEl);
1753
+ this.headerMenuIsOpenSig.set(true);
1754
+ }
1755
+ closeHeaderMenu() {
1756
+ this.headerMenuIsOpenSig.set(false);
1757
+ this.headerMenuOpenForColumnSig.set(null);
1758
+ this.headerMenuAnchorElementSig.set(null);
1759
+ }
1760
+ headerMenuPinLeft() {
1761
+ const col = this.headerMenuOpenForColumnSig();
1762
+ if (col && this.isPinned(col) !== "left") {
1763
+ this.pinColumn(col, "left");
1764
+ this.closeHeaderMenu();
1765
+ }
1766
+ }
1767
+ headerMenuPinRight() {
1768
+ const col = this.headerMenuOpenForColumnSig();
1769
+ if (col && this.isPinned(col) !== "right") {
1770
+ this.pinColumn(col, "right");
1771
+ this.closeHeaderMenu();
1772
+ }
1773
+ }
1774
+ headerMenuUnpin() {
1775
+ const col = this.headerMenuOpenForColumnSig();
1776
+ if (col && this.isPinned(col)) {
1777
+ this.unpinColumn(col);
1778
+ this.closeHeaderMenu();
1779
+ }
1780
+ }
1781
+ // --- Get state result ---
1782
+ getState() {
1783
+ const p = this.props();
1784
+ const cellSel = this.cellSelection();
1785
+ const layout = {
1786
+ flatColumns: this.flatColumns(),
1787
+ visibleCols: this.visibleCols(),
1788
+ visibleColumnCount: this.visibleColumnCount(),
1789
+ totalColCount: this.totalColCount(),
1790
+ colOffset: this.colOffset(),
1791
+ hasCheckboxCol: this.hasCheckboxCol(),
1792
+ hasRowNumbersCol: this.hasRowNumbersCol(),
1793
+ rowIndexByRowId: this.rowIndexByRowId(),
1794
+ containerWidth: this.layoutHelper.containerWidthSig(),
1795
+ minTableWidth: this.minTableWidth(),
1796
+ desiredTableWidth: this.desiredTableWidth(),
1797
+ columnSizingOverrides: this.layoutHelper.columnSizingOverridesSig(),
1798
+ setColumnSizingOverrides: (overrides) => this.layoutHelper.columnSizingOverridesSig.set(overrides),
1799
+ onColumnResized: p?.onColumnResized,
1800
+ onAutosizeColumn: p?.onAutosizeColumn
1801
+ };
1802
+ const rowSelection = {
1803
+ selectedRowIds: this.selectedRowIds(),
1804
+ updateSelection: (ids) => this.updateSelection(ids),
1805
+ handleRowCheckboxChange: (rowId, checked, rowIndex, shiftKey) => this.handleRowCheckboxChange(rowId, checked, rowIndex, shiftKey),
1806
+ handleSelectAll: (checked) => this.handleSelectAll(checked),
1807
+ allSelected: this.allSelected(),
1808
+ someSelected: this.someSelected()
1809
+ };
1810
+ const editing = {
1811
+ editingCell: this.editingHelper.editingCellSig(),
1812
+ setEditingCell: (cell) => this.setEditingCell(cell),
1813
+ pendingEditorValue: this.editingHelper.pendingEditorValueSig(),
1814
+ setPendingEditorValue: (v) => this.setPendingEditorValue(v),
1815
+ commitCellEdit: (item, colId, oldVal, newVal, rowIdx, globalColIdx) => this.commitCellEdit(item, colId, oldVal, newVal, rowIdx, globalColIdx),
1816
+ cancelPopoverEdit: () => this.cancelPopoverEdit(),
1817
+ popoverAnchorEl: this.editingHelper.popoverAnchorElSig(),
1818
+ setPopoverAnchorEl: (el) => this.editingHelper.popoverAnchorElSig.set(el)
1819
+ };
1820
+ const interaction = {
1821
+ activeCell: cellSel ? this.interactionHelper.activeCellSig() : null,
1822
+ setActiveCell: cellSel ? (cell) => this.setActiveCell(cell) : void 0,
1823
+ selectionRange: cellSel ? this.interactionHelper.selectionRangeSig() : null,
1824
+ setSelectionRange: cellSel ? (range) => this.setSelectionRange(range) : void 0,
1825
+ handleCellMouseDown: cellSel ? (e, r, c) => this.handleCellMouseDown(e, r, c) : NOOP_MOUSE,
1826
+ handleSelectAllCells: cellSel ? () => this.handleSelectAllCells() : NOOP,
1827
+ hasCellSelection: cellSel ? this.hasCellSelection() : false,
1828
+ handleGridKeyDown: cellSel ? (e) => this.handleGridKeyDown(e) : NOOP_KEY,
1829
+ handleFillHandleMouseDown: cellSel ? (e) => this.handleFillHandleMouseDown(e) : void 0,
1830
+ handleCopy: cellSel ? () => this.handleCopy() : NOOP,
1831
+ handleCut: cellSel ? () => this.handleCut() : NOOP,
1832
+ handlePaste: cellSel ? () => this.handlePaste() : NOOP_ASYNC,
1833
+ cutRange: cellSel ? this.interactionHelper.cutRangeSig() : null,
1834
+ copyRange: cellSel ? this.interactionHelper.copyRangeSig() : null,
1835
+ clearClipboardRanges: cellSel ? () => this.clearClipboardRanges() : NOOP,
1836
+ canUndo: this.canUndo(),
1837
+ canRedo: this.canRedo(),
1838
+ onUndo: () => this.undo(),
1839
+ onRedo: () => this.redo(),
1840
+ isDragging: cellSel ? this.interactionHelper.isDraggingSig() : false
1841
+ };
1842
+ const contextMenu = {
1843
+ menuPosition: cellSel ? this.interactionHelper.contextMenuPositionSig() : null,
1844
+ setMenuPosition: cellSel ? (pos) => this.setContextMenuPosition(pos) : void 0,
1845
+ handleCellContextMenu: cellSel ? (e) => this.handleCellContextMenu(e) : NOOP_CTX,
1846
+ closeContextMenu: cellSel ? () => this.closeContextMenu() : NOOP
1847
+ };
1848
+ const viewModels = {
1849
+ headerFilterInput: {
1850
+ sortBy: p?.sortBy,
1851
+ sortDirection: p?.sortDirection ?? "asc",
1852
+ onColumnSort: (columnKey, direction) => p?.onColumnSort(columnKey, direction),
1853
+ filters: p?.filters ?? {},
1854
+ onFilterChange: (key, value) => p?.onFilterChange(key, value),
1855
+ filterOptions: p?.filterOptions ?? {},
1856
+ loadingFilterOptions: p?.loadingFilterOptions ?? {},
1857
+ peopleSearch: p?.peopleSearch
1858
+ },
1859
+ cellDescriptorInput: {
1860
+ editingCell: this.editingHelper.editingCellSig(),
1861
+ activeCell: cellSel ? this.interactionHelper.activeCellSig() : null,
1862
+ selectionRange: cellSel ? this.interactionHelper.selectionRangeSig() : null,
1863
+ cutRange: cellSel ? this.interactionHelper.cutRangeSig() : null,
1864
+ copyRange: cellSel ? this.interactionHelper.copyRangeSig() : null,
1865
+ colOffset: this.colOffset(),
1866
+ itemsLength: p?.items.length ?? 0,
1867
+ getRowId: p?.getRowId ?? ((item) => item["id"]),
1868
+ editable: p?.editable,
1869
+ onCellValueChanged: this.wrappedOnCellValueChanged(),
1870
+ isDragging: cellSel ? this.interactionHelper.isDraggingSig() : false
1871
+ },
1872
+ statusBarConfig: this.statusBarConfig(),
1873
+ showEmptyInGrid: this.showEmptyInGrid(),
1874
+ onCellError: p?.onCellError
1875
+ };
1876
+ const openForColumn = this.headerMenuOpenForColumnSig();
1877
+ const currentPinState = openForColumn ? p?.pinnedColumns?.[openForColumn] : void 0;
1878
+ const pinning = {
1879
+ pinnedColumns: p?.pinnedColumns ?? {},
1880
+ pinColumn: (columnId, side) => this.pinColumn(columnId, side),
1881
+ unpinColumn: (columnId) => this.unpinColumn(columnId),
1882
+ isPinned: (columnId) => this.isPinned(columnId),
1883
+ headerMenu: {
1884
+ isOpen: this.headerMenuIsOpenSig(),
1885
+ openForColumn,
1886
+ anchorElement: this.headerMenuAnchorElementSig(),
1887
+ open: (columnId, anchorEl) => this.openHeaderMenu(columnId, anchorEl),
1888
+ close: () => this.closeHeaderMenu(),
1889
+ handlePinLeft: () => this.headerMenuPinLeft(),
1890
+ handlePinRight: () => this.headerMenuPinRight(),
1891
+ handleUnpin: () => this.headerMenuUnpin(),
1892
+ canPinLeft: currentPinState !== "left",
1893
+ canPinRight: currentPinState !== "right",
1894
+ canUnpin: !!currentPinState
1895
+ }
1896
+ };
1897
+ return { layout, rowSelection, editing, interaction, contextMenu, viewModels, pinning };
1898
+ }
1899
+ // --- Private helpers (drag selection delegated to interactionHelper) ---
1900
+ onWindowMouseMove(e) {
1901
+ this.interactionHelper.onWindowMouseMove(e, this.colOffset(), this.wrapperEl());
1902
+ }
1903
+ onWindowMouseUp() {
1904
+ this.interactionHelper.onWindowMouseUp(this.colOffset(), this.wrapperEl());
1905
+ }
1906
+ setupFillHandleDrag() {
1907
+ const p = this.props();
1908
+ const fillDragStart = this.interactionHelper.fillDragStart;
1909
+ if (!fillDragStart || p?.editable === false || !this.wrappedOnCellValueChanged()) return;
1910
+ const colOff = this.colOffset();
1911
+ const fillStart = fillDragStart;
1912
+ let fillDragEnd = { endRow: fillStart.startRow, endCol: fillStart.startCol };
1913
+ let liveFillRange = null;
1914
+ let lastFillMousePos = null;
1915
+ const resolveRange = (cx, cy) => {
1916
+ const target = document.elementFromPoint(cx, cy);
1917
+ const cell = target?.closest?.("[data-row-index][data-col-index]");
1918
+ const wrapper = this.wrapperEl();
1919
+ if (!cell || !wrapper?.contains(cell)) return null;
1920
+ const r = parseInt(cell.getAttribute("data-row-index") ?? "", 10);
1921
+ const c = parseInt(cell.getAttribute("data-col-index") ?? "", 10);
1922
+ if (Number.isNaN(r) || Number.isNaN(c) || c < colOff) return null;
1923
+ const dataCol = c - colOff;
1924
+ return normalizeSelectionRange({
1925
+ startRow: fillStart.startRow,
1926
+ startCol: fillStart.startCol,
1927
+ endRow: r,
1928
+ endCol: dataCol
1929
+ });
1930
+ };
1931
+ const onMove = (e) => {
1932
+ lastFillMousePos = { cx: e.clientX, cy: e.clientY };
1933
+ if (this.interactionHelper.fillRafId) cancelAnimationFrame(this.interactionHelper.fillRafId);
1934
+ this.interactionHelper.fillRafId = requestAnimationFrame(() => {
1935
+ this.interactionHelper.fillRafId = 0;
1936
+ if (!lastFillMousePos) return;
1937
+ const newRange = resolveRange(lastFillMousePos.cx, lastFillMousePos.cy);
1938
+ if (!newRange) return;
1939
+ if (liveFillRange && liveFillRange.startRow === newRange.startRow && liveFillRange.startCol === newRange.startCol && liveFillRange.endRow === newRange.endRow && liveFillRange.endCol === newRange.endCol) return;
1940
+ liveFillRange = newRange;
1941
+ fillDragEnd = { endRow: newRange.endRow, endCol: newRange.endCol };
1942
+ this.interactionHelper.applyDragAttrs(newRange, colOff, this.wrapperEl());
1943
+ });
1944
+ };
1945
+ const onUp = () => {
1946
+ window.removeEventListener("mousemove", onMove, true);
1947
+ window.removeEventListener("mouseup", onUp, true);
1948
+ this.interactionHelper.fillMoveHandler = null;
1949
+ this.interactionHelper.fillUpHandler = null;
1950
+ if (this.interactionHelper.fillRafId) {
1951
+ cancelAnimationFrame(this.interactionHelper.fillRafId);
1952
+ this.interactionHelper.fillRafId = 0;
1953
+ }
1954
+ if (lastFillMousePos) {
1955
+ const flushed = resolveRange(lastFillMousePos.cx, lastFillMousePos.cy);
1956
+ if (flushed) {
1957
+ liveFillRange = flushed;
1958
+ fillDragEnd = { endRow: flushed.endRow, endCol: flushed.endCol };
1959
+ }
1960
+ }
1961
+ this.interactionHelper.clearDragAttrs(this.wrapperEl());
1962
+ const norm = normalizeSelectionRange({
1963
+ startRow: fillStart.startRow,
1964
+ startCol: fillStart.startCol,
1965
+ endRow: fillDragEnd.endRow,
1966
+ endCol: fillDragEnd.endCol
1967
+ });
1968
+ this.setSelectionRange(norm);
1969
+ this.setActiveCell({ rowIndex: fillDragEnd.endRow, columnIndex: fillDragEnd.endCol + colOff });
1970
+ if (!p) return;
1971
+ const items = p.items;
1972
+ const visibleCols = this.visibleCols();
1973
+ const startItem = items[norm.startRow];
1974
+ const startColDef = visibleCols[norm.startCol];
1975
+ const onCellValueChanged = this.wrappedOnCellValueChanged();
1976
+ if (startItem && startColDef && onCellValueChanged) {
1977
+ const startValue = getCellValue(startItem, startColDef);
1978
+ this.beginBatch();
1979
+ for (let row = norm.startRow; row <= norm.endRow; row++) {
1980
+ for (let col = norm.startCol; col <= norm.endCol; col++) {
1981
+ if (row === fillStart.startRow && col === fillStart.startCol) continue;
1982
+ if (row >= items.length || col >= visibleCols.length) continue;
1983
+ const item = items[row];
1984
+ const colDef = visibleCols[col];
1985
+ const colEditable = colDef.editable === true || typeof colDef.editable === "function" && colDef.editable(item);
1986
+ if (!colEditable) continue;
1987
+ const oldValue = getCellValue(item, colDef);
1988
+ const result = parseValue(startValue, oldValue, item, colDef);
1989
+ if (!result.valid) continue;
1990
+ onCellValueChanged({ item, columnId: colDef.columnId, oldValue, newValue: result.value, rowIndex: row });
1991
+ }
1992
+ }
1993
+ this.endBatch();
1994
+ }
1995
+ this.interactionHelper.fillDragStart = null;
1996
+ };
1997
+ this.interactionHelper.fillMoveHandler = onMove;
1998
+ this.interactionHelper.fillUpHandler = onUp;
1999
+ this.ngZone.runOutsideAngular(() => {
2000
+ window.addEventListener("mousemove", onMove, true);
2001
+ window.addEventListener("mouseup", onUp, true);
2002
+ });
2003
+ }
2004
+ };
2005
+ DataGridStateService = __decorateClass([
2006
+ Injectable()
2007
+ ], DataGridStateService);
2008
+ var RESIZE_HANDLE_ZONE = 8;
2009
+ var ColumnReorderService = class {
2010
+ constructor() {
2011
+ this.destroyRef = inject(DestroyRef);
2012
+ // --- Input signals (set by consuming component) ---
2013
+ this.columns = signal([]);
2014
+ this.columnOrder = signal(void 0);
2015
+ this.onColumnOrderChange = signal(void 0);
2016
+ this.enabled = signal(true);
2017
+ this.wrapperEl = signal(null);
2018
+ // --- Internal state ---
2019
+ this.isDragging = signal(false);
2020
+ this.dropIndicatorX = signal(null);
2021
+ // Imperative drag tracking (not reactive)
2022
+ this.rafId = 0;
2023
+ this.cleanupFn = null;
2024
+ // Refs for latest values (captured in closure)
2025
+ this.latestDropTargetIndex = null;
2026
+ this.destroyRef.onDestroy(() => {
2027
+ if (this.cleanupFn) {
2028
+ this.cleanupFn();
2029
+ this.cleanupFn = null;
2030
+ }
2031
+ if (this.rafId) {
2032
+ cancelAnimationFrame(this.rafId);
2033
+ this.rafId = 0;
2034
+ }
2035
+ });
2036
+ }
2037
+ /**
2038
+ * Call this from the header cell's mousedown handler.
2039
+ * @param columnId - The column being dragged
2040
+ * @param event - The native MouseEvent
2041
+ */
2042
+ handleHeaderMouseDown(columnId, event) {
2043
+ if (!this.enabled()) return;
2044
+ if (!this.onColumnOrderChange()) return;
2045
+ if (event.button !== 0) return;
2046
+ const target = event.currentTarget;
2047
+ const rect = target.getBoundingClientRect();
2048
+ if (event.clientX > rect.right - RESIZE_HANDLE_ZONE) return;
2049
+ const cols = this.columns();
2050
+ const colIndex = cols.findIndex((c) => c.columnId === columnId);
2051
+ if (colIndex === -1) return;
2052
+ event.preventDefault();
2053
+ const startX = event.clientX;
2054
+ let hasMoved = false;
2055
+ this.latestDropTargetIndex = null;
2056
+ const prevUserSelect = document.body.style.userSelect;
2057
+ document.body.style.userSelect = "none";
2058
+ const onMove = (moveEvent) => {
2059
+ if (!hasMoved && Math.abs(moveEvent.clientX - startX) < 5) return;
2060
+ if (!hasMoved) {
2061
+ hasMoved = true;
2062
+ this.isDragging.set(true);
2063
+ }
2064
+ if (this.rafId) cancelAnimationFrame(this.rafId);
2065
+ this.rafId = requestAnimationFrame(() => {
2066
+ this.rafId = 0;
2067
+ const wrapper = this.wrapperEl();
2068
+ if (!wrapper) return;
2069
+ const headerCells = wrapper.querySelectorAll("th[data-column-id]");
2070
+ const rects = [];
2071
+ for (let i = 0; i < headerCells.length; i++) {
2072
+ const th = headerCells[i];
2073
+ const id = th.getAttribute("data-column-id");
2074
+ if (!id) continue;
2075
+ const thRect = th.getBoundingClientRect();
2076
+ rects.push({
2077
+ columnId: id,
2078
+ left: thRect.left,
2079
+ right: thRect.right,
2080
+ centerX: thRect.left + thRect.width / 2
2081
+ });
2082
+ }
2083
+ const result = this.calculateDrop(columnId, moveEvent.clientX, rects);
2084
+ this.latestDropTargetIndex = result.dropIndex;
2085
+ this.dropIndicatorX.set(result.indicatorX);
2086
+ });
2087
+ };
2088
+ const cleanup = () => {
2089
+ window.removeEventListener("mousemove", onMove, true);
2090
+ window.removeEventListener("mouseup", onUp, true);
2091
+ this.cleanupFn = null;
2092
+ document.body.style.userSelect = prevUserSelect;
2093
+ if (this.rafId) {
2094
+ cancelAnimationFrame(this.rafId);
2095
+ this.rafId = 0;
2096
+ }
2097
+ };
2098
+ const onUp = () => {
2099
+ cleanup();
2100
+ if (hasMoved && this.latestDropTargetIndex != null) {
2101
+ const currentOrder = this.columnOrder() ?? this.columns().map((c) => c.columnId);
2102
+ const newOrder = reorderColumnArray(currentOrder, columnId, this.latestDropTargetIndex);
2103
+ this.onColumnOrderChange()?.(newOrder);
2104
+ }
2105
+ this.isDragging.set(false);
2106
+ this.dropIndicatorX.set(null);
2107
+ };
2108
+ window.addEventListener("mousemove", onMove, true);
2109
+ window.addEventListener("mouseup", onUp, true);
2110
+ this.cleanupFn = cleanup;
2111
+ }
2112
+ /**
2113
+ * Calculate drop target from mouse position and header cell rects.
2114
+ * Same logic as React's useColumnReorder inline calculation.
2115
+ */
2116
+ calculateDrop(draggedColumnId, mouseX, rects) {
2117
+ if (rects.length === 0) {
2118
+ return { dropIndex: null, indicatorX: null };
2119
+ }
2120
+ const order = this.columnOrder() ?? this.columns().map((c) => c.columnId);
2121
+ const currentIndex = order.indexOf(draggedColumnId);
2122
+ let bestIndex = 0;
2123
+ let indicatorX = null;
2124
+ if (mouseX <= rects[0].centerX) {
2125
+ bestIndex = 0;
2126
+ indicatorX = rects[0].left;
2127
+ } else if (mouseX >= rects[rects.length - 1].centerX) {
2128
+ bestIndex = rects.length;
2129
+ indicatorX = rects[rects.length - 1].right;
2130
+ } else {
2131
+ for (let i = 0; i < rects.length - 1; i++) {
2132
+ if (mouseX >= rects[i].centerX && mouseX < rects[i + 1].centerX) {
2133
+ bestIndex = i + 1;
2134
+ indicatorX = rects[i].right;
2135
+ break;
2136
+ }
2137
+ }
2138
+ }
2139
+ const targetOrderIndex = bestIndex < rects.length ? order.indexOf(rects[bestIndex]?.columnId ?? "") : order.length;
2140
+ if (currentIndex === targetOrderIndex || currentIndex + 1 === targetOrderIndex) {
2141
+ return { dropIndex: targetOrderIndex, indicatorX: null };
2142
+ }
2143
+ return { dropIndex: targetOrderIndex, indicatorX };
2144
+ }
2145
+ };
2146
+ ColumnReorderService = __decorateClass([
2147
+ Injectable()
2148
+ ], ColumnReorderService);
2149
+ var PASSTHROUGH_THRESHOLD = 100;
2150
+ var VirtualScrollService = class {
2151
+ constructor() {
2152
+ this.destroyRef = inject(DestroyRef);
2153
+ // --- Input signals (set by consuming component) ---
2154
+ this.totalRows = signal(0);
2155
+ this.config = signal({ rowHeight: 36 });
2156
+ this.containerHeight = signal(0);
2157
+ // --- Internal state ---
2158
+ this.scrollTop = signal(0);
2159
+ // Scrollable container reference for programmatic scrolling
2160
+ this.containerEl = null;
2161
+ // --- Derived computed signals ---
2162
+ this.rowHeight = computed(() => this.config().rowHeight ?? 36);
2163
+ this.overscan = computed(() => this.config().overscan ?? 5);
2164
+ this.enabled = computed(() => this.config().enabled !== false);
2165
+ /** Whether virtual scrolling is actually active (enabled + enough rows). */
2166
+ this.isActive = computed(() => this.enabled() && this.totalRows() >= PASSTHROUGH_THRESHOLD);
2167
+ /** The visible range of rows with spacer offsets. */
2168
+ this.visibleRange = computed(() => {
2169
+ if (!this.isActive()) {
2170
+ return {
2171
+ startIndex: 0,
2172
+ endIndex: Math.max(0, this.totalRows() - 1),
2173
+ offsetTop: 0,
2174
+ offsetBottom: 0
2175
+ };
2176
+ }
2177
+ return computeVisibleRange(
2178
+ this.scrollTop(),
2179
+ this.rowHeight(),
2180
+ this.containerHeight(),
2181
+ this.totalRows(),
2182
+ this.overscan()
2183
+ );
2184
+ });
2185
+ /** Total scrollable height in pixels. */
2186
+ this.totalHeight = computed(() => computeTotalHeight(this.totalRows(), this.rowHeight()));
2187
+ this.destroyRef.onDestroy(() => {
2188
+ this.containerEl = null;
2189
+ });
2190
+ }
2191
+ /**
2192
+ * Set the scrollable container element.
2193
+ * Used for programmatic scrolling (scrollToRow).
2194
+ */
2195
+ setContainer(el) {
2196
+ this.containerEl = el;
2197
+ }
2198
+ /**
2199
+ * Call this from the container's scroll event handler.
2200
+ */
2201
+ onScroll(event) {
2202
+ const target = event.target;
2203
+ this.scrollTop.set(target.scrollTop);
2204
+ }
2205
+ /**
2206
+ * Scroll to a specific row index.
2207
+ * @param index - The row index to scroll to.
2208
+ * @param align - Where to position the row: 'start' (top), 'center', or 'end' (bottom). Default: 'start'.
2209
+ */
2210
+ scrollToRow(index, align = "start") {
2211
+ const container = this.containerEl;
2212
+ if (!container) return;
2213
+ const targetScrollTop = getScrollTopForRow(
2214
+ index,
2215
+ this.rowHeight(),
2216
+ this.containerHeight(),
2217
+ align
2218
+ );
2219
+ container.scrollTo({ top: targetScrollTop, behavior: "auto" });
2220
+ }
2221
+ /**
2222
+ * Update the virtual scroll configuration.
2223
+ */
2224
+ updateConfig(updates) {
2225
+ this.config.update((prev) => ({ ...prev, ...updates }));
2226
+ }
2227
+ };
2228
+ VirtualScrollService = __decorateClass([
2229
+ Injectable()
2230
+ ], VirtualScrollService);
2231
+ var PANEL_LABELS = { columns: "Columns", filters: "Filters" };
2232
+ var SideBarComponent = class {
2233
+ constructor() {
2234
+ this.sideBarProps = null;
2235
+ this.panelLabels = PANEL_LABELS;
2236
+ }
2237
+ onTabClick(panel) {
2238
+ const props = this.sideBarProps;
2239
+ if (props) props.onPanelChange(props.activePanel === panel ? null : panel);
2240
+ }
2241
+ allVisible() {
2242
+ const props = this.sideBarProps;
2243
+ if (!props) return false;
2244
+ return props.columns.every((c) => props.visibleColumns.has(c.columnId));
2245
+ }
2246
+ onSelectAll() {
2247
+ const props = this.sideBarProps;
2248
+ if (!props) return;
2249
+ const next = new Set(props.visibleColumns);
2250
+ props.columns.forEach((c) => next.add(c.columnId));
2251
+ props.onSetVisibleColumns(next);
2252
+ }
2253
+ onClearAll() {
2254
+ const props = this.sideBarProps;
2255
+ if (!props) return;
2256
+ const next = /* @__PURE__ */ new Set();
2257
+ props.columns.forEach((c) => {
2258
+ if (c.required && props.visibleColumns.has(c.columnId)) next.add(c.columnId);
2259
+ });
2260
+ props.onSetVisibleColumns(next);
2261
+ }
2262
+ onVisibilityChange(columnKey, visible) {
2263
+ this.sideBarProps?.onVisibilityChange(columnKey, visible);
2264
+ }
2265
+ getTextFilterValue(filterField) {
2266
+ const filters = this.sideBarProps?.filters;
2267
+ const fv = filters?.[filterField];
2268
+ return fv?.type === "text" ? fv.value : "";
2269
+ }
2270
+ onTextFilterChange(filterField, value) {
2271
+ this.sideBarProps?.onFilterChange(filterField, value ? { type: "text", value } : void 0);
2272
+ }
2273
+ getDateFrom(filterField) {
2274
+ const fv = this.sideBarProps?.filters?.[filterField];
2275
+ return fv?.type === "date" ? fv.value.from ?? "" : "";
2276
+ }
2277
+ getDateTo(filterField) {
2278
+ const fv = this.sideBarProps?.filters?.[filterField];
2279
+ return fv?.type === "date" ? fv.value.to ?? "" : "";
2280
+ }
2281
+ onDateFromChange(filterField, value) {
2282
+ const fv = this.sideBarProps?.filters?.[filterField];
2283
+ const existing = fv?.type === "date" ? fv.value : {};
2284
+ const from = value || void 0;
2285
+ const to = existing.to;
2286
+ this.sideBarProps?.onFilterChange(filterField, from || to ? { type: "date", value: { from, to } } : void 0);
2287
+ }
2288
+ onDateToChange(filterField, value) {
2289
+ const fv = this.sideBarProps?.filters?.[filterField];
2290
+ const existing = fv?.type === "date" ? fv.value : {};
2291
+ const to = value || void 0;
2292
+ const from = existing.from;
2293
+ this.sideBarProps?.onFilterChange(filterField, from || to ? { type: "date", value: { from, to } } : void 0);
2294
+ }
2295
+ getFilterOptions(filterField) {
2296
+ return this.sideBarProps?.filterOptions?.[filterField] ?? [];
2297
+ }
2298
+ isMultiSelectChecked(filterField, opt) {
2299
+ const fv = this.sideBarProps?.filters?.[filterField];
2300
+ return fv?.type === "multiSelect" ? fv.value.includes(opt) : false;
2301
+ }
2302
+ onMultiSelectChange(filterField, opt, checked) {
2303
+ const fv = this.sideBarProps?.filters?.[filterField];
2304
+ const current = fv?.type === "multiSelect" ? fv.value : [];
2305
+ const next = checked ? [...current, opt] : current.filter((v) => v !== opt);
2306
+ this.sideBarProps?.onFilterChange(filterField, next.length > 0 ? { type: "multiSelect", value: next } : void 0);
2307
+ }
2308
+ };
2309
+ __decorateClass([
2310
+ Input()
2311
+ ], SideBarComponent.prototype, "sideBarProps", 2);
2312
+ SideBarComponent = __decorateClass([
2313
+ Component({
2314
+ selector: "ogrid-sidebar",
2315
+ standalone: true,
2316
+ changeDetection: ChangeDetectionStrategy.OnPush,
2317
+ imports: [NgTemplateOutlet],
2318
+ styles: [`
2319
+ .ogrid-sidebar-root { display: flex; flex-direction: row; flex-shrink: 0; }
2320
+ .ogrid-sidebar-tab-strip {
2321
+ display: flex; flex-direction: column;
2322
+ width: var(--ogrid-sidebar-tab-size, 36px);
2323
+ background: var(--ogrid-header-bg, rgba(0, 0, 0, 0.04));
2324
+ }
2325
+ .ogrid-sidebar-tab-strip--left { border-right: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12)); }
2326
+ .ogrid-sidebar-tab-strip--right { border-left: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12)); }
2327
+ .ogrid-sidebar-tab {
2328
+ width: var(--ogrid-sidebar-tab-size, 36px);
2329
+ height: var(--ogrid-sidebar-tab-size, 36px);
2330
+ border: none; cursor: pointer;
2331
+ color: var(--ogrid-fg, rgba(0, 0, 0, 0.87)); font-size: 14px;
2332
+ display: flex; align-items: center; justify-content: center;
2333
+ background: transparent; font-weight: normal;
2334
+ }
2335
+ .ogrid-sidebar-tab--active { background: var(--ogrid-bg, #ffffff); font-weight: bold; }
2336
+ .ogrid-sidebar-panel {
2337
+ width: var(--ogrid-sidebar-panel-width, 240px);
2338
+ display: flex; flex-direction: column; overflow: hidden;
2339
+ background: var(--ogrid-bg, #ffffff); color: var(--ogrid-fg, rgba(0, 0, 0, 0.87));
2340
+ }
2341
+ .ogrid-sidebar-panel--left { border-right: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12)); }
2342
+ .ogrid-sidebar-panel--right { border-left: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12)); }
2343
+ .ogrid-sidebar-panel-header {
2344
+ display: flex; justify-content: space-between; align-items: center;
2345
+ padding: 8px 12px; border-bottom: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12)); font-weight: 600;
2346
+ }
2347
+ .ogrid-sidebar-panel-close {
2348
+ border: none; background: transparent; cursor: pointer;
2349
+ font-size: 16px; color: var(--ogrid-fg, rgba(0, 0, 0, 0.87));
2350
+ }
2351
+ .ogrid-sidebar-panel-body { flex: 1; overflow-y: auto; padding: 8px 12px; }
2352
+ .ogrid-sidebar-actions { display: flex; gap: 8px; margin-bottom: 8px; }
2353
+ .ogrid-sidebar-action-btn {
2354
+ flex: 1; cursor: pointer;
2355
+ background: var(--ogrid-header-bg, rgba(0, 0, 0, 0.04)); color: var(--ogrid-fg, rgba(0, 0, 0, 0.87));
2356
+ border: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12)); border-radius: 4px; padding: 4px 8px;
2357
+ }
2358
+ .ogrid-sidebar-col-label { display: flex; align-items: center; gap: 6px; padding: 2px 0; cursor: pointer; }
2359
+ .ogrid-sidebar-empty { color: var(--ogrid-fg-muted, rgba(0, 0, 0, 0.5)); font-style: italic; }
2360
+ .ogrid-sidebar-filter-group { margin-bottom: 12px; }
2361
+ .ogrid-sidebar-filter-label { font-weight: 500; margin-bottom: 4px; font-size: 13px; }
2362
+ .ogrid-sidebar-text-input {
2363
+ width: 100%; box-sizing: border-box; padding: 4px 6px;
2364
+ background: var(--ogrid-bg, #ffffff); color: var(--ogrid-fg, rgba(0, 0, 0, 0.87));
2365
+ border: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12)); border-radius: 4px;
2366
+ }
2367
+ .ogrid-sidebar-date-row { display: flex; flex-direction: column; gap: 4px; }
2368
+ .ogrid-sidebar-date-label { display: flex; align-items: center; gap: 4px; font-size: 12px; }
2369
+ .ogrid-sidebar-date-input {
2370
+ flex: 1; padding: 2px 4px;
2371
+ background: var(--ogrid-bg, #ffffff); color: var(--ogrid-fg, rgba(0, 0, 0, 0.87));
2372
+ border: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12)); border-radius: 4px;
2373
+ }
2374
+ .ogrid-sidebar-multiselect-list { max-height: 120px; overflow-y: auto; }
2375
+ .ogrid-sidebar-multiselect-item {
2376
+ display: flex; align-items: center; gap: 4px;
2377
+ padding: 1px 0; cursor: pointer; font-size: 13px;
2378
+ }
2379
+ `],
2380
+ template: `
2381
+ <div class="ogrid-sidebar-root" role="complementary" aria-label="Side bar">
2382
+ @if (sideBarProps?.position === 'left') {
2383
+ <ng-container *ngTemplateOutlet="tabStripTpl"></ng-container>
2384
+ <ng-container *ngTemplateOutlet="panelContentTpl"></ng-container>
2385
+ }
2386
+ @if (sideBarProps?.position === 'right') {
2387
+ <ng-container *ngTemplateOutlet="panelContentTpl"></ng-container>
2388
+ <ng-container *ngTemplateOutlet="tabStripTpl"></ng-container>
2389
+ }
2390
+ </div>
2391
+
2392
+ <ng-template #tabStripTpl>
2393
+ <div
2394
+ class="ogrid-sidebar-tab-strip"
2395
+ [class.ogrid-sidebar-tab-strip--left]="sideBarProps?.position === 'left'"
2396
+ [class.ogrid-sidebar-tab-strip--right]="sideBarProps?.position === 'right'"
2397
+ role="tablist"
2398
+ aria-label="Side bar tabs"
2399
+ >
2400
+ @for (panel of sideBarProps?.panels ?? []; track panel) {
2401
+ <button
2402
+ role="tab"
2403
+ class="ogrid-sidebar-tab"
2404
+ [class.ogrid-sidebar-tab--active]="sideBarProps?.activePanel === panel"
2405
+ [attr.aria-selected]="sideBarProps?.activePanel === panel"
2406
+ [attr.aria-label]="panelLabels[panel]"
2407
+ (click)="onTabClick(panel)"
2408
+ [title]="panelLabels[panel]"
2409
+ >
2410
+ {{ panel === 'columns' ? '\u2261' : '\u2A65' }}
2411
+ </button>
2412
+ }
2413
+ </div>
2414
+ </ng-template>
2415
+
2416
+ <ng-template #panelContentTpl>
2417
+ @if (sideBarProps?.activePanel) {
2418
+ <div
2419
+ role="tabpanel"
2420
+ class="ogrid-sidebar-panel"
2421
+ [class.ogrid-sidebar-panel--left]="sideBarProps?.position === 'left'"
2422
+ [class.ogrid-sidebar-panel--right]="sideBarProps?.position === 'right'"
2423
+ [attr.aria-label]="panelLabels[sideBarProps!.activePanel!]"
2424
+ >
2425
+ <div class="ogrid-sidebar-panel-header">
2426
+ <span>{{ panelLabels[sideBarProps!.activePanel!] }}</span>
2427
+ <button (click)="sideBarProps?.onPanelChange(null)" class="ogrid-sidebar-panel-close" aria-label="Close panel">&times;</button>
2428
+ </div>
2429
+ <div class="ogrid-sidebar-panel-body">
2430
+ @if (sideBarProps?.activePanel === 'columns') {
2431
+ <div class="ogrid-sidebar-actions">
2432
+ <button (click)="onSelectAll()" [disabled]="allVisible()" class="ogrid-sidebar-action-btn">Select All</button>
2433
+ <button (click)="onClearAll()" class="ogrid-sidebar-action-btn">Clear All</button>
2434
+ </div>
2435
+ @for (col of sideBarProps?.columns ?? []; track col.columnId) {
2436
+ <label class="ogrid-sidebar-col-label">
2437
+ <input type="checkbox" [checked]="sideBarProps?.visibleColumns?.has(col.columnId)" (change)="onVisibilityChange(col.columnId, $any($event.target).checked)" [disabled]="col.required" />
2438
+ <span>{{ col.name }}</span>
2439
+ </label>
2440
+ }
2441
+ }
2442
+ @if (sideBarProps?.activePanel === 'filters') {
2443
+ @if ((sideBarProps?.filterableColumns ?? []).length === 0) {
2444
+ <div class="ogrid-sidebar-empty">No filterable columns</div>
2445
+ }
2446
+ @for (col of sideBarProps?.filterableColumns ?? []; track col.columnId) {
2447
+ <div class="ogrid-sidebar-filter-group">
2448
+ <div class="ogrid-sidebar-filter-label">{{ col.name }}</div>
2449
+ @if (col.filterType === 'text') {
2450
+ <input
2451
+ type="text"
2452
+ class="ogrid-sidebar-text-input"
2453
+ [value]="getTextFilterValue(col.filterField)"
2454
+ (input)="onTextFilterChange(col.filterField, $any($event.target).value)"
2455
+ [placeholder]="'Filter ' + col.name + '...'"
2456
+ [attr.aria-label]="'Filter ' + col.name"
2457
+ />
2458
+ }
2459
+ @if (col.filterType === 'date') {
2460
+ <div class="ogrid-sidebar-date-row">
2461
+ <label class="ogrid-sidebar-date-label">
2462
+ From:
2463
+ <input type="date" class="ogrid-sidebar-date-input" [value]="getDateFrom(col.filterField)" (change)="onDateFromChange(col.filterField, $any($event.target).value)" [attr.aria-label]="col.name + ' from date'" />
2464
+ </label>
2465
+ <label class="ogrid-sidebar-date-label">
2466
+ To:
2467
+ <input type="date" class="ogrid-sidebar-date-input" [value]="getDateTo(col.filterField)" (change)="onDateToChange(col.filterField, $any($event.target).value)" [attr.aria-label]="col.name + ' to date'" />
2468
+ </label>
2469
+ </div>
2470
+ }
2471
+ @if (col.filterType === 'multiSelect') {
2472
+ <div class="ogrid-sidebar-multiselect-list" role="group" [attr.aria-label]="col.name + ' options'">
2473
+ @for (opt of getFilterOptions(col.filterField); track opt) {
2474
+ <label class="ogrid-sidebar-multiselect-item">
2475
+ <input type="checkbox" [checked]="isMultiSelectChecked(col.filterField, opt)" (change)="onMultiSelectChange(col.filterField, opt, $any($event.target).checked)" />
2476
+ <span>{{ opt }}</span>
2477
+ </label>
2478
+ }
2479
+ </div>
2480
+ }
2481
+ </div>
2482
+ }
2483
+ }
2484
+ </div>
2485
+ </div>
2486
+ }
2487
+ </ng-template>
2488
+ `
2489
+ })
2490
+ ], SideBarComponent);
2491
+
2492
+ // src/styles/ogrid-theme-vars.ts
2493
+ var OGRID_THEME_VARS_CSS = `
2494
+ /* \u2500\u2500\u2500 OGrid Theme Variables \u2500\u2500\u2500 */
2495
+ :root {
2496
+ --ogrid-bg: #ffffff;
2497
+ --ogrid-fg: rgba(0, 0, 0, 0.87);
2498
+ --ogrid-fg-secondary: rgba(0, 0, 0, 0.6);
2499
+ --ogrid-fg-muted: rgba(0, 0, 0, 0.5);
2500
+ --ogrid-border: rgba(0, 0, 0, 0.12);
2501
+ --ogrid-header-bg: rgba(0, 0, 0, 0.04);
2502
+ --ogrid-hover-bg: rgba(0, 0, 0, 0.04);
2503
+ --ogrid-selected-row-bg: #e6f0fb;
2504
+ --ogrid-active-cell-bg: rgba(0, 0, 0, 0.02);
2505
+ --ogrid-range-bg: rgba(33, 115, 70, 0.12);
2506
+ --ogrid-accent: #0078d4;
2507
+ --ogrid-selection-color: #217346;
2508
+ --ogrid-loading-overlay: rgba(255, 255, 255, 0.7);
2509
+ }
2510
+ @media (prefers-color-scheme: dark) {
2511
+ :root:not([data-theme="light"]) {
2512
+ --ogrid-bg: #1e1e1e;
2513
+ --ogrid-fg: rgba(255, 255, 255, 0.87);
2514
+ --ogrid-fg-secondary: rgba(255, 255, 255, 0.6);
2515
+ --ogrid-fg-muted: rgba(255, 255, 255, 0.5);
2516
+ --ogrid-border: rgba(255, 255, 255, 0.12);
2517
+ --ogrid-header-bg: rgba(255, 255, 255, 0.06);
2518
+ --ogrid-hover-bg: rgba(255, 255, 255, 0.08);
2519
+ --ogrid-selected-row-bg: #1a3a5c;
2520
+ --ogrid-active-cell-bg: rgba(255, 255, 255, 0.06);
2521
+ --ogrid-range-bg: rgba(46, 160, 67, 0.15);
2522
+ --ogrid-accent: #4da6ff;
2523
+ --ogrid-selection-color: #2ea043;
2524
+ --ogrid-loading-overlay: rgba(0, 0, 0, 0.7);
2525
+ }
2526
+ }
2527
+ [data-theme="dark"] {
2528
+ --ogrid-bg: #1e1e1e;
2529
+ --ogrid-fg: rgba(255, 255, 255, 0.87);
2530
+ --ogrid-fg-secondary: rgba(255, 255, 255, 0.6);
2531
+ --ogrid-fg-muted: rgba(255, 255, 255, 0.5);
2532
+ --ogrid-border: rgba(255, 255, 255, 0.12);
2533
+ --ogrid-header-bg: rgba(255, 255, 255, 0.06);
2534
+ --ogrid-hover-bg: rgba(255, 255, 255, 0.08);
2535
+ --ogrid-selected-row-bg: #1a3a5c;
2536
+ --ogrid-active-cell-bg: rgba(255, 255, 255, 0.06);
2537
+ --ogrid-range-bg: rgba(46, 160, 67, 0.15);
2538
+ --ogrid-accent: #4da6ff;
2539
+ --ogrid-selection-color: #2ea043;
2540
+ --ogrid-loading-overlay: rgba(0, 0, 0, 0.7);
2541
+ }`;
2542
+
2543
+ // src/components/ogrid-layout.component.ts
2544
+ var OGridLayoutComponent = class {
2545
+ constructor() {
2546
+ this.hasToolbar = false;
2547
+ this.hasToolbarBelow = false;
2548
+ this.hasPagination = false;
2549
+ this.sideBar = null;
2550
+ this.fullScreen = false;
2551
+ this.isFullScreen = false;
2552
+ this.borderRadius = GRID_BORDER_RADIUS;
2553
+ this.escListener = null;
2554
+ }
2555
+ get rootClass() {
2556
+ const base = (this.className ?? "") + " ogrid-layout-root";
2557
+ return this.isFullScreen ? base + " ogrid-layout-root--fullscreen" : base;
2558
+ }
2559
+ toggleFullScreen() {
2560
+ this.isFullScreen = !this.isFullScreen;
2561
+ if (this.isFullScreen) {
2562
+ this.escListener = (e) => {
2563
+ if (e.key === "Escape") {
2564
+ this.isFullScreen = false;
2565
+ this.removeEscListener();
2566
+ }
2567
+ };
2568
+ document.addEventListener("keydown", this.escListener);
2569
+ } else {
2570
+ this.removeEscListener();
2571
+ }
2572
+ }
2573
+ removeEscListener() {
2574
+ if (this.escListener) {
2575
+ document.removeEventListener("keydown", this.escListener);
2576
+ this.escListener = null;
2577
+ }
2578
+ }
2579
+ };
2580
+ __decorateClass([
2581
+ Input()
2582
+ ], OGridLayoutComponent.prototype, "className", 2);
2583
+ __decorateClass([
2584
+ Input()
2585
+ ], OGridLayoutComponent.prototype, "hasToolbar", 2);
2586
+ __decorateClass([
2587
+ Input()
2588
+ ], OGridLayoutComponent.prototype, "hasToolbarBelow", 2);
2589
+ __decorateClass([
2590
+ Input()
2591
+ ], OGridLayoutComponent.prototype, "hasPagination", 2);
2592
+ __decorateClass([
2593
+ Input()
2594
+ ], OGridLayoutComponent.prototype, "sideBar", 2);
2595
+ __decorateClass([
2596
+ Input()
2597
+ ], OGridLayoutComponent.prototype, "fullScreen", 2);
2598
+ OGridLayoutComponent = __decorateClass([
2599
+ Component({
2600
+ selector: "ogrid-layout",
2601
+ standalone: true,
2602
+ encapsulation: ViewEncapsulation.None,
2603
+ changeDetection: ChangeDetectionStrategy.OnPush,
2604
+ imports: [SideBarComponent],
2605
+ styles: [OGRID_THEME_VARS_CSS, `
2606
+ :host { display: block; height: 100%; }
2607
+ .ogrid-layout-root { display: flex; flex-direction: column; height: 100%; }
2608
+ .ogrid-layout-root--fullscreen {
2609
+ position: fixed; inset: 0; z-index: 9999;
2610
+ background: var(--ogrid-bg, #ffffff);
2611
+ }
2612
+ .ogrid-layout-container {
2613
+ border: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12));
2614
+ overflow: hidden; display: flex; flex-direction: column;
2615
+ flex: 1; min-height: 0; background: var(--ogrid-bg, #ffffff);
2616
+ color: var(--ogrid-fg, rgba(0, 0, 0, 0.87));
2617
+ }
2618
+ .ogrid-layout-root--fullscreen .ogrid-layout-container {
2619
+ border: none; border-radius: 0 !important;
2620
+ }
2621
+ .ogrid-layout-toolbar {
2622
+ display: flex; justify-content: space-between; align-items: center;
2623
+ padding: 6px 12px; background: var(--ogrid-header-bg, rgba(0, 0, 0, 0.04));
2624
+ gap: 8px; flex-wrap: wrap; min-height: 0;
2625
+ }
2626
+ .ogrid-layout-toolbar--has-below { border-bottom: none; }
2627
+ .ogrid-layout-toolbar--no-below { border-bottom: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12)); }
2628
+ .ogrid-layout-toolbar-left { display: flex; align-items: center; gap: 8px; }
2629
+ .ogrid-layout-toolbar-right { display: flex; align-items: center; gap: 8px; }
2630
+ .ogrid-layout-toolbar-below {
2631
+ border-bottom: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12));
2632
+ padding: 6px 12px; background: var(--ogrid-header-bg, rgba(0, 0, 0, 0.04));
2633
+ }
2634
+ .ogrid-layout-grid-area { width: 100%; min-width: 0; min-height: 0; flex: 1; display: flex; }
2635
+ .ogrid-layout-grid-content { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
2636
+ .ogrid-layout-footer {
2637
+ border-top: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12));
2638
+ background: var(--ogrid-header-bg, rgba(0, 0, 0, 0.04)); padding: 6px 12px;
2639
+ }
2640
+ .ogrid-fullscreen-btn {
2641
+ background: none; border: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12));
2642
+ border-radius: 4px; padding: 4px 6px; cursor: pointer;
2643
+ display: flex; align-items: center; justify-content: center;
2644
+ color: var(--ogrid-fg, rgba(0, 0, 0, 0.87));
2645
+ }
2646
+ .ogrid-fullscreen-btn:hover { background: var(--ogrid-hover-bg, rgba(0, 0, 0, 0.04)); }
2647
+ `],
2648
+ template: `
2649
+ <div [class]="rootClass">
2650
+ <div class="ogrid-layout-container" [style.border-radius.px]="borderRadius">
2651
+ <!-- Toolbar strip -->
2652
+ @if (hasToolbar || fullScreen) {
2653
+ <div
2654
+ class="ogrid-layout-toolbar"
2655
+ [class.ogrid-layout-toolbar--has-below]="hasToolbarBelow"
2656
+ [class.ogrid-layout-toolbar--no-below]="!hasToolbarBelow"
2657
+ >
2658
+ <div class="ogrid-layout-toolbar-left">
2659
+ <ng-content select="[toolbar]"></ng-content>
2660
+ </div>
2661
+ <div class="ogrid-layout-toolbar-right">
2662
+ <ng-content select="[toolbarEnd]"></ng-content>
2663
+ @if (fullScreen) {
2664
+ <button type="button" class="ogrid-fullscreen-btn"
2665
+ [attr.title]="isFullScreen ? 'Exit fullscreen' : 'Fullscreen'"
2666
+ [attr.aria-label]="isFullScreen ? 'Exit fullscreen' : 'Fullscreen'"
2667
+ (click)="toggleFullScreen()">
2668
+ @if (isFullScreen) {
2669
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
2670
+ <polyline points="4 10 0 10 0 14"></polyline>
2671
+ <polyline points="12 6 16 6 16 2"></polyline>
2672
+ <line x1="0" y1="10" x2="4" y2="6"></line>
2673
+ <line x1="16" y1="6" x2="12" y2="10"></line>
2674
+ </svg>
2675
+ } @else {
2676
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
2677
+ <polyline points="10 2 14 2 14 6"></polyline>
2678
+ <polyline points="6 14 2 14 2 10"></polyline>
2679
+ <line x1="14" y1="2" x2="10" y2="6"></line>
2680
+ <line x1="2" y1="14" x2="6" y2="10"></line>
2681
+ </svg>
2682
+ }
2683
+ </button>
2684
+ }
2685
+ </div>
2686
+ </div>
2687
+ }
2688
+
2689
+ <!-- Secondary toolbar row -->
2690
+ @if (hasToolbarBelow) {
2691
+ <div class="ogrid-layout-toolbar-below">
2692
+ <ng-content select="[toolbarBelow]"></ng-content>
2693
+ </div>
2694
+ }
2695
+
2696
+ <!-- Grid area -->
2697
+ <div class="ogrid-layout-grid-area">
2698
+ @if (sideBar && sideBar.position === 'left') {
2699
+ <ogrid-sidebar [sideBarProps]="sideBar"></ogrid-sidebar>
2700
+ }
2701
+ <div class="ogrid-layout-grid-content">
2702
+ <ng-content></ng-content>
2703
+ </div>
2704
+ @if (sideBar && sideBar.position !== 'left') {
2705
+ <ogrid-sidebar [sideBarProps]="sideBar"></ogrid-sidebar>
2706
+ }
2707
+ </div>
2708
+
2709
+ <!-- Footer strip (pagination) -->
2710
+ @if (hasPagination) {
2711
+ <div class="ogrid-layout-footer">
2712
+ <ng-content select="[pagination]"></ng-content>
2713
+ </div>
2714
+ }
2715
+ </div>
2716
+ </div>
2717
+ `
2718
+ })
2719
+ ], OGridLayoutComponent);
2720
+ var StatusBarComponent = class {
2721
+ constructor() {
2722
+ this.filteredCount = void 0;
2723
+ this.selectedCount = void 0;
2724
+ this.selectedCellCount = void 0;
2725
+ this.aggregation = void 0;
2726
+ this.suppressRowCount = void 0;
2727
+ this.classNames = void 0;
2728
+ this.cachedParts = [];
2729
+ }
2730
+ ngOnChanges() {
2731
+ this.cachedParts = getStatusBarParts({
2732
+ totalCount: this.totalCount,
2733
+ filteredCount: this.filteredCount,
2734
+ selectedCount: this.selectedCount,
2735
+ selectedCellCount: this.selectedCellCount,
2736
+ aggregation: this.aggregation ?? void 0,
2737
+ suppressRowCount: this.suppressRowCount
2738
+ });
2739
+ }
2740
+ getParts() {
2741
+ return this.cachedParts;
2742
+ }
2743
+ };
2744
+ __decorateClass([
2745
+ Input({ required: true })
2746
+ ], StatusBarComponent.prototype, "totalCount", 2);
2747
+ __decorateClass([
2748
+ Input()
2749
+ ], StatusBarComponent.prototype, "filteredCount", 2);
2750
+ __decorateClass([
2751
+ Input()
2752
+ ], StatusBarComponent.prototype, "selectedCount", 2);
2753
+ __decorateClass([
2754
+ Input()
2755
+ ], StatusBarComponent.prototype, "selectedCellCount", 2);
2756
+ __decorateClass([
2757
+ Input()
2758
+ ], StatusBarComponent.prototype, "aggregation", 2);
2759
+ __decorateClass([
2760
+ Input()
2761
+ ], StatusBarComponent.prototype, "suppressRowCount", 2);
2762
+ __decorateClass([
2763
+ Input()
2764
+ ], StatusBarComponent.prototype, "classNames", 2);
2765
+ StatusBarComponent = __decorateClass([
2766
+ Component({
2767
+ selector: "ogrid-status-bar",
2768
+ standalone: true,
2769
+ changeDetection: ChangeDetectionStrategy.OnPush,
2770
+ template: `
2771
+ <div [class]="classNames?.statusBar ?? ''" role="status" aria-live="polite">
2772
+ @for (part of getParts(); track part.key) {
2773
+ <span [class]="classNames?.statusBarItem ?? ''">
2774
+ <span [class]="classNames?.statusBarLabel ?? ''">{{ part.label }}</span>
2775
+ <span [class]="classNames?.statusBarValue ?? ''">{{ part.value.toLocaleString() }}</span>
2776
+ </span>
2777
+ }
2778
+ </div>
2779
+ `
2780
+ })
2781
+ ], StatusBarComponent);
2782
+ var GridContextMenuComponent = class {
2783
+ constructor() {
2784
+ this.destroyRef = inject(DestroyRef);
2785
+ this.hasSelection = false;
2786
+ this.canUndoProp = false;
2787
+ this.canRedoProp = false;
2788
+ this.classNames = void 0;
2789
+ this.copyAction = new EventEmitter();
2790
+ this.cutAction = new EventEmitter();
2791
+ this.pasteAction = new EventEmitter();
2792
+ this.selectAllAction = new EventEmitter();
2793
+ this.undoAction = new EventEmitter();
2794
+ this.redoAction = new EventEmitter();
2795
+ this.closeAction = new EventEmitter();
2796
+ this.menuItems = GRID_CONTEXT_MENU_ITEMS;
2797
+ this.formatShortcutFn = formatShortcut;
2798
+ this.clickOutsideHandler = (e) => {
2799
+ const el = this.menuRef?.nativeElement;
2800
+ if (el && !el.contains(e.target)) this.closeAction.emit();
2801
+ };
2802
+ this.keyDownHandler = (e) => {
2803
+ if (e.key === "Escape") this.closeAction.emit();
2804
+ };
2805
+ document.addEventListener("mousedown", this.clickOutsideHandler, true);
2806
+ document.addEventListener("keydown", this.keyDownHandler, true);
2807
+ this.destroyRef.onDestroy(() => {
2808
+ document.removeEventListener("mousedown", this.clickOutsideHandler, true);
2809
+ document.removeEventListener("keydown", this.keyDownHandler, true);
2810
+ });
2811
+ }
2812
+ isDisabled(item) {
2813
+ if (item.disabledWhenNoSelection && !this.hasSelection) return true;
2814
+ if (item.id === "undo" && !this.canUndoProp) return true;
2815
+ if (item.id === "redo" && !this.canRedoProp) return true;
2816
+ return false;
2817
+ }
2818
+ onItemClick(id) {
2819
+ switch (id) {
2820
+ case "copy":
2821
+ this.copyAction.emit();
2822
+ break;
2823
+ case "cut":
2824
+ this.cutAction.emit();
2825
+ break;
2826
+ case "paste":
2827
+ this.pasteAction.emit();
2828
+ break;
2829
+ case "selectAll":
2830
+ this.selectAllAction.emit();
2831
+ break;
2832
+ case "undo":
2833
+ this.undoAction.emit();
2834
+ break;
2835
+ case "redo":
2836
+ this.redoAction.emit();
2837
+ break;
2838
+ }
2839
+ this.closeAction.emit();
2840
+ }
2841
+ };
2842
+ __decorateClass([
2843
+ Input({ required: true })
2844
+ ], GridContextMenuComponent.prototype, "x", 2);
2845
+ __decorateClass([
2846
+ Input({ required: true })
2847
+ ], GridContextMenuComponent.prototype, "y", 2);
2848
+ __decorateClass([
2849
+ Input()
2850
+ ], GridContextMenuComponent.prototype, "hasSelection", 2);
2851
+ __decorateClass([
2852
+ Input()
2853
+ ], GridContextMenuComponent.prototype, "canUndoProp", 2);
2854
+ __decorateClass([
2855
+ Input()
2856
+ ], GridContextMenuComponent.prototype, "canRedoProp", 2);
2857
+ __decorateClass([
2858
+ Input()
2859
+ ], GridContextMenuComponent.prototype, "classNames", 2);
2860
+ __decorateClass([
2861
+ Output()
2862
+ ], GridContextMenuComponent.prototype, "copyAction", 2);
2863
+ __decorateClass([
2864
+ Output()
2865
+ ], GridContextMenuComponent.prototype, "cutAction", 2);
2866
+ __decorateClass([
2867
+ Output()
2868
+ ], GridContextMenuComponent.prototype, "pasteAction", 2);
2869
+ __decorateClass([
2870
+ Output()
2871
+ ], GridContextMenuComponent.prototype, "selectAllAction", 2);
2872
+ __decorateClass([
2873
+ Output()
2874
+ ], GridContextMenuComponent.prototype, "undoAction", 2);
2875
+ __decorateClass([
2876
+ Output()
2877
+ ], GridContextMenuComponent.prototype, "redoAction", 2);
2878
+ __decorateClass([
2879
+ Output()
2880
+ ], GridContextMenuComponent.prototype, "closeAction", 2);
2881
+ __decorateClass([
2882
+ ViewChild("menuRef")
2883
+ ], GridContextMenuComponent.prototype, "menuRef", 2);
2884
+ GridContextMenuComponent = __decorateClass([
2885
+ Component({
2886
+ selector: "ogrid-context-menu",
2887
+ standalone: true,
2888
+ changeDetection: ChangeDetectionStrategy.OnPush,
2889
+ template: `
2890
+ <div
2891
+ #menuRef
2892
+ [class]="classNames?.contextMenu ?? ''"
2893
+ role="menu"
2894
+ [style.left.px]="x"
2895
+ [style.top.px]="y"
2896
+ aria-label="Grid context menu"
2897
+ >
2898
+ @for (item of menuItems; track item.id) {
2899
+ @if (item.dividerBefore) {
2900
+ <div [class]="classNames?.contextMenuDivider ?? ''"></div>
2901
+ }
2902
+ <button
2903
+ type="button"
2904
+ [class]="classNames?.contextMenuItem ?? ''"
2905
+ (click)="onItemClick(item.id)"
2906
+ [disabled]="isDisabled(item)"
2907
+ >
2908
+ <span [class]="classNames?.contextMenuItemLabel ?? ''">{{ item.label }}</span>
2909
+ @if (item.shortcut) {
2910
+ <span [class]="classNames?.contextMenuItemShortcut ?? ''">
2911
+ {{ formatShortcutFn(item.shortcut) }}
2912
+ </span>
2913
+ }
2914
+ </button>
2915
+ }
2916
+ </div>
2917
+ `
2918
+ })
2919
+ ], GridContextMenuComponent);
2920
+ var MarchingAntsOverlayComponent = class {
2921
+ constructor() {
2922
+ this.destroyRef = inject(DestroyRef);
2923
+ this.selectionRange = null;
2924
+ this.copyRange = null;
2925
+ this.cutRange = null;
2926
+ this.colOffset = 0;
2927
+ this.columnSizingVersion = 0;
2928
+ this.items = [];
2929
+ this.visibleColumns = void 0;
2930
+ this.columnOrder = void 0;
2931
+ this.selRect = signal(null);
2932
+ this.clipRect = signal(null);
2933
+ this.rafId = 0;
2934
+ this.resizeObserver = null;
2935
+ injectGlobalStyles("ogrid-marching-ants-keyframes", "@keyframes ogrid-marching-ants{to{stroke-dashoffset:-8}}");
2936
+ this.destroyRef.onDestroy(() => {
2937
+ if (this.rafId) cancelAnimationFrame(this.rafId);
2938
+ if (this.resizeObserver) this.resizeObserver.disconnect();
2939
+ });
2940
+ }
2941
+ ngOnChanges(_changes) {
2942
+ this.recalculate();
2943
+ }
2944
+ recalculate() {
2945
+ const container = this.containerEl;
2946
+ const selRange = this.selectionRange;
2947
+ const clipRange = this.copyRange ?? this.cutRange;
2948
+ const colOff = this.colOffset;
2949
+ void this.columnSizingVersion;
2950
+ void this.items;
2951
+ void this.visibleColumns;
2952
+ void this.columnOrder;
2953
+ if (this.resizeObserver) {
2954
+ this.resizeObserver.disconnect();
2955
+ this.resizeObserver = null;
2956
+ }
2957
+ if (!selRange && !clipRange) {
2958
+ this.selRect.set(null);
2959
+ this.clipRect.set(null);
2960
+ return;
2961
+ }
2962
+ const measureAll = () => {
2963
+ if (!container) {
2964
+ this.selRect.set(null);
2965
+ this.clipRect.set(null);
2966
+ return;
2967
+ }
2968
+ this.selRect.set(selRange ? measureRange(container, selRange, colOff) : null);
2969
+ this.clipRect.set(clipRange ? measureRange(container, clipRange, colOff) : null);
2970
+ };
2971
+ if (this.rafId) cancelAnimationFrame(this.rafId);
2972
+ this.rafId = requestAnimationFrame(measureAll);
2973
+ if (container) {
2974
+ this.resizeObserver = new ResizeObserver(measureAll);
2975
+ this.resizeObserver.observe(container);
2976
+ }
2977
+ }
2978
+ clipRangeMatchesSel() {
2979
+ const selRange = this.selectionRange;
2980
+ const clipRange = this.copyRange ?? this.cutRange;
2981
+ return selRange != null && clipRange != null && selRange.startRow === clipRange.startRow && selRange.startCol === clipRange.startCol && selRange.endRow === clipRange.endRow && selRange.endCol === clipRange.endCol;
2982
+ }
2983
+ max0(n) {
2984
+ return Math.max(0, n);
2985
+ }
2986
+ };
2987
+ __decorateClass([
2988
+ Input({ required: true })
2989
+ ], MarchingAntsOverlayComponent.prototype, "containerEl", 2);
2990
+ __decorateClass([
2991
+ Input()
2992
+ ], MarchingAntsOverlayComponent.prototype, "selectionRange", 2);
2993
+ __decorateClass([
2994
+ Input()
2995
+ ], MarchingAntsOverlayComponent.prototype, "copyRange", 2);
2996
+ __decorateClass([
2997
+ Input()
2998
+ ], MarchingAntsOverlayComponent.prototype, "cutRange", 2);
2999
+ __decorateClass([
3000
+ Input()
3001
+ ], MarchingAntsOverlayComponent.prototype, "colOffset", 2);
3002
+ __decorateClass([
3003
+ Input()
3004
+ ], MarchingAntsOverlayComponent.prototype, "columnSizingVersion", 2);
3005
+ __decorateClass([
3006
+ Input()
3007
+ ], MarchingAntsOverlayComponent.prototype, "items", 2);
3008
+ __decorateClass([
3009
+ Input()
3010
+ ], MarchingAntsOverlayComponent.prototype, "visibleColumns", 2);
3011
+ __decorateClass([
3012
+ Input()
3013
+ ], MarchingAntsOverlayComponent.prototype, "columnOrder", 2);
3014
+ MarchingAntsOverlayComponent = __decorateClass([
3015
+ Component({
3016
+ selector: "ogrid-marching-ants-overlay",
3017
+ standalone: true,
3018
+ changeDetection: ChangeDetectionStrategy.OnPush,
3019
+ styles: [`
3020
+ .ogrid-marching-ants-svg { position: absolute; pointer-events: none; overflow: visible; }
3021
+ .ogrid-marching-ants-svg--selection { z-index: 4; }
3022
+ .ogrid-marching-ants-svg--clip { z-index: 5; }
3023
+ `],
3024
+ template: `
3025
+ @if (selRect() && !clipRangeMatchesSel()) {
3026
+ <svg
3027
+ class="ogrid-marching-ants-svg ogrid-marching-ants-svg--selection"
3028
+ [style.top.px]="selRect()!.top"
3029
+ [style.left.px]="selRect()!.left"
3030
+ [style.width.px]="selRect()!.width"
3031
+ [style.height.px]="selRect()!.height"
3032
+ aria-hidden="true"
3033
+ >
3034
+ <rect
3035
+ x="1" y="1"
3036
+ [attr.width]="max0(selRect()!.width - 2)"
3037
+ [attr.height]="max0(selRect()!.height - 2)"
3038
+ fill="none"
3039
+ stroke="var(--ogrid-selection, #217346)"
3040
+ stroke-width="2"
3041
+ />
3042
+ </svg>
3043
+ }
3044
+ @if (clipRect()) {
3045
+ <svg
3046
+ class="ogrid-marching-ants-svg ogrid-marching-ants-svg--clip"
3047
+ [style.top.px]="clipRect()!.top"
3048
+ [style.left.px]="clipRect()!.left"
3049
+ [style.width.px]="clipRect()!.width"
3050
+ [style.height.px]="clipRect()!.height"
3051
+ aria-hidden="true"
3052
+ >
3053
+ <rect
3054
+ x="1" y="1"
3055
+ [attr.width]="max0(clipRect()!.width - 2)"
3056
+ [attr.height]="max0(clipRect()!.height - 2)"
3057
+ fill="none"
3058
+ stroke="var(--ogrid-selection, #217346)"
3059
+ stroke-width="2"
3060
+ stroke-dasharray="4 4"
3061
+ style="animation: ogrid-marching-ants 0.5s linear infinite"
3062
+ />
3063
+ </svg>
3064
+ }
3065
+ `
3066
+ })
3067
+ ], MarchingAntsOverlayComponent);
3068
+ var EmptyStateComponent = class {
3069
+ constructor() {
3070
+ this.message = void 0;
3071
+ this.hasActiveFilters = false;
3072
+ this.render = void 0;
3073
+ this.clearAll = new EventEmitter();
3074
+ }
3075
+ };
3076
+ __decorateClass([
3077
+ Input()
3078
+ ], EmptyStateComponent.prototype, "message", 2);
3079
+ __decorateClass([
3080
+ Input()
3081
+ ], EmptyStateComponent.prototype, "hasActiveFilters", 2);
3082
+ __decorateClass([
3083
+ Input()
3084
+ ], EmptyStateComponent.prototype, "render", 2);
3085
+ __decorateClass([
3086
+ Output()
3087
+ ], EmptyStateComponent.prototype, "clearAll", 2);
3088
+ EmptyStateComponent = __decorateClass([
3089
+ Component({
3090
+ selector: "ogrid-empty-state",
3091
+ standalone: true,
3092
+ changeDetection: ChangeDetectionStrategy.OnPush,
3093
+ imports: [NgTemplateOutlet],
3094
+ styles: [`
3095
+ .ogrid-empty-state-clear-btn {
3096
+ background: none; border: none; color: inherit;
3097
+ text-decoration: underline; cursor: pointer; padding: 0; font: inherit;
3098
+ }
3099
+ `],
3100
+ template: `
3101
+ @if (render) {
3102
+ <ng-container [ngTemplateOutlet]="render"></ng-container>
3103
+ } @else if (message) {
3104
+ {{ message }}
3105
+ } @else if (hasActiveFilters) {
3106
+ No items match your current filters. Try adjusting your search or
3107
+ <button type="button" (click)="clearAll.emit()" class="ogrid-empty-state-clear-btn">
3108
+ clear all filters
3109
+ </button>
3110
+ to see all items.
3111
+ } @else {
3112
+ There are no items available at this time.
3113
+ }
3114
+ `
3115
+ })
3116
+ ], EmptyStateComponent);
3117
+ var BaseOGridComponent = class {
3118
+ constructor() {
3119
+ this.propsSignal = signal(void 0);
3120
+ this.ogridService = inject(OGridService);
3121
+ effect(() => {
3122
+ const p = this.propsSignal();
3123
+ if (p) this.ogridService.configure(p);
3124
+ });
3125
+ }
3126
+ set props(value) {
3127
+ this.propsSignal.set(value);
3128
+ }
3129
+ get showToolbar() {
3130
+ return this.ogridService.columnChooserPlacement() === "toolbar" || this.ogridService.toolbar() != null || this.ogridService.fullScreen();
3131
+ }
3132
+ onPageSizeChange(size) {
3133
+ this.ogridService.pagination().setPageSize(size);
3134
+ }
3135
+ };
3136
+ __decorateClass([
3137
+ Input({ required: true })
3138
+ ], BaseOGridComponent.prototype, "props", 1);
3139
+ function createDebouncedSignal(source, delayMs) {
3140
+ const debouncedValue = signal(source());
3141
+ effect((onCleanup) => {
3142
+ const currentValue = source();
3143
+ const timeoutId = setTimeout(() => {
3144
+ debouncedValue.set(currentValue);
3145
+ }, delayMs);
3146
+ onCleanup(() => clearTimeout(timeoutId));
3147
+ });
3148
+ return debouncedValue;
3149
+ }
3150
+ function createDebouncedCallback(fn, delayMs) {
3151
+ let timeoutId = null;
3152
+ return ((...args) => {
3153
+ if (timeoutId !== null) {
3154
+ clearTimeout(timeoutId);
3155
+ }
3156
+ timeoutId = setTimeout(() => {
3157
+ fn(...args);
3158
+ timeoutId = null;
3159
+ }, delayMs);
3160
+ });
3161
+ }
3162
+
3163
+ // src/utils/latestRef.ts
3164
+ function createLatestCallback(fn) {
3165
+ return ((...args) => {
3166
+ return fn()(...args);
3167
+ });
3168
+ }
3169
+
3170
+ // src/components/base-datagrid-table.component.ts
3171
+ var BaseDataGridTableComponent = class {
3172
+ constructor() {
3173
+ this.stateService = inject(DataGridStateService);
3174
+ this.columnReorderService = inject(ColumnReorderService);
3175
+ this.virtualScrollService = inject(VirtualScrollService);
3176
+ this.lastMouseShift = false;
3177
+ this.columnSizingVersion = signal(0);
3178
+ /** Dirty flag — set when column layout changes, cleared after measurement. */
3179
+ this.measureDirty = signal(true);
3180
+ /** DOM-measured column widths from the last layout pass.
3181
+ * Used as a minWidth floor to prevent columns from shrinking
3182
+ * when new data loads (e.g. server-side pagination). */
3183
+ this.measuredColumnWidths = signal({});
3184
+ // Signal-backed view child elements — set from ngAfterViewInit.
3185
+ // @ViewChild is a plain property (not a signal), so effects/computed that read it
3186
+ // only evaluate once during construction when the ref is still undefined.
3187
+ this.wrapperElSignal = signal(null);
3188
+ this.tableContainerElSignal = signal(null);
3189
+ // --- Delegated state ---
3190
+ this.state = computed(() => this.stateService.getState());
3191
+ // Intermediate computed signals — narrow slices of state() so leaf computeds
3192
+ // only recompute when their specific sub-state changes.
3193
+ this.layoutState = computed(() => this.state().layout);
3194
+ this.rowSelectionState = computed(() => this.state().rowSelection);
3195
+ this.editingState = computed(() => this.state().editing);
3196
+ this.interactionState = computed(() => this.state().interaction);
3197
+ this.contextMenuState = computed(() => this.state().contextMenu);
3198
+ this.viewModelsState = computed(() => this.state().viewModels);
3199
+ this.pinningState = computed(() => this.state().pinning);
3200
+ this.tableContainerEl = computed(() => this.tableContainerElSignal());
3201
+ this.items = computed(() => this.getProps()?.items ?? []);
3202
+ this.getRowId = computed(() => this.getProps()?.getRowId ?? ((item) => item["id"]));
3203
+ this.isLoading = computed(() => this.getProps()?.isLoading ?? false);
3204
+ this.loadingMessage = computed(() => "Loading\u2026");
3205
+ this.layoutModeFit = computed(() => (this.getProps()?.layoutMode ?? "fill") === "content");
3206
+ this.rowHeightCssVar = computed(() => {
3207
+ const rh = this.getProps()?.rowHeight;
3208
+ return rh ? `${rh}px` : null;
3209
+ });
3210
+ this.ariaLabel = computed(() => this.getProps()?.["aria-label"] ?? "Data grid");
3211
+ this.ariaLabelledBy = computed(() => this.getProps()?.["aria-labelledby"]);
3212
+ this.stickyHeader = computed(() => this.getProps()?.stickyHeader ?? true);
3213
+ this.emptyState = computed(() => this.getProps()?.emptyState);
3214
+ this.currentPage = computed(() => this.getProps()?.currentPage ?? 1);
3215
+ this.pageSize = computed(() => this.getProps()?.pageSize ?? 25);
3216
+ this.rowNumberOffset = computed(() => this.hasRowNumbersCol() ? (this.currentPage() - 1) * this.pageSize() : 0);
3217
+ this.propsVisibleColumns = computed(() => this.getProps()?.visibleColumns);
3218
+ this.propsColumnOrder = computed(() => this.getProps()?.columnOrder);
3219
+ // State service outputs — read from narrow intermediate signals
3220
+ this.visibleCols = computed(() => this.layoutState().visibleCols);
3221
+ this.hasCheckboxCol = computed(() => this.layoutState().hasCheckboxCol);
3222
+ this.hasRowNumbersCol = computed(() => this.layoutState().hasRowNumbersCol);
3223
+ this.colOffset = computed(() => this.layoutState().colOffset);
3224
+ this.containerWidth = computed(() => this.layoutState().containerWidth);
3225
+ this.minTableWidth = computed(() => this.layoutState().minTableWidth);
3226
+ this.desiredTableWidth = computed(() => this.layoutState().desiredTableWidth);
3227
+ this.columnSizingOverrides = computed(() => this.layoutState().columnSizingOverrides);
3228
+ this.selectedRowIds = computed(() => this.rowSelectionState().selectedRowIds);
3229
+ this.allSelected = computed(() => this.rowSelectionState().allSelected);
3230
+ this.someSelected = computed(() => this.rowSelectionState().someSelected);
3231
+ this.editingCell = computed(() => this.editingState().editingCell);
3232
+ this.pendingEditorValue = computed(() => this.editingState().pendingEditorValue);
3233
+ this.activeCell = computed(() => this.interactionState().activeCell);
3234
+ this.selectionRange = computed(() => this.interactionState().selectionRange);
3235
+ this.hasCellSelection = computed(() => this.interactionState().hasCellSelection);
3236
+ this.cutRange = computed(() => this.interactionState().cutRange);
3237
+ this.copyRange = computed(() => this.interactionState().copyRange);
3238
+ this.canUndo = computed(() => this.interactionState().canUndo);
3239
+ this.canRedo = computed(() => this.interactionState().canRedo);
3240
+ this.isDragging = computed(() => this.interactionState().isDragging);
3241
+ this.menuPosition = computed(() => this.contextMenuState().menuPosition);
3242
+ this.statusBarConfig = computed(() => this.viewModelsState().statusBarConfig);
3243
+ this.showEmptyInGrid = computed(() => this.viewModelsState().showEmptyInGrid);
3244
+ this.headerFilterInput = computed(() => this.viewModelsState().headerFilterInput);
3245
+ this.cellDescriptorInput = computed(() => this.viewModelsState().cellDescriptorInput);
3246
+ // Pinning state
3247
+ this.pinnedColumnsMap = computed(() => this.pinningState().pinnedColumns);
3248
+ // Virtual scrolling
3249
+ this.vsEnabled = computed(() => this.virtualScrollService.isActive());
3250
+ this.vsVisibleRange = computed(() => this.virtualScrollService.visibleRange());
3251
+ this.vsTopSpacerHeight = computed(() => {
3252
+ if (!this.vsEnabled()) return 0;
3253
+ return this.vsVisibleRange().offsetTop;
3254
+ });
3255
+ this.vsBottomSpacerHeight = computed(() => {
3256
+ if (!this.vsEnabled()) return 0;
3257
+ return this.vsVisibleRange().offsetBottom;
3258
+ });
3259
+ this.vsVisibleItems = computed(() => {
3260
+ const items = this.items();
3261
+ if (!this.vsEnabled()) return items;
3262
+ const range = this.vsVisibleRange();
3263
+ return items.slice(range.startIndex, Math.min(range.endIndex + 1, items.length));
3264
+ });
3265
+ this.vsStartIndex = computed(() => {
3266
+ if (!this.vsEnabled()) return 0;
3267
+ return this.vsVisibleRange().startIndex;
3268
+ });
3269
+ // Popover editing
3270
+ this.popoverAnchorEl = computed(() => this.editingState().popoverAnchorEl);
3271
+ this.pendingEditorValueForPopover = computed(() => this.editingState().pendingEditorValue);
3272
+ this.allowOverflowX = computed(() => {
3273
+ const p = this.getProps();
3274
+ if (p?.suppressHorizontalScroll) return false;
3275
+ const cw = this.containerWidth();
3276
+ const mtw = this.minTableWidth();
3277
+ const dtw = this.desiredTableWidth();
3278
+ return cw > 0 && (mtw > cw || dtw > cw);
3279
+ });
3280
+ this.selectionCellCount = computed(() => {
3281
+ const sr = this.selectionRange();
3282
+ if (!sr) return void 0;
3283
+ return (Math.abs(sr.endRow - sr.startRow) + 1) * (Math.abs(sr.endCol - sr.startCol) + 1);
3284
+ });
3285
+ // Header rows from column definition
3286
+ this.headerRows = computed(() => {
3287
+ const p = this.getProps();
3288
+ if (!p) return [];
3289
+ return buildHeaderRows(p.columns, p.visibleColumns);
3290
+ });
3291
+ // Pre-computed column layouts
3292
+ this.columnLayouts = computed(() => {
3293
+ const cols = this.visibleCols();
3294
+ const props = this.getProps();
3295
+ const pinnedCols = props?.pinnedColumns ?? {};
3296
+ const measuredWidths = this.measuredColumnWidths();
3297
+ const sizingOverrides = this.columnSizingOverrides();
3298
+ return cols.map((col) => {
3299
+ const runtimePinned = pinnedCols[col.columnId];
3300
+ const pinnedLeft = runtimePinned === "left" || col.pinned === "left";
3301
+ const pinnedRight = runtimePinned === "right" || col.pinned === "right";
3302
+ const w = this.getColumnWidth(col);
3303
+ const hasResizeOverride = !!sizingOverrides[col.columnId];
3304
+ const measuredW = measuredWidths[col.columnId];
3305
+ const baseMinWidth = col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
3306
+ const effectiveMinWidth = hasResizeOverride ? w : Math.max(baseMinWidth, measuredW ?? 0);
3307
+ return {
3308
+ col,
3309
+ pinnedLeft,
3310
+ pinnedRight,
3311
+ minWidth: effectiveMinWidth,
3312
+ width: w
3313
+ };
3314
+ });
3315
+ });
3316
+ // Compute sticky offsets for pinned columns (single pass from both ends)
3317
+ this.pinningOffsets = computed(() => {
3318
+ const layouts = this.columnLayouts();
3319
+ const leftOffsets = {};
3320
+ const rightOffsets = {};
3321
+ let leftAcc = 0;
3322
+ if (this.hasCheckboxCol()) leftAcc += CHECKBOX_COLUMN_WIDTH;
3323
+ if (this.hasRowNumbersCol()) leftAcc += ROW_NUMBER_COLUMN_WIDTH;
3324
+ let rightAcc = 0;
3325
+ const len = layouts.length;
3326
+ for (let i = 0; i < len; i++) {
3327
+ const leftLayout = layouts[i];
3328
+ if (leftLayout.pinnedLeft) {
3329
+ leftOffsets[leftLayout.col.columnId] = leftAcc;
3330
+ leftAcc += leftLayout.width + CELL_PADDING;
3331
+ }
3332
+ const ri = len - 1 - i;
3333
+ const rightLayout = layouts[ri];
3334
+ if (rightLayout.pinnedRight) {
3335
+ rightOffsets[rightLayout.col.columnId] = rightAcc;
3336
+ rightAcc += rightLayout.width + CELL_PADDING;
3337
+ }
3338
+ }
3339
+ return { leftOffsets, rightOffsets };
3340
+ });
3341
+ /** Memoized column menu handlers — avoids recreating objects on every CD cycle */
3342
+ this.columnMenuHandlersMap = computed(() => {
3343
+ const cols = this.visibleCols();
3344
+ const map = /* @__PURE__ */ new Map();
3345
+ for (const col of cols) {
3346
+ map.set(col.columnId, this.buildColumnMenuHandlers(col.columnId));
3347
+ }
3348
+ return map;
3349
+ });
3350
+ }
3351
+ /** Lifecycle hook — populate element signals from @ViewChild refs */
3352
+ ngAfterViewInit() {
3353
+ const wrapper = this.getWrapperRef()?.nativeElement ?? null;
3354
+ const tableContainer = this.getTableContainerRef()?.nativeElement ?? null;
3355
+ if (wrapper) this.wrapperElSignal.set(wrapper);
3356
+ if (tableContainer) this.tableContainerElSignal.set(tableContainer);
3357
+ this.measureColumnWidths();
3358
+ }
3359
+ /** Lifecycle hook — re-measure column widths only when layout changed */
3360
+ ngAfterViewChecked() {
3361
+ if (this.measureDirty()) {
3362
+ this.measureDirty.set(false);
3363
+ this.measureColumnWidths();
3364
+ }
3365
+ }
3366
+ /** Measure actual th widths from the DOM and update the measuredColumnWidths signal.
3367
+ * Only updates the signal when values actually change, to avoid render loops. */
3368
+ measureColumnWidths() {
3369
+ const wrapper = this.getWrapperRef()?.nativeElement;
3370
+ if (!wrapper) return;
3371
+ const headerCells = wrapper.querySelectorAll("th[data-column-id]");
3372
+ if (headerCells.length === 0) return;
3373
+ const measured = {};
3374
+ headerCells.forEach((cell) => {
3375
+ const colId = cell.getAttribute("data-column-id");
3376
+ if (colId) measured[colId] = cell.offsetWidth;
3377
+ });
3378
+ const prev = this.measuredColumnWidths();
3379
+ let changed = Object.keys(measured).length !== Object.keys(prev).length;
3380
+ if (!changed) {
3381
+ for (const key in measured) {
3382
+ if (prev[key] !== measured[key]) {
3383
+ changed = true;
3384
+ break;
3385
+ }
3386
+ }
3387
+ }
3388
+ if (changed) {
3389
+ this.measuredColumnWidths.set(measured);
3390
+ }
3391
+ }
3392
+ /**
3393
+ * Initialize base wiring effects. Must be called from subclass constructor.
3394
+ *
3395
+ * **Timing:** Angular requires `effect()` to be created inside an injection
3396
+ * context (constructor or field initializer). On the first run, signals like
3397
+ * `wrapperElSignal()` return `null` because the DOM hasn't been created yet.
3398
+ * After `ngAfterViewInit` sets these signals, Angular's signal graph
3399
+ * automatically re-runs each effect. The null guards inside each effect body
3400
+ * ensure the first (null) run is a safe no-op.
3401
+ *
3402
+ * Sequence:
3403
+ * 1. Constructor → `initBase()` → effects created, first run (signals null → no-ops)
3404
+ * 2. `ngAfterViewInit` → `wrapperElSignal.set(el)` → effects re-run with real values
3405
+ */
3406
+ initBase() {
3407
+ effect(() => {
3408
+ const p = this.getProps();
3409
+ if (p) this.stateService.props.set(p);
3410
+ });
3411
+ effect(() => {
3412
+ const el = this.wrapperElSignal();
3413
+ if (el) {
3414
+ this.stateService.wrapperEl.set(el);
3415
+ this.columnReorderService.wrapperEl.set(el);
3416
+ }
3417
+ });
3418
+ effect(() => {
3419
+ const p = this.getProps();
3420
+ if (p) {
3421
+ const cols = this.visibleCols();
3422
+ this.columnReorderService.columns.set(cols);
3423
+ this.columnReorderService.columnOrder.set(p.columnOrder);
3424
+ this.columnReorderService.onColumnOrderChange.set(p.onColumnOrderChange);
3425
+ this.columnReorderService.enabled.set(p.columnReorder === true);
3426
+ }
3427
+ });
3428
+ effect(() => {
3429
+ this.visibleCols();
3430
+ this.columnSizingOverrides();
3431
+ this.columnSizingVersion();
3432
+ this.measureDirty.set(true);
3433
+ });
3434
+ effect(() => {
3435
+ const p = this.getProps();
3436
+ if (p) {
3437
+ this.virtualScrollService.totalRows.set(p.items.length);
3438
+ if (p.virtualScroll) {
3439
+ this.virtualScrollService.updateConfig({
3440
+ enabled: p.virtualScroll.enabled,
3441
+ rowHeight: p.virtualScroll.rowHeight,
3442
+ overscan: p.virtualScroll.overscan
3443
+ });
3444
+ }
3445
+ }
3446
+ });
3447
+ effect(() => {
3448
+ const el = this.wrapperElSignal();
3449
+ if (el) {
3450
+ this.virtualScrollService.setContainer(el);
3451
+ this.virtualScrollService.containerHeight.set(el.clientHeight);
3452
+ }
3453
+ });
3454
+ }
3455
+ // --- Helper methods ---
3456
+ /** Lookup effective min-width for a column (includes measured width floor) */
3457
+ getEffectiveMinWidth(col) {
3458
+ const layout = this.columnLayouts().find((l) => l.col.columnId === col.columnId);
3459
+ return layout?.minWidth ?? col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
3460
+ }
3461
+ /**
3462
+ * Returns derived cell interaction metadata (non-event attributes) for use in templates.
3463
+ * Mirrors React's getCellInteractionProps for the Angular view layer.
3464
+ * Event handlers (mousedown, click, dblclick, contextmenu) are still bound inline in templates.
3465
+ */
3466
+ getCellInteractionProps(descriptor) {
3467
+ return {
3468
+ tabIndex: descriptor.isActive ? 0 : -1,
3469
+ dataRowIndex: descriptor.rowIndex,
3470
+ dataColIndex: descriptor.globalColIndex,
3471
+ dataInRange: descriptor.isInRange ? "true" : null,
3472
+ role: descriptor.canEditAny ? "button" : null
3473
+ };
3474
+ }
3475
+ asColumnDef(colDef) {
3476
+ return colDef;
3477
+ }
3478
+ visibleColIndex(col) {
3479
+ return this.visibleCols().indexOf(col);
3480
+ }
3481
+ getColumnWidth(col) {
3482
+ const overrides = this.columnSizingOverrides();
3483
+ const override = overrides[col.columnId];
3484
+ if (override) return override.widthPx;
3485
+ return col.defaultWidth ?? col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
3486
+ }
3487
+ getFilterConfig(col) {
3488
+ return getHeaderFilterConfig(col, this.headerFilterInput());
3489
+ }
3490
+ /** Build column menu handler object for a single column */
3491
+ buildColumnMenuHandlers(columnId) {
3492
+ return {
3493
+ onPinLeft: () => this.onPinColumn(columnId, "left"),
3494
+ onPinRight: () => this.onPinColumn(columnId, "right"),
3495
+ onUnpin: () => this.onUnpinColumn(columnId),
3496
+ onSortAsc: () => this.onSortAsc(columnId),
3497
+ onSortDesc: () => this.onSortDesc(columnId),
3498
+ onClearSort: () => this.onClearSort(columnId),
3499
+ onAutosizeThis: () => this.onAutosizeColumn(columnId),
3500
+ onAutosizeAll: () => this.onAutosizeAllColumns(),
3501
+ onClose: () => {
3502
+ }
3503
+ };
3504
+ }
3505
+ /** Get memoized handlers for a column */
3506
+ getColumnMenuHandlersMemoized(columnId) {
3507
+ return this.columnMenuHandlersMap().get(columnId) ?? this.buildColumnMenuHandlers(columnId);
3508
+ }
3509
+ getCellDescriptor(item, col, rowIndex, colIdx) {
3510
+ return getCellRenderDescriptor(item, col, rowIndex, colIdx, this.cellDescriptorInput());
3511
+ }
3512
+ resolveCellContent(col, item, displayValue) {
3513
+ return resolveCellDisplayContent(col, item, displayValue);
3514
+ }
3515
+ resolveCellStyleFn(col, item) {
3516
+ return resolveCellStyle(col, item);
3517
+ }
3518
+ buildPopoverEditorProps(item, col, descriptor) {
3519
+ return buildPopoverEditorProps(item, col, descriptor, this.pendingEditorValue(), {
3520
+ setPendingEditorValue: (value) => this.setPendingEditorValue(value),
3521
+ commitCellEdit: (item2, columnId, oldValue, newValue, rowIndex, globalColIndex) => this.commitEdit(item2, columnId, oldValue, newValue, rowIndex, globalColIndex),
3522
+ cancelPopoverEdit: () => this.cancelPopoverEdit()
3523
+ });
3524
+ }
3525
+ /** Check if a specific cell is the active cell (PrimeNG inline template helper). */
3526
+ isActiveCell(rowIndex, colIdx) {
3527
+ const ac = this.activeCell();
3528
+ if (!ac) return false;
3529
+ return ac.rowIndex === rowIndex && ac.columnIndex === colIdx + this.colOffset();
3530
+ }
3531
+ /** Check if a cell is within the current selection range (PrimeNG inline template helper). */
3532
+ isInSelectionRange(rowIndex, colIdx) {
3533
+ const range = this.selectionRange();
3534
+ if (!range) return false;
3535
+ const minR = Math.min(range.startRow, range.endRow);
3536
+ const maxR = Math.max(range.startRow, range.endRow);
3537
+ const minC = Math.min(range.startCol, range.endCol);
3538
+ const maxC = Math.max(range.startCol, range.endCol);
3539
+ return rowIndex >= minR && rowIndex <= maxR && colIdx >= minC && colIdx <= maxC;
3540
+ }
3541
+ /** Check if a cell is the selection end cell for fill handle display. */
3542
+ isSelectionEndCell(rowIndex, colIdx) {
3543
+ const range = this.selectionRange();
3544
+ if (!range || this.isDragging() || this.copyRange() || this.cutRange()) return false;
3545
+ return rowIndex === range.endRow && colIdx === range.endCol;
3546
+ }
3547
+ /** Get cell background color based on selection state. */
3548
+ getCellBackground(rowIndex, colIdx) {
3549
+ if (this.isInSelectionRange(rowIndex, colIdx)) return "var(--ogrid-range-bg, rgba(33, 115, 70, 0.08))";
3550
+ return null;
3551
+ }
3552
+ /** Resolve editor type from column definition. */
3553
+ getEditorType(col, _item) {
3554
+ if (col.cellEditor === "text" || col.cellEditor === "select" || col.cellEditor === "checkbox" || col.cellEditor === "date" || col.cellEditor === "richSelect") {
3555
+ return col.cellEditor;
3556
+ }
3557
+ if (col.type === "date") return "date";
3558
+ if (col.type === "boolean") return "checkbox";
3559
+ return "text";
3560
+ }
3561
+ getSelectValues(col) {
3562
+ const params = col.cellEditorParams;
3563
+ if (params && typeof params === "object" && "values" in params) {
3564
+ return params.values.map(String);
3565
+ }
3566
+ return [];
3567
+ }
3568
+ formatDateForInput(value) {
3569
+ if (!value) return "";
3570
+ const d = new Date(String(value));
3571
+ if (Number.isNaN(d.getTime())) return "";
3572
+ return d.toISOString().split("T")[0];
3573
+ }
3574
+ getPinnedLeftOffset(columnId) {
3575
+ const offsets = this.pinningOffsets();
3576
+ return offsets.leftOffsets[columnId] ?? null;
3577
+ }
3578
+ getPinnedRightOffset(columnId) {
3579
+ const offsets = this.pinningOffsets();
3580
+ return offsets.rightOffsets[columnId] ?? null;
3581
+ }
3582
+ // --- Virtual scroll event handler ---
3583
+ onWrapperScroll(event) {
3584
+ this.virtualScrollService.onScroll(event);
3585
+ }
3586
+ // --- Popover editor helpers ---
3587
+ setPopoverAnchorEl(el) {
3588
+ this.state().editing.setPopoverAnchorEl(el);
3589
+ }
3590
+ setPendingEditorValue(value) {
3591
+ this.state().editing.setPendingEditorValue(value);
3592
+ }
3593
+ cancelPopoverEdit() {
3594
+ this.state().editing.cancelPopoverEdit();
3595
+ }
3596
+ commitPopoverEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex) {
3597
+ this.state().editing.commitCellEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex);
3598
+ }
3599
+ // --- Event handlers ---
3600
+ onWrapperMouseDown(event) {
3601
+ this.lastMouseShift = event.shiftKey;
3602
+ }
3603
+ onGridKeyDown(event) {
3604
+ this.state().interaction.handleGridKeyDown(event);
3605
+ }
3606
+ onCellMouseDown(event, rowIndex, globalColIndex) {
3607
+ this.state().interaction.handleCellMouseDown(event, rowIndex, globalColIndex);
3608
+ }
3609
+ onCellClick(rowIndex, globalColIndex) {
3610
+ this.state().interaction.setActiveCell?.({ rowIndex, columnIndex: globalColIndex });
3611
+ }
3612
+ onCellContextMenu(event) {
3613
+ this.state().contextMenu.handleCellContextMenu(event);
3614
+ }
3615
+ onCellDblClick(rowId, columnId) {
3616
+ this.state().editing.setEditingCell({ rowId, columnId });
3617
+ }
3618
+ onFillHandleMouseDown(event) {
3619
+ this.state().interaction.handleFillHandleMouseDown?.(event);
3620
+ }
3621
+ onResizeStart(event, col) {
3622
+ event.preventDefault();
3623
+ this.state().interaction.setActiveCell?.(null);
3624
+ this.state().interaction.setSelectionRange?.(null);
3625
+ this.getWrapperRef()?.nativeElement.focus({ preventScroll: true });
3626
+ const startX = event.clientX;
3627
+ const startWidth = this.getColumnWidth(col);
3628
+ const minWidth = col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
3629
+ const onMove = (e) => {
3630
+ const delta = e.clientX - startX;
3631
+ const newWidth = Math.max(minWidth, startWidth + delta);
3632
+ const overrides = { ...this.columnSizingOverrides(), [col.columnId]: { widthPx: newWidth } };
3633
+ this.state().layout.setColumnSizingOverrides(overrides);
3634
+ this.columnSizingVersion.update((v) => v + 1);
3635
+ };
3636
+ const onUp = () => {
3637
+ window.removeEventListener("mousemove", onMove);
3638
+ window.removeEventListener("mouseup", onUp);
3639
+ const finalWidth = this.getColumnWidth(col);
3640
+ this.state().layout.onColumnResized?.(col.columnId, finalWidth);
3641
+ };
3642
+ window.addEventListener("mousemove", onMove);
3643
+ window.addEventListener("mouseup", onUp);
3644
+ }
3645
+ onSelectAllChange(event) {
3646
+ const checked = event.target.checked;
3647
+ this.state().rowSelection.handleSelectAll(!!checked);
3648
+ }
3649
+ onRowClick(event, rowId) {
3650
+ const p = this.getProps();
3651
+ if (p?.rowSelection !== "single") return;
3652
+ const ids = this.selectedRowIds();
3653
+ this.state().rowSelection.updateSelection(ids.has(rowId) ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([rowId]));
3654
+ }
3655
+ onRowCheckboxChange(rowId, event, rowIndex) {
3656
+ const checked = event.target.checked;
3657
+ this.state().rowSelection.handleRowCheckboxChange(rowId, checked, rowIndex, this.lastMouseShift);
3658
+ }
3659
+ commitEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex) {
3660
+ this.state().editing.commitCellEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex);
3661
+ }
3662
+ cancelEdit() {
3663
+ this.state().editing.setEditingCell(null);
3664
+ }
3665
+ onEditorKeydown(event, item, columnId, oldValue, rowIndex, globalColIndex) {
3666
+ if (event.key === "Enter") {
3667
+ event.preventDefault();
3668
+ const newValue = event.target.value;
3669
+ this.commitEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex);
3670
+ } else if (event.key === "Escape") {
3671
+ event.preventDefault();
3672
+ this.cancelEdit();
3673
+ }
3674
+ }
3675
+ closeContextMenu() {
3676
+ this.state().contextMenu.closeContextMenu();
3677
+ }
3678
+ handleCopy() {
3679
+ this.state().interaction.handleCopy();
3680
+ }
3681
+ handleCut() {
3682
+ this.state().interaction.handleCut();
3683
+ }
3684
+ handlePaste() {
3685
+ void this.state().interaction.handlePaste();
3686
+ }
3687
+ handleSelectAllCells() {
3688
+ this.state().interaction.handleSelectAllCells();
3689
+ }
3690
+ onUndo() {
3691
+ this.state().interaction.onUndo?.();
3692
+ }
3693
+ onRedo() {
3694
+ this.state().interaction.onRedo?.();
3695
+ }
3696
+ onHeaderMouseDown(columnId, event) {
3697
+ this.columnReorderService.handleHeaderMouseDown(columnId, event);
3698
+ }
3699
+ // --- Column pinning methods ---
3700
+ onPinColumn(columnId, side) {
3701
+ this.state().pinning.pinColumn(columnId, side);
3702
+ }
3703
+ onUnpinColumn(columnId) {
3704
+ this.state().pinning.unpinColumn(columnId);
3705
+ }
3706
+ isPinned(columnId) {
3707
+ return this.state().pinning.isPinned(columnId);
3708
+ }
3709
+ getPinState(columnId) {
3710
+ const pinned = this.isPinned(columnId);
3711
+ return {
3712
+ canPinLeft: pinned !== "left",
3713
+ canPinRight: pinned !== "right",
3714
+ canUnpin: !!pinned
3715
+ };
3716
+ }
3717
+ // --- Column sorting methods ---
3718
+ onSortAsc(columnId) {
3719
+ const props = this.getProps();
3720
+ props?.onColumnSort?.(columnId, "asc");
3721
+ }
3722
+ onSortDesc(columnId) {
3723
+ const props = this.getProps();
3724
+ props?.onColumnSort?.(columnId, "desc");
3725
+ }
3726
+ onClearSort(columnId) {
3727
+ const props = this.getProps();
3728
+ const col = columnId ?? props?.sortBy;
3729
+ if (col) {
3730
+ props?.onColumnSort?.(col, null);
3731
+ }
3732
+ }
3733
+ getSortState(columnId) {
3734
+ const props = this.getProps();
3735
+ if (props?.sortBy === columnId) {
3736
+ return props.sortDirection ?? "asc";
3737
+ }
3738
+ return null;
3739
+ }
3740
+ // --- Column autosize methods ---
3741
+ onAutosizeColumn(columnId) {
3742
+ const col = this.visibleCols().find((c) => c.columnId === columnId);
3743
+ if (!col) return;
3744
+ const width = measureColumnContentWidth(columnId, col.minWidth, this.tableContainerEl() ?? void 0);
3745
+ this.state().layout.setColumnSizingOverrides({
3746
+ ...this.columnSizingOverrides(),
3747
+ [columnId]: { widthPx: width }
3748
+ });
3749
+ (this.state().layout.onAutosizeColumn ?? this.state().layout.onColumnResized)?.(columnId, width);
3750
+ }
3751
+ onAutosizeAllColumns() {
3752
+ const tableEl = this.tableContainerEl() ?? void 0;
3753
+ const overrides = {};
3754
+ for (const col of this.visibleCols()) {
3755
+ const width = measureColumnContentWidth(col.columnId, col.minWidth, tableEl);
3756
+ overrides[col.columnId] = { widthPx: width };
3757
+ (this.state().layout.onAutosizeColumn ?? this.state().layout.onColumnResized)?.(col.columnId, width);
3758
+ }
3759
+ this.state().layout.setColumnSizingOverrides({
3760
+ ...this.columnSizingOverrides(),
3761
+ ...overrides
3762
+ });
3763
+ }
3764
+ };
3765
+ var BaseColumnHeaderFilterComponent = class {
3766
+ constructor() {
3767
+ // Signal-backed inputs used by computed() — plain @Input properties aren't tracked by computed()
3768
+ this._filterType = signal("none");
3769
+ this._selectedValues = signal(void 0);
3770
+ this._options = signal(void 0);
3771
+ this._textValue = signal("");
3772
+ this._selectedUser = signal(void 0);
3773
+ this._dateValue = signal(void 0);
3774
+ this.isSorted = false;
3775
+ this.isSortedDescending = false;
3776
+ this.onSort = void 0;
3777
+ this.onFilterChange = void 0;
3778
+ this.isLoadingOptions = false;
3779
+ this.onTextChange = void 0;
3780
+ this.onUserChange = void 0;
3781
+ this.peopleSearch = void 0;
3782
+ this.onDateChange = void 0;
3783
+ // Internal state signals
3784
+ this.isFilterOpen = signal(false);
3785
+ this.tempTextValue = signal("");
3786
+ this.searchText = signal("");
3787
+ this.tempSelected = signal(/* @__PURE__ */ new Set());
3788
+ this.peopleSearchText = signal("");
3789
+ this.peopleSuggestions = signal([]);
3790
+ this.isPeopleLoading = signal(false);
3791
+ this.tempDateFrom = signal("");
3792
+ this.tempDateTo = signal("");
3793
+ // Popover position
3794
+ this.popoverTop = signal(0);
3795
+ this.popoverLeft = signal(0);
3796
+ this.peopleDebounceTimer = null;
3797
+ // Computed signals
3798
+ this.hasActiveFilter = computed(() => {
3799
+ const ft = this._filterType();
3800
+ if (ft === "text") return !!this._textValue();
3801
+ if (ft === "multiSelect") return (this._selectedValues()?.length ?? 0) > 0;
3802
+ if (ft === "people") return this._selectedUser() != null;
3803
+ if (ft === "date") return this._dateValue() != null;
3804
+ return false;
3805
+ });
3806
+ this.filteredOptions = computed(() => {
3807
+ const opts = this._options() ?? [];
3808
+ const search = this.searchText().toLowerCase().trim();
3809
+ if (!search) return opts;
3810
+ return opts.filter((o) => o.toLowerCase().includes(search));
3811
+ });
3812
+ }
3813
+ set filterType(v) {
3814
+ this._filterType.set(v);
3815
+ }
3816
+ get filterType() {
3817
+ return this._filterType();
3818
+ }
3819
+ set selectedValues(v) {
3820
+ this._selectedValues.set(v);
3821
+ }
3822
+ get selectedValues() {
3823
+ return this._selectedValues();
3824
+ }
3825
+ set options(v) {
3826
+ this._options.set(v);
3827
+ }
3828
+ get options() {
3829
+ return this._options();
3830
+ }
3831
+ set textValue(v) {
3832
+ this._textValue.set(v);
3833
+ }
3834
+ get textValue() {
3835
+ return this._textValue();
3836
+ }
3837
+ set selectedUser(v) {
3838
+ this._selectedUser.set(v);
3839
+ }
3840
+ get selectedUser() {
3841
+ return this._selectedUser();
3842
+ }
3843
+ set dateValue(v) {
3844
+ this._dateValue.set(v);
3845
+ }
3846
+ get dateValue() {
3847
+ return this._dateValue();
3848
+ }
3849
+ // Utility methods
3850
+ asInputValue(event) {
3851
+ return event.target.value;
3852
+ }
3853
+ toggleFilter(event) {
3854
+ event.stopPropagation();
3855
+ if (this.isFilterOpen()) {
3856
+ this.isFilterOpen.set(false);
3857
+ return;
3858
+ }
3859
+ this.tempTextValue.set(this.textValue);
3860
+ this.tempSelected.set(new Set(this.selectedValues ?? []));
3861
+ this.searchText.set("");
3862
+ this.peopleSearchText.set("");
3863
+ this.peopleSuggestions.set([]);
3864
+ const dv = this.dateValue;
3865
+ this.tempDateFrom.set(dv?.from ?? "");
3866
+ this.tempDateTo.set(dv?.to ?? "");
3867
+ const el = this.getHeaderEl()?.nativeElement;
3868
+ if (el) {
3869
+ const rect = el.getBoundingClientRect();
3870
+ this.popoverTop.set(rect.bottom + 4);
3871
+ this.popoverLeft.set(rect.left);
3872
+ }
3873
+ this.isFilterOpen.set(true);
3874
+ }
3875
+ // --- Text filter handlers ---
3876
+ onTextKeydown(event) {
3877
+ event.stopPropagation();
3878
+ if (event.key === "Enter") {
3879
+ event.preventDefault();
3880
+ this.handleTextApply();
3881
+ }
3882
+ }
3883
+ handleTextApply() {
3884
+ if (this.onTextChange) this.onTextChange(this.tempTextValue());
3885
+ this.isFilterOpen.set(false);
3886
+ }
3887
+ handleTextClear() {
3888
+ this.tempTextValue.set("");
3889
+ if (this.onTextChange) this.onTextChange("");
3890
+ this.isFilterOpen.set(false);
3891
+ }
3892
+ // --- MultiSelect filter handlers ---
3893
+ handleCheckboxChange(option, event) {
3894
+ const checked = event.target.checked;
3895
+ this.tempSelected.update((s) => {
3896
+ const next = new Set(s);
3897
+ if (checked) next.add(option);
3898
+ else next.delete(option);
3899
+ return next;
3900
+ });
3901
+ }
3902
+ handleSelectAllFiltered() {
3903
+ this.tempSelected.update((s) => {
3904
+ const next = new Set(s);
3905
+ for (const opt of this.filteredOptions()) next.add(opt);
3906
+ return next;
3907
+ });
3908
+ }
3909
+ handleClearSelection() {
3910
+ this.tempSelected.set(/* @__PURE__ */ new Set());
3911
+ }
3912
+ handleApplyMultiSelect() {
3913
+ if (this.onFilterChange) this.onFilterChange(Array.from(this.tempSelected()));
3914
+ this.isFilterOpen.set(false);
3915
+ }
3916
+ // --- People filter handlers ---
3917
+ onPeopleSearchInput(event) {
3918
+ const value = event.target.value;
3919
+ this.peopleSearchText.set(value);
3920
+ if (this.peopleDebounceTimer) clearTimeout(this.peopleDebounceTimer);
3921
+ const query = value.trim();
3922
+ if (!query) {
3923
+ this.peopleSuggestions.set([]);
3924
+ this.isPeopleLoading.set(false);
3925
+ return;
3926
+ }
3927
+ this.isPeopleLoading.set(true);
3928
+ this.peopleDebounceTimer = setTimeout(() => {
3929
+ const fn = this.peopleSearch;
3930
+ if (!fn) return;
3931
+ fn(query).then((results) => {
3932
+ this.peopleSuggestions.set(results);
3933
+ this.isPeopleLoading.set(false);
3934
+ }).catch(() => {
3935
+ this.peopleSuggestions.set([]);
3936
+ this.isPeopleLoading.set(false);
3937
+ });
3938
+ }, 300);
3939
+ }
3940
+ handleUserSelect(user) {
3941
+ if (this.onUserChange) this.onUserChange(user);
3942
+ this.isFilterOpen.set(false);
3943
+ }
3944
+ handleClearUser() {
3945
+ if (this.onUserChange) this.onUserChange(void 0);
3946
+ this.isFilterOpen.set(false);
3947
+ }
3948
+ // --- Date filter handlers ---
3949
+ handleDateApply() {
3950
+ const from = this.tempDateFrom();
3951
+ const to = this.tempDateTo();
3952
+ if (this.onDateChange) {
3953
+ if (!from && !to) {
3954
+ this.onDateChange(void 0);
3955
+ } else {
3956
+ this.onDateChange({ from: from || void 0, to: to || void 0 });
3957
+ }
3958
+ }
3959
+ this.isFilterOpen.set(false);
3960
+ }
3961
+ handleDateClear() {
3962
+ this.tempDateFrom.set("");
3963
+ this.tempDateTo.set("");
3964
+ if (this.onDateChange) this.onDateChange(void 0);
3965
+ this.isFilterOpen.set(false);
3966
+ }
3967
+ /** Clean up debounce timer on destroy. */
3968
+ ngOnDestroy() {
3969
+ if (this.peopleDebounceTimer) {
3970
+ clearTimeout(this.peopleDebounceTimer);
3971
+ this.peopleDebounceTimer = null;
3972
+ }
3973
+ }
3974
+ // --- Document click handler (for click-outside to close) ---
3975
+ onDocumentClick(event, selectorName) {
3976
+ const el = event.target;
3977
+ if (!el.closest(selectorName) && !el.closest(".ogrid-header-filter__popover")) {
3978
+ this.isFilterOpen.set(false);
3979
+ }
3980
+ }
3981
+ };
3982
+ __decorateClass([
3983
+ Input({ required: true })
3984
+ ], BaseColumnHeaderFilterComponent.prototype, "columnKey", 2);
3985
+ __decorateClass([
3986
+ Input({ required: true })
3987
+ ], BaseColumnHeaderFilterComponent.prototype, "columnName", 2);
3988
+ __decorateClass([
3989
+ Input({ required: true })
3990
+ ], BaseColumnHeaderFilterComponent.prototype, "filterType", 1);
3991
+ __decorateClass([
3992
+ Input()
3993
+ ], BaseColumnHeaderFilterComponent.prototype, "selectedValues", 1);
3994
+ __decorateClass([
3995
+ Input()
3996
+ ], BaseColumnHeaderFilterComponent.prototype, "options", 1);
3997
+ __decorateClass([
3998
+ Input()
3999
+ ], BaseColumnHeaderFilterComponent.prototype, "textValue", 1);
4000
+ __decorateClass([
4001
+ Input()
4002
+ ], BaseColumnHeaderFilterComponent.prototype, "selectedUser", 1);
4003
+ __decorateClass([
4004
+ Input()
4005
+ ], BaseColumnHeaderFilterComponent.prototype, "dateValue", 1);
4006
+ __decorateClass([
4007
+ Input()
4008
+ ], BaseColumnHeaderFilterComponent.prototype, "isSorted", 2);
4009
+ __decorateClass([
4010
+ Input()
4011
+ ], BaseColumnHeaderFilterComponent.prototype, "isSortedDescending", 2);
4012
+ __decorateClass([
4013
+ Input()
4014
+ ], BaseColumnHeaderFilterComponent.prototype, "onSort", 2);
4015
+ __decorateClass([
4016
+ Input()
4017
+ ], BaseColumnHeaderFilterComponent.prototype, "onFilterChange", 2);
4018
+ __decorateClass([
4019
+ Input()
4020
+ ], BaseColumnHeaderFilterComponent.prototype, "isLoadingOptions", 2);
4021
+ __decorateClass([
4022
+ Input()
4023
+ ], BaseColumnHeaderFilterComponent.prototype, "onTextChange", 2);
4024
+ __decorateClass([
4025
+ Input()
4026
+ ], BaseColumnHeaderFilterComponent.prototype, "onUserChange", 2);
4027
+ __decorateClass([
4028
+ Input()
4029
+ ], BaseColumnHeaderFilterComponent.prototype, "peopleSearch", 2);
4030
+ __decorateClass([
4031
+ Input()
4032
+ ], BaseColumnHeaderFilterComponent.prototype, "onDateChange", 2);
4033
+ var BaseColumnChooserComponent = class {
4034
+ constructor() {
4035
+ this._columns = signal([]);
4036
+ this._visibleColumns = signal(/* @__PURE__ */ new Set());
4037
+ this.visibilityChange = new EventEmitter();
4038
+ // Dropdown state
4039
+ this.isOpen = signal(false);
4040
+ // Computed counts (signal-backed so computed() tracks changes)
4041
+ this.visibleCount = computed(() => this._visibleColumns().size);
4042
+ this.totalCount = computed(() => this._columns().length);
4043
+ }
4044
+ set columns(v) {
4045
+ this._columns.set(v);
4046
+ }
4047
+ get columns() {
4048
+ return this._columns();
4049
+ }
4050
+ set visibleColumns(v) {
4051
+ this._visibleColumns.set(v);
4052
+ }
4053
+ get visibleColumns() {
4054
+ return this._visibleColumns();
4055
+ }
4056
+ toggle() {
4057
+ this.isOpen.update((v) => !v);
4058
+ }
4059
+ onCheckboxChange(columnKey, event) {
4060
+ const checked = event.target.checked;
4061
+ this.visibilityChange.emit({ columnKey, visible: checked });
4062
+ }
4063
+ onToggle(columnKey, checked) {
4064
+ this.visibilityChange.emit({ columnKey, visible: checked });
4065
+ }
4066
+ selectAll() {
4067
+ for (const col of this.columns) {
4068
+ if (!this.visibleColumns.has(col.columnId)) {
4069
+ this.visibilityChange.emit({ columnKey: col.columnId, visible: true });
4070
+ }
4071
+ }
4072
+ }
4073
+ clearAll() {
4074
+ for (const col of this.columns) {
4075
+ if (this.visibleColumns.has(col.columnId)) {
4076
+ this.visibilityChange.emit({ columnKey: col.columnId, visible: false });
4077
+ }
4078
+ }
4079
+ }
4080
+ onClearAll() {
4081
+ for (const col of this.columns) {
4082
+ if (col.required !== true && this.visibleColumns.has(col.columnId)) {
4083
+ this.visibilityChange.emit({ columnKey: col.columnId, visible: false });
4084
+ }
4085
+ }
4086
+ }
4087
+ onSelectAll() {
4088
+ for (const col of this.columns) {
4089
+ if (!this.visibleColumns.has(col.columnId)) {
4090
+ this.visibilityChange.emit({ columnKey: col.columnId, visible: true });
4091
+ }
4092
+ }
4093
+ }
4094
+ };
4095
+ __decorateClass([
4096
+ Input({ required: true })
4097
+ ], BaseColumnChooserComponent.prototype, "columns", 1);
4098
+ __decorateClass([
4099
+ Input({ required: true })
4100
+ ], BaseColumnChooserComponent.prototype, "visibleColumns", 1);
4101
+ __decorateClass([
4102
+ Output()
4103
+ ], BaseColumnChooserComponent.prototype, "visibilityChange", 2);
4104
+ var BasePaginationControlsComponent = class {
4105
+ constructor() {
4106
+ this._currentPage = signal(1);
4107
+ this._pageSize = signal(25);
4108
+ this._totalCount = signal(0);
4109
+ this._pageSizeOptions = signal(void 0);
4110
+ this._entityLabelPlural = signal("items");
4111
+ this.pageChange = new EventEmitter();
4112
+ this.pageSizeChange = new EventEmitter();
4113
+ this.labelPlural = computed(() => this._entityLabelPlural() ?? "items");
4114
+ this.vm = computed(() => {
4115
+ const opts = this._pageSizeOptions();
4116
+ return getPaginationViewModel(
4117
+ this._currentPage(),
4118
+ this._pageSize(),
4119
+ this._totalCount(),
4120
+ opts ? { pageSizeOptions: opts } : void 0
4121
+ );
4122
+ });
4123
+ }
4124
+ set currentPage(v) {
4125
+ this._currentPage.set(v);
4126
+ }
4127
+ get currentPage() {
4128
+ return this._currentPage();
4129
+ }
4130
+ set pageSize(v) {
4131
+ this._pageSize.set(v);
4132
+ }
4133
+ get pageSize() {
4134
+ return this._pageSize();
4135
+ }
4136
+ set totalCount(v) {
4137
+ this._totalCount.set(v);
4138
+ }
4139
+ get totalCount() {
4140
+ return this._totalCount();
4141
+ }
4142
+ set pageSizeOptions(v) {
4143
+ this._pageSizeOptions.set(v);
4144
+ }
4145
+ get pageSizeOptions() {
4146
+ return this._pageSizeOptions();
4147
+ }
4148
+ set entityLabelPlural(v) {
4149
+ this._entityLabelPlural.set(v);
4150
+ }
4151
+ get entityLabelPlural() {
4152
+ return this._entityLabelPlural();
4153
+ }
4154
+ onPageSizeSelect(event) {
4155
+ const value = Number(event.target.value);
4156
+ this.pageSizeChange.emit(value);
4157
+ }
4158
+ onPageSizeChange(value) {
4159
+ this.pageSizeChange.emit(Number(value));
4160
+ }
4161
+ };
4162
+ __decorateClass([
4163
+ Input({ required: true })
4164
+ ], BasePaginationControlsComponent.prototype, "currentPage", 1);
4165
+ __decorateClass([
4166
+ Input({ required: true })
4167
+ ], BasePaginationControlsComponent.prototype, "pageSize", 1);
4168
+ __decorateClass([
4169
+ Input({ required: true })
4170
+ ], BasePaginationControlsComponent.prototype, "totalCount", 1);
4171
+ __decorateClass([
4172
+ Input()
4173
+ ], BasePaginationControlsComponent.prototype, "pageSizeOptions", 1);
4174
+ __decorateClass([
4175
+ Input()
4176
+ ], BasePaginationControlsComponent.prototype, "entityLabelPlural", 1);
4177
+ __decorateClass([
4178
+ Output()
4179
+ ], BasePaginationControlsComponent.prototype, "pageChange", 2);
4180
+ __decorateClass([
4181
+ Output()
4182
+ ], BasePaginationControlsComponent.prototype, "pageSizeChange", 2);
4183
+ var BaseInlineCellEditorComponent = class {
4184
+ constructor() {
4185
+ this.commit = new EventEmitter();
4186
+ this.cancel = new EventEmitter();
4187
+ this.localValue = signal("");
4188
+ this.highlightedIndex = signal(0);
4189
+ this.selectOptions = signal([]);
4190
+ this.searchText = signal("");
4191
+ this.filteredOptions = computed(() => {
4192
+ const options = this.selectOptions();
4193
+ const search = this.searchText().trim().toLowerCase();
4194
+ if (!search) return options;
4195
+ return options.filter((v) => this.getDisplayText(v).toLowerCase().includes(search));
4196
+ });
4197
+ this._initialized = false;
4198
+ }
4199
+ ngOnInit() {
4200
+ this._initialized = true;
4201
+ this.syncFromInputs();
4202
+ }
4203
+ ngOnChanges() {
4204
+ if (this._initialized) {
4205
+ this.syncFromInputs();
4206
+ }
4207
+ }
4208
+ syncFromInputs() {
4209
+ const v = this.value;
4210
+ this.localValue.set(v != null ? String(v) : "");
4211
+ const col = this.column;
4212
+ if (col?.cellEditorParams?.values) {
4213
+ const vals = col.cellEditorParams.values;
4214
+ this.selectOptions.set(vals);
4215
+ const initialIdx = vals.findIndex((opt) => String(opt) === String(v));
4216
+ this.highlightedIndex.set(Math.max(initialIdx, 0));
4217
+ }
4218
+ }
4219
+ ngAfterViewInit() {
4220
+ setTimeout(() => {
4221
+ const richSelectInput = this.richSelectInput?.nativeElement;
4222
+ if (richSelectInput) {
4223
+ richSelectInput.focus();
4224
+ richSelectInput.select();
4225
+ this.positionFixedDropdown(this.richSelectWrapper, this.richSelectDropdown);
4226
+ return;
4227
+ }
4228
+ const selectWrap = this.selectWrapper?.nativeElement;
4229
+ if (selectWrap) {
4230
+ selectWrap.focus();
4231
+ this.positionFixedDropdown(this.selectWrapper, this.selectDropdown);
4232
+ return;
4233
+ }
4234
+ const el = this.inputEl?.nativeElement;
4235
+ if (el) {
4236
+ el.focus();
4237
+ if (el instanceof HTMLInputElement && el.type === "text") {
4238
+ el.select();
4239
+ }
4240
+ }
4241
+ });
4242
+ }
4243
+ commitValue(value) {
4244
+ this.commit.emit(value);
4245
+ }
4246
+ onTextKeyDown(e) {
4247
+ if (e.key === "Enter") {
4248
+ e.preventDefault();
4249
+ this.commitValue(this.localValue());
4250
+ } else if (e.key === "Escape") {
4251
+ e.preventDefault();
4252
+ this.cancel.emit();
4253
+ } else if (e.key === "Tab") {
4254
+ e.preventDefault();
4255
+ this.commitValue(this.localValue());
4256
+ }
4257
+ }
4258
+ getDisplayText(value) {
4259
+ const formatValue = this.column?.cellEditorParams?.formatValue;
4260
+ if (formatValue) return formatValue(value);
4261
+ return value != null ? String(value) : "";
4262
+ }
4263
+ onCustomSelectKeyDown(e) {
4264
+ const options = this.selectOptions();
4265
+ switch (e.key) {
4266
+ case "ArrowDown":
4267
+ e.preventDefault();
4268
+ this.highlightedIndex.set(Math.min(this.highlightedIndex() + 1, options.length - 1));
4269
+ this.scrollOptionIntoView(this.selectDropdown);
4270
+ break;
4271
+ case "ArrowUp":
4272
+ e.preventDefault();
4273
+ this.highlightedIndex.set(Math.max(this.highlightedIndex() - 1, 0));
4274
+ this.scrollOptionIntoView(this.selectDropdown);
4275
+ break;
4276
+ case "Enter":
4277
+ e.preventDefault();
4278
+ e.stopPropagation();
4279
+ if (options.length > 0 && this.highlightedIndex() < options.length) {
4280
+ this.commitValue(options[this.highlightedIndex()]);
4281
+ }
4282
+ break;
4283
+ case "Tab":
4284
+ e.preventDefault();
4285
+ if (options.length > 0 && this.highlightedIndex() < options.length) {
4286
+ this.commitValue(options[this.highlightedIndex()]);
4287
+ }
4288
+ break;
4289
+ case "Escape":
4290
+ e.preventDefault();
4291
+ e.stopPropagation();
4292
+ this.cancel.emit();
4293
+ break;
4294
+ }
4295
+ }
4296
+ onRichSelectSearch(text) {
4297
+ this.searchText.set(text);
4298
+ this.highlightedIndex.set(0);
4299
+ }
4300
+ onRichSelectKeyDown(e) {
4301
+ const options = this.filteredOptions();
4302
+ switch (e.key) {
4303
+ case "ArrowDown":
4304
+ e.preventDefault();
4305
+ this.highlightedIndex.set(Math.min(this.highlightedIndex() + 1, options.length - 1));
4306
+ this.scrollOptionIntoView(this.richSelectDropdown);
4307
+ break;
4308
+ case "ArrowUp":
4309
+ e.preventDefault();
4310
+ this.highlightedIndex.set(Math.max(this.highlightedIndex() - 1, 0));
4311
+ this.scrollOptionIntoView(this.richSelectDropdown);
4312
+ break;
4313
+ case "Enter":
4314
+ e.preventDefault();
4315
+ e.stopPropagation();
4316
+ if (options.length > 0 && this.highlightedIndex() < options.length) {
4317
+ this.commitValue(options[this.highlightedIndex()]);
4318
+ }
4319
+ break;
4320
+ case "Escape":
4321
+ e.preventDefault();
4322
+ e.stopPropagation();
4323
+ this.cancel.emit();
4324
+ break;
4325
+ }
4326
+ }
4327
+ onCheckboxKeyDown(e) {
4328
+ if (e.key === "Escape") {
4329
+ e.preventDefault();
4330
+ this.cancel.emit();
4331
+ }
4332
+ }
4333
+ onTextBlur() {
4334
+ this.commitValue(this.localValue());
4335
+ }
4336
+ getInputStyle() {
4337
+ const baseStyle = "width:100%;box-sizing:border-box;padding:6px 10px;border:none;outline:none;font:inherit;background:transparent;color:inherit;";
4338
+ const col = this.column;
4339
+ if (col.type === "numeric") {
4340
+ return baseStyle + "text-align:right;";
4341
+ }
4342
+ return baseStyle;
4343
+ }
4344
+ /** Position a dropdown using fixed positioning to escape overflow clipping. */
4345
+ positionFixedDropdown(wrapperRef, dropdownRef) {
4346
+ const wrapper = wrapperRef?.nativeElement;
4347
+ const dropdown = dropdownRef?.nativeElement;
4348
+ if (!wrapper || !dropdown) return;
4349
+ const rect = wrapper.getBoundingClientRect();
4350
+ const maxH = 200;
4351
+ const spaceBelow = window.innerHeight - rect.bottom;
4352
+ const flipUp = spaceBelow < maxH && rect.top > spaceBelow;
4353
+ dropdown.style.position = "fixed";
4354
+ dropdown.style.left = `${rect.left}px`;
4355
+ dropdown.style.width = `${rect.width}px`;
4356
+ dropdown.style.maxHeight = `${maxH}px`;
4357
+ dropdown.style.zIndex = "9999";
4358
+ dropdown.style.right = "auto";
4359
+ if (flipUp) {
4360
+ dropdown.style.top = "auto";
4361
+ dropdown.style.bottom = `${window.innerHeight - rect.top}px`;
4362
+ } else {
4363
+ dropdown.style.top = `${rect.bottom}px`;
4364
+ }
4365
+ }
4366
+ /** Scroll the highlighted option into view within a dropdown element. */
4367
+ scrollOptionIntoView(dropdownRef) {
4368
+ setTimeout(() => {
4369
+ const dropdown = dropdownRef?.nativeElement;
4370
+ if (!dropdown) return;
4371
+ const highlighted = dropdown.children[this.highlightedIndex()];
4372
+ highlighted?.scrollIntoView({ block: "nearest" });
4373
+ });
4374
+ }
4375
+ };
4376
+ __decorateClass([
4377
+ Input({ required: true })
4378
+ ], BaseInlineCellEditorComponent.prototype, "value", 2);
4379
+ __decorateClass([
4380
+ Input({ required: true })
4381
+ ], BaseInlineCellEditorComponent.prototype, "item", 2);
4382
+ __decorateClass([
4383
+ Input({ required: true })
4384
+ ], BaseInlineCellEditorComponent.prototype, "column", 2);
4385
+ __decorateClass([
4386
+ Input({ required: true })
4387
+ ], BaseInlineCellEditorComponent.prototype, "rowIndex", 2);
4388
+ __decorateClass([
4389
+ Input({ required: true })
4390
+ ], BaseInlineCellEditorComponent.prototype, "editorType", 2);
4391
+ __decorateClass([
4392
+ Output()
4393
+ ], BaseInlineCellEditorComponent.prototype, "commit", 2);
4394
+ __decorateClass([
4395
+ Output()
4396
+ ], BaseInlineCellEditorComponent.prototype, "cancel", 2);
4397
+ __decorateClass([
4398
+ ViewChild("inputEl")
4399
+ ], BaseInlineCellEditorComponent.prototype, "inputEl", 2);
4400
+ __decorateClass([
4401
+ ViewChild("selectWrapper")
4402
+ ], BaseInlineCellEditorComponent.prototype, "selectWrapper", 2);
4403
+ __decorateClass([
4404
+ ViewChild("selectDropdown")
4405
+ ], BaseInlineCellEditorComponent.prototype, "selectDropdown", 2);
4406
+ __decorateClass([
4407
+ ViewChild("richSelectWrapper")
4408
+ ], BaseInlineCellEditorComponent.prototype, "richSelectWrapper", 2);
4409
+ __decorateClass([
4410
+ ViewChild("richSelectInput")
4411
+ ], BaseInlineCellEditorComponent.prototype, "richSelectInput", 2);
4412
+ __decorateClass([
4413
+ ViewChild("richSelectDropdown")
4414
+ ], BaseInlineCellEditorComponent.prototype, "richSelectDropdown", 2);
4415
+
4416
+ // src/components/inline-cell-editor-template.ts
4417
+ var INLINE_CELL_EDITOR_TEMPLATE = `
4418
+ @switch (editorType) {
4419
+ @case ('text') {
4420
+ <input
4421
+ #inputEl
4422
+ type="text"
4423
+ [value]="localValue()"
4424
+ (input)="localValue.set($any($event.target).value)"
4425
+ (keydown)="onTextKeyDown($event)"
4426
+ (blur)="onTextBlur()"
4427
+ [style]="getInputStyle()"
4428
+ />
4429
+ }
4430
+ @case ('richSelect') {
4431
+ <div #richSelectWrapper
4432
+ style="width:100%;height:100%;display:flex;align-items:center;padding:6px 10px;box-sizing:border-box;min-width:0;position:relative">
4433
+ <input
4434
+ #richSelectInput
4435
+ type="text"
4436
+ [value]="searchText()"
4437
+ (input)="onRichSelectSearch($any($event.target).value)"
4438
+ (keydown)="onRichSelectKeyDown($event)"
4439
+ placeholder="Search..."
4440
+ style="width:100%;padding:0;border:none;background:transparent;color:inherit;font:inherit;font-size:13px;outline:none;min-width:0"
4441
+ />
4442
+ <div #richSelectDropdown role="listbox"
4443
+ style="position:absolute;top:100%;left:0;right:0;max-height:200px;overflow-y:auto;background:var(--ogrid-bg, #fff);border:1px solid var(--ogrid-border, rgba(0,0,0,0.12));z-index:10;box-shadow:0 4px 16px rgba(0,0,0,0.2)">
4444
+ @for (opt of filteredOptions(); track opt; let i = $index) {
4445
+ <div role="option"
4446
+ [attr.aria-selected]="i === highlightedIndex()"
4447
+ (click)="commitValue(opt)"
4448
+ [style]="i === highlightedIndex() ? 'padding:6px 8px;cursor:pointer;color:var(--ogrid-fg, #242424);background:var(--ogrid-bg-hover, #e8f0fe)' : 'padding:6px 8px;cursor:pointer;color:var(--ogrid-fg, #242424)'">
4449
+ {{ getDisplayText(opt) }}
4450
+ </div>
4451
+ }
4452
+ @if (filteredOptions().length === 0) {
4453
+ <div style="padding:6px 8px;color:var(--ogrid-muted, #999)">No matches</div>
4454
+ }
4455
+ </div>
4456
+ </div>
4457
+ }
4458
+ @case ('select') {
4459
+ <div #selectWrapper tabindex="0"
4460
+ style="width:100%;height:100%;display:flex;align-items:center;padding:6px 10px;box-sizing:border-box;min-width:0;position:relative"
4461
+ (keydown)="onCustomSelectKeyDown($event)">
4462
+ <div style="display:flex;align-items:center;justify-content:space-between;width:100%;cursor:pointer;font-size:13px;color:inherit">
4463
+ <span>{{ getDisplayText(value) }}</span>
4464
+ <span style="margin-left:4px;font-size:10px;opacity:0.5">&#9662;</span>
4465
+ </div>
4466
+ <div #selectDropdown role="listbox"
4467
+ style="position:absolute;top:100%;left:0;right:0;max-height:200px;overflow-y:auto;background:var(--ogrid-bg, #fff);border:1px solid var(--ogrid-border, rgba(0,0,0,0.12));z-index:10;box-shadow:0 4px 16px rgba(0,0,0,0.2)">
4468
+ @for (opt of selectOptions(); track opt; let i = $index) {
4469
+ <div role="option"
4470
+ [attr.aria-selected]="i === highlightedIndex()"
4471
+ (click)="commitValue(opt)"
4472
+ [style]="i === highlightedIndex() ? 'padding:6px 8px;cursor:pointer;color:var(--ogrid-fg, #242424);background:var(--ogrid-bg-hover, #e8f0fe)' : 'padding:6px 8px;cursor:pointer;color:var(--ogrid-fg, #242424)'">
4473
+ {{ getDisplayText(opt) }}
4474
+ </div>
4475
+ }
4476
+ </div>
4477
+ </div>
4478
+ }
4479
+ @case ('checkbox') {
4480
+ <div style="display:flex;align-items:center;justify-content:center;width:100%;height:100%">
4481
+ <input
4482
+ type="checkbox"
4483
+ [checked]="!!localValue()"
4484
+ (change)="commitValue($any($event.target).checked)"
4485
+ (keydown)="onCheckboxKeyDown($event)"
4486
+ />
4487
+ </div>
4488
+ }
4489
+ @case ('date') {
4490
+ <input
4491
+ #inputEl
4492
+ type="date"
4493
+ [value]="localValue()"
4494
+ (change)="commitValue($any($event.target).value)"
4495
+ (keydown)="onTextKeyDown($event)"
4496
+ (blur)="onTextBlur()"
4497
+ [style]="getInputStyle()"
4498
+ />
4499
+ }
4500
+ @default {
4501
+ <input
4502
+ #inputEl
4503
+ type="text"
4504
+ [value]="localValue()"
4505
+ (input)="localValue.set($any($event.target).value)"
4506
+ (keydown)="onTextKeyDown($event)"
4507
+ (blur)="onTextBlur()"
4508
+ [style]="getInputStyle()"
4509
+ />
4510
+ }
4511
+ }
4512
+ `;
4513
+ var INLINE_CELL_EDITOR_STYLES = `
4514
+ :host {
4515
+ display: block;
4516
+ width: 100%;
4517
+ height: 100%;
4518
+ }
4519
+ `;
4520
+ var POPOVER_CELL_EDITOR_TEMPLATE = `
4521
+ <div #anchorEl
4522
+ class="ogrid-popover-anchor"
4523
+ [attr.data-row-index]="rowIndex"
4524
+ [attr.data-col-index]="globalColIndex"
4525
+ >
4526
+ {{ displayValue }}
4527
+ </div>
4528
+ @if (showEditor()) {
4529
+ <div class="ogrid-popover-editor-overlay" (click)="handleOverlayClick()">
4530
+ <div class="ogrid-popover-editor-content" #editorContainer></div>
4531
+ </div>
4532
+ }
4533
+ `;
4534
+ var POPOVER_CELL_EDITOR_OVERLAY_STYLES = `
4535
+ :host { display: contents; }
4536
+ .ogrid-popover-editor-overlay {
4537
+ position: fixed; inset: 0; z-index: 1000;
4538
+ background: rgba(0,0,0,0.3);
4539
+ display: flex; align-items: center; justify-content: center;
4540
+ }
4541
+ .ogrid-popover-editor-content {
4542
+ background: var(--ogrid-bg, #ffffff); border-radius: 4px; padding: 16px;
4543
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
4544
+ max-width: 90vw; max-height: 90vh; overflow: auto;
4545
+ color: var(--ogrid-fg, rgba(0, 0, 0, 0.87));
4546
+ }
4547
+ `;
4548
+ var BasePopoverCellEditorComponent = class {
4549
+ constructor() {
4550
+ this.injector = inject(Injector);
4551
+ this.envInjector = inject(EnvironmentInjector);
4552
+ this.showEditor = signal(false);
4553
+ effect(() => {
4554
+ const anchor = this.anchorRef;
4555
+ if (anchor) {
4556
+ setTimeout(() => this.showEditor.set(true), 0);
4557
+ }
4558
+ });
4559
+ effect((onCleanup) => {
4560
+ const container = this.editorContainerRef;
4561
+ const props = this.editorProps;
4562
+ const col = this.column;
4563
+ if (!container || !this.showEditor() || typeof col.cellEditor !== "function") return;
4564
+ const EditorComponent = col.cellEditor;
4565
+ const componentRef = createComponent(EditorComponent, {
4566
+ environmentInjector: this.envInjector,
4567
+ elementInjector: this.injector
4568
+ });
4569
+ Object.assign(componentRef.instance, props);
4570
+ componentRef.changeDetectorRef.detectChanges();
4571
+ container.nativeElement.appendChild(componentRef.location.nativeElement);
4572
+ onCleanup(() => componentRef.destroy());
4573
+ });
4574
+ }
4575
+ handleOverlayClick() {
4576
+ this.onCancel();
4577
+ }
4578
+ };
4579
+ __decorateClass([
4580
+ Input({ required: true })
4581
+ ], BasePopoverCellEditorComponent.prototype, "item", 2);
4582
+ __decorateClass([
4583
+ Input({ required: true })
4584
+ ], BasePopoverCellEditorComponent.prototype, "column", 2);
4585
+ __decorateClass([
4586
+ Input({ required: true })
4587
+ ], BasePopoverCellEditorComponent.prototype, "rowIndex", 2);
4588
+ __decorateClass([
4589
+ Input({ required: true })
4590
+ ], BasePopoverCellEditorComponent.prototype, "globalColIndex", 2);
4591
+ __decorateClass([
4592
+ Input({ required: true })
4593
+ ], BasePopoverCellEditorComponent.prototype, "displayValue", 2);
4594
+ __decorateClass([
4595
+ Input({ required: true })
4596
+ ], BasePopoverCellEditorComponent.prototype, "editorProps", 2);
4597
+ __decorateClass([
4598
+ Input({ required: true })
4599
+ ], BasePopoverCellEditorComponent.prototype, "onCancel", 2);
4600
+ __decorateClass([
4601
+ ViewChild("anchorEl")
4602
+ ], BasePopoverCellEditorComponent.prototype, "anchorRef", 2);
4603
+ __decorateClass([
4604
+ ViewChild("editorContainer")
4605
+ ], BasePopoverCellEditorComponent.prototype, "editorContainerRef", 2);
4606
+
4607
+ export { BaseColumnChooserComponent, BaseColumnHeaderFilterComponent, BaseDataGridTableComponent, BaseInlineCellEditorComponent, BaseOGridComponent, BasePaginationControlsComponent, BasePopoverCellEditorComponent, ColumnReorderService, DataGridEditingHelper, DataGridInteractionHelper, DataGridLayoutHelper, DataGridStateService, EmptyStateComponent, GridContextMenuComponent, INLINE_CELL_EDITOR_STYLES, INLINE_CELL_EDITOR_TEMPLATE, MarchingAntsOverlayComponent, OGRID_THEME_VARS_CSS, OGridLayoutComponent, OGridService, POPOVER_CELL_EDITOR_OVERLAY_STYLES, POPOVER_CELL_EDITOR_TEMPLATE, SideBarComponent, StatusBarComponent, VirtualScrollService, createDebouncedCallback, createDebouncedSignal, createLatestCallback };