@alaarab/ogrid-angular 2.2.0 → 2.4.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.
package/dist/esm/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { flattenColumns, getMultiSelectFilterFields, deriveFilterOptionsFromData, processClientSideData, validateColumns, processClientSideDataAsync, validateRowIds, computeNextSortState, mergeFilter, CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, parseValue, UndoRedoStack, rangesEqual, normalizeSelectionRange, formatSelectionAsTsv, parseTsvClipboard, getCellValue, computeTabNavigation, applyFillValues, computeAggregations, getDataGridStatusBarConfig, computeVisibleRange, computeTotalHeight, computeVisibleColumnRange, getScrollTopForRow, validateVirtualScrollConfig, GRID_BORDER_RADIUS, getStatusBarParts, GRID_CONTEXT_MENU_ITEMS, formatShortcut, injectGlobalStyles, partitionColumnsForVirtualization, buildHeaderRows, getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildPopoverEditorProps, measureColumnContentWidth, getPaginationViewModel, ROW_NUMBER_COLUMN_WIDTH, reorderColumnArray, findCtrlArrowTarget, measureRange } from '@alaarab/ogrid-core';
1
+ import { handleFormulaBarKeyDown, FormulaEngine, createGridDataAccessor, columnLetterToIndex, flattenColumns, getMultiSelectFilterFields, deriveFilterOptionsFromData, processClientSideData, deriveFormulaBarText, extractFormulaReferences, validateColumns, processClientSideDataAsync, validateRowIds, computeNextSortState, mergeFilter, CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, parseValue, UndoRedoStack, rangesEqual, normalizeSelectionRange, formatSelectionAsTsv, parseTsvClipboard, getCellValue, applyCellDeletion, getScrollTopForRow, computeTabNavigation, applyFillValues, computeAggregations, getDataGridStatusBarConfig, computeVisibleRange, computeTotalHeight, computeVisibleColumnRange, validateVirtualScrollConfig, GRID_BORDER_RADIUS, getStatusBarParts, GRID_CONTEXT_MENU_ITEMS, formatShortcut, injectGlobalStyles, partitionColumnsForVirtualization, buildHeaderRows, getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildPopoverEditorProps, measureColumnContentWidth, getPaginationViewModel, getColumnHeaderMenuItems, ROW_NUMBER_COLUMN_WIDTH, reorderColumnArray, findCtrlArrowTarget, measureRange, FORMULA_REF_COLORS } from '@alaarab/ogrid-core';
2
2
  export * from '@alaarab/ogrid-core';
3
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';
4
+ import { Injectable, Input, Component, ChangeDetectionStrategy, ViewEncapsulation, Output, ViewChild, input, output, viewChild, effect, inject, DestroyRef, signal, computed, NgZone, EventEmitter, Injector, EnvironmentInjector, createComponent } from '@angular/core';
5
5
  import { NgTemplateOutlet } from '@angular/common';
6
6
 
7
7
  var __defProp = Object.defineProperty;
@@ -14,6 +14,201 @@ var __decorateClass = (decorators, target, key, kind) => {
14
14
  if (kind && result) __defProp(target, key, result);
15
15
  return result;
16
16
  };
17
+ var FormulaEngineService = class {
18
+ constructor() {
19
+ this.destroyRef = inject(DestroyRef);
20
+ // --- Internal state ---
21
+ this.engine = null;
22
+ this.initialLoaded = false;
23
+ // --- Data references (updated via configure or setData) ---
24
+ this.items = [];
25
+ this.flatColumns = [];
26
+ // --- Signals ---
27
+ /** Whether formula support is currently enabled. */
28
+ this.enabled = signal(false);
29
+ /** Last recalculation result, for UI to react to formula changes. */
30
+ this.lastRecalcResult = signal(null);
31
+ /** Number of formulas currently registered. */
32
+ this.formulaCount = computed(() => {
33
+ this.lastRecalcResult();
34
+ return this.engine?.getAllFormulas().length ?? 0;
35
+ });
36
+ this.destroyRef.onDestroy(() => {
37
+ this.engine?.clear();
38
+ this.engine = null;
39
+ });
40
+ }
41
+ /**
42
+ * Configure the formula engine. Call this when the grid component initializes.
43
+ *
44
+ * Lazily creates the FormulaEngine only when `formulas: true`.
45
+ */
46
+ configure(options) {
47
+ const { formulas, initialFormulas, formulaFunctions, onFormulaRecalc, namedRanges, sheets } = options;
48
+ this.onFormulaRecalcFn = onFormulaRecalc;
49
+ if (formulas && !this.engine) {
50
+ this.engine = new FormulaEngine({
51
+ customFunctions: formulaFunctions,
52
+ namedRanges
53
+ });
54
+ if (sheets) {
55
+ for (const [name, accessor] of Object.entries(sheets)) {
56
+ this.engine.registerSheet(name, accessor);
57
+ }
58
+ }
59
+ this.enabled.set(true);
60
+ } else if (!formulas && this.engine) {
61
+ this.engine.clear();
62
+ this.engine = null;
63
+ this.enabled.set(false);
64
+ this.lastRecalcResult.set(null);
65
+ this.initialLoaded = false;
66
+ }
67
+ if (formulas && this.engine && initialFormulas && !this.initialLoaded) {
68
+ this.initialLoaded = true;
69
+ const accessor = this.createAccessor();
70
+ const result = this.engine.loadFormulas(initialFormulas, accessor);
71
+ if (result.updatedCells.length > 0) {
72
+ this.lastRecalcResult.set(result);
73
+ this.onFormulaRecalcFn?.(result);
74
+ }
75
+ }
76
+ }
77
+ /**
78
+ * Update the data references used by the accessor bridge.
79
+ * Call this whenever the grid's items or columns change.
80
+ */
81
+ setData(items, flatColumns) {
82
+ this.items = items;
83
+ this.flatColumns = flatColumns;
84
+ }
85
+ /**
86
+ * Set or clear a formula for a cell. Triggers recalculation of dependents.
87
+ */
88
+ setFormula(col, row, formula, accessor) {
89
+ if (!this.engine) return;
90
+ const acc = accessor ?? this.createAccessor();
91
+ const result = this.engine.setFormula(col, row, formula, acc);
92
+ if (result.updatedCells.length > 0) {
93
+ this.lastRecalcResult.set(result);
94
+ this.onFormulaRecalcFn?.(result);
95
+ }
96
+ }
97
+ /**
98
+ * Notify the engine that a non-formula cell's value changed.
99
+ * Triggers recalculation of any formulas that depend on this cell.
100
+ */
101
+ onCellChanged(col, row, accessor) {
102
+ if (!this.engine) return;
103
+ const acc = accessor ?? this.createAccessor();
104
+ const result = this.engine.onCellChanged(col, row, acc);
105
+ if (result.updatedCells.length > 0) {
106
+ this.lastRecalcResult.set(result);
107
+ this.onFormulaRecalcFn?.(result);
108
+ }
109
+ }
110
+ /**
111
+ * Get the formula engine's computed value for a cell coordinate.
112
+ */
113
+ getValue(col, row) {
114
+ return this.engine?.getValue(col, row);
115
+ }
116
+ /**
117
+ * Check if a cell has a formula.
118
+ */
119
+ hasFormula(col, row) {
120
+ return this.engine?.hasFormula(col, row) ?? false;
121
+ }
122
+ /**
123
+ * Get the formula string for a cell.
124
+ */
125
+ getFormula(col, row) {
126
+ return this.engine?.getFormula(col, row);
127
+ }
128
+ /**
129
+ * Trigger a full recalculation of all formulas.
130
+ */
131
+ recalcAll(accessor) {
132
+ if (!this.engine) return;
133
+ const acc = accessor ?? this.createAccessor();
134
+ const result = this.engine.recalcAll(acc);
135
+ if (result.updatedCells.length > 0) {
136
+ this.lastRecalcResult.set(result);
137
+ this.onFormulaRecalcFn?.(result);
138
+ }
139
+ }
140
+ /**
141
+ * Get all formulas for serialization.
142
+ */
143
+ getAllFormulas() {
144
+ return this.engine?.getAllFormulas() ?? [];
145
+ }
146
+ /**
147
+ * Register a custom function at runtime.
148
+ */
149
+ registerFunction(name, fn) {
150
+ this.engine?.registerFunction(name, fn);
151
+ }
152
+ /**
153
+ * Clear all formulas and cached values.
154
+ */
155
+ clear() {
156
+ this.engine?.clear();
157
+ this.lastRecalcResult.set(null);
158
+ }
159
+ /**
160
+ * Define a named range.
161
+ */
162
+ defineNamedRange(name, ref) {
163
+ this.engine?.defineNamedRange(name, ref);
164
+ }
165
+ /**
166
+ * Remove a named range.
167
+ */
168
+ removeNamedRange(name) {
169
+ this.engine?.removeNamedRange(name);
170
+ }
171
+ /**
172
+ * Register a sheet accessor for cross-sheet references.
173
+ */
174
+ registerSheet(name, accessor) {
175
+ this.engine?.registerSheet(name, accessor);
176
+ }
177
+ /**
178
+ * Unregister a sheet accessor.
179
+ */
180
+ unregisterSheet(name) {
181
+ this.engine?.unregisterSheet(name);
182
+ }
183
+ /**
184
+ * Get all cells that a cell depends on (deep, transitive).
185
+ */
186
+ getPrecedents(col, row) {
187
+ return this.engine?.getPrecedents(col, row) ?? [];
188
+ }
189
+ /**
190
+ * Get all cells that depend on a cell (deep, transitive).
191
+ */
192
+ getDependents(col, row) {
193
+ return this.engine?.getDependents(col, row) ?? [];
194
+ }
195
+ /**
196
+ * Get full audit trail for a cell.
197
+ */
198
+ getAuditTrail(col, row) {
199
+ return this.engine?.getAuditTrail(col, row) ?? null;
200
+ }
201
+ // --- Private helpers ---
202
+ /** Create a data accessor that bridges grid data to formula coordinates. */
203
+ createAccessor() {
204
+ return createGridDataAccessor(this.items, this.flatColumns);
205
+ }
206
+ };
207
+ FormulaEngineService = __decorateClass([
208
+ Injectable()
209
+ ], FormulaEngineService);
210
+
211
+ // src/services/ogrid.service.ts
17
212
  var DEFAULT_PAGE_SIZE = 25;
