@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.
@@ -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>;
@@ -245,7 +248,7 @@ export declare abstract class BaseDataGridTableComponent<T = unknown> {
245
248
  };
246
249
  getCellDescriptor(item: T, col: IColumnDef<T>, rowIndex: number, colIdx: number): CellRenderDescriptor;
247
250
  resolveCellContent(col: IColumnDef<T>, item: T, displayValue: unknown): unknown;
248
- resolveCellStyleFn(col: IColumnDef<T>, item: T): Record<string, string> | undefined;
251
+ resolveCellStyleFn(col: IColumnDef<T>, item: T, displayValue?: unknown): Record<string, string> | undefined;
249
252
  buildPopoverEditorProps(item: T, col: IColumnDef<T>, descriptor: CellRenderDescriptor): unknown;
250
253
  /** Check if a specific cell is the active cell (PrimeNG inline template helper). */
251
254
  isActiveCell(rowIndex: number, colIdx: number): 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 {};
@@ -2,5 +2,5 @@
2
2
  * Shared inline cell editor template used by all Angular UI packages.
3
3
  * The template is identical across Material, PrimeNG, and Radix implementations.
4
4
  */
5
- export declare const INLINE_CELL_EDITOR_TEMPLATE = "\n @switch (editorType) {\n @case ('text') {\n <input\n #inputEl\n type=\"text\"\n [value]=\"localValue()\"\n (input)=\"localValue.set($any($event.target).value)\"\n (keydown)=\"onTextKeyDown($event)\"\n (blur)=\"onTextBlur()\"\n [style]=\"getInputStyle()\"\n />\n }\n @case ('richSelect') {\n <div #richSelectWrapper\n style=\"width:100%;height:100%;display:flex;align-items:center;padding:6px 10px;box-sizing:border-box;min-width:0;position:relative\">\n <input\n #richSelectInput\n type=\"text\"\n [value]=\"searchText()\"\n (input)=\"onRichSelectSearch($any($event.target).value)\"\n (keydown)=\"onRichSelectKeyDown($event)\"\n placeholder=\"Search...\"\n style=\"width:100%;padding:0;border:none;background:transparent;color:inherit;font:inherit;font-size:13px;outline:none;min-width:0\"\n />\n <div #richSelectDropdown role=\"listbox\"\n 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)\">\n @for (opt of filteredOptions(); track opt; let i = $index) {\n <div role=\"option\"\n [attr.aria-selected]=\"i === highlightedIndex()\"\n (click)=\"commitValue(opt)\"\n [style]=\"i === highlightedIndex() ? 'padding:6px 8px;cursor:pointer;color:var(--ogrid-fg, #242424);background:var(--ogrid-bg-hover, #e8f0fe)' : 'padding:6px 8px;cursor:pointer;color:var(--ogrid-fg, #242424)'\">\n {{ getDisplayText(opt) }}\n </div>\n }\n @if (filteredOptions().length === 0) {\n <div style=\"padding:6px 8px;color:var(--ogrid-muted, #999)\">No matches</div>\n }\n </div>\n </div>\n }\n @case ('select') {\n <div #selectWrapper tabindex=\"0\"\n style=\"width:100%;height:100%;display:flex;align-items:center;padding:6px 10px;box-sizing:border-box;min-width:0;position:relative\"\n (keydown)=\"onCustomSelectKeyDown($event)\">\n <div style=\"display:flex;align-items:center;justify-content:space-between;width:100%;cursor:pointer;font-size:13px;color:inherit\">\n <span>{{ getDisplayText(value) }}</span>\n <span style=\"margin-left:4px;font-size:10px;opacity:0.5\">&#9662;</span>\n </div>\n <div #selectDropdown role=\"listbox\"\n 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)\">\n @for (opt of selectOptions(); track opt; let i = $index) {\n <div role=\"option\"\n [attr.aria-selected]=\"i === highlightedIndex()\"\n (click)=\"commitValue(opt)\"\n [style]=\"i === highlightedIndex() ? 'padding:6px 8px;cursor:pointer;color:var(--ogrid-fg, #242424);background:var(--ogrid-bg-hover, #e8f0fe)' : 'padding:6px 8px;cursor:pointer;color:var(--ogrid-fg, #242424)'\">\n {{ getDisplayText(opt) }}\n </div>\n }\n </div>\n </div>\n }\n @case ('checkbox') {\n <div style=\"display:flex;align-items:center;justify-content:center;width:100%;height:100%\">\n <input\n type=\"checkbox\"\n [checked]=\"!!localValue()\"\n (change)=\"commitValue($any($event.target).checked)\"\n (keydown)=\"onCheckboxKeyDown($event)\"\n />\n </div>\n }\n @case ('date') {\n <input\n #inputEl\n type=\"date\"\n [value]=\"localValue()\"\n (change)=\"commitValue($any($event.target).value)\"\n (keydown)=\"onTextKeyDown($event)\"\n (blur)=\"onTextBlur()\"\n [style]=\"getInputStyle()\"\n />\n }\n @default {\n <input\n #inputEl\n type=\"text\"\n [value]=\"localValue()\"\n (input)=\"localValue.set($any($event.target).value)\"\n (keydown)=\"onTextKeyDown($event)\"\n (blur)=\"onTextBlur()\"\n [style]=\"getInputStyle()\"\n />\n }\n }\n";
5
+ export declare const INLINE_CELL_EDITOR_TEMPLATE = "\n @switch (editorType) {\n @case ('text') {\n <input\n #inputEl\n type=\"text\"\n [value]=\"localValue()\"\n (input)=\"localValue.set($any($event.target).value)\"\n (keydown)=\"onTextKeyDown($event)\"\n (blur)=\"onTextBlur()\"\n [style]=\"getInputStyle()\"\n />\n }\n @case ('richSelect') {\n <div #richSelectWrapper\n style=\"width:100%;height:100%;display:flex;align-items:center;padding:6px 10px;box-sizing:border-box;min-width:0;position:relative\">\n <input\n #richSelectInput\n type=\"text\"\n [value]=\"searchText()\"\n (input)=\"onRichSelectSearch($any($event.target).value)\"\n (keydown)=\"onRichSelectKeyDown($event)\"\n placeholder=\"Search...\"\n style=\"width:100%;padding:0;border:none;background:transparent;color:inherit;font:inherit;font-size:13px;outline:none;min-width:0\"\n />\n <div #richSelectDropdown role=\"listbox\"\n 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\">\n @for (opt of filteredOptions(); track opt; let i = $index) {\n <div role=\"option\"\n [attr.aria-selected]=\"i === highlightedIndex()\"\n (click)=\"commitValue(opt)\"\n [style]=\"i === highlightedIndex() ? 'padding:6px 8px;cursor:pointer;color:var(--ogrid-fg, #242424);background:var(--ogrid-bg-hover, #e8f0fe)' : 'padding:6px 8px;cursor:pointer;color:var(--ogrid-fg, #242424)'\">\n {{ getDisplayText(opt) }}\n </div>\n }\n @if (filteredOptions().length === 0) {\n <div style=\"padding:6px 8px;color:var(--ogrid-muted, #999)\">No matches</div>\n }\n </div>\n </div>\n }\n @case ('select') {\n <div #selectWrapper tabindex=\"0\"\n style=\"width:100%;height:100%;display:flex;align-items:center;padding:6px 10px;box-sizing:border-box;min-width:0;position:relative\"\n (keydown)=\"onCustomSelectKeyDown($event)\">\n <div style=\"display:flex;align-items:center;justify-content:space-between;width:100%;cursor:pointer;font-size:13px;color:inherit\">\n <span>{{ getDisplayText(value) }}</span>\n <span style=\"margin-left:4px;font-size:10px;opacity:0.5\">&#9662;</span>\n </div>\n <div #selectDropdown role=\"listbox\"\n 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\">\n @for (opt of selectOptions(); track opt; let i = $index) {\n <div role=\"option\"\n [attr.aria-selected]=\"i === highlightedIndex()\"\n (click)=\"commitValue(opt)\"\n [style]=\"i === highlightedIndex() ? 'padding:6px 8px;cursor:pointer;color:var(--ogrid-fg, #242424);background:var(--ogrid-bg-hover, #e8f0fe)' : 'padding:6px 8px;cursor:pointer;color:var(--ogrid-fg, #242424)'\">\n {{ getDisplayText(opt) }}\n </div>\n }\n </div>\n </div>\n }\n @case ('checkbox') {\n <div style=\"display:flex;align-items:center;justify-content:center;width:100%;height:100%\">\n <input\n type=\"checkbox\"\n [checked]=\"!!localValue()\"\n (change)=\"commitValue($any($event.target).checked)\"\n (keydown)=\"onCheckboxKeyDown($event)\"\n />\n </div>\n }\n @case ('date') {\n <input\n #inputEl\n type=\"date\"\n [value]=\"localValue()\"\n (change)=\"commitValue($any($event.target).value)\"\n (keydown)=\"onTextKeyDown($event)\"\n (blur)=\"onTextBlur()\"\n [style]=\"getInputStyle()\"\n />\n }\n @default {\n <input\n #inputEl\n type=\"text\"\n [value]=\"localValue()\"\n (input)=\"localValue.set($any($event.target).value)\"\n (keydown)=\"onTextKeyDown($event)\"\n (blur)=\"onTextBlur()\"\n [style]=\"getInputStyle()\"\n />\n }\n }\n";
6
6
  export declare const INLINE_CELL_EDITOR_STYLES = "\n :host {\n display: block;\n width: 100%;\n height: 100%;\n }\n";
