@alaarab/ogrid-angular 2.0.23 → 2.1.0

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 (33) hide show
  1. package/dist/esm/components/base-column-header-filter.component.js +26 -11
  2. package/dist/esm/components/base-datagrid-table.component.js +64 -46
  3. package/dist/esm/components/base-ogrid.component.js +36 -0
  4. package/dist/esm/components/base-popover-cell-editor.component.js +6 -7
  5. package/dist/esm/components/empty-state.component.js +2 -2
  6. package/dist/esm/components/grid-context-menu.component.js +17 -17
  7. package/dist/esm/components/ogrid-layout.component.js +2 -49
  8. package/dist/esm/components/sidebar.component.js +2 -6
  9. package/dist/esm/components/status-bar.component.js +6 -2
  10. package/dist/esm/index.js +5 -1
  11. package/dist/esm/services/datagrid-editing.service.js +52 -0
  12. package/dist/esm/services/datagrid-interaction.service.js +667 -0
  13. package/dist/esm/services/datagrid-layout.service.js +151 -0
  14. package/dist/esm/services/datagrid-state.service.js +130 -865
  15. package/dist/esm/services/ogrid.service.js +61 -26
  16. package/dist/esm/utils/index.js +1 -1
  17. package/dist/esm/utils/latestRef.js +0 -5
  18. package/dist/types/components/base-column-header-filter.component.d.ts +2 -0
  19. package/dist/types/components/base-datagrid-table.component.d.ts +21 -5
  20. package/dist/types/components/base-ogrid.component.d.ts +10 -0
  21. package/dist/types/components/grid-context-menu.component.d.ts +5 -5
  22. package/dist/types/components/sidebar.component.d.ts +0 -2
  23. package/dist/types/components/status-bar.component.d.ts +4 -1
  24. package/dist/types/index.d.ts +5 -1
  25. package/dist/types/services/datagrid-editing.service.d.ts +31 -0
  26. package/dist/types/services/datagrid-interaction.service.d.ts +86 -0
  27. package/dist/types/services/datagrid-layout.service.d.ts +36 -0
  28. package/dist/types/services/datagrid-state.service.d.ts +20 -39
  29. package/dist/types/services/ogrid.service.d.ts +8 -3
  30. package/dist/types/types/dataGridTypes.d.ts +1 -1
  31. package/dist/types/utils/index.d.ts +1 -1
  32. package/dist/types/utils/latestRef.d.ts +0 -5
  33. package/package.json +10 -3
@@ -116,12 +116,14 @@ export class BaseColumnHeaderFilterComponent {
116
116
  }
117
117
  }
118
118
  handleTextApply() {
119
- this.onTextChange(this.tempTextValue());
119
+ if (this.onTextChange)
120
+ this.onTextChange(this.tempTextValue());
120
121
  this.isFilterOpen.set(false);
121
122
  }
122
123
  handleTextClear() {
123
124
  this.tempTextValue.set('');
124
- this.onTextChange('');
125
+ if (this.onTextChange)
126
+ this.onTextChange('');
125
127
  this.isFilterOpen.set(false);
126
128
  }
127
129
  // --- MultiSelect filter handlers ---
@@ -148,7 +150,8 @@ export class BaseColumnHeaderFilterComponent {
148
150
  this.tempSelected.set(new Set());
149
151
  }
150
152
  handleApplyMultiSelect() {
151
- this.onFilterChange(Array.from(this.tempSelected()));
153
+ if (this.onFilterChange)
154
+ this.onFilterChange(Array.from(this.tempSelected()));
152
155
  this.isFilterOpen.set(false);
153
156
  }
154
157
  // --- People filter handlers ---
@@ -180,31 +183,43 @@ export class BaseColumnHeaderFilterComponent {
180
183
  }, 300);
181
184
  }
182
185
  handleUserSelect(user) {
183
- this.onUserChange(user);
186
+ if (this.onUserChange)
187
+ this.onUserChange(user);
184
188
  this.isFilterOpen.set(false);
185
189
  }
186
190
  handleClearUser() {
187
- this.onUserChange(undefined);
191
+ if (this.onUserChange)
192
+ this.onUserChange(undefined);
188
193
  this.isFilterOpen.set(false);
189
194
  }
190
195
  // --- Date filter handlers ---