18
213
  var EMPTY_LOADING_OPTIONS = {};
19
214
  var DEFAULT_PANELS = ["columns", "filters"];
@@ -76,6 +271,52 @@ var OGridService = class {
76
271
  this.ariaLabel = signal(void 0);
77
272
  this.ariaLabelledBy = signal(void 0);
78
273
  this.workerSort = signal(false);
274
+ this.showRowNumbers = signal(false);
275
+ this.cellReferences = signal(false);
276
+ this.formulasEnabled = signal(false);
277
+ this.initialFormulas = signal(void 0);
278
+ this.onFormulaRecalc = signal(void 0);
279
+ this.formulaFunctions = signal(void 0);
280
+ this.namedRanges = signal(void 0);
281
+ this.sheets = signal(void 0);
282
+ this.sheetDefs = signal(void 0);
283
+ this.activeSheet = signal(void 0);
284
+ this.onSheetChange = signal(void 0);
285
+ this.onSheetAdd = signal(void 0);
286
+ /** Active cell reference string (e.g. 'A1') updated by DataGridTable when cellReferences is enabled. */
287
+ this.activeCellRef = signal(null);
288
+ /** Active cell coordinates (0-based col/row). */
289
+ this.activeCellCoords = signal(null);
290
+ /** Stable callback passed to DataGridTable to update activeCellRef + coords. */
291
+ this.handleActiveCellChange = (ref) => {
292
+ this.activeCellRef.set(ref);
293
+ if (ref) {
294
+ const m = ref.match(/^([A-Z]+)(\d+)$/);
295
+ if (m) {
296
+ this.activeCellCoords.set({ col: columnLetterToIndex(m[1]), row: parseInt(m[2], 10) - 1 });
297
+ } else {
298
+ this.activeCellCoords.set(null);
299
+ }
300
+ } else {
301
+ this.activeCellCoords.set(null);
302
+ }
303
+ };
304
+ // --- Formula bar state ---
305
+ this.formulaBarEditing = signal(false);
306
+ this.formulaBarEditText = signal("");
307
+ // --- Formula engine ---
308
+ this.formulaService = new FormulaEngineService();
309
+ /** Monotonic counter incremented on formula recalculation — drives cache invalidation. */
310
+ this.formulaVersion = signal(0);
311
+ // Stable formula method references for dataGridProps (avoid per-recompute arrow functions)
312
+ this.getFormulaValueFn = (col, row) => this.formulaService.getValue(col, row);
313
+ this.hasFormulaFn = (col, row) => this.formulaService.hasFormula(col, row);
314
+ this.getFormulaFn = (col, row) => this.formulaService.getFormula(col, row);
315
+ this.setFormulaFn = (col, row, formula) => this.formulaService.setFormula(col, row, formula ?? "");
316
+ this.onFormulaCellChangedFn = (col, row) => this.formulaService.onCellChanged(col, row);
317
+ this.getPrecedentsFn = (col, row) => this.formulaService.getPrecedents(col, row);
318
+ this.getDependentsFn = (col, row) => this.formulaService.getDependents(col, row);
319
+ this.getAuditTrailFn = (col, row) => this.formulaService.getAuditTrail(col, row);
79
320
  // --- Internal state signals ---
80
321
  this.internalData = signal([]);
81
322
  this.internalLoading = signal(false);
@@ -228,6 +469,52 @@ var OGridService = class {
228
469
  toggle: (panel) => this.sideBarActivePanel.update((p) => p === panel ? null : panel),
229
470
  close: () => this.sideBarActivePanel.set(null)
230
471
  }));
