@alaarab/ogrid-angular 2.1.3 → 2.1.5

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
@@ -1,746 +0,0 @@
1
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
6
- };
7
- import { Injectable, signal, computed, effect, DestroyRef, inject } from '@angular/core';
8
- import { mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, flattenColumns, processClientSideData, computeNextSortState, validateColumns, validateRowIds, } from '@alaarab/ogrid-core';
9
- const DEFAULT_PAGE_SIZE = 25;
10
- const EMPTY_LOADING_OPTIONS = {};
11
- const DEFAULT_PANELS = ['columns', 'filters'];
12
- /**
13
- * Top-level orchestration service for OGrid: manages pagination, sorting, filtering,
14
- * column visibility, sidebar, and server-side data fetching via Angular signals.
15
- *
16
- * Port of React's useOGrid hook.
17
- */
18
- let OGridService = class OGridService {
19
- constructor() {
20
- this.destroyRef = inject(DestroyRef);
21
- // --- Input signals (set by the component consuming this service) ---
22
- this.columnsProp = signal([]);
23
- this.getRowId = signal((item) => item['id']);
24
- this.data = signal(undefined);
25
- this.dataSource = signal(undefined);
26
- this.controlledPage = signal(undefined);
27
- this.controlledPageSize = signal(undefined);
28
- this.controlledSort = signal(undefined);
29
- this.controlledFilters = signal(undefined);
30
- this.controlledVisibleColumns = signal(undefined);
31
- this.controlledLoading = signal(undefined);
32
- this.onPageChange = signal(undefined);
33
- this.onPageSizeChange = signal(undefined);
34
- this.onSortChange = signal(undefined);
35
- this.onFiltersChange = signal(undefined);
36
- this.onVisibleColumnsChange = signal(undefined);
37
- this.columnOrder = signal(undefined);
38
- this.onColumnOrderChange = signal(undefined);
39
- this.onColumnResized = signal(undefined);
40
- this.onAutosizeColumn = signal(undefined);
41
- this.onColumnPinned = signal(undefined);
42
- this.defaultPageSize = signal(DEFAULT_PAGE_SIZE);
43
- this.defaultSortBy = signal(undefined);
44
- this.defaultSortDirection = signal('asc');
45
- this.toolbar = signal(undefined);
46
- this.toolbarBelow = signal(undefined);
47
- this.emptyState = signal(undefined);
48
- this.entityLabelPlural = signal('items');
49
- this.className = signal(undefined);
50
- this.layoutMode = signal('fill');
51
- this.suppressHorizontalScroll = signal(undefined);
52
- this.stickyHeader = signal(true);
53
- this.fullScreen = signal(false);
54
- this.editable = signal(undefined);
55
- this.cellSelection = signal(undefined);
56
- this.density = signal('normal');
57
- this.rowHeight = signal(undefined);
58
- this.onCellValueChanged = signal(undefined);
59
- this.onUndo = signal(undefined);
60
- this.onRedo = signal(undefined);
61
- this.canUndo = signal(undefined);
62
- this.canRedo = signal(undefined);
63
- this.rowSelection = signal('none');
64
- this.selectedRows = signal(undefined);
65
- this.onSelectionChange = signal(undefined);
66
- this.statusBar = signal(undefined);
67
- this.pageSizeOptions = signal(undefined);
68
- this.sideBarConfig = signal(undefined);
69
- this.onFirstDataRendered = signal(undefined);
70
- this.onError = signal(undefined);
71
- this.columnChooserProp = signal(undefined);
72
- this.columnReorder = signal(undefined);
73
- this.virtualScroll = signal(undefined);
74
- this.ariaLabel = signal(undefined);
75
- this.ariaLabelledBy = signal(undefined);
76
- // --- Internal state signals ---
77
- this.internalData = signal([]);
78
- this.internalLoading = signal(false);
79
- this.internalPage = signal(1);
80
- this.internalPageSizeOverride = signal(null);
81
- this.internalSortOverride = signal(null);
82
- this.internalFilters = signal({});
83
- this.internalVisibleColumnsOverride = signal(null);
84
- this.internalSelectedRows = signal(new Set());
85
- this.columnWidthOverrides = signal({});
86
- this.pinnedOverrides = signal({});
87
- // Server-side state
88
- this.serverItems = signal([]);
89
- this.serverTotalCount = signal(0);
90
- this.serverLoading = signal(true);
91
- this.fetchAbortController = null;
92
- this.filterAbortController = null;
93
- this.refreshCounter = signal(0);
94
- this.firstDataRendered = signal(false);
95
- // Side bar state
96
- this.sideBarActivePanel = signal(null);
97
- // Filter options state
98
- this.serverFilterOptions = signal({});
99
- this.loadingFilterOptions = signal({});
100
- // --- Derived computed signals ---
101
- this.columns = computed(() => flattenColumns(this.columnsProp()));
102
- this.isServerSide = computed(() => this.dataSource() != null);
103
- this.isClientSide = computed(() => !this.isServerSide());
104
- this.displayData = computed(() => this.data() ?? this.internalData());
105
- this.displayLoading = computed(() => this.controlledLoading() ?? this.internalLoading());
106
- this.defaultSortField = computed(() => this.defaultSortBy() ?? this.columns()[0]?.columnId ?? '');
107
- this.page = computed(() => this.controlledPage() ?? this.internalPage());
108
- this.pageSize = computed(() => this.controlledPageSize() ?? this.internalPageSizeOverride() ?? this.defaultPageSize());
109
- this.sort = computed(() => this.controlledSort() ?? this.internalSortOverride() ?? {
110
- field: this.defaultSortField(),
111
- direction: this.defaultSortDirection(),
112
- });
113
- this.filters = computed(() => this.controlledFilters() ?? this.internalFilters());
114
- this.visibleColumns = computed(() => {
115
- const controlled = this.controlledVisibleColumns();
116
- if (controlled)
117
- return controlled;
118
- const override = this.internalVisibleColumnsOverride();
119
- if (override)
120
- return override;
121
- const cols = this.columns();
122
- if (cols.length === 0)
123
- return new Set();
124
- const visible = cols.filter((c) => c.defaultVisible !== false).map((c) => c.columnId);
125
- return new Set(visible.length > 0 ? visible : cols.map((c) => c.columnId));
126
- });
127
- this.effectiveSelectedRows = computed(() => this.selectedRows() ?? this.internalSelectedRows());
128
- this.columnChooserPlacement = computed(() => {
129
- const prop = this.columnChooserProp();
130
- return prop === false ? 'none' : prop === 'sidebar' ? 'sidebar' : 'toolbar';
131
- });
132
- this.multiSelectFilterFields = computed(() => getMultiSelectFilterFields(this.columns()));
133
- this.hasServerFilterOptions = computed(() => this.dataSource()?.fetchFilterOptions != null);
134
- this.clientFilterOptions = computed(() => {
135
- if (this.hasServerFilterOptions())
136
- return this.serverFilterOptions();
137
- return deriveFilterOptionsFromData(this.displayData(), this.columns());
138
- });
139
- this.clientItemsAndTotal = computed(() => {
140
- if (!this.isClientSide())
141
- return null;
142
- const rows = processClientSideData(this.displayData(), this.columns(), this.filters(), this.sort().field, this.sort().direction);
143
- const total = rows.length;
144
- const start = (this.page() - 1) * this.pageSize();
145
- const paged = rows.slice(start, start + this.pageSize());
146
- return { items: paged, totalCount: total };
147
- });
148
- this.displayItems = computed(() => {
149
- const cit = this.clientItemsAndTotal();
150
- return this.isClientSide() && cit ? cit.items : this.serverItems();
151
- });
152
- this.displayTotalCount = computed(() => {
153
- const cit = this.clientItemsAndTotal();
154
- return this.isClientSide() && cit ? cit.totalCount : this.serverTotalCount();
155
- });
156
- this.hasActiveFilters = computed(() => {
157
- return Object.values(this.filters()).some((v) => v !== undefined);
158
- });
159
- this.columnChooserColumns = computed(() => this.columns().map((c) => ({
160
- columnId: c.columnId,
161
- name: c.name,
162
- required: c.required === true,
163
- })));
164
- this.statusBarConfig = computed(() => {
165
- const sb = this.statusBar();
166
- if (!sb)
167
- return undefined;
168
- if (typeof sb === 'object')
169
- return sb;
170
- const totalData = this.isClientSide() ? (this.data()?.length ?? 0) : this.serverTotalCount();
171
- const filteredData = this.displayTotalCount();
172
- return {
173
- totalCount: totalData,
174
- filteredCount: this.hasActiveFilters() ? filteredData : undefined,
175
- selectedCount: this.effectiveSelectedRows().size,
176
- suppressRowCount: true,
177
- };
178
- });
179
- this.isLoadingResolved = computed(() => {
180
- return (this.isServerSide() && this.serverLoading()) || this.displayLoading();
181
- });
182
- // Side bar
183
- this.sideBarEnabled = computed(() => {
184
- const config = this.sideBarConfig();
185
- return config != null && config !== false;
186
- });
187
- this.sideBarParsed = computed(() => {
188
- const config = this.sideBarConfig();
189
- if (!this.sideBarEnabled() || config === true) {
190
- return { panels: DEFAULT_PANELS, position: 'right', defaultPanel: null };
191
- }
192
- const def = config;
193
- return {
194
- panels: def.panels ?? DEFAULT_PANELS,
195
- position: def.position ?? 'right',
196
- defaultPanel: def.defaultPanel ?? null,
197
- };
198
- });
199
- this.filterableColumns = computed(() => this.columns()
200
- .filter((c) => c.filterable && c.filterable.type)
201
- .map((c) => ({
202
- columnId: c.columnId,
203
- name: c.name,
204
- filterField: c.filterable?.filterField ?? c.columnId,
205
- filterType: c.filterable?.type,
206
- })));
207
- this.sideBarState = computed(() => ({
208
- isEnabled: this.sideBarEnabled(),
209
- activePanel: this.sideBarActivePanel(),
210
- setActivePanel: (panel) => this.sideBarActivePanel.set(panel),
211
- panels: this.sideBarParsed().panels,
212
- position: this.sideBarParsed().position,
213
- isOpen: this.sideBarActivePanel() !== null,
214
- toggle: (panel) => this.sideBarActivePanel.update((p) => p === panel ? null : panel),
215
- close: () => this.sideBarActivePanel.set(null),
216
- }));
217
- // --- Pre-computed stable callback references for dataGridProps ---
218
- // These avoid recreating arrow functions on every dataGridProps recomputation.
219
- this.handleSortFn = (columnKey, direction) => this.handleSort(columnKey, direction);
220
- this.handleColumnResizedFn = (columnId, width) => this.handleColumnResized(columnId, width);
221
- this.handleColumnPinnedFn = (columnId, pinned) => this.handleColumnPinned(columnId, pinned);
222
- this.handleSelectionChangeFn = (event) => this.handleSelectionChange(event);
223
- this.handleFilterChangeFn = (key, value) => this.handleFilterChange(key, value);
224
- this.clearAllFiltersFn = () => this.setFilters({});
225
- this.setPageFn = (p) => this.setPage(p);
226
- this.setPageSizeFn = (size) => this.setPageSize(size);
227
- this.handleVisibilityChangeFn = (columnKey, isVisible) => this.handleVisibilityChange(columnKey, isVisible);
228
- // --- Data grid props computed ---
229
- this.dataGridProps = computed(() => ({
230
- items: this.displayItems(),
231
- columns: this.columnsProp(),
232
- getRowId: this.getRowId(),
233
- sortBy: this.sort().field,
234
- sortDirection: this.sort().direction,
235
- onColumnSort: this.handleSortFn,
236
- visibleColumns: this.visibleColumns(),
237
- columnOrder: this.columnOrder(),
238
- onColumnOrderChange: this.onColumnOrderChange(),
239
- onColumnResized: this.handleColumnResizedFn,
240
- onAutosizeColumn: this.onAutosizeColumn(),
241
- onColumnPinned: this.handleColumnPinnedFn,
242
- pinnedColumns: this.pinnedOverrides(),
243
- initialColumnWidths: this.columnWidthOverrides(),
244
- editable: this.editable(),
245
- cellSelection: this.cellSelection(),
246
- density: this.density(),
247
- rowHeight: this.rowHeight(),
248
- onCellValueChanged: this.onCellValueChanged(),
249
- onUndo: this.onUndo(),
250
- onRedo: this.onRedo(),
251
- canUndo: this.canUndo(),
252
- canRedo: this.canRedo(),
253
- rowSelection: this.rowSelection(),
254
- selectedRows: this.effectiveSelectedRows(),
255
- onSelectionChange: this.handleSelectionChangeFn,
256
- statusBar: this.statusBarConfig(),
257
- isLoading: this.isLoadingResolved(),
258
- filters: this.filters(),
259
- onFilterChange: this.handleFilterChangeFn,
260
- filterOptions: this.clientFilterOptions(),
261
- loadingFilterOptions: this.dataSource()?.fetchFilterOptions ? this.loadingFilterOptions() : EMPTY_LOADING_OPTIONS,
262
- peopleSearch: this.dataSource()?.searchPeople?.bind(this.dataSource()),
263
- getUserByEmail: this.dataSource()?.getUserByEmail?.bind(this.dataSource()),
264
- layoutMode: this.layoutMode(),
265
- suppressHorizontalScroll: this.suppressHorizontalScroll(),
266
- stickyHeader: this.stickyHeader(),
267
- columnReorder: this.columnReorder(),
268
- virtualScroll: this.virtualScroll(),
269
- 'aria-label': this.ariaLabel(),
270
- 'aria-labelledby': this.ariaLabelledBy(),
271
- emptyState: {
272
- hasActiveFilters: this.hasActiveFilters(),
273
- onClearAll: this.clearAllFiltersFn,
274
- message: this.emptyState()?.message,
275
- render: this.emptyState()?.render,
276
- },
277
- }));
278
- this.pagination = computed(() => ({
279
- page: this.page(),
280
- pageSize: this.pageSize(),
281
- displayTotalCount: this.displayTotalCount(),
282
- setPage: this.setPageFn,
283
- setPageSize: this.setPageSizeFn,
284
- pageSizeOptions: this.pageSizeOptions(),
285
- entityLabelPlural: this.entityLabelPlural(),
286
- }));
287
- this.columnChooser = computed(() => ({
288
- columns: this.columnChooserColumns(),
289
- visibleColumns: this.visibleColumns(),
290
- onVisibilityChange: this.handleVisibilityChangeFn,
291
- placement: this.columnChooserPlacement(),
292
- }));
293
- this.filtersResult = computed(() => ({
294
- hasActiveFilters: this.hasActiveFilters(),
295
- setFilters: (f) => this.setFilters(f),
296
- }));
297
- this.sideBarProps = computed(() => {
298
- const state = this.sideBarState();
299
- if (!state.isEnabled)
300
- return null;
301
- return {
302
- activePanel: state.activePanel,
303
- onPanelChange: state.setActivePanel,
304
- panels: state.panels,
305
- position: state.position,
306
- columns: this.columnChooserColumns(),
307
- visibleColumns: this.visibleColumns(),
308
- onVisibilityChange: (columnKey, visible) => this.handleVisibilityChange(columnKey, visible),
309
- onSetVisibleColumns: (cols) => this.setVisibleColumns(cols),
310
- filterableColumns: this.filterableColumns(),
311
- filters: this.filters(),
312
- onFilterChange: (key, value) => this.handleFilterChange(key, value),
313
- filterOptions: this.clientFilterOptions(),
314
- };
315
- });
316
- // Validate columns once (on first non-empty columns signal)
317
- let columnsValidated = false;
318
- effect(() => {
319
- const cols = this.columns();
320
- if (!columnsValidated && cols.length > 0) {
321
- columnsValidated = true;
322
- validateColumns(cols);
323
- }
324
- });
325
- // Server-side data fetching effect
326
- effect((onCleanup) => {
327
- const ds = this.dataSource();
328
- if (!this.isServerSide() || !ds) {
329
- if (!this.isServerSide())
330
- this.serverLoading.set(false);
331
- return;
332
- }
333
- const page = this.page();
334
- const pageSize = this.pageSize();
335
- const sort = this.sort();
336
- const filters = this.filters();
337
- // Read refreshCounter to trigger re-fetches
338
- this.refreshCounter();
339
- const controller = new AbortController();
340
- this.fetchAbortController = controller;
341
- this.serverLoading.set(true);
342
- ds.fetchPage({ page, pageSize, sort: { field: sort.field, direction: sort.direction }, filters })
343
- .then((res) => {
344
- if (controller.signal.aborted)
345
- return;
346
- this.serverItems.set(res.items);
347
- this.serverTotalCount.set(res.totalCount);
348
- })
349
- .catch((err) => {
350
- if (controller.signal.aborted)
351
- return;
352
- this.onError()?.(err);
353
- this.serverItems.set([]);
354
- this.serverTotalCount.set(0);
355
- })
356
- .finally(() => {
357
- if (!controller.signal.aborted)
358
- this.serverLoading.set(false);
359
- });
360
- onCleanup(() => {
361
- controller.abort();
362
- });
363
- });
364
- // Fire onFirstDataRendered once; also validate row IDs on first data
365
- let rowIdsValidated = false;
366
- effect(() => {
367
- const items = this.displayItems();
368
- if (!this.firstDataRendered() && items.length > 0) {
369
- this.firstDataRendered.set(true);
370
- this.onFirstDataRendered()?.();
371
- }
372
- if (!rowIdsValidated && items.length > 0) {
373
- rowIdsValidated = true;
374
- validateRowIds(items, this.getRowId());
375
- }
376
- });
377
- // Load server filter options
378
- effect((onCleanup) => {
379
- const ds = this.dataSource();
380
- const fields = this.multiSelectFilterFields();
381
- const fetcher = ds && 'fetchFilterOptions' in ds && typeof ds.fetchFilterOptions === 'function'
382
- ? ds.fetchFilterOptions.bind(ds)
383
- : undefined;
384
- if (!fetcher || fields.length === 0) {
385
- this.serverFilterOptions.set({});
386
- this.loadingFilterOptions.set({});
387
- return;
388
- }
389
- const controller = new AbortController();
390
- this.filterAbortController = controller;
391
- const loading = {};
392
- fields.forEach((f) => { loading[f] = true; });
393
- this.loadingFilterOptions.set(loading);
394
- const results = {};
395
- Promise.all(fields.map(async (field) => {
396
- try {
397
- results[field] = await fetcher(field);
398
- }
399
- catch {
400
- results[field] = [];
401
- }
402
- })).then(() => {
403
- if (controller.signal.aborted)
404
- return;
405
- this.serverFilterOptions.set(results);
406
- this.loadingFilterOptions.set({});
407
- });
408
- onCleanup(() => {
409
- controller.abort();
410
- });
411
- });
412
- // Initialize sidebar default panel
413
- effect(() => {
414
- const parsed = this.sideBarParsed();
415
- if (parsed.defaultPanel) {
416
- this.sideBarActivePanel.set(parsed.defaultPanel);
417
- }
418
- });
419
- // Cleanup on destroy — abort in-flight requests and reset callback signals
420
- this.destroyRef.onDestroy(() => {
421
- this.fetchAbortController?.abort();
422
- this.filterAbortController?.abort();
423
- this.fetchAbortController = null;
424
- this.filterAbortController = null;
425
- this.onPageChange.set(undefined);
426
- this.onPageSizeChange.set(undefined);
427
- this.onSortChange.set(undefined);
428
- this.onFiltersChange.set(undefined);
429
- this.onVisibleColumnsChange.set(undefined);
430
- this.onColumnOrderChange.set(undefined);
431
- this.onColumnResized.set(undefined);
432
- this.onAutosizeColumn.set(undefined);
433
- this.onColumnPinned.set(undefined);
434
- this.onCellValueChanged.set(undefined);
435
- this.onSelectionChange.set(undefined);
436
- this.onFirstDataRendered.set(undefined);
437
- this.onError.set(undefined);
438
- this.onUndo.set(undefined);
439
- this.onRedo.set(undefined);
440
- });
441
- }
442
- // --- Setters ---
443
- setPage(p) {
444
- if (this.controlledPage() === undefined)
445
- this.internalPage.set(p);
446
- this.onPageChange()?.(p);
447
- }
448
- setPageSize(size) {
449
- if (this.controlledPageSize() === undefined)
450
- this.internalPageSizeOverride.set(size);
451
- this.onPageSizeChange()?.(size);
452
- this.setPage(1);
453
- }
454
- setSort(s) {
455
- if (this.controlledSort() === undefined)
456
- this.internalSortOverride.set(s);
457
- this.onSortChange()?.(s);
458
- this.setPage(1);
459
- }
460
- setFilters(f) {
461
- if (this.controlledFilters() === undefined)
462
- this.internalFilters.set(f);
463
- this.onFiltersChange()?.(f);
464
- this.setPage(1);
465
- }
466
- setVisibleColumns(cols) {
467
- if (this.controlledVisibleColumns() === undefined)
468
- this.internalVisibleColumnsOverride.set(cols);
469
- this.onVisibleColumnsChange()?.(cols);
470
- }
471
- handleSort(columnKey, direction) {
472
- this.setSort(computeNextSortState(this.sort(), columnKey, direction));
473
- }
474
- handleFilterChange(key, value) {
475
- this.setFilters(mergeFilter(this.filters(), key, value));
476
- }
477
- handleVisibilityChange(columnKey, isVisible) {
478
- const next = new Set(this.visibleColumns());
479
- if (isVisible)
480
- next.add(columnKey);
481
- else
482
- next.delete(columnKey);
483
- this.setVisibleColumns(next);
484
- }
485
- handleSelectionChange(event) {
486
- if (this.selectedRows() === undefined) {
487
- this.internalSelectedRows.set(new Set(event.selectedRowIds));
488
- }
489
- this.onSelectionChange()?.(event);
490
- }
491
- handleColumnResized(columnId, width) {
492
- this.columnWidthOverrides.update((prev) => ({ ...prev, [columnId]: width }));
493
- this.onColumnResized()?.(columnId, width);
494
- }
495
- handleColumnPinned(columnId, pinned) {
496
- this.pinnedOverrides.update((prev) => {
497
- if (pinned === null) {
498
- const { [columnId]: _removed, ...rest } = prev;
499
- return rest;
500
- }
501
- return { ...prev, [columnId]: pinned };
502
- });
503
- this.onColumnPinned()?.(columnId, pinned);
504
- }
505
- // --- Configure from props ---
506
- configure(props) {
507
- this.columnsProp.set(props.columns);
508
- this.getRowId.set(props.getRowId);
509
- if ('data' in props && props.data !== undefined)
510
- this.data.set(props.data);
511
- if ('dataSource' in props && props.dataSource !== undefined)
512
- this.dataSource.set(props.dataSource);
513
- if (props.page !== undefined)
514
- this.controlledPage.set(props.page);
515
- if (props.pageSize !== undefined)
516
- this.controlledPageSize.set(props.pageSize);
517
- if (props.sort !== undefined)
518
- this.controlledSort.set(props.sort);
519
- if (props.filters !== undefined)
520
- this.controlledFilters.set(props.filters);
521
- if (props.visibleColumns !== undefined)
522
- this.controlledVisibleColumns.set(props.visibleColumns);
523
- if (props.isLoading !== undefined)
524
- this.controlledLoading.set(props.isLoading);
525
- if (props.onPageChange)
526
- this.onPageChange.set(props.onPageChange);
527
- if (props.onPageSizeChange)
528
- this.onPageSizeChange.set(props.onPageSizeChange);
529
- if (props.onSortChange)
530
- this.onSortChange.set(props.onSortChange);
531
- if (props.onFiltersChange)
532
- this.onFiltersChange.set(props.onFiltersChange);
533
- if (props.onVisibleColumnsChange)
534
- this.onVisibleColumnsChange.set(props.onVisibleColumnsChange);
535
- if (props.columnOrder !== undefined)
536
- this.columnOrder.set(props.columnOrder);
537
- if (props.onColumnOrderChange)
538
- this.onColumnOrderChange.set(props.onColumnOrderChange);
539
- if (props.onColumnResized)
540
- this.onColumnResized.set(props.onColumnResized);
541
- if (props.onAutosizeColumn)
542
- this.onAutosizeColumn.set(props.onAutosizeColumn);
543
- if (props.onColumnPinned)
544
- this.onColumnPinned.set(props.onColumnPinned);
545
- if (props.defaultPageSize !== undefined)
546
- this.defaultPageSize.set(props.defaultPageSize);
547
- if (props.defaultSortBy !== undefined)
548
- this.defaultSortBy.set(props.defaultSortBy);
549
- if (props.defaultSortDirection !== undefined)
550
- this.defaultSortDirection.set(props.defaultSortDirection);
551
- if (props.editable !== undefined)
552
- this.editable.set(props.editable);
553
- if (props.cellSelection !== undefined)
554
- this.cellSelection.set(props.cellSelection);
555
- if (props.density !== undefined)
556
- this.density.set(props.density);
557
- if (props.rowHeight !== undefined)
558
- this.rowHeight.set(props.rowHeight);
559
- if (props.onCellValueChanged)
560
- this.onCellValueChanged.set(props.onCellValueChanged);
561
- if (props.onUndo)
562
- this.onUndo.set(props.onUndo);
563
- if (props.onRedo)
564
- this.onRedo.set(props.onRedo);
565
- if (props.canUndo !== undefined)
566
- this.canUndo.set(props.canUndo);
567
- if (props.canRedo !== undefined)
568
- this.canRedo.set(props.canRedo);
569
- if (props.rowSelection !== undefined)
570
- this.rowSelection.set(props.rowSelection);
571
- if (props.selectedRows !== undefined)
572
- this.selectedRows.set(props.selectedRows);
573
- if (props.onSelectionChange)
574
- this.onSelectionChange.set(props.onSelectionChange);
575
- if (props.statusBar !== undefined)
576
- this.statusBar.set(props.statusBar);
577
- if (props.pageSizeOptions !== undefined)
578
- this.pageSizeOptions.set(props.pageSizeOptions);
579
- if (props.sideBar !== undefined)
580
- this.sideBarConfig.set(props.sideBar);
581
- if (props.onFirstDataRendered)
582
- this.onFirstDataRendered.set(props.onFirstDataRendered);
583
- if (props.onError)
584
- this.onError.set(props.onError);
585
- if (props.columnChooser !== undefined)
586
- this.columnChooserProp.set(props.columnChooser);
587
- if (props.columnReorder !== undefined)
588
- this.columnReorder.set(props.columnReorder);
589
- if (props.virtualScroll !== undefined)
590
- this.virtualScroll.set(props.virtualScroll);
591
- if (props.entityLabelPlural !== undefined)
592
- this.entityLabelPlural.set(props.entityLabelPlural);
593
- if (props.className !== undefined)
594
- this.className.set(props.className);
595
- if (props.layoutMode !== undefined)
596
- this.layoutMode.set(props.layoutMode);
597
- if (props.suppressHorizontalScroll !== undefined)
598
- this.suppressHorizontalScroll.set(props.suppressHorizontalScroll);
599
- if (props.stickyHeader !== undefined)
600
- this.stickyHeader.set(props.stickyHeader);
601
- if (props.fullScreen !== undefined)
602
- this.fullScreen.set(props.fullScreen);
603
- if (props['aria-label'] !== undefined)
604
- this.ariaLabel.set(props['aria-label']);
605
- if (props['aria-labelledby'] !== undefined)
606
- this.ariaLabelledBy.set(props['aria-labelledby']);
607
- }
608
- // --- API ---
609
- // --- Column Pinning Methods ---
610
- /**
611
- * Pin a column to the left or right edge.
612
- */
613
- pinColumn(columnId, side) {
614
- this.pinnedOverrides.update((prev) => ({ ...prev, [columnId]: side }));
615
- this.onColumnPinned()?.(columnId, side);
616
- }
617
- /**
618
- * Unpin a column (remove sticky positioning).
619
- */
620
- unpinColumn(columnId) {
621
- this.pinnedOverrides.update((prev) => {
622
- const { [columnId]: _, ...next } = prev;
623
- return next;
624
- });
625
- this.onColumnPinned()?.(columnId, null);
626
- }
627
- /**
628
- * Check if a column is pinned and which side.
629
- */
630
- isPinned(columnId) {
631
- return this.pinnedOverrides()[columnId];
632
- }
633
- /**
634
- * Compute sticky left offsets for left-pinned columns.
635
- * Returns a map of columnId -> left offset in pixels.
636
- */
637
- computeLeftOffsets(visibleCols, columnWidths, defaultWidth, hasCheckboxColumn, checkboxColumnWidth) {
638
- const offsets = {};
639
- const pinned = this.pinnedOverrides();
640
- let left = hasCheckboxColumn ? checkboxColumnWidth : 0;
641
- for (const col of visibleCols) {
642
- if (pinned[col.columnId] === 'left') {
643
- offsets[col.columnId] = left;
644
- left += columnWidths[col.columnId] ?? defaultWidth;
645
- }
646
- }
647
- return offsets;
648
- }
649
- /**
650
- * Compute sticky right offsets for right-pinned columns.
651
- * Returns a map of columnId -> right offset in pixels.
652
- */
653
- computeRightOffsets(visibleCols, columnWidths, defaultWidth) {
654
- const offsets = {};
655
- const pinned = this.pinnedOverrides();
656
- let right = 0;
657
- for (let i = visibleCols.length - 1; i >= 0; i--) {
658
- const col = visibleCols[i];
659
- if (pinned[col.columnId] === 'right') {
660
- offsets[col.columnId] = right;
661
- right += columnWidths[col.columnId] ?? defaultWidth;
662
- }
663
- }
664
- return offsets;
665
- }
666
- getApi() {
667
- return {
668
- setRowData: (d) => {
669
- if (!this.isServerSide())
670
- this.internalData.set(d);
671
- },
672
- setLoading: (loading) => this.internalLoading.set(loading),
673
- getColumnState: () => ({
674
- visibleColumns: Array.from(this.visibleColumns()),
675
- sort: this.sort(),
676
- columnOrder: this.columnOrder() ?? undefined,
677
- columnWidths: Object.keys(this.columnWidthOverrides()).length > 0 ? this.columnWidthOverrides() : undefined,
678
- filters: Object.keys(this.filters()).length > 0 ? this.filters() : undefined,
679
- pinnedColumns: Object.keys(this.pinnedOverrides()).length > 0 ? this.pinnedOverrides() : undefined,
680
- }),
681
- applyColumnState: (state) => {
682
- if (state.visibleColumns)
683
- this.setVisibleColumns(new Set(state.visibleColumns));
684
- if (state.sort)
685
- this.setSort(state.sort);
686
- if (state.columnOrder)
687
- this.onColumnOrderChange()?.(state.columnOrder);
688
- if (state.columnWidths)
689
- this.columnWidthOverrides.set(state.columnWidths);
690
- if (state.filters)
691
- this.setFilters(state.filters);
692
- if (state.pinnedColumns)
693
- this.pinnedOverrides.set(state.pinnedColumns);
694
- },
695
- setFilterModel: (filters) => this.setFilters(filters),
696
- getSelectedRows: () => Array.from(this.effectiveSelectedRows()),
697
- setSelectedRows: (rowIds) => {
698
- if (this.selectedRows() === undefined)
699
- this.internalSelectedRows.set(new Set(rowIds));
700
- },
701
- selectAll: () => {
702
- const allIds = new Set(this.displayItems().map((item) => this.getRowId()(item)));
703
- if (this.selectedRows() === undefined)
704
- this.internalSelectedRows.set(allIds);
705
- this.onSelectionChange()?.({
706
- selectedRowIds: Array.from(allIds),
707
- selectedItems: this.displayItems(),
708
- });
709
- },
710
- deselectAll: () => {
711
- if (this.selectedRows() === undefined)
712
- this.internalSelectedRows.set(new Set());
713
- this.onSelectionChange()?.({ selectedRowIds: [], selectedItems: [] });
714
- },
715
- clearFilters: () => this.setFilters({}),
716
- clearSort: () => this.setSort({ field: this.defaultSortField(), direction: this.defaultSortDirection() }),
717
- resetGridState: (options) => {
718
- this.setFilters({});
719
- this.setSort({ field: this.defaultSortField(), direction: this.defaultSortDirection() });
720
- if (!options?.keepSelection) {
721
- if (this.selectedRows() === undefined)
722
- this.internalSelectedRows.set(new Set());
723
- this.onSelectionChange()?.({ selectedRowIds: [], selectedItems: [] });
724
- }
725
- },
726
- getDisplayedRows: () => this.displayItems(),
727
- refreshData: () => {
728
- if (this.isServerSide()) {
729
- this.refreshCounter.update((c) => c + 1);
730
- }
731
- },
732
- getColumnOrder: () => this.columnOrder() ?? this.columns().map((c) => c.columnId),
733
- setColumnOrder: (order) => {
734
- this.onColumnOrderChange()?.(order);
735
- },
736
- scrollToRow: (_index, _options) => {
737
- // Scrolling is handled by VirtualScrollService at the UI layer.
738
- // The UI component should wire this to VirtualScrollService.scrollToRow().
739
- },
740
- };
741
- }
742
- };
743
- OGridService = __decorate([
744
- Injectable()
745
- ], OGridService);
746
- export { OGridService };