@alaarab/ogrid-js 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.
@@ -268,6 +268,24 @@
268
268
  background: var(--ogrid-header-bg, #f5f5f5);
269
269
  }
270
270
 
271
+ /* ── Column Letter Row (A, B, C...) ── */
272
+
273
+ .ogrid-column-letter-row {
274
+ background: var(--ogrid-column-letter-bg, var(--ogrid-header-bg));
275
+ }
276
+
277
+ .ogrid-column-letter-cell {
278
+ text-align: center;
279
+ font-size: 11px;
280
+ font-weight: 500;
281
+ color: var(--ogrid-fg-muted, rgba(0, 0, 0, 0.5));
282
+ padding: 2px 4px;
283
+ background: var(--ogrid-column-letter-bg, var(--ogrid-header-bg));
284
+ border-bottom: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12));
285
+ user-select: none;
286
+ font-variant-numeric: tabular-nums;
287
+ }
288
+
271
289
  /* ── Resize Handle ── */
272
290
 
273
291
  .ogrid-resize-handle {
@@ -392,6 +410,12 @@
392
410
 
393
411
  /* Cell range/drag highlights: qualify with .ogrid-container to reach specificity 0,3,0,
394
412
  beating row-hover rules (.ogrid-table tbody tr:hover td = 0,2,3). */
413
+ /* Active cell inside a selection range: suppress outline (Excel behavior) */
414
+ .ogrid-cell[data-active-cell='true'][data-in-range='true'] {
415
+ outline: none;
416
+ background: var(--ogrid-bg, #fff);
417
+ }
418
+
395
419
  .ogrid-container .ogrid-cell[data-in-range='true'] {
396
420
  background: var(--ogrid-bg-range, rgba(33, 115, 70, 0.12));
397
421
  }
@@ -404,6 +428,12 @@
404
428
  background: var(--ogrid-bg, #fff);
405
429
  }
406
430
 
431
+ /* Hide active cell outline during drag */
432
+ .ogrid-cell[data-active-cell='true'][data-drag-range],
433
+ .ogrid-cell[data-active-cell='true'][data-drag-anchor] {
434
+ outline: none;
435
+ }
436
+
407
437
  /* ── Fill Handle ── */
408
438
 
409
439
  .ogrid-fill-handle {
@@ -120,6 +120,13 @@ export declare class OGrid<T> {
120
120
  private cellEditor;
121
121
  private contextMenu;
122
122
  private layoutState;
123
+ private formulaEngine;
124
+ private formulaBar;
125
+ private formulaBarContainer;
126
+ /** Tracks the text currently displayed/edited in the formula bar. */
127
+ private formulaBarText;
128
+ /** Whether the formula bar input is currently in editing mode. */
129
+ private formulaBarEditing;
123
130
  private events;
124
131
  private unsubscribes;
125
132
  private containerEl;
@@ -130,6 +137,7 @@ export declare class OGrid<T> {
130
137
  private options;
131
138
  private isFullScreen;
132
139
  private fullscreenBtn;
140
+ private nameBoxEl;
133
141
  private renderingHelper;
134
142
  private eventWiringHelper;
135
143
  /** The imperative grid API (extends React's IOGridApi with JS-specific methods). */
@@ -144,6 +152,11 @@ export declare class OGrid<T> {
144
152
  private startCellEdit;
145
153
  private buildFilterConfigs;
146
154
  private handleFilterIconClick;
155
+ /** Build a grid data accessor for the formula engine from current state. */
156
+ private buildFormulaAccessor;
157
+ private handleFormulaBarCommit;
158
+ private handleFormulaBarCancel;
159
+ private handleFormulaBarStartEditing;
147
160
  /** Subscribe to grid events. */
148
161
  on<K extends keyof OGridEvents<T>>(event: K, handler: (data: OGridEvents<T>[K]) => void): void;
149
162
  /** Unsubscribe from grid events. */
@@ -0,0 +1,37 @@
1
+ /**
2
+ * FormulaBar — DOM-based Excel-style formula bar for the vanilla JS grid.
3
+ *
4
+ * Layout: [Name Box] [fx] [Formula Input]
5
+ *
6
+ * Uses --ogrid-* CSS variables for theming. Matches the React FormulaBar
7
+ * component behavior exactly.
8
+ */
9
+ export interface FormulaBarCallbacks {
10
+ /** Called when the user presses Enter to commit the formula/value. */
11
+ onCommit: () => void;
12
+ /** Called when the user presses Escape to cancel editing. */
13
+ onCancel: () => void;
14
+ /** Called when the input text changes. */
15
+ onInputChange: (text: string) => void;
16
+ /** Called when the user clicks the input to start editing. */
17
+ onStartEditing: () => void;
18
+ }
19
+ export declare class FormulaBar {
20
+ private el;
21
+ private nameBoxEl;
22
+ private inputEl;
23
+ private isEditing;
24
+ private callbacks;
25
+ constructor(callbacks: FormulaBarCallbacks);
26
+ /** Create the formula bar DOM and append it to the given container. */
27
+ mount(container: HTMLElement): void;
28
+ /** Update the formula bar display with the current active cell ref and formula text. */
29
+ update(cellRef: string | null, formulaText: string): void;
30
+ /** Set editing state. When true, the input becomes editable and receives focus. */
31
+ setEditing(editing: boolean): void;
32
+ /** Remove the formula bar from the DOM and clean up event listeners. */
33
+ destroy(): void;
34
+ private handleKeyDown;
35
+ private handleInput;
36
+ private handleClick;
37
+ }
@@ -30,5 +30,9 @@ export { VirtualScrollState } from './state/VirtualScrollState';
30
30
  export { MarchingAntsOverlay } from './components/MarchingAntsOverlay';
31
31
  export { SideBarState } from './state/SideBarState';
32
32
  export { HeaderFilterState } from './state/HeaderFilterState';
33
+ export { FormulaEngineState } from './state/FormulaEngineState';
34
+ export type { FormulaEngineStateOptions } from './state/FormulaEngineState';
33
35
  export { SideBar } from './components/SideBar';
34
36
  export { HeaderFilter } from './components/HeaderFilter';
37
+ export { FormulaBar } from './components/FormulaBar';
38
+ export type { FormulaBarCallbacks } from './components/FormulaBar';
@@ -3,6 +3,7 @@ import type { IActiveCell, ISelectionRange } from '@alaarab/ogrid-core';
3
3
  import type { GridState } from '../state/GridState';
4
4
  import type { HeaderFilterState, HeaderFilterConfig } from '../state/HeaderFilterState';
5
5
  import type { VirtualScrollState } from '../state/VirtualScrollState';
6
+ import type { FormulaEngineState } from '../state/FormulaEngineState';
6
7
  export interface TableRendererInteractionState {
7
8
  activeCell: IActiveCell | null;
8
9
  selectionRange: ISelectionRange | null;
@@ -27,6 +28,8 @@ export interface TableRendererInteractionState {
27
28
  allSelected?: boolean;
28
29
  someSelected?: boolean;
29
30
  showRowNumbers?: boolean;
31
+ showColumnLetters?: boolean;
32
+ showNameBox?: boolean;
30
33
  pinnedColumns?: Record<string, 'left' | 'right'>;
31
34
  leftOffsets?: Record<string, number>;
32
35
  rightOffsets?: Record<string, number>;
@@ -66,7 +69,9 @@ export declare class TableRenderer<T> {
66
69
  private lastPinnedColumns;
67
70
  private lastAllSelected;
68
71
  private lastSomeSelected;
72
+ private formulaEngine;
69
73
  constructor(container: HTMLElement, state: GridState<T>);
74
+ setFormulaEngine(engine: FormulaEngineState): void;
70
75
  setVirtualScrollState(vs: VirtualScrollState): void;
71
76
  setHeaderFilterState(state: HeaderFilterState, configs: Map<string, HeaderFilterConfig>): void;
72
77
  setOnFilterIconClick(handler: (columnId: string, headerEl: HTMLElement) => void): void;
@@ -11,6 +11,16 @@ export interface ClipboardParams<T> {
11
11
  colOffset: number;
12
12
  editable?: boolean;
13
13
  onCellValueChanged?: (event: ICellValueChangedEvent<T>) => void;
14
+ /** When true, enables formula-aware copy/paste. */
15
+ formulas?: boolean;
16
+ /** Flat (unfiltered) column list used to map visible col to flat col index. */
17
+ flatColumns?: IColumnDef<T>[];
18
+ /** Returns the formula string for a flat column + row, or undefined if none. */
19
+ getFormula?: (col: number, row: number) => string | undefined;
20
+ /** Returns true if a flat column + row has a formula. */
21
+ hasFormula?: (col: number, row: number) => boolean;
22
+ /** Sets or clears a formula for a flat column + row. */
23
+ setFormula?: (col: number, row: number, formula: string | null) => void;
14
24
  }
15
25
  export declare class ClipboardState<T> {
16
26
  private emitter;
@@ -1,4 +1,4 @@
1
- import type { IActiveCell, ISelectionRange, IColumnDef, ICellValueChangedEvent } from '@alaarab/ogrid-core';
1
+ import type { IActiveCell, ISelectionRange, IColumnDef, ICellValueChangedEvent, IFillFormulaOptions } from '@alaarab/ogrid-core';
2
2
  interface FillHandleEvents extends Record<string, unknown> {
3
3
  fillRangeChange: {
4
4
  fillRange: ISelectionRange | null;
@@ -12,6 +12,8 @@ export interface FillHandleParams<T> {
12
12
  colOffset: number;
13
13
  beginBatch?: () => void;
14
14
  endBatch?: () => void;
15
+ /** Optional formula-aware fill options. When provided, cells with formulas adjust references during fill. */
16
+ formulaOptions?: IFillFormulaOptions<T>;
15
17
  }
16
18
  /**
17
19
  * Manages Excel-style fill handle drag-to-fill for cell ranges (vanilla JS).
@@ -0,0 +1,78 @@
1
+ import type { IGridDataAccessor, IFormulaFunction, IRecalcResult, IAuditEntry, IAuditTrail } from '@alaarab/ogrid-core';
2
+ /** Options for FormulaEngineState. */
3
+ export interface FormulaEngineStateOptions {
4
+ /** Enable formula support. Engine is only created when true. */
5
+ formulas?: boolean;
6
+ /** Formulas to load on initialization (requires an accessor via `initialize()`). */
7
+ initialFormulas?: Array<{
8
+ col: number;
9
+ row: number;
10
+ formula: string;
11
+ }>;
12
+ /** Custom formula functions to register with the engine. */
13
+ formulaFunctions?: Record<string, IFormulaFunction>;
14
+ /** Callback invoked after every recalculation. */
15
+ onFormulaRecalc?: (result: IRecalcResult) => void;
16
+ /** Named ranges: name → cell/range reference string. */
17
+ namedRanges?: Record<string, string>;
18
+ /** Sheet accessors for cross-sheet references. */
19
+ sheets?: Record<string, IGridDataAccessor>;
20
+ }
21
+ /**
22
+ * FormulaEngineState — wraps the core `FormulaEngine` for the vanilla JS grid.
23
+ *
24
+ * Follows the same EventEmitter pattern as other JS state classes. The engine
25
+ * is lazily created only when `formulas` is true in the options, keeping the
26
+ * cost at zero for grids that don't use formulas.
27
+ *
28
+ * ## Events
29
+ * - `formulaRecalc` — emitted after every recalculation with `IRecalcResult`.
30
+ */
31
+ export declare class FormulaEngineState {
32
+ private emitter;
33
+ private engine;
34
+ private readonly options;
35
+ constructor(options: FormulaEngineStateOptions);
36
+ /**
37
+ * Initialize with an accessor — loads `initialFormulas` if provided.
38
+ * Must be called after the grid data is available so the accessor is valid.
39
+ */
40
+ initialize(accessor: IGridDataAccessor): void;
41
+ /**
42
+ * Set or clear a formula for a cell. Triggers recalculation of dependents
43
+ * and emits `formulaRecalc`.
44
+ */
45
+ setFormula(col: number, row: number, formula: string | null, accessor: IGridDataAccessor): IRecalcResult | undefined;
46
+ /**
47
+ * Notify the engine that a non-formula cell's value changed.
48
+ * Triggers recalculation of any formulas that depend on the changed cell.
49
+ */
50
+ onCellChanged(col: number, row: number, accessor: IGridDataAccessor): IRecalcResult | undefined;
51
+ /** Get the computed value for a formula cell (or undefined if no formula). */
52
+ getValue(col: number, row: number): unknown | undefined;
53
+ /** Check if a cell has a formula. */
54
+ hasFormula(col: number, row: number): boolean;
55
+ /** Get the formula string for a cell (or undefined if no formula). */
56
+ getFormula(col: number, row: number): string | undefined;
57
+ /** Whether the formula engine is active. */
58
+ isEnabled(): boolean;
59
+ /** Define a named range. */
60
+ defineNamedRange(name: string, ref: string): void;
61
+ /** Remove a named range. */
62
+ removeNamedRange(name: string): void;
63
+ /** Register a sheet accessor for cross-sheet references. */
64
+ registerSheet(name: string, accessor: IGridDataAccessor): void;
65
+ /** Unregister a sheet accessor. */
66
+ unregisterSheet(name: string): void;
67
+ /** Get all cells that a cell depends on (deep, transitive). */
68
+ getPrecedents(col: number, row: number): IAuditEntry[];
69
+ /** Get all cells that depend on a cell (deep, transitive). */
70
+ getDependents(col: number, row: number): IAuditEntry[];
71
+ /** Get full audit trail for a cell. */
72
+ getAuditTrail(col: number, row: number): IAuditTrail | null;
73
+ /** Subscribe to the `formulaRecalc` event. Returns an unsubscribe function. */
74
+ onFormulaRecalc(handler: (result: IRecalcResult) => void): () => void;
75
+ /** Clean up all listeners. */
76
+ destroy(): void;
77
+ private emitRecalc;
78
+ }
@@ -1,6 +1,7 @@
1
1
  import type { IColumnDef, IColumnGroupDef } from '../types/columnTypes';
2
2
  import type { RowId, IFilters, OGridOptions, IJsOGridApi } from '../types/gridTypes';
3
3
  import type { FilterValue } from '@alaarab/ogrid-core';
4
+ import type { FormulaEngineState } from './FormulaEngineState';
4
5
  interface StateChangeEvent {
5
6
  type: 'data' | 'sort' | 'filter' | 'page' | 'columns' | 'loading';
6
7
  }
@@ -29,6 +30,7 @@ export declare class GridState<T> {
29
30
  private _stickyHeader;
30
31
  private _fullScreen;
31
32
  private _workerSort;
33
+ private _formulaEngine;
32
34
  private _filterOptions;
33
35
  private _columnOrder;
34
36
  private _visibleColsCache;
@@ -88,6 +90,8 @@ export declare class GridState<T> {
88
90
  refreshData(): void;
89
91
  onStateChange(handler: (event: StateChangeEvent) => void): () => void;
90
92
  getApi(): IJsOGridApi<T>;
93
+ /** Wire in the formula engine so exportToCsv can use it. */
94
+ setFormulaEngine(engine: FormulaEngineState): void;
91
95
  destroy(): void;
92
96
  }
93
97
  export {};
@@ -1,6 +1,6 @@
1
1
  import type { IColumnDef, IColumnGroupDef, ICellValueChangedEvent } from './columnTypes';
2
- import type { RowId, IFilters, IDataSource, RowSelectionMode, IRowSelectionChangeEvent, IOGridApi, ISideBarDef, IStatusBarProps, IVirtualScrollConfig } 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, } from '@alaarab/ogrid-core';
2
+ import type { RowId, IFilters, IDataSource, RowSelectionMode, IRowSelectionChangeEvent, IOGridApi, ISideBarDef, IStatusBarProps, IVirtualScrollConfig, IFormulaFunction, IRecalcResult, IGridDataAccessor } 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, } from '@alaarab/ogrid-core';
4
4
  /** Standardized cell event parameter for cell interaction callbacks. */
5
5
  export interface CellEvent {
6
6
  /** Zero-based row index within the current page. */
@@ -17,7 +17,9 @@ export interface CellEvent {
17
17
  /** Extended API for the vanilla JS package (adds methods not in the core IOGridApi). */
18
18
  export interface IJsOGridApi<T> extends IOGridApi<T> {
19
19
  /** Export displayed rows to CSV and trigger a download. */
20
- exportToCsv: (filename?: string) => void;
20
+ exportToCsv: (filename?: string, options?: {
21
+ exportMode?: 'values' | 'formulas';
22
+ }) => void;
21
23
  /** Scroll to a specific row by index (virtual scrolling). */
22
24
  scrollToRow: (index: number, options?: {
23
25
  align?: 'start' | 'center' | 'end';
@@ -54,6 +56,8 @@ export interface OGridOptions<T> {
54
56
  onCellValueChanged?: (event: ICellValueChangedEvent<T>) => void;
55
57
  /** Show row numbers column. Default: false. */
56
58
  showRowNumbers?: boolean;
59
+ /** Enable Excel-style cell references: column letter headers, row numbers, and name box. Implies showRowNumbers. */
60
+ cellReferences?: boolean;
57
61
  /** Status bar configuration or boolean to enable/disable with defaults. */
58
62
  statusBar?: boolean | IStatusBarProps;
59
63
  /** Plural label for the entity type (e.g. 'items'). Used in status bar and empty state. */
@@ -131,6 +135,22 @@ export interface OGridOptions<T> {
131
135
  toolbarBelow?: HTMLElement | null;
132
136
  /** Custom keydown handler. Called before grid's built-in handling. Call event.preventDefault() to suppress grid default. */
133
137
  onKeyDown?: (event: KeyboardEvent) => void;
138
+ /** Enable Excel-like formula support. When true, cells starting with '=' are treated as formulas. Default: false. */
139
+ formulas?: boolean;
140
+ /** Initial formulas to load when the formula engine initializes. */
141
+ initialFormulas?: Array<{
142
+ col: number;
143
+ row: number;
144
+ formula: string;
145
+ }>;
146
+ /** Called when formula recalculation produces updated cell values (e.g. cascade from an edited cell). */
147
+ onFormulaRecalc?: (result: IRecalcResult) => void;
148
+ /** Custom formula functions to register with the formula engine (e.g. { MYFUNC: { minArgs: 1, maxArgs: 1, evaluate: ... } }). */
149
+ formulaFunctions?: Record<string, IFormulaFunction>;
150
+ /** Named ranges for the formula engine: name → cell/range ref string (e.g. { Revenue: 'A1:A10' }). */
151
+ namedRanges?: Record<string, string>;
152
+ /** Sheet accessors for cross-sheet formula references (e.g. { Sheet2: accessor }). */
153
+ sheets?: Record<string, IGridDataAccessor>;
134
154
  }
135
155
  /** Events emitted by the OGrid instance. */
136
156
  export interface OGridEvents<T> extends Record<string, unknown> {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-js",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "OGrid vanilla JS – framework-free data grid with sorting, filtering, pagination, and spreadsheet-style editing.",
5
5
  "main": "dist/esm/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -36,7 +36,7 @@
36
36
  "node": ">=18"
37
37
  },
38
38
  "dependencies": {
39
- "@alaarab/ogrid-core": "2.2.0"
39
+ "@alaarab/ogrid-core": "2.4.0"
40
40
  },
41
41
  "sideEffects": [
42
42
  "**/*.css"