472
+ // --- Formula bar derived state ---
473
+ /** Display text derived from active cell (formula string or raw value). */
474
+ this.formulaBarDisplayText = computed(() => {
475
+ const coords = this.activeCellCoords();
476
+ if (!coords) return "";
477
+ const getFormula = this.formulaService.enabled() ? (c, r) => this.formulaService.getFormula(c, r) : void 0;
478
+ const items = this.displayItems();
479
+ const cols = this.columns();
480
+ const getRawValue = (c, r) => {
481
+ if (r < 0 || r >= items.length || c < 0 || c >= cols.length) return void 0;
482
+ return getCellValue(items[r], cols[c]);
483
+ };
484
+ return deriveFormulaBarText(coords.col, coords.row, getFormula, getRawValue);
485
+ });
486
+ /** Formula text shown in the bar: edit text when editing, display text otherwise. */
487
+ this.formulaBarText = computed(
488
+ () => this.formulaBarEditing() ? this.formulaBarEditText() : this.formulaBarDisplayText()
489
+ );
490
+ /** References extracted from the current formula text (for highlighting). */
491
+ this.formulaBarReferences = computed(
492
+ () => extractFormulaReferences(this.formulaBarText())
493
+ );
494
+ // Stable formula bar callbacks (avoid new closures per computed)
495
+ this.formulaBarOnInputChangeFn = (text) => {
496
+ this.formulaBarEditText.set(text);
497
+ };
498
+ this.formulaBarOnCommitFn = () => {
499
+ this.commitFormulaBar();
500
+ };
501
+ this.formulaBarOnCancelFn = () => {
502
+ this.cancelFormulaBar();
503
+ };
504
+ this.formulaBarStartEditingFn = () => {
505
+ this.startFormulaBarEditing();
506
+ };
507
+ /** Aggregate formula bar state for template consumption. */
508
+ this.formulaBarState = computed(() => ({
509
+ cellRef: this.activeCellRef(),
510
+ formulaText: this.formulaBarText(),
511
+ isEditing: this.formulaBarEditing(),
512
+ onInputChange: this.formulaBarOnInputChangeFn,
513
+ onCommit: this.formulaBarOnCommitFn,
514
+ onCancel: this.formulaBarOnCancelFn,
515
+ startEditing: this.formulaBarStartEditingFn,
516
+ referencedCells: this.formulaBarReferences()
517
+ }));
231
518
  // --- Pre-computed stable callback references for dataGridProps ---
232
519
  // These avoid recreating arrow functions on every dataGridProps recomputation.
233
520
  this.handleSortFn = (columnKey, direction) => this.handleSort(columnKey, direction);
@@ -267,6 +554,12 @@ var OGridService = class {
267
554
  rowSelection: this.rowSelection(),
268
555
  selectedRows: this.effectiveSelectedRows(),
269
556
  onSelectionChange: this.handleSelectionChangeFn,
557
+ showRowNumbers: this.showRowNumbers() || this.cellReferences() || this.formulasEnabled(),
558
+ showColumnLetters: !!(this.cellReferences() || this.formulasEnabled()),
559
+ showNameBox: !!(this.cellReferences() && !this.formulasEnabled()),
560
+ onActiveCellChange: this.cellReferences() || this.formulasEnabled() ? this.handleActiveCellChange : void 0,
561
+ currentPage: this.page(),
562
+ pageSize: this.pageSize(),
270
563
  statusBar: this.statusBarConfig(),
271
564
  isLoading: this.isLoadingResolved(),
272
565
  filters: this.filters(),
@@ -287,7 +580,20 @@ var OGridService = class {
287
580
  onClearAll: this.clearAllFiltersFn,
288
581
  message: this.emptyState()?.message,
289
582
  render: this.emptyState()?.render
290
- }
583
+ },
584
+ formulas: this.formulasEnabled(),
585
+ formulaVersion: this.formulaVersion(),
586
+ formulaReferences: this.formulaBarReferences().length > 0 ? this.formulaBarReferences() : void 0,
587
+ ...this.formulaService.enabled() ? {
588
+ getFormulaValue: this.getFormulaValueFn,
589
+ hasFormula: this.hasFormulaFn,
590
+ getFormula: this.getFormulaFn,
591
+ setFormula: this.setFormulaFn,
592
+ onFormulaCellChanged: this.onFormulaCellChangedFn,
593
+ getPrecedents: this.getPrecedentsFn,
594
+ getDependents: this.getDependentsFn,
595
+ getAuditTrail: this.getAuditTrailFn
596
+ } : {}
291
597
  }));
