@alaarab/ogrid-js 2.2.0 → 2.3.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 {
@@ -130,6 +130,7 @@ export declare class OGrid<T> {
130
130
  private options;
131
131
  private isFullScreen;
132
132
  private fullscreenBtn;
133
+ private nameBoxEl;
133
134
  private renderingHelper;
134
135
  private eventWiringHelper;
135
136
  /** The imperative grid API (extends React's IOGridApi with JS-specific methods). */
@@ -30,5 +30,7 @@ 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';
@@ -27,6 +27,8 @@ export interface TableRendererInteractionState {
27
27
  allSelected?: boolean;
28
28
  someSelected?: boolean;
29
29
  showRowNumbers?: boolean;
30
+ showColumnLetters?: boolean;
31
+ showNameBox?: boolean;
30
32
  pinnedColumns?: Record<string, 'left' | 'right'>;
31
33
  leftOffsets?: Record<string, number>;
32
34
  rightOffsets?: Record<string, number>;
@@ -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.3.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.3.0"
40
40
  },
41
41
  "sideEffects": [
42
42
  "**/*.css"