@@ -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;
@@ -6,6 +8,13 @@ export declare class OGridLayoutComponent {
6
8
  hasPagination: boolean;
7
9
  sideBar: SideBarProps | null;
8
10
  fullScreen: boolean;
11
+ showNameBox: boolean;
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;
9
18
  isFullScreen: boolean;
10
19
  readonly borderRadius = 6;
11
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';
@@ -13,12 +13,17 @@ export { DataGridEditingHelper } from './services/datagrid-editing.service';
13
13
  export { DataGridInteractionHelper } from './services/datagrid-interaction.service';
14
14
  export { ColumnReorderService } from './services/column-reorder.service';
15
15
  export { VirtualScrollService } from './services/virtual-scroll.service';
16
+ export { FormulaEngineService } from './services/formula-engine.service';
17
+ export type { FormulaEngineConfig } from './services/formula-engine.service';
16
18
  export { OGridLayoutComponent } from './components/ogrid-layout.component';
17
19
  export { StatusBarComponent } from './components/status-bar.component';
18
20
  export { GridContextMenuComponent } from './components/grid-context-menu.component';
19
21
  export { SideBarComponent } from './components/sidebar.component';
20
22
  export type { SideBarProps, SideBarFilterColumn } from './components/sidebar.component';
21
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';
22
27
  export { EmptyStateComponent } from './components/empty-state.component';
23
28
  export { BaseOGridComponent } from './components/base-ogrid.component';
24
29
  export { BaseDataGridTableComponent } from './components/base-datagrid-table.component';
@@ -28,6 +33,7 @@ export { BaseColumnChooserComponent } from './components/base-column-chooser.com
28
33
  export type { IColumnChooserProps } from './components/base-column-chooser.component';
29
34
  export { BasePaginationControlsComponent } from './components/base-pagination-controls.component';
30
35
  export { BaseInlineCellEditorComponent } from './components/base-inline-cell-editor.component';
36
+ export { BaseColumnHeaderMenuComponent } from './components/base-column-header-menu.component';
31
37
  export { INLINE_CELL_EDITOR_TEMPLATE, INLINE_CELL_EDITOR_STYLES } from './components/inline-cell-editor-template';
32
38
  export { BasePopoverCellEditorComponent, POPOVER_CELL_EDITOR_TEMPLATE, POPOVER_CELL_EDITOR_OVERLAY_STYLES } from './components/base-popover-cell-editor.component';
33
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;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * FormulaEngineService — Angular service for integrating the formula engine with the grid.
3
+ *
4
+ * Lazily creates a FormulaEngine instance when configured with `formulas: true`.
5
+ * Provides an accessor bridge between grid data and formula coordinates.
6
+ * Uses Angular signals for reactive state.
7
+ *
8
+ * Port of React's useFormulaEngine hook.
9
+ */
10
+ import type { IGridDataAccessor, IFormulaFunction, IRecalcResult, IColumnDef, IAuditEntry, IAuditTrail } from '@alaarab/ogrid-core';
11
+ export interface FormulaEngineConfig {
12
+ /** Enable formula support. */
13
+ formulas?: boolean;
14
+ /** Initial formulas to load on first configure. */
15
+ initialFormulas?: Array<{
16
+ col: number;
17
+ row: number;
18
+ formula: string;
19
+ }>;
20
+ /** Custom formula functions to register. */
21
+ formulaFunctions?: Record<string, IFormulaFunction>;
22
+ /** Called when recalculation produces cascading updates. */
23
+ onFormulaRecalc?: (result: IRecalcResult) => void;
24
+ /** Named ranges: name → cell/range reference string. */
25
+ namedRanges?: Record<string, string>;
26
+ /** Sheet accessors for cross-sheet references. */
27
+ sheets?: Record<string, IGridDataAccessor>;
28
+ }
29
+ /**
30
+ * Per-component injectable service that wraps FormulaEngine from @alaarab/ogrid-core.
31
+ *
32
+ * Not providedIn: 'root' — provide it per component so each grid instance
33
+ * gets its own formula engine.
34
+ */
35
+ export declare class FormulaEngineService<T = unknown> {
36
+ private destroyRef;
37
+ private engine;
38
+ private initialLoaded;
39
+ private onFormulaRecalcFn;
40
+ private items;
41
+ private flatColumns;
42
+ /** Whether formula support is currently enabled. */
43
+ readonly enabled: import("@angular/core").WritableSignal<boolean>;
44
+ /** Last recalculation result, for UI to react to formula changes. */
45
+ readonly lastRecalcResult: import("@angular/core").WritableSignal<IRecalcResult | null>;
46
+ /** Number of formulas currently registered. */
47
+ readonly formulaCount: import("@angular/core").Signal<number>;
48
+ constructor();
49
+ /**
50
+ * Configure the formula engine. Call this when the grid component initializes.
51
+ *
52
+ * Lazily creates the FormulaEngine only when `formulas: true`.
53
+ */
54
+ configure(options: FormulaEngineConfig): void;
55
+ /**
56
+ * Update the data references used by the accessor bridge.
57
+ * Call this whenever the grid's items or columns change.
58
+ */
59
+ setData(items: T[], flatColumns: IColumnDef<T>[]): void;
60
+ /**
61
+ * Set or clear a formula for a cell. Triggers recalculation of dependents.
62
+ */
63
+ setFormula(col: number, row: number, formula: string | null, accessor?: IGridDataAccessor): void;
64
+ /**
65
+ * Notify the engine that a non-formula cell's value changed.
66
+ * Triggers recalculation of any formulas that depend on this cell.
67
+ */
68
+ onCellChanged(col: number, row: number, accessor?: IGridDataAccessor): void;
69
+ /**
70
+ * Get the formula engine's computed value for a cell coordinate.
71
+ */
72
+ getValue(col: number, row: number): unknown | undefined;
73
+ /**
74
+ * Check if a cell has a formula.
75
+ */
76
+ hasFormula(col: number, row: number): boolean;
77
+ /**
78
+ * Get the formula string for a cell.
79
+ */
80
+ getFormula(col: number, row: number): string | undefined;
81
+ /**
82
+ * Trigger a full recalculation of all formulas.
83
+ */
84
+ recalcAll(accessor?: IGridDataAccessor): void;
85
+ /**
86
+ * Get all formulas for serialization.
87
+ */
88
+ getAllFormulas(): Array<{
89
+ col: number;
90
+ row: number;
91
+ formula: string;
92
+ }>;
93
+ /**
94
+ * Register a custom function at runtime.
95
+ */
96
+ registerFunction(name: string, fn: IFormulaFunction): void;
97
+ /**
98
+ * Clear all formulas and cached values.
99
+ */
100
+ clear(): void;
101
+ /**
102
+ * Define a named range.
103
+ */
104
+ defineNamedRange(name: string, ref: string): void;
105
+ /**
106
+ * Remove a named range.
107
+ */
108
+ removeNamedRange(name: string): void;
109
+ /**
110
+ * Register a sheet accessor for cross-sheet references.
111
+ */
112
+ registerSheet(name: string, accessor: IGridDataAccessor): void;
113
+ /**
114
+ * Unregister a sheet accessor.
115
+ */
116
+ unregisterSheet(name: string): void;
117
+ /**
118
+ * Get all cells that a cell depends on (deep, transitive).
119
+ */
120
+ getPrecedents(col: number, row: number): IAuditEntry[];
121
+ /**
122
+ * Get all cells that depend on a cell (deep, transitive).
123
+ */
124
+ getDependents(col: number, row: number): IAuditEntry[];
125
+ /**
126
+ * Get full audit trail for a cell.
127
+ */
128
+ getAuditTrail(col: number, row: number): IAuditTrail | null;
129
+ /** Create a data accessor that bridges grid data to formula coordinates. */
130
+ private createAccessor;
131
+ }
@@ -1,4 +1,5 @@
1
- import type { RowId, IOGridApi, IFilters, FilterValue, IRowSelectionChangeEvent, IStatusBarProps, IColumnDefinition, IDataSource, ISideBarDef, IVirtualScrollConfig, SideBarPanelId } from '../types';
1
+ import type { FormulaReference } from '@alaarab/ogrid-core';
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';
4
5
  import type { SideBarProps } from '../components/sidebar.component';
@@ -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;
@@ -109,6 +121,44 @@ export declare class OGridService<T> {
109
121
  readonly ariaLabel: import("@angular/core").WritableSignal<string | undefined>;
110
122
  readonly ariaLabelledBy: import("@angular/core").WritableSignal<string | undefined>;
111
123
  readonly workerSort: import("@angular/core").WritableSignal<boolean>;
124
+ readonly showRowNumbers: import("@angular/core").WritableSignal<boolean>;
125
+ readonly cellReferences: import("@angular/core").WritableSignal<boolean>;
126
+ readonly formulasEnabled: import("@angular/core").WritableSignal<boolean>;
127
+ readonly initialFormulas: import("@angular/core").WritableSignal<{
128
+ col: number;
129
+ row: number;
130
+ formula: string;
131
+ }[] | undefined>;
132
+ readonly onFormulaRecalc: import("@angular/core").WritableSignal<((result: IRecalcResult) => void) | undefined>;
133
+ readonly formulaFunctions: import("@angular/core").WritableSignal<Record<string, IFormulaFunction> | undefined>;
134
+ readonly namedRanges: import("@angular/core").WritableSignal<Record<string, string> | undefined>;
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>;
140
+ /** Active cell reference string (e.g. 'A1') updated by DataGridTable when cellReferences is enabled. */
141
+ readonly activeCellRef: import("@angular/core").WritableSignal<string | null>;
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. */
148
+ private readonly handleActiveCellChange;
149
+ private readonly formulaBarEditing;
150
+ private readonly formulaBarEditText;
151
+ private readonly formulaService;
152
+ /** Monotonic counter incremented on formula recalculation — drives cache invalidation. */
153
+ readonly formulaVersion: import("@angular/core").WritableSignal<number>;
154
+ private readonly getFormulaValueFn;
155
+ private readonly hasFormulaFn;
156
+ private readonly getFormulaFn;
157
+ private readonly setFormulaFn;
158
+ private readonly onFormulaCellChangedFn;
159
+ private readonly getPrecedentsFn;
160
+ private readonly getDependentsFn;
161
+ private readonly getAuditTrailFn;
112
162
  private readonly internalData;
113
163
  private readonly internalLoading;
114
164
  private readonly internalPage;
@@ -179,6 +229,18 @@ export declare class OGridService<T> {
179
229
  filterType: "text" | "multiSelect" | "people" | "date";
180
230
  }[]>;
181
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>;
182
244
  private readonly handleSortFn;
183
245
  private readonly handleColumnResizedFn;
184
246
  private readonly handleColumnPinnedFn;
@@ -208,6 +270,9 @@ export declare class OGridService<T> {
208
270
  handleSelectionChange(event: IRowSelectionChangeEvent<T>): void;
209
271
  handleColumnResized(columnId: string, width: number): void;
210
272
  handleColumnPinned(columnId: string, pinned: 'left' | 'right' | null): void;
273
+ private startFormulaBarEditing;
274
+ private commitFormulaBar;
275
+ private cancelFormulaBar;
211
276
  configure(props: IOGridProps<T>): void;
212
277
  /**
213
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, } 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 } 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>)[];
@@ -72,6 +72,32 @@ interface IOGridBaseProps<T> {
72
72
  onError?: (error: unknown) => void;
73
73
  onCellError?: (error: Error, info: unknown) => void;
74
74
  showRowNumbers?: boolean;
75
+ /** Enable Excel-style cell references: column letter headers, row numbers, and name box. Implies showRowNumbers. */
76
+ cellReferences?: boolean;
77
+ /** Enable Excel-like formula support. When true, cells starting with '=' are treated as formulas. Default: false. */
78
+ formulas?: boolean;
79
+ /** Initial formulas to load when the formula engine initializes. */
80
+ initialFormulas?: Array<{
81
+ col: number;
82
+ row: number;
83
+ formula: string;
84
+ }>;
85
+ /** Called when formula recalculation produces updated cell values (e.g. cascade from an edited cell). */
86
+ onFormulaRecalc?: (result: IRecalcResult) => void;
87
+ /** Custom formula functions to register with the formula engine (e.g. { MYFUNC: { minArgs: 1, maxArgs: 1, evaluate: ... } }). */
88
+ formulaFunctions?: Record<string, IFormulaFunction>;
89
+ /** Named ranges for the formula engine: name → cell/range ref string (e.g. { Revenue: 'A1:A10' }). */
90
+ namedRanges?: Record<string, string>;
91
+ /** Sheet accessors for cross-sheet formula references (e.g. { Sheet2: accessor }). */
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;
75
101
  'aria-label'?: string;
76
102
  'aria-labelledby'?: string;
77
103
  }
@@ -123,6 +149,9 @@ export interface IOGridDataGridProps<T> {
123
149
  selectedRows?: Set<RowId>;
124
150
  onSelectionChange?: (event: IRowSelectionChangeEvent<T>) => void;
125
151
  showRowNumbers?: boolean;
152
+ showColumnLetters?: boolean;
153
+ showNameBox?: boolean;
154
+ onActiveCellChange?: (ref: string | null) => void;
126
155
  currentPage?: number;
127
156
  pageSize?: number;
128
157
  statusBar?: IStatusBarProps;
@@ -147,4 +176,26 @@ export interface IOGridDataGridProps<T> {
147
176
  'aria-labelledby'?: string;
148
177
  /** Custom keydown handler. Called before grid's built-in handling. Call event.preventDefault() to suppress grid default. */
149
178
  onKeyDown?: (event: KeyboardEvent) => void;
179
+ /** Enable formula support. When true, cell values starting with '=' are treated as formulas. */
180
+ formulas?: boolean;
181
+ /** Get the formula engine's computed value for a cell, or undefined if no formula. */
182
+ getFormulaValue?: (col: number, row: number) => unknown;
183
+ /** Check if a cell has a formula. */
184
+ hasFormula?: (col: number, row: number) => boolean;
185
+ /** Get the formula string for a cell. */
186
+ getFormula?: (col: number, row: number) => string | undefined;
187
+ /** Set a formula for a cell (called from edit commit when value starts with '='). */
188
+ setFormula?: (col: number, row: number, formula: string | null) => void;
189
+ /** Notify the formula engine that a non-formula cell changed. */
190
+ onFormulaCellChanged?: (col: number, row: number) => void;
191
+ /** Get all cells that a cell depends on (deep, transitive). */
192
+ getPrecedents?: (col: number, row: number) => IAuditEntry[];
193
+ /** Get all cells that depend on a cell (deep, transitive). */
194
+ getDependents?: (col: number, row: number) => IAuditEntry[];
195
+ /** Get full audit trail for a cell. */
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[];
150
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, } 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.2.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.2.0"
38
+ "@alaarab/ogrid-core": "2.4.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@angular/core": "^21.0.0",