292
598
  this.pagination = computed(() => ({
293
599
  page: this.page(),
@@ -444,6 +750,29 @@ var OGridService = class {
444
750
  this.sideBarActivePanel.set(parsed.defaultPanel);
445
751
  }
446
752
  });
753
+ effect(() => {
754
+ this.activeCellCoords();
755
+ this.formulaBarEditing.set(false);
756
+ });
757
+ effect(() => {
758
+ const userRecalcCb = this.onFormulaRecalc();
759
+ this.formulaService.configure({
760
+ formulas: this.formulasEnabled(),
761
+ initialFormulas: this.initialFormulas(),
762
+ formulaFunctions: this.formulaFunctions(),
763
+ onFormulaRecalc: (result) => {
764
+ this.formulaVersion.update((v) => v + 1);
765
+ userRecalcCb?.(result);
766
+ },
767
+ namedRanges: this.namedRanges(),
768
+ sheets: this.sheets()
769
+ });
770
+ });
771
+ effect(() => {
772
+ const items = this.displayItems();
773
+ const cols = this.columns();
774
+ this.formulaService.setData(items, cols);
775
+ });
447
776
  this.destroyRef.onDestroy(() => {
448
777
  this.fetchAbortController?.abort();
449
778
  this.filterAbortController?.abort();
@@ -522,6 +851,33 @@ var OGridService = class {
522
851
  });
523
852
  this.onColumnPinned()?.(columnId, pinned);
524
853
  }
854
+ // --- Formula bar methods ---
855
+ startFormulaBarEditing() {
856
+ this.formulaBarEditText.set(this.formulaBarDisplayText());
857
+ this.formulaBarEditing.set(true);
858
+ }
859
+ commitFormulaBar() {
860
+ const coords = this.activeCellCoords();
861
+ if (!coords) return;
862
+ const text = this.formulaBarEditText().trim();
863
+ if (text.startsWith("=")) {
864
+ this.formulaService.setFormula(coords.col, coords.row, text);
865
+ this.formulaVersion.update((v) => v + 1);
866
+ } else {
867
+ this.formulaService.setFormula(coords.col, coords.row, null);
868
+ this.onCellValueChanged()?.({
869
+ rowIndex: coords.row,
870
+ columnId: this.columns()[coords.col]?.columnId ?? "",
871
+ oldValue: void 0,
872
+ newValue: text
873
+ });
874
+ }
875
+ this.formulaBarEditing.set(false);
876
+ }
877
+ cancelFormulaBar() {
878
+ this.formulaBarEditing.set(false);
879
+ this.formulaBarEditText.set("");
880
+ }
525
881
  // --- Configure from props ---
526
882
  configure(props) {
527
883
  this.columnsProp.set(props.columns);
@@ -575,6 +931,18 @@ var OGridService = class {
575
931
  if (props.columnReorder !== void 0) this.columnReorder.set(props.columnReorder);
576
932
  if (props.virtualScroll !== void 0) this.virtualScroll.set(props.virtualScroll);
577
933
  if (props.workerSort !== void 0) this.workerSort.set(props.workerSort);
934
+ if (props.showRowNumbers !== void 0) this.showRowNumbers.set(props.showRowNumbers);
935
+ if (props.cellReferences !== void 0) this.cellReferences.set(props.cellReferences);
936
+ if (props.formulas !== void 0) this.formulasEnabled.set(props.formulas);
937
+ if (props.initialFormulas !== void 0) this.initialFormulas.set(props.initialFormulas);
938
+ if (props.onFormulaRecalc) this.onFormulaRecalc.set(props.onFormulaRecalc);
939
+ if (props.formulaFunctions !== void 0) this.formulaFunctions.set(props.formulaFunctions);
940
+ if (props.namedRanges !== void 0) this.namedRanges.set(props.namedRanges);
941
+ if (props.sheets !== void 0) this.sheets.set(props.sheets);
942
+ if (props.sheetDefs !== void 0) this.sheetDefs.set(props.sheetDefs);
943
+ if (props.activeSheet !== void 0) this.activeSheet.set(props.activeSheet);
944
+ if (props.onSheetChange) this.onSheetChange.set(props.onSheetChange);
945
+ if (props.onSheetAdd) this.onSheetAdd.set(props.onSheetAdd);
578
946
  if (props.entityLabelPlural !== void 0) this.entityLabelPlural.set(props.entityLabelPlural);
579
947
  if (props.className !== void 0) this.className.set(props.className);
580
948
  if (props.layoutMode !== void 0) this.layoutMode.set(props.layoutMode);
@@ -1112,7 +1480,7 @@ var DataGridInteractionHelper = class {
1112
1480
  const maxColIndex = visibleColumnCount - 1 + colOffset;
1113
1481
  if (items.length === 0) return;
1114
1482
  if (activeCell === null) {
1115
- if (["ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight", "Tab", "Enter", "Home", "End"].includes(e.key)) {
1483
+ if (["ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight", "Tab", "Enter", "Home", "End", "PageDown", "PageUp"].includes(e.key)) {
1116
1484
  this.setActiveCell({ rowIndex: 0, columnIndex: colOffset });
1117
1485
  e.preventDefault();
1118
1486
  }
@@ -1268,6 +1636,36 @@ var DataGridInteractionHelper = class {
1268
1636
  this.setActiveCell({ rowIndex: newRowEnd, columnIndex: maxColIndex });
1269
1637
  break;
1270
1638
  }
1639
+ case "PageDown":
1640
+ case "PageUp": {
1641
+ e.preventDefault();
1642
+ let pageSize = 10;
1643
+ let rowHeight = 36;
1644
+ if (wrapperEl) {
1645
+ const firstRow = wrapperEl.querySelector("tbody tr");
1646
+ if (firstRow && firstRow.offsetHeight > 0) {
1647
+ rowHeight = firstRow.offsetHeight;
1648
+ pageSize = Math.max(1, Math.floor(wrapperEl.clientHeight / rowHeight));
1649
+ }
1650
+ }
1651
+ const pgDir = e.key === "PageDown" ? 1 : -1;
1652
+ const newRowPage = Math.max(0, Math.min(rowIndex + pgDir * pageSize, maxRowIndex));
1653
+ if (shift) {
1654
+ this.setSelectionRange(normalizeSelectionRange({
1655
+ startRow: selectionRange?.startRow ?? rowIndex,
1656
+ startCol: selectionRange?.startCol ?? dataColIndex,
1657
+ endRow: newRowPage,
1658
+ endCol: selectionRange?.endCol ?? dataColIndex
1659
+ }));
1660
+ } else {
1661
+ this.setSelectionRange({ startRow: newRowPage, startCol: dataColIndex, endRow: newRowPage, endCol: dataColIndex });
1662
+ }
1663
+ this.setActiveCell({ rowIndex: newRowPage, columnIndex });
1664
+ if (wrapperEl) {
1665
+ wrapperEl.scrollTop = getScrollTopForRow(newRowPage, rowHeight, wrapperEl.clientHeight, "center");
1666
+ }
1667
+ break;
1668
+ }
1271
1669
  case "Enter":
1272
1670
  case "F2": {
1273
1671
  e.preventDefault();
@@ -1341,20 +1739,8 @@ var DataGridInteractionHelper = class {
1341
1739
  const range = selectionRange ?? (activeCell != null ? { startRow: activeCell.rowIndex, startCol: activeCell.columnIndex - colOffset, endRow: activeCell.rowIndex, endCol: activeCell.columnIndex - colOffset } : null);
1342
1740
  if (range == null) break;
1343
1741
  e.preventDefault();
1344
- const norm = normalizeSelectionRange(range);
1345
- for (let r = norm.startRow; r <= norm.endRow; r++) {
1346
- for (let c = norm.startCol; c <= norm.endCol; c++) {
1347
- if (r >= items.length || c >= visibleCols.length) continue;
1348
- const item = items[r];
1349
- const col = visibleCols[c];
1350
- const colEditable = col.editable === true || typeof col.editable === "function" && col.editable(item);
1351
- if (!colEditable) continue;
1352
- const oldValue = getCellValue(item, col);
1353
- const result = parseValue("", oldValue, item, col);
1354
- if (!result.valid) continue;
1355
- wrappedOnCellValueChanged({ item, columnId: col.columnId, oldValue, newValue: result.value, rowIndex: r });
1356
- }
1357
- }
1742
+ const deleteEvents = applyCellDeletion(normalizeSelectionRange(range), items, visibleCols);
1743
+ for (const evt of deleteEvents) wrappedOnCellValueChanged(evt);
1358
1744
  break;
1359
1745
  }
1360
1746
  case "F10":
@@ -1987,7 +2373,10 @@ var DataGridStateService = class {
1987
2373
  getRowId: p?.getRowId ?? ((item) => item["id"]),
1988
2374
  editable: p?.editable,
1989
2375
  onCellValueChanged: this.wrappedOnCellValueChanged(),
1990
- isDragging: cellSel ? this.interactionHelper.isDraggingSig() : false
2376
+ isDragging: cellSel ? this.interactionHelper.isDraggingSig() : false,
2377
+ getFormulaValue: p?.getFormulaValue,
2378
+ hasFormula: p?.hasFormula,
2379
+ formulaVersion: p?.formulaVersion
1991
2380
  },
1992
2381
  statusBarConfig: this.statusBarConfig(),
1993
2382
  showEmptyInGrid: this.showEmptyInGrid(),
@@ -2634,6 +3023,205 @@ SideBarComponent = __decorateClass([
2634
3023
  `
2635
3024
  })
2636
3025
  ], SideBarComponent);
3026
+ var FormulaBarComponent = class {
3027
+ constructor() {
3028
+ /** Active cell reference (e.g. "A1"). */
3029
+ this.cellRef = input(null);
3030
+ /** Text displayed/edited in the formula input. */
3031
+ this.formulaText = input("");
3032
+ /** Whether the input is in editing mode. */
3033
+ this.isEditing = input(false);
3034
+ /** Called when the user changes the input text. */
3035
+ this.inputChange = output();
3036
+ /** Commit the formula bar value. */
3037
+ this.commit = output();
3038
+ /** Cancel editing. */
3039
+ this.cancel = output();
3040
+ /** Start editing the formula bar. */
3041
+ this.startEditing = output();
3042
+ this.inputEl = viewChild("formulaInput");
3043
+ effect(() => {
3044
+ if (this.isEditing()) {
3045
+ const el = this.inputEl()?.nativeElement;
3046
+ if (el) el.focus();
3047
+ }
3048
+ });
3049
+ }
3050
+ onInput(event) {
3051
+ this.inputChange.emit(event.target.value);
3052
+ }
3053
+ onKeyDown(event) {
3054
+ handleFormulaBarKeyDown(
3055
+ event.key,
3056
+ () => event.preventDefault(),
3057
+ () => this.commit.emit(),
3058
+ () => this.cancel.emit()
3059
+ );
3060
+ }
3061
+ onClick() {
3062
+ if (!this.isEditing()) {
3063
+ this.startEditing.emit();
3064
+ }
3065
+ }
3066
+ };
3067
+ FormulaBarComponent = __decorateClass([
3068
+ Component({
3069
+ selector: "ogrid-formula-bar",
3070
+ standalone: true,
3071
+ encapsulation: ViewEncapsulation.None,
3072
+ changeDetection: ChangeDetectionStrategy.OnPush,
3073
+ styles: [`
3074
+ .ogrid-formula-bar {
3075
+ display: flex;
3076
+ align-items: center;
3077
+ border-bottom: 1px solid var(--ogrid-border, #e0e0e0);
3078
+ background: var(--ogrid-bg, #fff);
3079
+ min-height: 28px;
3080
+ font-size: 13px;
3081
+ }
3082
+ .ogrid-formula-bar__name-box {
3083
+ font-family: monospace;
3084
+ font-size: 12px;
3085
+ font-weight: 500;
3086
+ padding: 2px 8px;
3087
+ border-right: 1px solid var(--ogrid-border, #e0e0e0);
3088
+ background: var(--ogrid-bg, #fff);
3089
+ color: var(--ogrid-fg, #242424);
3090
+ min-width: 52px;
3091
+ text-align: center;
3092
+ line-height: 24px;
3093
+ user-select: none;
3094
+ white-space: nowrap;
3095
+ }
3096
+ .ogrid-formula-bar__fx {
3097
+ padding: 2px 8px;
3098
+ font-style: italic;
3099
+ font-weight: 600;
3100
+ color: var(--ogrid-muted-fg, #888);
3101
+ user-select: none;
3102
+ border-right: 1px solid var(--ogrid-border, #e0e0e0);
3103
+ line-height: 24px;
3104
+ font-size: 12px;
3105
+ }
3106
+ .ogrid-formula-bar__input {
3107
+ flex: 1;
3108
+ border: none;
3109
+ outline: none;
3110
+ padding: 2px 8px;
3111
+ font-family: monospace;
3112
+ font-size: 12px;
3113
+ line-height: 24px;
3114
+ background: transparent;
3115
+ color: var(--ogrid-fg, #242424);
3116
+ min-width: 0;
3117
+ }
3118
+ `],
3119
+ template: `
3120
+ <div class="ogrid-formula-bar" role="toolbar" aria-label="Formula bar">
3121
+ <div class="ogrid-formula-bar__name-box" aria-label="Active cell reference">
3122
+ {{ cellRef() ?? '\u2014' }}
3123
+ </div>
3124
+ <div class="ogrid-formula-bar__fx" aria-hidden="true">fx</div>
3125
+ <input
3126
+ #formulaInput
3127
+ type="text"
3128
+ class="ogrid-formula-bar__input"
3129
+ [value]="formulaText()"
3130
+ [readOnly]="!isEditing()"
3131
+ (input)="onInput($event)"
3132
+ (keydown)="onKeyDown($event)"
3133
+ (click)="onClick()"
3134
+ (dblclick)="onClick()"
3135
+ aria-label="Formula input"
3136
+ [attr.spellcheck]="false"
3137
+ autocomplete="off"
3138
+ />
3139
+ </div>
3140
+ `
3141
+ })
3142
+ ], FormulaBarComponent);
3143
+ var SheetTabsComponent = class {
3144
+ constructor() {
3145
+ this.sheets = input.required();
3146
+ this.activeSheet = input.required();
3147
+ this.showAddButton = input(false);
3148
+ this.sheetChange = output();
3149
+ this.sheetAdd = output();
3150
+ }
3151
+ };
3152
+ SheetTabsComponent = __decorateClass([
3153
+ Component({
3154
+ selector: "ogrid-sheet-tabs",
3155
+ standalone: true,
3156
+ encapsulation: ViewEncapsulation.None,
3157
+ changeDetection: ChangeDetectionStrategy.OnPush,
3158
+ styles: [`
3159
+ .ogrid-sheet-tabs {
3160
+ display: flex;
3161
+ align-items: center;
3162
+ border-top: 1px solid var(--ogrid-border, #e0e0e0);
3163
+ background: var(--ogrid-header-bg, #f5f5f5);
3164
+ min-height: 30px;
3165
+ overflow-x: auto;
3166
+ overflow-y: hidden;
3167
+ gap: 0;
3168
+ font-size: 12px;
3169
+ }
3170
+ .ogrid-sheet-tabs__add-btn {
3171
+ background: none;
3172
+ border: none;
3173
+ cursor: pointer;
3174
+ padding: 4px 10px;
3175
+ font-size: 16px;
3176
+ line-height: 22px;
3177
+ color: var(--ogrid-fg-secondary, #666);
3178
+ flex-shrink: 0;
3179
+ }
3180
+ .ogrid-sheet-tabs__tab {
3181
+ background: none;
3182
+ border: none;
3183
+ border-bottom: 2px solid transparent;
3184
+ cursor: pointer;
3185
+ padding: 4px 16px;
3186
+ font-size: 12px;
3187
+ line-height: 22px;
3188
+ color: var(--ogrid-fg, #242424);
3189
+ white-space: nowrap;
3190
+ position: relative;
3191
+ }
3192
+ .ogrid-sheet-tabs__tab--active {
3193
+ font-weight: 600;
3194
+ border-bottom-color: var(--ogrid-primary, #217346);
3195
+ background: var(--ogrid-bg, #fff);
3196
+ }
3197
+ `],
3198
+ template: `
3199
+ <div class="ogrid-sheet-tabs" role="tablist" aria-label="Sheet tabs">
3200
+ @if (showAddButton()) {
3201
+ <button
3202
+ type="button"
3203
+ class="ogrid-sheet-tabs__add-btn"
3204
+ (click)="sheetAdd.emit()"
3205
+ title="Add sheet"
3206
+ aria-label="Add sheet"
3207
+ >+</button>
3208
+ }
3209
+ @for (sheet of sheets(); track sheet.id) {
3210
+ @let isActive = sheet.id === activeSheet();
3211
+ <button
3212
+ type="button"
3213
+ role="tab"
3214
+ class="ogrid-sheet-tabs__tab"
3215
+ [class.ogrid-sheet-tabs__tab--active]="isActive"
3216
+ [attr.aria-selected]="isActive"
3217
+ [style.border-bottom-color]="isActive && sheet.color ? sheet.color : null"
3218
+ (click)="sheetChange.emit(sheet.id)"
3219
+ >{{ sheet.name }}</button>
3220
+ }
3221
+ </div>
3222
+ `
3223
+ })
3224
+ ], SheetTabsComponent);
2637
3225
 
2638
3226
  // src/styles/ogrid-theme-vars.ts
2639
3227
  var OGRID_THEME_VARS_CSS = `
@@ -2750,6 +3338,9 @@ var OGridLayoutComponent = class {
2750
3338
  this.hasPagination = false;
2751
3339
  this.sideBar = null;
2752
3340
  this.fullScreen = false;
3341
+ this.showNameBox = false;
3342
+ this.activeCellRef = null;
3343
+ this.formulaBar = null;
2753
3344
  this.isFullScreen = false;
2754
3345
  this.borderRadius = GRID_BORDER_RADIUS;
2755
3346
  this.escListener = null;
@@ -2797,13 +3388,34 @@ __decorateClass([
2797
3388
  __decorateClass([
2798
3389
  Input()
2799
3390
  ], OGridLayoutComponent.prototype, "fullScreen", 2);
3391
+ __decorateClass([
3392
+ Input()
3393
+ ], OGridLayoutComponent.prototype, "showNameBox", 2);
3394
+ __decorateClass([
3395
+ Input()
3396
+ ], OGridLayoutComponent.prototype, "activeCellRef", 2);
3397
+ __decorateClass([
3398
+ Input()
3399
+ ], OGridLayoutComponent.prototype, "formulaBar", 2);
3400
+ __decorateClass([
3401
+ Input()
3402
+ ], OGridLayoutComponent.prototype, "sheetDefs", 2);
3403
+ __decorateClass([
3404
+ Input()
3405
+ ], OGridLayoutComponent.prototype, "activeSheet", 2);
3406
+ __decorateClass([
3407
+ Input()
3408
+ ], OGridLayoutComponent.prototype, "onSheetChange", 2);
3409
+ __decorateClass([
3410
+ Input()
3411
+ ], OGridLayoutComponent.prototype, "onSheetAdd", 2);
2800
3412
  OGridLayoutComponent = __decorateClass([
2801
3413
  Component({
2802
3414
  selector: "ogrid-layout",
2803
3415
  standalone: true,
2804
3416
  encapsulation: ViewEncapsulation.None,
2805
3417
  changeDetection: ChangeDetectionStrategy.OnPush,
2806
- imports: [SideBarComponent],
3418
+ imports: [SideBarComponent, FormulaBarComponent, SheetTabsComponent],
2807
3419
  styles: [OGRID_THEME_VARS_CSS, `
2808
3420
  :host { display: block; height: 100%; }
2809
3421
  .ogrid-layout-root { display: flex; flex-direction: column; height: 100%; }
@@ -2846,18 +3458,28 @@ OGridLayoutComponent = __decorateClass([
2846
3458
  color: var(--ogrid-fg, rgba(0, 0, 0, 0.87));
2847
3459
  }
2848
3460
  .ogrid-fullscreen-btn:hover { background: var(--ogrid-hover-bg, rgba(0, 0, 0, 0.04)); }
3461
+ .ogrid-name-box {
3462
+ display: inline-flex; align-items: center; padding: 0 8px;
3463
+ font-family: 'Consolas', 'Courier New', monospace; font-size: 12px;
3464
+ border: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12)); border-radius: 3px;
3465
+ height: 24px; margin-right: 8px; background: var(--ogrid-bg, #fff);
3466
+ min-width: 40px; color: var(--ogrid-fg-secondary, rgba(0, 0, 0, 0.6));
3467
+ }
2849
3468
  `],
2850
3469
  template: `
2851
3470
  <div [class]="rootClass">
2852
3471
  <div class="ogrid-layout-container" [style.border-radius.px]="isFullScreen ? 0 : borderRadius">
2853
3472
  <!-- Toolbar strip -->
2854
- @if (hasToolbar || fullScreen) {
3473
+ @if (hasToolbar || fullScreen || showNameBox) {
2855
3474
  <div
2856
3475
  class="ogrid-layout-toolbar"
2857
3476
  [class.ogrid-layout-toolbar--has-below]="hasToolbarBelow"
2858
3477
  [class.ogrid-layout-toolbar--no-below]="!hasToolbarBelow"
2859
3478
  >
2860
3479
  <div class="ogrid-layout-toolbar-left">
3480
+ @if (showNameBox) {
3481
+ <div class="ogrid-name-box">{{ activeCellRef ?? '\u2014' }}</div>
3482
+ }
2861
3483
  <ng-content select="[toolbar]"></ng-content>
2862
3484
  </div>
2863
3485
  <div class="ogrid-layout-toolbar-right">
@@ -2895,6 +3517,19 @@ OGridLayoutComponent = __decorateClass([
2895
3517
  </div>
2896
3518
  }
2897
3519
 
3520
+ <!-- Formula bar (between toolbar and grid) -->
3521
+ @if (formulaBar) {
3522
+ <ogrid-formula-bar
3523
+ [cellRef]="formulaBar.cellRef"
3524
+ [formulaText]="formulaBar.formulaText"
3525
+ [isEditing]="formulaBar.isEditing"
3526
+ (inputChange)="formulaBar.onInputChange($event)"
3527
+ (commit)="formulaBar.onCommit()"
3528
+ (cancel)="formulaBar.onCancel()"
3529
+ (startEditing)="formulaBar.startEditing()"
3530
+ />
3531
+ }
3532
+
2898
3533
  <!-- Grid area -->
2899
3534
  <div class="ogrid-layout-grid-area">
2900
3535
  @if (sideBar && sideBar.position === 'left') {
@@ -2908,6 +3543,17 @@ OGridLayoutComponent = __decorateClass([
2908
3543
  }
2909
3544
  </div>
2910
3545
 
3546
+ <!-- Sheet tabs (between grid and footer) -->
3547
+ @if (sheetDefs && sheetDefs.length > 0 && activeSheet) {
3548
+ <ogrid-sheet-tabs
3549
+ [sheets]="sheetDefs"
3550
+ [activeSheet]="activeSheet"
3551
+ [showAddButton]="!!onSheetAdd"
3552
+ (sheetChange)="onSheetChange?.($event)"
3553
+ (sheetAdd)="onSheetAdd?.()"
3554
+ />
3555
+ }
3556
+
2911
3557
  <!-- Footer strip (pagination) -->
2912
3558
  @if (hasPagination) {
2913
3559
  <div class="ogrid-layout-footer">
@@ -3271,6 +3917,94 @@ MarchingAntsOverlayComponent = __decorateClass([
3271
3917
  `
3272
3918
  })
3273
3919
  ], MarchingAntsOverlayComponent);
3920
+ function measureRef(container, ref, colOffset) {
3921
+ const startCol = ref.col + colOffset;
3922
+ const endCol = (ref.endCol ?? ref.col) + colOffset;
3923
+ const endRow = ref.endRow ?? ref.row;
3924
+ const tl = container.querySelector(
3925
+ `[data-row-index="${ref.row}"][data-col-index="${startCol}"]`
3926
+ );
3927
+ const br = container.querySelector(
3928
+ `[data-row-index="${endRow}"][data-col-index="${endCol}"]`
3929
+ );
3930
+ if (!tl || !br) return null;
3931
+ const cRect = container.getBoundingClientRect();
3932
+ const tlRect = tl.getBoundingClientRect();
3933
+ const brRect = br.getBoundingClientRect();
3934
+ return {
3935
+ top: Math.round(tlRect.top - cRect.top),
3936
+ left: Math.round(tlRect.left - cRect.left),
3937
+ width: Math.round(brRect.right - tlRect.left),
3938
+ height: Math.round(brRect.bottom - tlRect.top),
3939
+ color: FORMULA_REF_COLORS[ref.colorIndex % FORMULA_REF_COLORS.length]
3940
+ };
3941
+ }
3942
+ var FormulaRefOverlayComponent = class {
3943
+ constructor() {
3944
+ /** The positioned container that wraps the table. */
3945
+ this.containerEl = input(null);
3946
+ /** References to highlight. */
3947
+ this.references = input([]);
3948
+ /** Column offset (1 when checkbox/row-number columns are present). */
3949
+ this.colOffset = input(0);
3950
+ this.rects = signal([]);
3951
+ this.rafId = 0;
3952
+ effect(() => {
3953
+ const refs = this.references();
3954
+ const container = this.containerEl();
3955
+ const colOff = this.colOffset();
3956
+ cancelAnimationFrame(this.rafId);
3957
+ if (!container || refs.length === 0) {
3958
+ this.rects.set([]);
3959
+ return;
3960
+ }
3961
+ this.rafId = requestAnimationFrame(() => {
3962
+ const measured = [];
3963
+ for (const ref of refs) {
3964
+ const r = measureRef(container, ref, colOff);
3965
+ if (r) measured.push(r);
3966
+ }
3967
+ this.rects.set(measured);
3968
+ });
3969
+ });
3970
+ }
3971
+ max0(v) {
3972
+ return Math.max(0, v);
3973
+ }
3974
+ };
3975
+ FormulaRefOverlayComponent = __decorateClass([
3976
+ Component({
3977
+ selector: "ogrid-formula-ref-overlay",
3978
+ standalone: true,
3979
+ encapsulation: ViewEncapsulation.None,
3980
+ changeDetection: ChangeDetectionStrategy.OnPush,
3981
+ template: `
3982
+ @for (r of rects(); track $index) {
3983
+ <svg
3984
+ [style.position]="'absolute'"
3985
+ [style.top.px]="r.top"
3986
+ [style.left.px]="r.left"
3987
+ [style.width.px]="r.width"
3988
+ [style.height.px]="r.height"
3989
+ [style.pointerEvents]="'none'"
3990
+ [style.zIndex]="3"
3991
+ [style.overflow]="'visible'"
3992
+ aria-hidden="true"
3993
+ >
3994
+ <rect
3995
+ x="1" y="1"
3996
+ [attr.width]="max0(r.width - 2)"
3997
+ [attr.height]="max0(r.height - 2)"
3998
+ fill="none"
3999
+ [attr.stroke]="r.color"
4000
+ stroke-width="2"
4001
+ style="shape-rendering: crispEdges"
4002
+ />
4003
+ </svg>
4004
+ }
4005
+ `
4006
+ })
4007
+ ], FormulaRefOverlayComponent);
3274
4008
  var EmptyStateComponent = class {
3275
4009
  constructor() {
3276
4010
  this.message = void 0;
@@ -3787,8 +4521,8 @@ var BaseDataGridTableComponent = class {
3787
4521
  return "";
3788
4522
  }
3789
4523
  }
3790
- resolveCellStyleFn(col, item) {
3791
- return resolveCellStyle(col, item);
4524
+ resolveCellStyleFn(col, item, displayValue) {
4525
+ return resolveCellStyle(col, item, displayValue);
3792
4526
  }
3793
4527
  buildPopoverEditorProps(item, col, descriptor) {
3794
4528
  return buildPopoverEditorProps(item, col, descriptor, this.pendingEditorValue(), {
@@ -4495,7 +5229,11 @@ var BaseInlineCellEditorComponent = class {
4495
5229
  }
4496
5230
  syncFromInputs() {
4497
5231
  const v = this.value;
4498
- this.localValue.set(v != null ? String(v) : "");
5232
+ let strVal = v != null ? String(v) : "";
5233
+ if (this.editorType === "date" && strVal.match(/^\d{4}-\d{2}-\d{2}/)) {
5234
+ strVal = strVal.substring(0, 10);
5235
+ }
5236
+ this.localValue.set(strVal);
4499
5237
  const col = this.column;
4500
5238
  if (col?.cellEditorParams?.values) {
4501
5239
  const vals = col.cellEditorParams.values;
@@ -4522,7 +5260,12 @@ var BaseInlineCellEditorComponent = class {
4522
5260
  const el = this.inputEl?.nativeElement;
4523
5261
  if (el) {
4524
5262
  el.focus();
4525
- if (el instanceof HTMLInputElement && el.type === "text") {
5263
+ if (el instanceof HTMLInputElement && el.type === "date") {
5264
+ try {
5265
+ el.showPicker();
5266
+ } catch {
5267
+ }
5268
+ } else if (el instanceof HTMLInputElement && el.type === "text") {
4526
5269
  el.select();
4527
5270
  }
4528
5271
  }
@@ -4644,6 +5387,7 @@ var BaseInlineCellEditorComponent = class {
4644
5387
  dropdown.style.maxHeight = `${maxH}px`;
4645
5388
  dropdown.style.zIndex = "9999";
4646
5389
  dropdown.style.right = "auto";
5390
+ dropdown.style.textAlign = "left";
4647
5391
  if (flipUp) {
4648
5392
  dropdown.style.top = "auto";
4649
5393
  dropdown.style.bottom = `${window.innerHeight - rect.top}px`;
@@ -4700,6 +5444,88 @@ __decorateClass([
4700
5444
  __decorateClass([
4701
5445
  ViewChild("richSelectDropdown")
4702
5446
  ], BaseInlineCellEditorComponent.prototype, "richSelectDropdown", 2);
5447
+ var BaseColumnHeaderMenuComponent = class {
5448
+ constructor() {
5449
+ // Signal-backed inputs so computed() tracks changes reactively
5450
+ this._canPinLeft = signal(true);
5451
+ this._canPinRight = signal(true);
5452
+ this._canUnpin = signal(false);
5453
+ this._currentSort = signal(null);
5454
+ this._isSortable = signal(true);
5455
+ this._isResizable = signal(true);
5456
+ this.handlers = {};
5457
+ this.menuItems = computed(
5458
+ () => getColumnHeaderMenuItems({
5459
+ canPinLeft: this._canPinLeft(),
5460
+ canPinRight: this._canPinRight(),
5461
+ canUnpin: this._canUnpin(),
5462
+ currentSort: this._currentSort(),
5463
+ isSortable: this._isSortable(),
5464
+ isResizable: this._isResizable()
5465
+ })
5466
+ );
5467
+ }
5468
+ set canPinLeft(v) {
5469
+ this._canPinLeft.set(v);
5470
+ }
5471
+ set canPinRight(v) {
5472
+ this._canPinRight.set(v);
5473
+ }
5474
+ set canUnpin(v) {
5475
+ this._canUnpin.set(v);
5476
+ }
5477
+ set currentSort(v) {
5478
+ this._currentSort.set(v);
5479
+ }
5480
+ set isSortable(v) {
5481
+ this._isSortable.set(v);
5482
+ }
5483
+ set isResizable(v) {
5484
+ this._isResizable.set(v);
5485
+ }
5486
+ handleMenuItemClick(itemId) {
5487
+ const h = this.handlers;
5488
+ const actionMap = {
5489
+ pinLeft: h.onPinLeft,
5490
+ pinRight: h.onPinRight,
5491
+ unpin: h.onUnpin,
5492
+ sortAsc: h.onSortAsc,
5493
+ sortDesc: h.onSortDesc,
5494
+ clearSort: h.onClearSort,
5495
+ autosizeThis: h.onAutosizeThis,
5496
+ autosizeAll: h.onAutosizeAll
5497
+ };
5498
+ const action = actionMap[itemId];
5499
+ if (action) {
5500
+ action();
5501
+ h.onClose?.();
5502
+ }
5503
+ }
5504
+ };
5505
+ __decorateClass([
5506
+ Input({ required: true })
5507
+ ], BaseColumnHeaderMenuComponent.prototype, "columnId", 2);
5508
+ __decorateClass([
5509
+ Input()
5510
+ ], BaseColumnHeaderMenuComponent.prototype, "canPinLeft", 1);
5511
+ __decorateClass([
5512
+ Input()
5513
+ ], BaseColumnHeaderMenuComponent.prototype, "canPinRight", 1);
5514
+ __decorateClass([
5515
+ Input()
5516
+ ], BaseColumnHeaderMenuComponent.prototype, "canUnpin", 1);
5517
+ __decorateClass([
5518
+ Input()
5519
+ ], BaseColumnHeaderMenuComponent.prototype, "currentSort", 1);
5520
+ __decorateClass([
5521
+ Input()
5522
+ ], BaseColumnHeaderMenuComponent.prototype, "isSortable", 1);
5523
+ __decorateClass([
5524
+ Input()
5525
+ ], BaseColumnHeaderMenuComponent.prototype, "isResizable", 1);
5526
+ __decorateClass([
5527
+ Input()
5528
+ ], BaseColumnHeaderMenuComponent.prototype, "handlers", 2);
4703
5529
 
4704
5530
  // src/components/inline-cell-editor-template.ts
4705
5531
  var INLINE_CELL_EDITOR_TEMPLATE = `
@@ -4728,7 +5554,7 @@ var INLINE_CELL_EDITOR_TEMPLATE = `
4728
5554
  style="width:100%;padding:0;border:none;background:transparent;color:inherit;font:inherit;font-size:13px;outline:none;min-width:0"
4729
5555
  />
4730
5556
  <div #richSelectDropdown role="listbox"
4731
- 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)">
5557
+ 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);text-align:left">
4732
5558
  @for (opt of filteredOptions(); track opt; let i = $index) {
4733
5559
  <div role="option"
4734
5560
  [attr.aria-selected]="i === highlightedIndex()"
@@ -4752,7 +5578,7 @@ var INLINE_CELL_EDITOR_TEMPLATE = `
4752
5578
  <span style="margin-left:4px;font-size:10px;opacity:0.5">&#9662;</span>
4753
5579
  </div>
4754
5580
  <div #selectDropdown role="listbox"
4755
- 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)">
5581
+ 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);text-align:left">
4756
5582
  @for (opt of selectOptions(); track opt; let i = $index) {
4757
5583
  <div role="option"
4758
5584
  [attr.aria-selected]="i === highlightedIndex()"
@@ -4892,4 +5718,4 @@ __decorateClass([
4892
5718
  ViewChild("editorContainer")
4893
5719
  ], BasePopoverCellEditorComponent.prototype, "editorContainerRef", 2);
4894
5720
 
4895
- 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 };
5721
+ export { BaseColumnChooserComponent, BaseColumnHeaderFilterComponent, BaseColumnHeaderMenuComponent, BaseDataGridTableComponent, BaseInlineCellEditorComponent, BaseOGridComponent, BasePaginationControlsComponent, BasePopoverCellEditorComponent, ColumnReorderService, DataGridEditingHelper, DataGridInteractionHelper, DataGridLayoutHelper, DataGridStateService, EmptyStateComponent, FormulaBarComponent, FormulaEngineService, FormulaRefOverlayComponent, 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, SheetTabsComponent, SideBarComponent, StatusBarComponent, VirtualScrollService, createDebouncedCallback, createDebouncedSignal, createLatestCallback };