@alaarab/ogrid-angular 2.3.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 { FormulaEngine, getCellValue, flattenColumns, getMultiSelectFilterFields, deriveFilterOptionsFromData, processClientSideData, validateColumns, processClientSideDataAsync, validateRowIds, computeNextSortState, mergeFilter, CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, parseValue, UndoRedoStack, rangesEqual, normalizeSelectionRange, formatSelectionAsTsv, parseTsvClipboard, 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, 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;
@@ -199,21 +199,9 @@ var FormulaEngineService = class {
199
199
  return this.engine?.getAuditTrail(col, row) ?? null;
200
200
  }
201
201
  // --- Private helpers ---
202
- /**
203
- * Create a data accessor that bridges grid data to formula coordinates.
204
- */
202
+ /** Create a data accessor that bridges grid data to formula coordinates. */
205
203
  createAccessor() {
206
- const items = this.items;
207
- const cols = this.flatColumns;
208
- return {
209
- getCellValue: (col, row) => {
210
- if (row < 0 || row >= items.length) return null;
211
- if (col < 0 || col >= cols.length) return null;
212
- return getCellValue(items[row], cols[col]);
213
- },
214
- getRowCount: () => items.length,
215
- getColumnCount: () => cols.length
216
- };
204
+ return createGridDataAccessor(this.items, this.flatColumns);
217
205
  }
218
206
  };
219
207
  FormulaEngineService = __decorateClass([
@@ -291,14 +279,35 @@ var OGridService = class {
291
279
  this.formulaFunctions = signal(void 0);
292
280
  this.namedRanges = signal(void 0);
293
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);
294
286
  /** Active cell reference string (e.g. 'A1') updated by DataGridTable when cellReferences is enabled. */
295
287
  this.activeCellRef = signal(null);
296
- /** Stable callback passed to DataGridTable to update activeCellRef. */
288
+ /** Active cell coordinates (0-based col/row). */
289
+ this.activeCellCoords = signal(null);
290
+ /** Stable callback passed to DataGridTable to update activeCellRef + coords. */
297
291
  this.handleActiveCellChange = (ref) => {
298
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
+ }
299
303
  };
304
+ // --- Formula bar state ---
305
+ this.formulaBarEditing = signal(false);
306
+ this.formulaBarEditText = signal("");
300
307
  // --- Formula engine ---
301
308
  this.formulaService = new FormulaEngineService();
309
+ /** Monotonic counter incremented on formula recalculation — drives cache invalidation. */
310
+ this.formulaVersion = signal(0);
302
311
  // Stable formula method references for dataGridProps (avoid per-recompute arrow functions)
303
312
  this.getFormulaValueFn = (col, row) => this.formulaService.getValue(col, row);
304
313
  this.hasFormulaFn = (col, row) => this.formulaService.hasFormula(col, row);
@@ -460,6 +469,52 @@ var OGridService = class {
460
469
  toggle: (panel) => this.sideBarActivePanel.update((p) => p === panel ? null : panel),
461
470
  close: () => this.sideBarActivePanel.set(null)
462
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
+ }));
463
518
  // --- Pre-computed stable callback references for dataGridProps ---
464
519
  // These avoid recreating arrow functions on every dataGridProps recomputation.
465
520
  this.handleSortFn = (columnKey, direction) => this.handleSort(columnKey, direction);
@@ -499,10 +554,10 @@ var OGridService = class {
499
554
  rowSelection: this.rowSelection(),
500
555
  selectedRows: this.effectiveSelectedRows(),
501
556
  onSelectionChange: this.handleSelectionChangeFn,
502
- showRowNumbers: this.showRowNumbers() || this.cellReferences(),
503
- showColumnLetters: !!this.cellReferences(),
504
- showNameBox: !!this.cellReferences(),
505
- onActiveCellChange: this.cellReferences() ? this.handleActiveCellChange : void 0,
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,
506
561
  currentPage: this.page(),
507
562
  pageSize: this.pageSize(),
508
563
  statusBar: this.statusBarConfig(),
@@ -527,6 +582,8 @@ var OGridService = class {
527
582
  render: this.emptyState()?.render
528
583
  },
529
584
  formulas: this.formulasEnabled(),
585
+ formulaVersion: this.formulaVersion(),
586
+ formulaReferences: this.formulaBarReferences().length > 0 ? this.formulaBarReferences() : void 0,
530
587
  ...this.formulaService.enabled() ? {
531
588
  getFormulaValue: this.getFormulaValueFn,
532
589
  hasFormula: this.hasFormulaFn,
@@ -694,11 +751,19 @@ var OGridService = class {
694
751
  }
695
752
  });