191
196
  handleDateApply() {
192
197
  const from = this.tempDateFrom();
193
198
  const to = this.tempDateTo();
194
- if (!from && !to) {
195
- this.onDateChange(undefined);
196
- }
197
- else {
198
- this.onDateChange({ from: from || undefined, to: to || undefined });
199
+ if (this.onDateChange) {
200
+ if (!from && !to) {
201
+ this.onDateChange(undefined);
202
+ }
203
+ else {
204
+ this.onDateChange({ from: from || undefined, to: to || undefined });
205
+ }
199
206
  }
200
207
  this.isFilterOpen.set(false);
201
208
  }
202
209
  handleDateClear() {
203
210
  this.tempDateFrom.set('');
204
211
  this.tempDateTo.set('');
205
- this.onDateChange(undefined);
212
+ if (this.onDateChange)
213
+ this.onDateChange(undefined);
206
214
  this.isFilterOpen.set(false);
207
215
  }
216
+ /** Clean up debounce timer on destroy. */
217
+ ngOnDestroy() {
218
+ if (this.peopleDebounceTimer) {
219
+ clearTimeout(this.peopleDebounceTimer);
220
+ this.peopleDebounceTimer = null;
221
+ }
222
+ }
208
223
  // --- Document click handler (for click-outside to close) ---
209
224
  onDocumentClick(event, selectorName) {
210
225
  const el = event.target;
@@ -1,4 +1,4 @@
1
- import { signal, computed, effect } from '@angular/core';
1
+ import { signal, computed, effect, inject } from '@angular/core';
2
2
  import { DataGridStateService } from '../services/datagrid-state.service';
3
3
  import { ColumnReorderService } from '../services/column-reorder.service';
4
4
  import { VirtualScrollService } from '../services/virtual-scroll.service';
@@ -15,13 +15,13 @@ import { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayConte
15
15
  */
16
16
  export class BaseDataGridTableComponent {
17
17
  constructor() {
18
- this.stateService = new DataGridStateService();
19
- this.columnReorderService = new ColumnReorderService();
20
- this.virtualScrollService = new VirtualScrollService();
18
+ this.stateService = inject(DataGridStateService);
19
+ this.columnReorderService = inject(ColumnReorderService);
20
+ this.virtualScrollService = inject(VirtualScrollService);
21
21
  this.lastMouseShift = false;
22
22
  this.columnSizingVersion = signal(0);
23
23
  /** Dirty flag — set when column layout changes, cleared after measurement. */
24
- this.measureDirty = true;
24
+ this.measureDirty = signal(true);
25
25
  /** DOM-measured column widths from the last layout pass.
26
26
  * Used as a minWidth floor to prevent columns from shrinking
27
27
  * when new data loads (e.g. server-side pagination). */
@@ -33,8 +33,16 @@ export class BaseDataGridTableComponent {
33
33
  this.tableContainerElSignal = signal(null);
34
34
  // --- Delegated state ---
35
35
  this.state = computed(() => this.stateService.getState());
36
+ // Intermediate computed signals — narrow slices of state() so leaf computeds
37
+ // only recompute when their specific sub-state changes.
38
+ this.layoutState = computed(() => this.state().layout);
39
+ this.rowSelectionState = computed(() => this.state().rowSelection);
40
+ this.editingState = computed(() => this.state().editing);
41
+ this.interactionState = computed(() => this.state().interaction);
42
+ this.contextMenuState = computed(() => this.state().contextMenu);
43
+ this.viewModelsState = computed(() => this.state().viewModels);
44
+ this.pinningState = computed(() => this.state().pinning);
36
45
  this.tableContainerEl = computed(() => this.tableContainerElSignal());
37
- this.allItems = computed(() => this.getProps()?.items ?? []);
38
46
  this.items = computed(() => this.getProps()?.items ?? []);
39
47
  this.getRowId = computed(() => this.getProps()?.getRowId ?? ((item) => item['id']));
40
48
  this.isLoading = computed(() => this.getProps()?.isLoading ?? false);
@@ -52,35 +60,35 @@ export class BaseDataGridTableComponent {
52
60
  this.rowNumberOffset = computed(() => this.hasRowNumbersCol() ? (this.currentPage() - 1) * this.pageSize() : 0);
53
61
  this.propsVisibleColumns = computed(() => this.getProps()?.visibleColumns);
54
62
  this.propsColumnOrder = computed(() => this.getProps()?.columnOrder);
55
- // State service outputs
56
- this.visibleCols = computed(() => this.state().layout.visibleCols);
57
- this.hasCheckboxCol = computed(() => this.state().layout.hasCheckboxCol);
58
- this.hasRowNumbersCol = computed(() => this.state().layout.hasRowNumbersCol);
59
- this.colOffset = computed(() => this.state().layout.colOffset);
60
- this.containerWidth = computed(() => this.state().layout.containerWidth);
61
- this.minTableWidth = computed(() => this.state().layout.minTableWidth);
62
- this.desiredTableWidth = computed(() => this.state().layout.desiredTableWidth);
63
- this.columnSizingOverrides = computed(() => this.state().layout.columnSizingOverrides);
64
- this.selectedRowIds = computed(() => this.state().rowSelection.selectedRowIds);
65
- this.allSelected = computed(() => this.state().rowSelection.allSelected);
66
- this.someSelected = computed(() => this.state().rowSelection.someSelected);
67
- this.editingCell = computed(() => this.state().editing.editingCell);
68
- this.pendingEditorValue = computed(() => this.state().editing.pendingEditorValue);
69
- this.activeCell = computed(() => this.state().interaction.activeCell);
70
- this.selectionRange = computed(() => this.state().interaction.selectionRange);
71
- this.hasCellSelection = computed(() => this.state().interaction.hasCellSelection);
72
- this.cutRange = computed(() => this.state().interaction.cutRange);
73
- this.copyRange = computed(() => this.state().interaction.copyRange);
74
- this.canUndo = computed(() => this.state().interaction.canUndo);
75
- this.canRedo = computed(() => this.state().interaction.canRedo);
76
- this.isDragging = computed(() => this.state().interaction.isDragging);
77
- this.menuPosition = computed(() => this.state().contextMenu.menuPosition);
78
- this.statusBarConfig = computed(() => this.state().viewModels.statusBarConfig);
79
- this.showEmptyInGrid = computed(() => this.state().viewModels.showEmptyInGrid);
80
- this.headerFilterInput = computed(() => this.state().viewModels.headerFilterInput);
81
- this.cellDescriptorInput = computed(() => this.state().viewModels.cellDescriptorInput);
63
+ // State service outputs — read from narrow intermediate signals
64
+ this.visibleCols = computed(() => this.layoutState().visibleCols);
65
+ this.hasCheckboxCol = computed(() => this.layoutState().hasCheckboxCol);
66
+ this.hasRowNumbersCol = computed(() => this.layoutState().hasRowNumbersCol);
67
+ this.colOffset = computed(() => this.layoutState().colOffset);
68
+ this.containerWidth = computed(() => this.layoutState().containerWidth);
69
+ this.minTableWidth = computed(() => this.layoutState().minTableWidth);
70
+ this.desiredTableWidth = computed(() => this.layoutState().desiredTableWidth);
71
+ this.columnSizingOverrides = computed(() => this.layoutState().columnSizingOverrides);
72
+ this.selectedRowIds = computed(() => this.rowSelectionState().selectedRowIds);
73
+ this.allSelected = computed(() => this.rowSelectionState().allSelected);
74
+ this.someSelected = computed(() => this.rowSelectionState().someSelected);
75
+ this.editingCell = computed(() => this.editingState().editingCell);
76
+ this.pendingEditorValue = computed(() => this.editingState().pendingEditorValue);
77
+ this.activeCell = computed(() => this.interactionState().activeCell);
78
+ this.selectionRange = computed(() => this.interactionState().selectionRange);
79
+ this.hasCellSelection = computed(() => this.interactionState().hasCellSelection);
80
+ this.cutRange = computed(() => this.interactionState().cutRange);
81
+ this.copyRange = computed(() => this.interactionState().copyRange);
82
+ this.canUndo = computed(() => this.interactionState().canUndo);
83
+ this.canRedo = computed(() => this.interactionState().canRedo);
84
+ this.isDragging = computed(() => this.interactionState().isDragging);
85
+ this.menuPosition = computed(() => this.contextMenuState().menuPosition);
86
+ this.statusBarConfig = computed(() => this.viewModelsState().statusBarConfig);
87
+ this.showEmptyInGrid = computed(() => this.viewModelsState().showEmptyInGrid);
88
+ this.headerFilterInput = computed(() => this.viewModelsState().headerFilterInput);
89
+ this.cellDescriptorInput = computed(() => this.viewModelsState().cellDescriptorInput);
82
90
  // Pinning state
83
- this.pinnedColumnsMap = computed(() => this.state().pinning.pinnedColumns);
91
+ this.pinnedColumnsMap = computed(() => this.pinningState().pinnedColumns);
84
92
  // Virtual scrolling
85
93
  this.vsEnabled = computed(() => this.virtualScrollService.isActive());
86
94
  this.vsVisibleRange = computed(() => this.virtualScrollService.visibleRange());
@@ -95,7 +103,7 @@ export class BaseDataGridTableComponent {
95
103
  return this.vsVisibleRange().offsetBottom;
96
104
  });
97
105
  this.vsVisibleItems = computed(() => {
98
- const items = this.allItems();
106
+ const items = this.items();
99
107
  if (!this.vsEnabled())
100
108
  return items;
101
109
  const range = this.vsVisibleRange();
@@ -107,8 +115,8 @@ export class BaseDataGridTableComponent {
107
115
  return this.vsVisibleRange().startIndex;
108
116
  });
109
117
  // Popover editing
110
- this.popoverAnchorEl = computed(() => this.state().editing.popoverAnchorEl);
111
- this.pendingEditorValueForPopover = computed(() => this.state().editing.pendingEditorValue);
118
+ this.popoverAnchorEl = computed(() => this.editingState().popoverAnchorEl);
119
+ this.pendingEditorValueForPopover = computed(() => this.editingState().pendingEditorValue);
112
120
  this.allowOverflowX = computed(() => {
113
121
  const p = this.getProps();
114
122
  if (p?.suppressHorizontalScroll)
@@ -209,8 +217,8 @@ export class BaseDataGridTableComponent {
209
217
  }
210
218
  /** Lifecycle hook — re-measure column widths only when layout changed */
211
219
  ngAfterViewChecked() {
212
- if (this.measureDirty) {
213
- this.measureDirty = false;
220
+ if (this.measureDirty()) {
221
+ this.measureDirty.set(false);
214
222
  this.measureColumnWidths();
215
223
  }
216
224
  }
@@ -245,8 +253,18 @@ export class BaseDataGridTableComponent {
245
253
  }
246
254
  }
247
255
  /**
248
- * Initialize base wiring effects. Must be called from subclass constructor
249
- * (effects need to run inside an injection context).
256
+ * Initialize base wiring effects. Must be called from subclass constructor.
257
+ *
258
+ * **Timing:** Angular requires `effect()` to be created inside an injection
259
+ * context (constructor or field initializer). On the first run, signals like
260
+ * `wrapperElSignal()` return `null` because the DOM hasn't been created yet.
261
+ * After `ngAfterViewInit` sets these signals, Angular's signal graph
262
+ * automatically re-runs each effect. The null guards inside each effect body
263
+ * ensure the first (null) run is a safe no-op.
264
+ *
265
+ * Sequence:
266
+ * 1. Constructor → `initBase()` → effects created, first run (signals null → no-ops)
267
+ * 2. `ngAfterViewInit` → `wrapperElSignal.set(el)` → effects re-run with real values
250
268
  */
251
269
  initBase() {
252
270
  // Wire props to state service
@@ -280,7 +298,7 @@ export class BaseDataGridTableComponent {
280
298
  this.visibleCols();
281
299
  this.columnSizingOverrides();
282
300
  this.columnSizingVersion();
283
- this.measureDirty = true;
301
+ this.measureDirty.set(true);
284
302
  });
285
303
  // Wire virtual scroll service inputs
286
304
  effect(() => {
@@ -468,7 +486,7 @@ export class BaseDataGridTableComponent {
468
486
  this.state().interaction.handleCellMouseDown(event, rowIndex, globalColIndex);
469
487
  }
470
488
  onCellClick(rowIndex, globalColIndex) {
471
- this.state().interaction.setActiveCell({ rowIndex, columnIndex: globalColIndex });
489
+ this.state().interaction.setActiveCell?.({ rowIndex, columnIndex: globalColIndex });
472
490
  }
473
491
  onCellContextMenu(event) {
474
492
  this.state().contextMenu.handleCellContextMenu(event);
@@ -477,13 +495,13 @@ export class BaseDataGridTableComponent {
477
495
  this.state().editing.setEditingCell({ rowId, columnId });
478
496
  }
479
497
  onFillHandleMouseDown(event) {
480
- this.state().interaction.handleFillHandleMouseDown(event);
498
+ this.state().interaction.handleFillHandleMouseDown?.(event);
481
499
  }
482
500
  onResizeStart(event, col) {
483
501
  event.preventDefault();
484
502
  // Clear cell selection before resize (like React) so selection outlines don't persist during drag
485
- this.state().interaction.setActiveCell(null);
486
- this.state().interaction.setSelectionRange(null);
503
+ this.state().interaction.setActiveCell?.(null);
504
+ this.state().interaction.setSelectionRange?.(null);
487
505
  this.getWrapperRef()?.nativeElement.focus({ preventScroll: true });
488
506
  const startX = event.clientX;
489
507
  const startWidth = this.getColumnWidth(col);
@@ -0,0 +1,36 @@
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
+ /**
8
+ * Base class for OGrid top-level components (Material, Radix).
9
+ * Contains all shared TypeScript logic. Subclasses provide a @Component
10
+ * decorator with their own selector, template, and imports.
11
+ */
12
+ import { Input, signal, effect, inject } from '@angular/core';
13
+ import { OGridService } from '../services/ogrid.service';
14
+ export class BaseOGridComponent {
15
+ set props(value) {
16
+ this.propsSignal.set(value);
17
+ }
18
+ constructor() {
19
+ this.propsSignal = signal(undefined);
20
+ this.ogridService = inject(OGridService);
21
+ effect(() => {
22
+ const p = this.propsSignal();
23
+ if (p)
24
+ this.ogridService.configure(p);
25
+ });
26
+ }
27
+ get showToolbar() {
28
+ return this.ogridService.columnChooserPlacement() === 'toolbar' || this.ogridService.toolbar() != null;
29
+ }
30
+ onPageSizeChange(size) {
31
+ this.ogridService.pagination().setPageSize(size);
32
+ }
33
+ }
34
+ __decorate([
35
+ Input({ required: true })
36
+ ], BaseOGridComponent.prototype, "props", null);
@@ -59,27 +59,26 @@ export class BasePopoverCellEditorComponent {
59
59
  setTimeout(() => this.showEditor.set(true), 0);
60
60
  }
61
61
  });
62
- // Render custom editor component when container is available
63
- effect(() => {
62
+ // Render custom editor component when container is available.
63
+ // Angular's effect() ignores return values — use onCleanup() for cleanup.
64
+ effect((onCleanup) => {
64
65
  const container = this.editorContainerRef;
65
66
  const props = this.editorProps;
66
67
  const col = this.column;
67
68
  if (!container || !this.showEditor() || typeof col.cellEditor !== 'function')
68
69
  return;
69
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
- const EditorComponent = col.cellEditor; // ComponentType
70
+ const EditorComponent = col.cellEditor;
71
71
  const componentRef = createComponent(EditorComponent, {
72
72
  environmentInjector: this.envInjector,
73
73
  elementInjector: this.injector,
74
74
  });
75
75
  // Pass props to component instance
76
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
76
  Object.assign(componentRef.instance, props);
78
77
  componentRef.changeDetectorRef.detectChanges();
79
78
  // Append to DOM
80
79
  container.nativeElement.appendChild(componentRef.location.nativeElement);
81
- // Cleanup on destroy
82
- return () => componentRef.destroy();
80
+ // Cleanup when effect re-runs or component is destroyed
81
+ onCleanup(() => componentRef.destroy());
83
82
  });
84
83
  }
85
84
  handleOverlayClick() {
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
8
- import { CommonModule } from '@angular/common';
8
+ import { NgTemplateOutlet } from '@angular/common';
9
9
  let EmptyStateComponent = class EmptyStateComponent {
10
10
  constructor() {
11
11
  this.message = undefined;
@@ -31,7 +31,7 @@ EmptyStateComponent = __decorate([
31
31
  selector: 'ogrid-empty-state',
32
32
  standalone: true,
33
33
  changeDetection: ChangeDetectionStrategy.OnPush,
34
- imports: [CommonModule],
34
+ imports: [NgTemplateOutlet],
35
35
  styles: [`
36
36
  .ogrid-empty-state-clear-btn {
37
37
  background: none; border: none; color: inherit;
@@ -13,23 +13,23 @@ let GridContextMenuComponent = class GridContextMenuComponent {
13
13
  this.canUndoProp = false;
14
14
  this.canRedoProp = false;
15
15
  this.classNames = undefined;
16
- this.copy = new EventEmitter();
17
- this.cut = new EventEmitter();
18
- this.paste = new EventEmitter();
19
- this.selectAll = new EventEmitter();
16
+ this.copyAction = new EventEmitter();
17
+ this.cutAction = new EventEmitter();
18
+ this.pasteAction = new EventEmitter();
19
+ this.selectAllAction = new EventEmitter();
20
20
  this.undoAction = new EventEmitter();
21
21
  this.redoAction = new EventEmitter();
22
- this.close = new EventEmitter();
22
+ this.closeAction = new EventEmitter();
23
23
  this.menuItems = GRID_CONTEXT_MENU_ITEMS;
24
24
  this.formatShortcutFn = formatShortcut;
25
25
  this.clickOutsideHandler = (e) => {
26
26
  const el = this.menuRef?.nativeElement;
27
27
  if (el && !el.contains(e.target))
28
- this.close.emit();
28
+ this.closeAction.emit();
29
29
  };
30
30
  this.keyDownHandler = (e) => {
31
31
  if (e.key === 'Escape')
32
- this.close.emit();
32
+ this.closeAction.emit();
33
33
  };
34
34
  // Register listeners once on init (no signal dependencies needed)
35
35
  document.addEventListener('mousedown', this.clickOutsideHandler, true);
@@ -51,16 +51,16 @@ let GridContextMenuComponent = class GridContextMenuComponent {
51
51
  onItemClick(id) {
52
52
  switch (id) {
53
53
  case 'copy':
54
- this.copy.emit();
54
+ this.copyAction.emit();
55
55
  break;
56
56
  case 'cut':
57
- this.cut.emit();
57
+ this.cutAction.emit();
58
58
  break;
59
59
  case 'paste':
60
- this.paste.emit();
60
+ this.pasteAction.emit();
61
61
  break;
62
62
  case 'selectAll':
63
- this.selectAll.emit();
63
+ this.selectAllAction.emit();
64
64
  break;
65
65
  case 'undo':
66
66
  this.undoAction.emit();
@@ -69,7 +69,7 @@ let GridContextMenuComponent = class GridContextMenuComponent {
69
69
  this.redoAction.emit();
70
70
  break;
71
71
  }
72
- this.close.emit();
72
+ this.closeAction.emit();
73
73
  }
74
74
  };
75
75
  __decorate([
@@ -92,16 +92,16 @@ __decorate([
92
92
  ], GridContextMenuComponent.prototype, "classNames", void 0);
93
93
  __decorate([
94
94
  Output()
95
- ], GridContextMenuComponent.prototype, "copy", void 0);
95
+ ], GridContextMenuComponent.prototype, "copyAction", void 0);
96
96
  __decorate([
97
97
  Output()
98
- ], GridContextMenuComponent.prototype, "cut", void 0);
98
+ ], GridContextMenuComponent.prototype, "cutAction", void 0);
99
99
  __decorate([
100
100
  Output()
101
- ], GridContextMenuComponent.prototype, "paste", void 0);
101
+ ], GridContextMenuComponent.prototype, "pasteAction", void 0);
102
102
  __decorate([
103
103
  Output()
104
- ], GridContextMenuComponent.prototype, "selectAll", void 0);
104
+ ], GridContextMenuComponent.prototype, "selectAllAction", void 0);
105
105
  __decorate([
106
106
  Output()
107
107
  ], GridContextMenuComponent.prototype, "undoAction", void 0);
@@ -110,7 +110,7 @@ __decorate([
110
110
  ], GridContextMenuComponent.prototype, "redoAction", void 0);
111
111
  __decorate([
112
112
  Output()
113
- ], GridContextMenuComponent.prototype, "close", void 0);
113
+ ], GridContextMenuComponent.prototype, "closeAction", void 0);
114
114
  __decorate([
115
115
  ViewChild('menuRef')
116
116
  ], GridContextMenuComponent.prototype, "menuRef", void 0);
@@ -7,6 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { Component, Input, ViewEncapsulation, ChangeDetectionStrategy } from '@angular/core';
8
8
  import { SideBarComponent } from './sidebar.component';
9
9
  import { GRID_BORDER_RADIUS } from '@alaarab/ogrid-core';
10
+ import { OGRID_THEME_VARS_CSS } from '../styles/ogrid-theme-vars';
10
11
  let OGridLayoutComponent = class OGridLayoutComponent {
11
12
  constructor() {
12
13
  this.hasToolbar = false;
@@ -38,55 +39,7 @@ OGridLayoutComponent = __decorate([
38
39
  encapsulation: ViewEncapsulation.None,
39
40
  changeDetection: ChangeDetectionStrategy.OnPush,
40
41
  imports: [SideBarComponent],
41
- styles: [`
42
- /* ─── OGrid Theme Variables ─── */
43
- :root {
44
- --ogrid-bg: #ffffff;
45
- --ogrid-fg: rgba(0, 0, 0, 0.87);
46
- --ogrid-fg-secondary: rgba(0, 0, 0, 0.6);
47
- --ogrid-fg-muted: rgba(0, 0, 0, 0.5);
48
- --ogrid-border: rgba(0, 0, 0, 0.12);
49
- --ogrid-header-bg: rgba(0, 0, 0, 0.04);
50
- --ogrid-hover-bg: rgba(0, 0, 0, 0.04);
51
- --ogrid-selected-row-bg: #e6f0fb;
52
- --ogrid-active-cell-bg: rgba(0, 0, 0, 0.02);
53
- --ogrid-range-bg: rgba(33, 115, 70, 0.12);
54
- --ogrid-accent: #0078d4;
55
- --ogrid-selection-color: #217346;
56
- --ogrid-loading-overlay: rgba(255, 255, 255, 0.7);
57
- }
58
- @media (prefers-color-scheme: dark) {
59
- :root:not([data-theme="light"]) {
60
- --ogrid-bg: #1e1e1e;
61
- --ogrid-fg: rgba(255, 255, 255, 0.87);
62
- --ogrid-fg-secondary: rgba(255, 255, 255, 0.6);
63
- --ogrid-fg-muted: rgba(255, 255, 255, 0.5);
64
- --ogrid-border: rgba(255, 255, 255, 0.12);
65
- --ogrid-header-bg: rgba(255, 255, 255, 0.06);
66
- --ogrid-hover-bg: rgba(255, 255, 255, 0.08);
67
- --ogrid-selected-row-bg: #1a3a5c;
68
- --ogrid-active-cell-bg: rgba(255, 255, 255, 0.06);
69
- --ogrid-range-bg: rgba(46, 160, 67, 0.15);
70
- --ogrid-accent: #4da6ff;
71
- --ogrid-selection-color: #2ea043;
72
- --ogrid-loading-overlay: rgba(0, 0, 0, 0.7);
73
- }
74
- }
75
- [data-theme="dark"] {
76
- --ogrid-bg: #1e1e1e;
77
- --ogrid-fg: rgba(255, 255, 255, 0.87);
78
- --ogrid-fg-secondary: rgba(255, 255, 255, 0.6);
79
- --ogrid-fg-muted: rgba(255, 255, 255, 0.5);
80
- --ogrid-border: rgba(255, 255, 255, 0.12);
81
- --ogrid-header-bg: rgba(255, 255, 255, 0.06);
82
- --ogrid-hover-bg: rgba(255, 255, 255, 0.08);
83
- --ogrid-selected-row-bg: #1a3a5c;
84
- --ogrid-active-cell-bg: rgba(255, 255, 255, 0.06);
85
- --ogrid-range-bg: rgba(46, 160, 67, 0.15);
86
- --ogrid-accent: #4da6ff;
87
- --ogrid-selection-color: #2ea043;
88
- --ogrid-loading-overlay: rgba(0, 0, 0, 0.7);
89
- }
42
+ styles: [OGRID_THEME_VARS_CSS, `
90
43
  :host { display: block; height: 100%; }
91
44
  .ogrid-layout-root { display: flex; flex-direction: column; height: 100%; }
92
45
  .ogrid-layout-container {
@@ -5,16 +5,12 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
8
- import { CommonModule } from '@angular/common';
9
- const PANEL_WIDTH = 240;
10
- const TAB_WIDTH = 36;
8
+ import { NgTemplateOutlet } from '@angular/common';
11
9
  const PANEL_LABELS = { columns: 'Columns', filters: 'Filters' };
12
10
  let SideBarComponent = class SideBarComponent {
13
11
  constructor() {
14
12
  this.sideBarProps = null;
15
13
  this.panelLabels = PANEL_LABELS;
16
- this.tabWidth = TAB_WIDTH;
17
- this.panelWidth = PANEL_WIDTH;
18
14
  }
19
15
  onTabClick(panel) {
20
16
  const props = this.sideBarProps;
@@ -101,7 +97,7 @@ SideBarComponent = __decorate([
101
97
  selector: 'ogrid-sidebar',
102
98
  standalone: true,
103
99
  changeDetection: ChangeDetectionStrategy.OnPush,
104
- imports: [CommonModule],
100
+ imports: [NgTemplateOutlet],
105
101
  styles: [`
106
102
  .ogrid-sidebar-root { display: flex; flex-direction: row; flex-shrink: 0; }
107
103
  .ogrid-sidebar-tab-strip {
@@ -14,9 +14,10 @@ let StatusBarComponent = class StatusBarComponent {
14
14
  this.aggregation = undefined;
15
15
  this.suppressRowCount = undefined;
16
16
  this.classNames = undefined;
17
+ this.cachedParts = [];
17
18
  }
18
- getParts() {
19
- return getStatusBarParts({
19
+ ngOnChanges() {
20
+ this.cachedParts = getStatusBarParts({
20
21
  totalCount: this.totalCount,
21
22
  filteredCount: this.filteredCount,
22
23
  selectedCount: this.selectedCount,
@@ -25,6 +26,9 @@ let StatusBarComponent = class StatusBarComponent {
25
26
  suppressRowCount: this.suppressRowCount,
26
27
  });
27
28
  }
29
+ getParts() {
30
+ return this.cachedParts;
31
+ }
28
32
  };
29
33
  __decorate([
30
34
  Input({ required: true })
package/dist/esm/index.js CHANGED
@@ -6,6 +6,9 @@ export { toUserLike, isInSelectionRange, normalizeSelectionRange } from './types
6
6
  // Services
7
7
  export { OGridService } from './services/ogrid.service';
8
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';
9
12
  export { ColumnReorderService } from './services/column-reorder.service';
10
13
  export { VirtualScrollService } from './services/virtual-scroll.service';
11
14
  // Components
@@ -15,6 +18,7 @@ export { GridContextMenuComponent } from './components/grid-context-menu.compone
15
18
  export { SideBarComponent } from './components/sidebar.component';
16
19
  export { MarchingAntsOverlayComponent } from './components/marching-ants-overlay.component';
17
20
  export { EmptyStateComponent } from './components/empty-state.component';
21
+ export { BaseOGridComponent } from './components/base-ogrid.component';
18
22
  export { BaseDataGridTableComponent } from './components/base-datagrid-table.component';
19
23
  export { BaseColumnHeaderFilterComponent } from './components/base-column-header-filter.component';
20
24
  export { BaseColumnChooserComponent } from './components/base-column-chooser.component';
@@ -24,4 +28,4 @@ export { INLINE_CELL_EDITOR_TEMPLATE, INLINE_CELL_EDITOR_STYLES } from './compon
24
28
  export { BasePopoverCellEditorComponent, POPOVER_CELL_EDITOR_TEMPLATE, POPOVER_CELL_EDITOR_OVERLAY_STYLES } from './components/base-popover-cell-editor.component';
25
29
  // Shared styles
26
30
  export { OGRID_THEME_VARS_CSS } from './styles/ogrid-theme-vars';
27
- export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, createDebouncedSignal, createDebouncedCallback, debounce, createLatestRef, createLatestCallback, } from './utils';
31
+ export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, createDebouncedSignal, createDebouncedCallback, debounce, createLatestCallback, } from './utils';
@@ -0,0 +1,52 @@
1
+ import { signal } from '@angular/core';
2
+ import { parseValue, } from '@alaarab/ogrid-core';
3
+ /**
4
+ * Manages cell editing state, inline/popover editor, and commit/cancel logic.
5
+ * Extracted from DataGridStateService for modularity.
6
+ *
7
+ * Not @Injectable — instantiated and owned by DataGridStateService.
8
+ */
9
+ export class DataGridEditingHelper {
10
+ constructor(getVisibleCols, getItems, getWrappedOnCellValueChanged, setActiveCellFn) {
11
+ this.editingCellSig = signal(null);
12
+ this.pendingEditorValueSig = signal(undefined);
13
+ this.popoverAnchorElSig = signal(null);
14
+ this.getVisibleCols = getVisibleCols;
15
+ this.getItems = getItems;
16
+ this.getWrappedOnCellValueChanged = getWrappedOnCellValueChanged;
17
+ this.setActiveCellFn = setActiveCellFn;
18
+ }
19
+ setEditingCell(cell) {
20
+ this.editingCellSig.set(cell);
21
+ }
22
+ setPendingEditorValue(value) {
23
+ this.pendingEditorValueSig.set(value);
24
+ }
25
+ commitCellEdit(item, columnId, oldValue, newValue, rowIndex, globalColIndex) {
26
+ const col = this.getVisibleCols().find((c) => c.columnId === columnId);
27
+ if (col) {
28
+ const result = parseValue(newValue, oldValue, item, col);
29
+ if (!result.valid) {
30
+ this.editingCellSig.set(null);
31
+ this.popoverAnchorElSig.set(null);
32
+ this.pendingEditorValueSig.set(undefined);
33
+ return;
34
+ }
35
+ newValue = result.value;
36
+ }
37
+ const onCellValueChanged = this.getWrappedOnCellValueChanged();
38
+ onCellValueChanged?.({ item, columnId, oldValue, newValue, rowIndex });
39
+ this.editingCellSig.set(null);
40
+ this.popoverAnchorElSig.set(null);
41
+ this.pendingEditorValueSig.set(undefined);
42
+ const items = this.getItems();
43
+ if (rowIndex < items.length - 1) {
44
+ this.setActiveCellFn({ rowIndex: rowIndex + 1, columnIndex: globalColIndex });
45
+ }
46
+ }
47
+ cancelPopoverEdit() {
48
+ this.editingCellSig.set(null);
49
+ this.popoverAnchorElSig.set(null);
50
+ this.pendingEditorValueSig.set(undefined);
51
+ }
52
+ }