696
753
  effect(() => {
754
+ this.activeCellCoords();
755
+ this.formulaBarEditing.set(false);
756
+ });
757
+ effect(() => {
758
+ const userRecalcCb = this.onFormulaRecalc();
697
759
  this.formulaService.configure({
698
760
  formulas: this.formulasEnabled(),
699
761
  initialFormulas: this.initialFormulas(),
700
762
  formulaFunctions: this.formulaFunctions(),
701
- onFormulaRecalc: this.onFormulaRecalc(),
763
+ onFormulaRecalc: (result) => {
764
+ this.formulaVersion.update((v) => v + 1);
765
+ userRecalcCb?.(result);
766
+ },
702
767
  namedRanges: this.namedRanges(),
703
768
  sheets: this.sheets()
704
769
  });
@@ -786,6 +851,33 @@ var OGridService = class {
786
851
  });
787
852
  this.onColumnPinned()?.(columnId, pinned);
788
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
+ }
789
881
  // --- Configure from props ---
790
882
  configure(props) {
791
883
  this.columnsProp.set(props.columns);
@@ -847,6 +939,10 @@ var OGridService = class {
847
939
  if (props.formulaFunctions !== void 0) this.formulaFunctions.set(props.formulaFunctions);
848
940
  if (props.namedRanges !== void 0) this.namedRanges.set(props.namedRanges);
849
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);
850
946
  if (props.entityLabelPlural !== void 0) this.entityLabelPlural.set(props.entityLabelPlural);
851
947
  if (props.className !== void 0) this.className.set(props.className);
852
948
  if (props.layoutMode !== void 0) this.layoutMode.set(props.layoutMode);
@@ -2277,7 +2373,10 @@ var DataGridStateService = class {
2277
2373
  getRowId: p?.getRowId ?? ((item) => item["id"]),
2278
2374
  editable: p?.editable,
2279
2375
  onCellValueChanged: this.wrappedOnCellValueChanged(),
2280
- 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
2281
2380
  },
2282
2381
  statusBarConfig: this.statusBarConfig(),
2283
2382
  showEmptyInGrid: this.showEmptyInGrid(),
@@ -2924,6 +3023,205 @@ SideBarComponent = __decorateClass([
2924
3023
  `
2925
3024
  })
2926
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);
2927
3225
 
2928
3226
  // src/styles/ogrid-theme-vars.ts
2929
3227
  var OGRID_THEME_VARS_CSS = `
@@ -3042,6 +3340,7 @@ var OGridLayoutComponent = class {
3042
3340
  this.fullScreen = false;
3043
3341
  this.showNameBox = false;
3044
3342
  this.activeCellRef = null;
3343
+ this.formulaBar = null;
3045
3344
  this.isFullScreen = false;
3046
3345
  this.borderRadius = GRID_BORDER_RADIUS;
3047
3346
  this.escListener = null;
@@ -3095,13 +3394,28 @@ __decorateClass([
3095
3394
  __decorateClass([
3096
3395
  Input()
3097
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);
3098
3412
  OGridLayoutComponent = __decorateClass([
3099
3413
  Component({
3100
3414
  selector: "ogrid-layout",
3101
3415
  standalone: true,
3102
3416
  encapsulation: ViewEncapsulation.None,
3103
3417
  changeDetection: ChangeDetectionStrategy.OnPush,
3104
- imports: [SideBarComponent],
3418
+ imports: [SideBarComponent, FormulaBarComponent, SheetTabsComponent],
3105
3419
  styles: [OGRID_THEME_VARS_CSS, `
3106
3420
  :host { display: block; height: 100%; }
3107
3421
  .ogrid-layout-root { display: flex; flex-direction: column; height: 100%; }
@@ -3203,6 +3517,19 @@ OGridLayoutComponent = __decorateClass([
3203
3517
  </div>
3204
3518
  }
3205
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
+
3206
3533
  <!-- Grid area -->
3207
3534
  <div class="ogrid-layout-grid-area">
3208
3535
  @if (sideBar && sideBar.position === 'left') {
@@ -3216,6 +3543,17 @@ OGridLayoutComponent = __decorateClass([
3216
3543
  }
3217
3544
  </div>
3218
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
+
3219
3557
  <!-- Footer strip (pagination) -->
3220
3558
  @if (hasPagination) {
3221
3559
  <div class="ogrid-layout-footer">
@@ -3579,6 +3917,94 @@ MarchingAntsOverlayComponent = __decorateClass([
3579
3917
  `
3580
3918
  })
3581
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);
3582
4008
  var EmptyStateComponent = class {
3583
4009
  constructor() {
3584
4010
  this.message = void 0;
@@ -4803,7 +5229,11 @@ var BaseInlineCellEditorComponent = class {
4803
5229
  }
4804
5230
  syncFromInputs() {
4805
5231
  const v = this.value;
4806
- 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);
4807
5237
  const col = this.column;
4808
5238
  if (col?.cellEditorParams?.values) {
4809
5239
  const vals = col.cellEditorParams.values;
@@ -5014,6 +5444,88 @@ __decorateClass([
5014
5444
  __decorateClass([
5015
5445
  ViewChild("richSelectDropdown")
5016
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);
5017
5529
 
5018
5530
  // src/components/inline-cell-editor-template.ts
5019
5531
  var INLINE_CELL_EDITOR_TEMPLATE = `
@@ -5206,4 +5718,4 @@ __decorateClass([
5206
5718
  ViewChild("editorContainer")
5207
5719
  ], BasePopoverCellEditorComponent.prototype, "editorContainerRef", 2);
5208
5720
 
5209
- export { BaseColumnChooserComponent, BaseColumnHeaderFilterComponent, BaseDataGridTableComponent, BaseInlineCellEditorComponent, BaseOGridComponent, BasePaginationControlsComponent, BasePopoverCellEditorComponent, ColumnReorderService, DataGridEditingHelper, DataGridInteractionHelper, DataGridLayoutHelper, DataGridStateService, EmptyStateComponent, FormulaEngineService, 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 };
@@ -0,0 +1,30 @@
1
+ import { type IColumnHeaderMenuItem, type ColumnHeaderMenuHandlers } from '@alaarab/ogrid-core';
2
+ /**
3
+ * Abstract base class containing all shared TypeScript logic for ColumnHeaderMenu components.
4
+ * Framework-specific UI packages extend this with their templates and style overrides.
5
+ *
6
+ * Uses signal-backed @Input setters so that computed() tracks input changes reactively
7
+ * (plain @Input properties are not reactive in Angular signals).
8
+ *
9
+ * Subclasses must:
10
+ * 1. Provide a @Component decorator with template and styles
11
+ * 2. Implement their own menu open/close mechanism (mat-menu, p-menu, or native dropdown)
12
+ */
13
+ export declare abstract class BaseColumnHeaderMenuComponent {
14
+ columnId: string;
15
+ private readonly _canPinLeft;
16
+ private readonly _canPinRight;
17
+ private readonly _canUnpin;
18
+ private readonly _currentSort;
19
+ private readonly _isSortable;
20
+ private readonly _isResizable;
21
+ set canPinLeft(v: boolean);
22
+ set canPinRight(v: boolean);
23
+ set canUnpin(v: boolean);
24
+ set currentSort(v: 'asc' | 'desc' | null);
25
+ set isSortable(v: boolean);
26
+ set isResizable(v: boolean);
27
+ handlers: Partial<ColumnHeaderMenuHandlers>;
28
+ readonly menuItems: import("@angular/core").Signal<IColumnHeaderMenuItem[]>;
29
+ handleMenuItemClick(itemId: string): void;
30
+ }
@@ -126,6 +126,9 @@ export declare abstract class BaseDataGridTableComponent<T = unknown> {
126
126
  editable?: boolean;
127
127
  onCellValueChanged?: ((event: import("@alaarab/ogrid-core").ICellValueChangedEvent<T>) => void) | undefined;
128
128
  isDragging: boolean;
129
+ getFormulaValue?: (col: number, row: number) => unknown;
130
+ hasFormula?: (col: number, row: number) => boolean;
131
+ formulaVersion?: number;
129
132
  }>;
130
133
  readonly pinnedColumnsMap: import("@angular/core").Signal<Record<string, "left" | "right">>;
131
134
  readonly vsEnabled: import("@angular/core").Signal<boolean>;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * FormulaBarComponent -- Standalone Angular formula bar component.
3
+ *
4
+ * Layout: [Name Box] [fx] [Formula Input]
5
+ *
6
+ * Uses --ogrid-* CSS variables for theming.
7
+ * Port of React's FormulaBar component.
8
+ */
9
+ export declare class FormulaBarComponent {
10
+ /** Active cell reference (e.g. "A1"). */
11
+ readonly cellRef: import("@angular/core").InputSignal<string | null>;
12
+ /** Text displayed/edited in the formula input. */
13
+ readonly formulaText: import("@angular/core").InputSignal<string>;
14
+ /** Whether the input is in editing mode. */
15
+ readonly isEditing: import("@angular/core").InputSignal<boolean>;
16
+ /** Called when the user changes the input text. */
17
+ readonly inputChange: import("@angular/core").OutputEmitterRef<string>;
18
+ /** Commit the formula bar value. */
19
+ readonly commit: import("@angular/core").OutputEmitterRef<void>;
20
+ /** Cancel editing. */
21
+ readonly cancel: import("@angular/core").OutputEmitterRef<void>;
22
+ /** Start editing the formula bar. */
23
+ readonly startEditing: import("@angular/core").OutputEmitterRef<void>;
24
+ private readonly inputEl;
25
+ constructor();
26
+ onInput(event: Event): void;
27
+ onKeyDown(event: KeyboardEvent): void;
28
+ onClick(): void;
29
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * FormulaRefOverlayComponent -- Renders colored border overlays on cells
3
+ * referenced by the active formula, like Excel's reference highlighting.
4
+ *
5
+ * Port of React's FormulaRefOverlay component.
6
+ */
7
+ import { type FormulaReference } from '@alaarab/ogrid-core';
8
+ interface RefRect {
9
+ top: number;
10
+ left: number;
11
+ width: number;
12
+ height: number;
13
+ color: string;
14
+ }
15
+ export declare class FormulaRefOverlayComponent {
16
+ /** The positioned container that wraps the table. */
17
+ readonly containerEl: import("@angular/core").InputSignal<HTMLElement | null>;
18
+ /** References to highlight. */
19
+ readonly references: import("@angular/core").InputSignal<FormulaReference[]>;
20
+ /** Column offset (1 when checkbox/row-number columns are present). */
21
+ readonly colOffset: import("@angular/core").InputSignal<number>;
22
+ readonly rects: import("@angular/core").WritableSignal<RefRect[]>;
23
+ private rafId;
24
+ constructor();
25
+ max0(v: number): number;
26
+ }
27
+ export {};
@@ -1,4 +1,6 @@
1
1
  import type { SideBarProps } from './sidebar.component';
2
+ import type { OGridFormulaBarState } from '../services/ogrid.service';
3
+ import type { ISheetDef } from '@alaarab/ogrid-core';
2
4
  export declare class OGridLayoutComponent {
3
5
  className?: string;
4
6
  hasToolbar: boolean;
@@ -8,6 +10,11 @@ export declare class OGridLayoutComponent {
8
10
  fullScreen: boolean;
9
11
  showNameBox: boolean;
10
12
  activeCellRef: string | null;
13
+ formulaBar: OGridFormulaBarState | null;
14
+ sheetDefs: ISheetDef[] | undefined;
15
+ activeSheet: string | undefined;
16
+ onSheetChange: ((sheetId: string) => void) | undefined;
17
+ onSheetAdd: (() => void) | undefined;
11
18
  isFullScreen: boolean;
12
19
  readonly borderRadius = 6;
13
20
  private escListener;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * SheetTabsComponent -- Excel-style sheet tab bar at the bottom of the grid.
3
+ *
4
+ * Layout: [+] [Sheet1] [Sheet2] [Sheet3]
5
+ *
6
+ * Uses --ogrid-* CSS variables for theming.
7
+ * Port of React's SheetTabs component.
8
+ */
9
+ import type { ISheetDef } from '@alaarab/ogrid-core';
10
+ export declare class SheetTabsComponent {
11
+ readonly sheets: import("@angular/core").InputSignal<ISheetDef[]>;
12
+ readonly activeSheet: import("@angular/core").InputSignal<string>;
13
+ readonly showAddButton: import("@angular/core").InputSignal<boolean>;
14
+ readonly sheetChange: import("@angular/core").OutputEmitterRef<string>;
15
+ readonly sheetAdd: import("@angular/core").OutputEmitterRef<void>;
16
+ }
@@ -5,7 +5,7 @@ export type { IOGridProps, IOGridClientProps, IOGridServerProps, IOGridDataGridP
5
5
  export type { ColumnFilterType, IColumnFilterDef, IColumnMeta, ICellValueChangedEvent, CellEditorParams, IValueParserParams, IDateFilterValue, HeaderCell, HeaderRow, RowId, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, IOGridApi, RowSelectionMode, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, SideBarPanelId, ISideBarDef, } from './types';
6
6
  export { toUserLike, isInSelectionRange, normalizeSelectionRange } from './types';
7
7
  export { OGridService } from './services/ogrid.service';
8
- export type { ColumnChooserPlacement, OGridPagination, OGridColumnChooser, OGridFilters, OGridSideBarState, } from './services/ogrid.service';
8
+ export type { ColumnChooserPlacement, OGridPagination, OGridColumnChooser, OGridFilters, OGridSideBarState, OGridFormulaBarState, } from './services/ogrid.service';
9
9
  export { DataGridStateService } from './services/datagrid-state.service';
10
10
  export type { DataGridLayoutState, DataGridRowSelectionState, DataGridEditingState, DataGridCellInteractionState, DataGridContextMenuState, DataGridViewModelState, DataGridPinningState, DataGridStateResult, } from './services/datagrid-state.service';
11
11
  export { DataGridLayoutHelper } from './services/datagrid-layout.service';
@@ -21,6 +21,9 @@ export { GridContextMenuComponent } from './components/grid-context-menu.compone
21
21
  export { SideBarComponent } from './components/sidebar.component';
22
22
  export type { SideBarProps, SideBarFilterColumn } from './components/sidebar.component';
23
23
  export { MarchingAntsOverlayComponent } from './components/marching-ants-overlay.component';
24
+ export { FormulaBarComponent } from './components/formula-bar.component';
25
+ export { SheetTabsComponent } from './components/sheet-tabs.component';
26
+ export { FormulaRefOverlayComponent } from './components/formula-ref-overlay.component';
24
27
  export { EmptyStateComponent } from './components/empty-state.component';
25
28
  export { BaseOGridComponent } from './components/base-ogrid.component';
26
29
  export { BaseDataGridTableComponent } from './components/base-datagrid-table.component';
@@ -30,6 +33,7 @@ export { BaseColumnChooserComponent } from './components/base-column-chooser.com
30
33
  export type { IColumnChooserProps } from './components/base-column-chooser.component';
31
34
  export { BasePaginationControlsComponent } from './components/base-pagination-controls.component';
32
35
  export { BaseInlineCellEditorComponent } from './components/base-inline-cell-editor.component';
36
+ export { BaseColumnHeaderMenuComponent } from './components/base-column-header-menu.component';
33
37
  export { INLINE_CELL_EDITOR_TEMPLATE, INLINE_CELL_EDITOR_STYLES } from './components/inline-cell-editor-template';
34
38
  export { BasePopoverCellEditorComponent, POPOVER_CELL_EDITOR_TEMPLATE, POPOVER_CELL_EDITOR_OVERLAY_STYLES } from './components/base-popover-cell-editor.component';
35
39
  export { OGRID_THEME_VARS_CSS } from './styles/ogrid-theme-vars';
@@ -119,6 +119,9 @@ export interface DataGridViewModelState<T> {
119
119
  editable?: boolean;
120
120
  onCellValueChanged?: (event: ICellValueChangedEvent<T>) => void;
121
121
  isDragging: boolean;
122
+ getFormulaValue?: (col: number, row: number) => unknown;
123
+ hasFormula?: (col: number, row: number) => boolean;
124
+ formulaVersion?: number;
122
125
  };
123
126
  statusBarConfig: IStatusBarProps | null;
124
127
  showEmptyInGrid: boolean;
@@ -126,8 +126,6 @@ export declare class FormulaEngineService<T = unknown> {
126
126
  * Get full audit trail for a cell.
127
127
  */
128
128
  getAuditTrail(col: number, row: number): IAuditTrail | null;
129
- /**
130
- * Create a data accessor that bridges grid data to formula coordinates.
131
- */
129
+ /** Create a data accessor that bridges grid data to formula coordinates. */
132
130
  private createAccessor;
133
131
  }
@@ -1,3 +1,4 @@
1
+ import type { FormulaReference } from '@alaarab/ogrid-core';
1
2
  import type { RowId, IOGridApi, IFilters, FilterValue, IRowSelectionChangeEvent, IStatusBarProps, IColumnDefinition, IDataSource, ISideBarDef, IVirtualScrollConfig, SideBarPanelId, IFormulaFunction, IRecalcResult, IGridDataAccessor } from '../types';
2
3
  import type { IOGridProps, IOGridDataGridProps } from '../types';
3
4
  import type { IColumnDef, IColumnGroupDef, ICellValueChangedEvent } from '../types';
@@ -26,6 +27,17 @@ export interface OGridFilters {
26
27
  hasActiveFilters: boolean;
27
28
  setFilters: (f: IFilters) => void;
28
29
  }
30
+ /** Formula bar state and handlers. */
31
+ export interface OGridFormulaBarState {
32
+ cellRef: string | null;
33
+ formulaText: string;
34
+ isEditing: boolean;
35
+ onInputChange: (text: string) => void;
36
+ onCommit: () => void;
37
+ onCancel: () => void;
38
+ startEditing: () => void;
39
+ referencedCells: FormulaReference[];
40
+ }
29
41
  /** Side bar state. */
30
42
  export interface OGridSideBarState {
31
43
  isEnabled: boolean;
@@ -121,11 +133,24 @@ export declare class OGridService<T> {
121
133
  readonly formulaFunctions: import("@angular/core").WritableSignal<Record<string, IFormulaFunction> | undefined>;
122
134
  readonly namedRanges: import("@angular/core").WritableSignal<Record<string, string> | undefined>;
123
135
  readonly sheets: import("@angular/core").WritableSignal<Record<string, IGridDataAccessor> | undefined>;
136
+ readonly sheetDefs: import("@angular/core").WritableSignal<import("@alaarab/ogrid-core").ISheetDef[] | undefined>;
137
+ readonly activeSheet: import("@angular/core").WritableSignal<string | undefined>;
138
+ readonly onSheetChange: import("@angular/core").WritableSignal<((sheetId: string) => void) | undefined>;
139
+ readonly onSheetAdd: import("@angular/core").WritableSignal<(() => void) | undefined>;
124
140
  /** Active cell reference string (e.g. 'A1') updated by DataGridTable when cellReferences is enabled. */
125
141
  readonly activeCellRef: import("@angular/core").WritableSignal<string | null>;
126
- /** Stable callback passed to DataGridTable to update activeCellRef. */
142
+ /** Active cell coordinates (0-based col/row). */
143
+ readonly activeCellCoords: import("@angular/core").WritableSignal<{
144
+ col: number;
145
+ row: number;
146
+ } | null>;
147
+ /** Stable callback passed to DataGridTable to update activeCellRef + coords. */
127
148
  private readonly handleActiveCellChange;
149
+ private readonly formulaBarEditing;
150
+ private readonly formulaBarEditText;
128
151
  private readonly formulaService;
152
+ /** Monotonic counter incremented on formula recalculation — drives cache invalidation. */
153
+ readonly formulaVersion: import("@angular/core").WritableSignal<number>;
129
154
  private readonly getFormulaValueFn;
130
155
  private readonly hasFormulaFn;
131
156
  private readonly getFormulaFn;
@@ -204,6 +229,18 @@ export declare class OGridService<T> {
204
229
  filterType: "text" | "multiSelect" | "people" | "date";
205
230
  }[]>;
206
231
  readonly sideBarState: import("@angular/core").Signal<OGridSideBarState>;
232
+ /** Display text derived from active cell (formula string or raw value). */
233
+ private readonly formulaBarDisplayText;
234
+ /** Formula text shown in the bar: edit text when editing, display text otherwise. */
235
+ readonly formulaBarText: import("@angular/core").Signal<string>;
236
+ /** References extracted from the current formula text (for highlighting). */
237
+ readonly formulaBarReferences: import("@angular/core").Signal<FormulaReference[]>;
238
+ private readonly formulaBarOnInputChangeFn;
239
+ private readonly formulaBarOnCommitFn;
240
+ private readonly formulaBarOnCancelFn;
241
+ private readonly formulaBarStartEditingFn;
242
+ /** Aggregate formula bar state for template consumption. */
243
+ readonly formulaBarState: import("@angular/core").Signal<OGridFormulaBarState>;
207
244
  private readonly handleSortFn;
208
245
  private readonly handleColumnResizedFn;
209
246
  private readonly handleColumnPinnedFn;
@@ -233,6 +270,9 @@ export declare class OGridService<T> {
233
270
  handleSelectionChange(event: IRowSelectionChangeEvent<T>): void;
234
271
  handleColumnResized(columnId: string, width: number): void;
235
272
  handleColumnPinned(columnId: string, pinned: 'left' | 'right' | null): void;
273
+ private startFormulaBarEditing;
274
+ private commitFormulaBar;
275
+ private cancelFormulaBar;
236
276
  configure(props: IOGridProps<T>): void;
237
277
  /**
238
278
  * Pin a column to the left or right edge.
@@ -1,8 +1,8 @@
1
1
  import type { TemplateRef } from '@angular/core';
2
2
  import type { IColumnDef, IColumnGroupDef, ICellValueChangedEvent } from './columnTypes';
3
- export type { RowId, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, RowSelectionMode, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, SideBarPanelId, ISideBarDef, IVirtualScrollConfig, IOGridApi, IFormulaFunction, IRecalcResult, IGridDataAccessor, IAuditEntry, IAuditTrail, } from '@alaarab/ogrid-core';
3
+ export type { RowId, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, RowSelectionMode, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, SideBarPanelId, ISideBarDef, IVirtualScrollConfig, IOGridApi, IFormulaFunction, IRecalcResult, IGridDataAccessor, IAuditEntry, IAuditTrail, ISheetDef, FormulaReference, } from '@alaarab/ogrid-core';
4
4
  export { toUserLike, isInSelectionRange, normalizeSelectionRange } from '@alaarab/ogrid-core';
5
- import type { RowId, UserLike, IFilters, FilterValue, RowSelectionMode, IRowSelectionChangeEvent, IStatusBarProps, IDataSource, ISideBarDef, IVirtualScrollConfig, IFormulaFunction, IRecalcResult, IGridDataAccessor, IAuditEntry, IAuditTrail } from '@alaarab/ogrid-core';
5
+ import type { RowId, UserLike, IFilters, FilterValue, RowSelectionMode, IRowSelectionChangeEvent, IStatusBarProps, IDataSource, ISideBarDef, IVirtualScrollConfig, IFormulaFunction, IRecalcResult, IGridDataAccessor, IAuditEntry, IAuditTrail, ISheetDef, FormulaReference } from '@alaarab/ogrid-core';
6
6
  /** Base props shared by both client-side and server-side OGrid modes. */
7
7
  interface IOGridBaseProps<T> {
8
8
  columns: (IColumnDef<T> | IColumnGroupDef<T>)[];
@@ -90,6 +90,14 @@ interface IOGridBaseProps<T> {
90
90
  namedRanges?: Record<string, string>;
91
91
  /** Sheet accessors for cross-sheet formula references (e.g. { Sheet2: accessor }). */
92
92
  sheets?: Record<string, IGridDataAccessor>;
93
+ /** Sheet definitions for the tab bar at grid bottom. */
94
+ sheetDefs?: ISheetDef[];
95
+ /** Active sheet ID (controlled). */
96
+ activeSheet?: string;
97
+ /** Called when user clicks a sheet tab. */
98
+ onSheetChange?: (sheetId: string) => void;
99
+ /** Called when user clicks the "+" add sheet button. */
100
+ onSheetAdd?: () => void;
93
101
  'aria-label'?: string;
94
102
  'aria-labelledby'?: string;
95
103
  }
@@ -186,4 +194,8 @@ export interface IOGridDataGridProps<T> {
186
194
  getDependents?: (col: number, row: number) => IAuditEntry[];
187
195
  /** Get full audit trail for a cell. */
188
196
  getAuditTrail?: (col: number, row: number) => IAuditTrail | null;
197
+ /** Monotonic counter incremented on each formula recalculation — used for cache invalidation. */
198
+ formulaVersion?: number;
199
+ /** Cell references to highlight (from active formula in formula bar). */
200
+ formulaReferences?: FormulaReference[];
189
201
  }
@@ -1,3 +1,3 @@
1
1
  export type { ColumnFilterType, IColumnFilterDef, IColumnMeta, IColumnDef, IColumnGroupDef, IColumnDefinition, ICellValueChangedEvent, ICellEditorProps, CellEditorParams, IValueParserParams, IDateFilterValue, HeaderCell, HeaderRow, } from './columnTypes';
2
- export type { RowId, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, IOGridApi, IOGridProps, IOGridClientProps, IOGridServerProps, IOGridDataGridProps, RowSelectionMode, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, SideBarPanelId, ISideBarDef, IVirtualScrollConfig, IFormulaFunction, IRecalcResult, IGridDataAccessor, IAuditEntry, IAuditTrail, } from './dataGridTypes';
2
+ export type { RowId, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, IOGridApi, IOGridProps, IOGridClientProps, IOGridServerProps, IOGridDataGridProps, RowSelectionMode, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, SideBarPanelId, ISideBarDef, IVirtualScrollConfig, IFormulaFunction, IRecalcResult, IGridDataAccessor, IAuditEntry, IAuditTrail, ISheetDef, FormulaReference, } from './dataGridTypes';
3
3
  export { toUserLike, isInSelectionRange, normalizeSelectionRange } from './dataGridTypes';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-angular",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "OGrid Angular – Angular services, signals, and headless components for OGrid data grids.",
5
5
  "main": "dist/esm/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -35,7 +35,7 @@
35
35
  "node": ">=18"
36
36
  },
37
37
  "dependencies": {
38
- "@alaarab/ogrid-core": "2.3.0"
38
+ "@alaarab/ogrid-core": "2.4.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@angular/core": "^21.0.0",