@alaarab/ogrid-vue 2.3.0 → 2.4.1

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,4 +1,4 @@
1
- import { injectGlobalStyles, Z_INDEX, getStatusBarParts, measureRange, flattenColumns, getMultiSelectFilterFields, deriveFilterOptionsFromData, processClientSideData, processClientSideDataAsync, validateColumns, validateRowIds, computeRowSelectionState, buildCellIndex, UndoRedoStack, CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, computeAggregations, getDataGridStatusBarConfig, validateVirtualScrollConfig, computeVisibleRange, computeTotalHeight, computeVisibleColumnRange, partitionColumnsForVirtualization, formatCellReference, buildHeaderRows, indexToColumnLetter, ROW_NUMBER_COLUMN_WIDTH, getHeaderFilterConfig, getCellRenderDescriptor, buildInlineEditorProps, buildPopoverEditorProps, resolveCellDisplayContent, resolveCellStyle, rangesEqual, normalizeSelectionRange, formatSelectionAsTsv, parseTsvClipboard, applyPastedValues, applyCutClear, measureColumnContentWidth, getPinStateForColumn, parseValue, applyFillValues, applyCellDeletion, computeTabNavigation, computeArrowNavigation, computeNextSortState, mergeFilter, applyRangeRowSelection, getScrollTopForRow, FormulaEngine, getCellValue, calculateDropTarget, reorderColumnArray, computeAutoScrollSpeed } from '@alaarab/ogrid-core';
1
+ import { injectGlobalStyles, Z_INDEX, getStatusBarParts, FORMULA_BAR_STYLES, handleFormulaBarKeyDown, measureRange, FORMULA_REF_COLORS, deriveFormulaBarText, extractFormulaReferences, flattenColumns, getMultiSelectFilterFields, deriveFilterOptionsFromData, processClientSideData, processClientSideDataAsync, validateColumns, validateRowIds, computeRowSelectionState, buildCellIndex, UndoRedoStack, CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, computeAggregations, getDataGridStatusBarConfig, validateVirtualScrollConfig, computeVisibleRange, computeTotalHeight, computeVisibleColumnRange, partitionColumnsForVirtualization, formatCellReference, buildHeaderRows, indexToColumnLetter, ROW_NUMBER_COLUMN_ID, ROW_NUMBER_COLUMN_WIDTH, getHeaderFilterConfig, getCellRenderDescriptor, buildInlineEditorProps, buildPopoverEditorProps, resolveCellDisplayContent, resolveCellStyle, rangesEqual, normalizeSelectionRange, formatSelectionAsTsv, parseTsvClipboard, applyPastedValues, applyCutClear, measureColumnContentWidth, getPinStateForColumn, parseValue, applyFillValues, processFormulaBarCommit, applyCellDeletion, computeTabNavigation, computeArrowNavigation, computeNextSortState, mergeFilter, columnLetterToIndex, getCellValue, applyRangeRowSelection, getScrollTopForRow, FormulaEngine, calculateDropTarget, reorderColumnArray, ROW_NUMBER_COLUMN_MIN_WIDTH, createGridDataAccessor, computeAutoScrollSpeed } from '@alaarab/ogrid-core';
2
2
  export * from '@alaarab/ogrid-core';
3
3
  export { buildInlineEditorProps, buildPopoverEditorProps, getCellRenderDescriptor, getHeaderFilterConfig, isInSelectionRange, normalizeSelectionRange, resolveCellDisplayContent, resolveCellStyle, toUserLike } from '@alaarab/ogrid-core';
4
4
  import { defineComponent, ref, computed, onMounted, watch, toValue, onUnmounted, h, shallowRef, triggerRef, nextTick, Teleport, isRef, isReadonly, unref, customRef } from 'vue';
@@ -173,6 +173,211 @@ var StatusBar = defineComponent({
173
173
  };
174
174
  }
175
175
  });
176
+ var FormulaBar = defineComponent({
177
+ name: "FormulaBar",
178
+ props: {
179
+ cellRef: { type: [String, null], default: null },
180
+ formulaText: { type: String, required: true },
181
+ isEditing: { type: Boolean, required: true }
182
+ },
183
+ emits: ["inputChange", "commit", "cancel", "startEditing"],
184
+ setup(props, { emit }) {
185
+ const inputRef = ref(null);
186
+ watch(() => props.isEditing, (editing) => {
187
+ if (editing && inputRef.value) {
188
+ inputRef.value.focus();
189
+ }
190
+ });
191
+ return () => h("div", { style: FORMULA_BAR_STYLES.bar, role: "toolbar", "aria-label": "Formula bar" }, [
192
+ h(
193
+ "div",
194
+ { style: FORMULA_BAR_STYLES.nameBox, "aria-label": "Active cell reference" },
195
+ props.cellRef ?? "\u2014"
196
+ ),
197
+ h("div", { style: FORMULA_BAR_STYLES.fxLabel, "aria-hidden": "true" }, "fx"),
198
+ h("input", {
199
+ ref: inputRef,
200
+ type: "text",
201
+ style: FORMULA_BAR_STYLES.input,
202
+ value: props.formulaText,
203
+ readonly: !props.isEditing,
204
+ onInput: (e) => emit("inputChange", e.target.value),
205
+ onKeydown: (e) => handleFormulaBarKeyDown(e.key, () => e.preventDefault(), () => emit("commit"), () => emit("cancel")),
206
+ onClick: () => {
207
+ if (!props.isEditing) emit("startEditing");
208
+ },
209
+ onDblclick: () => {
210
+ if (!props.isEditing) emit("startEditing");
211
+ },
212
+ "aria-label": "Formula input",
213
+ spellcheck: false,
214
+ autocomplete: "off"
215
+ })
216
+ ]);
217
+ }
218
+ });
219
+ var barStyle = {
220
+ display: "flex",
221
+ alignItems: "center",
222
+ borderTop: "1px solid var(--ogrid-border, #e0e0e0)",
223
+ background: "var(--ogrid-header-bg, #f5f5f5)",
224
+ minHeight: "30px",
225
+ overflowX: "auto",
226
+ overflowY: "hidden",
227
+ gap: "0",
228
+ fontSize: "12px"
229
+ };
230
+ var addBtnStyle = {
231
+ background: "none",
232
+ border: "none",
233
+ cursor: "pointer",
234
+ padding: "4px 10px",
235
+ fontSize: "16px",
236
+ lineHeight: "22px",
237
+ color: "var(--ogrid-fg-secondary, #666)",
238
+ flexShrink: 0
239
+ };
240
+ var tabBaseStyle = {
241
+ background: "none",
242
+ border: "none",
243
+ borderBottom: "2px solid transparent",
244
+ cursor: "pointer",
245
+ padding: "4px 16px",
246
+ fontSize: "12px",
247
+ lineHeight: "22px",
248
+ color: "var(--ogrid-fg, #242424)",
249
+ whiteSpace: "nowrap",
250
+ position: "relative"
251
+ };
252
+ var activeTabStyle = {
253
+ ...tabBaseStyle,
254
+ fontWeight: 600,
255
+ borderBottomColor: "var(--ogrid-primary, #217346)",
256
+ background: "var(--ogrid-bg, #fff)"
257
+ };
258
+ var SheetTabs = defineComponent({
259
+ name: "SheetTabs",
260
+ props: {
261
+ sheets: { type: Array, required: true },
262
+ activeSheet: { type: String, required: true },
263
+ showAddButton: { type: Boolean, default: false }
264
+ },
265
+ emits: ["sheetChange", "sheetAdd"],
266
+ setup(props, { emit }) {
267
+ return () => h("div", { style: barStyle, role: "tablist", "aria-label": "Sheet tabs" }, [
268
+ props.showAddButton ? h("button", {
269
+ type: "button",
270
+ style: addBtnStyle,
271
+ onClick: () => emit("sheetAdd"),
272
+ title: "Add sheet",
273
+ "aria-label": "Add sheet"
274
+ }, "+") : null,
275
+ ...props.sheets.map((sheet) => {
276
+ const isActive = sheet.id === props.activeSheet;
277
+ const base = isActive ? activeTabStyle : tabBaseStyle;
278
+ const style = isActive && sheet.color ? { ...base, borderBottomColor: sheet.color } : base;
279
+ return h("button", {
280
+ key: sheet.id,
281
+ type: "button",
282
+ role: "tab",
283
+ "aria-selected": isActive,
284
+ style,
285
+ onClick: () => emit("sheetChange", sheet.id)
286
+ }, sheet.name);
287
+ })
288
+ ]);
289
+ }
290
+ });
291
+ function measureRef(container, r, colOffset) {
292
+ const startCol = r.col + colOffset;
293
+ const endCol = (r.endCol ?? r.col) + colOffset;
294
+ const endRow = r.endRow ?? r.row;
295
+ const tl = container.querySelector(
296
+ `[data-row-index="${r.row}"][data-col-index="${startCol}"]`
297
+ );
298
+ const br = container.querySelector(
299
+ `[data-row-index="${endRow}"][data-col-index="${endCol}"]`
300
+ );
301
+ if (!tl || !br) return null;
302
+ const cRect = container.getBoundingClientRect();
303
+ const tlRect = tl.getBoundingClientRect();
304
+ const brRect = br.getBoundingClientRect();
305
+ return {
306
+ top: Math.round(tlRect.top - cRect.top),
307
+ left: Math.round(tlRect.left - cRect.left),
308
+ width: Math.round(brRect.right - tlRect.left),
309
+ height: Math.round(brRect.bottom - tlRect.top),
310
+ color: FORMULA_REF_COLORS[r.colorIndex % FORMULA_REF_COLORS.length]
311
+ };
312
+ }
313
+ var FormulaRefOverlay = defineComponent({
314
+ name: "FormulaRefOverlay",
315
+ props: {
316
+ containerEl: { type: Object, default: null },
317
+ references: { type: Array, required: true },
318
+ colOffset: { type: Number, required: true }
319
+ },
320
+ setup(props) {
321
+ const rects = ref([]);
322
+ let rafId = 0;
323
+ function measureAll() {
324
+ const container = props.containerEl;
325
+ const refs = props.references;
326
+ if (!container || refs.length === 0) {
327
+ rects.value = [];
328
+ return;
329
+ }
330
+ const measured = [];
331
+ for (const r of refs) {
332
+ const rect = measureRef(container, r, props.colOffset);
333
+ if (rect) measured.push(rect);
334
+ }
335
+ rects.value = measured;
336
+ }
337
+ watch(
338
+ () => [props.references, props.containerEl, props.colOffset],
339
+ () => {
340
+ cancelAnimationFrame(rafId);
341
+ if (!props.containerEl || props.references.length === 0) {
342
+ rects.value = [];
343
+ return;
344
+ }
345
+ rafId = requestAnimationFrame(measureAll);
346
+ },
347
+ { immediate: true }
348
+ );
349
+ return () => {
350
+ if (rects.value.length === 0) return null;
351
+ return rects.value.map(
352
+ (r, i) => h("svg", {
353
+ key: i,
354
+ style: {
355
+ position: "absolute",
356
+ top: `${r.top}px`,
357
+ left: `${r.left}px`,
358
+ width: `${r.width}px`,
359
+ height: `${r.height}px`,
360
+ pointerEvents: "none",
361
+ zIndex: 3,
362
+ overflow: "visible"
363
+ },
364
+ "aria-hidden": "true"
365
+ }, [
366
+ h("rect", {
367
+ x: "1",
368
+ y: "1",
369
+ width: Math.max(0, r.width - 2),
370
+ height: Math.max(0, r.height - 2),
371
+ fill: "none",
372
+ stroke: r.color,
373
+ "stroke-width": "2",
374
+ style: "shape-rendering: crispEdges"
375
+ })
376
+ ])
377
+ );
378
+ };
379
+ }
380
+ });
176
381
  function useFilterOptions(dataSource, fields) {
177
382
  const filterOptions = ref({});
178
383
  const loadingOptions = ref({});
@@ -245,17 +450,7 @@ function useFormulaEngine(params) {
245
450
  let initialLoaded = false;
246
451
  const enabled = computed(() => formulas?.value ?? false);
247
452
  function createAccessor() {
248
- const currentItems = itemsRef.value;
249
- const currentCols = flatColumnsRef.value;
250
- return {
251
- getCellValue: (col, row) => {
252
- if (row < 0 || row >= currentItems.length) return null;
253
- if (col < 0 || col >= currentCols.length) return null;
254
- return getCellValue(currentItems[row], currentCols[col]);
255
- },
256
- getRowCount: () => currentItems.length,
257
- getColumnCount: () => currentCols.length
258
- };
453
+ return createGridDataAccessor(itemsRef.value, flatColumnsRef.value);
259
454
  }
260
455
  watch(
261
456
  enabled,
@@ -330,6 +525,53 @@ function useFormulaEngine(params) {
330
525
  getAuditTrail
331
526
  };
332
527
  }
528
+ function useFormulaBar(params) {
529
+ const { activeCol, activeRow, activeCellRef, getFormula, getRawValue, setFormula, onCellValueChanged } = params;
530
+ const isEditing = ref(false);
531
+ const editText = ref("");
532
+ const isFormulaBarEditing = ref(false);
533
+ const displayText = computed(
534
+ () => deriveFormulaBarText(activeCol.value, activeRow.value, getFormula, getRawValue)
535
+ );
536
+ watch([activeCol, activeRow], () => {
537
+ isEditing.value = false;
538
+ isFormulaBarEditing.value = false;
539
+ });
540
+ const startEditing = () => {
541
+ editText.value = displayText.value;
542
+ isEditing.value = true;
543
+ isFormulaBarEditing.value = true;
544
+ };
545
+ const onInputChange = (text) => {
546
+ editText.value = text;
547
+ };
548
+ const onCommit = () => {
549
+ const col = activeCol.value;
550
+ const row = activeRow.value;
551
+ if (col == null || row == null || !setFormula) return;
552
+ processFormulaBarCommit(editText.value, col, row, setFormula, onCellValueChanged);
553
+ isEditing.value = false;
554
+ isFormulaBarEditing.value = false;
555
+ };
556
+ const onCancel = () => {
557
+ isEditing.value = false;
558
+ isFormulaBarEditing.value = false;
559
+ editText.value = "";
560
+ };
561
+ const formulaText = computed(() => isEditing.value ? editText.value : displayText.value);
562
+ const referencedCells = computed(() => extractFormulaReferences(formulaText.value));
563
+ return {
564
+ cellRef: activeCellRef,
565
+ formulaText,
566
+ isEditing,
567
+ onInputChange,
568
+ onCommit,
569
+ onCancel,
570
+ startEditing,
571
+ referencedCells,
572
+ isFormulaBarEditing
573
+ };
574
+ }
333
575
  var DEFAULT_PANELS = ["columns", "filters"];
334
576
  function useSideBarState(params) {
335
577
  const { config } = params;
@@ -615,12 +857,17 @@ function useOGrid(props) {
615
857
  () => isClientSide.value && resolvedClientItems.value ? resolvedClientItems.value.totalCount : serverTotalCount.value
616
858
  );
617
859
  const formulasRef = computed(() => !!props.value.formulas);
860
+ const formulaVersionRef = ref(0);
861
+ const wrappedOnFormulaRecalc = (result) => {
862
+ formulaVersionRef.value += 1;
863
+ props.value.onFormulaRecalc?.(result);
864
+ };
618
865
  const formulaEngine = useFormulaEngine({
619
866
  formulas: formulasRef,
620
867
  items: displayItems,
621
868
  flatColumns: columns,
622
869
  initialFormulas: props.value.initialFormulas,
623
- onFormulaRecalc: props.value.onFormulaRecalc,
870
+ onFormulaRecalc: wrappedOnFormulaRecalc,
624
871
  formulaFunctions: props.value.formulaFunctions,
625
872
  namedRanges: props.value.namedRanges,
626
873
  sheets: props.value.sheets
@@ -713,9 +960,36 @@ function useOGrid(props) {
713
960
  const clearAllFilters = () => setFilters({});
714
961
  const isLoadingResolved = computed(() => isServerSide.value && loading.value || displayLoading.value);
715
962
  const activeCellRef = ref(null);
963
+ const activeCellCoords = ref(null);
716
964
  const onActiveCellChange = (cellRef) => {
717
965
  activeCellRef.value = cellRef;
966
+ if (cellRef) {
967
+ const m = cellRef.match(/^([A-Z]+)(\d+)$/);
968
+ if (m) {
969
+ activeCellCoords.value = { col: columnLetterToIndex(m[1]), row: parseInt(m[2], 10) - 1 };
970
+ } else {
971
+ activeCellCoords.value = null;
972
+ }
973
+ } else {
974
+ activeCellCoords.value = null;
975
+ }
718
976
  };
977
+ const formulaBarActiveCol = computed(() => activeCellCoords.value?.col ?? null);
978
+ const formulaBarActiveRow = computed(() => activeCellCoords.value?.row ?? null);
979
+ const getRawValue = (col, row) => {
980
+ const items = displayItems.value;
981
+ const cols = columns.value;
982
+ if (row < 0 || row >= items.length || col < 0 || col >= cols.length) return void 0;
983
+ return getCellValue(items[row], cols[col]);
984
+ };
985
+ const formulaBarState = useFormulaBar({
986
+ activeCol: formulaBarActiveCol,
987
+ activeRow: formulaBarActiveRow,
988
+ activeCellRef,
989
+ getFormula: formulaEngine.enabled.value ? formulaEngine.getFormula : void 0,
990
+ getRawValue,
991
+ setFormula: formulaEngine.enabled.value ? formulaEngine.setFormula : void 0
992
+ });
719
993
  const dataGridProps = computed(() => {
720
994
  const p = props.value;
721
995
  const ds = dataProps.value.dataSource;
@@ -743,10 +1017,11 @@ function useOGrid(props) {
743
1017
  rowSelection: p.rowSelection ?? "none",
744
1018
  selectedRows: effectiveSelectedRows.value,
745
1019
  onSelectionChange: handleSelectionChange,
746
- showRowNumbers: p.showRowNumbers || p.cellReferences,
747
- showColumnLetters: !!p.cellReferences,
748
- showNameBox: !!p.cellReferences,
749
- onActiveCellChange: p.cellReferences ? onActiveCellChange : void 0,
1020
+ showRowNumbers: p.showRowNumbers || p.cellReferences || p.formulas,
1021
+ showColumnLetters: !!(p.cellReferences || p.formulas),
1022
+ showNameBox: !!(p.cellReferences && !p.formulas),
1023
+ // formula bar includes name box
1024
+ onActiveCellChange: p.cellReferences || p.formulas ? onActiveCellChange : void 0,
750
1025
  currentPage: page.value,
751
1026
  pageSize: pageSize.value,
752
1027
  statusBar: statusBarConfig.value,
@@ -773,6 +1048,7 @@ function useOGrid(props) {
773
1048
  render: p.emptyState?.render
774
1049
  },
775
1050
  formulas: p.formulas,
1051
+ formulaVersion: formulaVersionRef.value,
776
1052
  ...formulaEngine.enabled.value ? {
777
1053
  getFormulaValue: formulaEngine.getFormulaValue,
778
1054
  hasFormula: formulaEngine.hasFormula,
@@ -782,7 +1058,8 @@ function useOGrid(props) {
782
1058
  getPrecedents: formulaEngine.getPrecedents,
783
1059
  getDependents: formulaEngine.getDependents,
784
1060
  getAuditTrail: formulaEngine.getAuditTrail
785
- } : {}
1061
+ } : {},
1062
+ formulaReferences: formulaBarState.referencedCells.value.length > 0 ? formulaBarState.referencedCells.value : void 0
786
1063
  };
787
1064
  });
788
1065
  const pagination = computed(() => ({
@@ -801,8 +1078,10 @@ function useOGrid(props) {
801
1078
  placement: columnChooserPlacement.value
802
1079
  }));
803
1080
  const layout = computed(() => {
804
- const showNameBox = !!props.value.cellReferences;
805
- let resolvedToolbar = props.value.toolbar;
1081
+ const p = props.value;
1082
+ const formulas = !!p.formulas;
1083
+ const showNameBox = !!p.cellReferences && !formulas;
1084
+ let resolvedToolbar = p.toolbar;
806
1085
  if (showNameBox) {
807
1086
  const nameBoxEl = h("div", {
808
1087
  style: {
@@ -823,13 +1102,32 @@ function useOGrid(props) {
823
1102
  }, activeCellRef.value ?? "\u2014");
824
1103
  resolvedToolbar = [nameBoxEl, resolvedToolbar];
825
1104
  }
1105
+ const formulaBarEl = formulas ? h(FormulaBar, {
1106
+ cellRef: formulaBarState.cellRef.value,
1107
+ formulaText: formulaBarState.formulaText.value,
1108
+ isEditing: formulaBarState.isEditing.value,
1109
+ onInputChange: formulaBarState.onInputChange,
1110
+ onCommit: formulaBarState.onCommit,
1111
+ onCancel: formulaBarState.onCancel,
1112
+ onStartEditing: formulaBarState.startEditing
1113
+ }) : void 0;
1114
+ const sheetTabsEl = p.sheetDefs && p.sheetDefs.length > 0 && p.activeSheet && p.onSheetChange ? h(SheetTabs, {
1115
+ sheets: p.sheetDefs,
1116
+ activeSheet: p.activeSheet,
1117
+ showAddButton: !!p.onSheetAdd,
1118
+ onSheetChange: p.onSheetChange,
1119
+ onSheetAdd: p.onSheetAdd ?? (() => {
1120
+ })
1121
+ }) : void 0;
826
1122
  return {
827
1123
  toolbar: resolvedToolbar,
828
- toolbarBelow: props.value.toolbarBelow,
829
- className: props.value.className,
830
- emptyState: props.value.emptyState,
1124
+ toolbarBelow: p.toolbarBelow,
1125
+ className: p.className,
1126
+ emptyState: p.emptyState,
831
1127
  sideBarProps: sideBarProps.value,
832
- fullScreen: props.value.fullScreen
1128
+ fullScreen: p.fullScreen,
1129
+ formulaBar: formulaBarEl,
1130
+ sheetTabs: sheetTabsEl
833
1131
  };
834
1132
  });
835
1133
  const filtersResult = computed(() => ({
@@ -1518,6 +1816,7 @@ function useKeyboardNavigation(params) {
1518
1816
  case "ArrowUp":
1519
1817
  case "ArrowRight":
1520
1818
  case "ArrowLeft": {
1819
+ if (editingCell != null) break;
1521
1820
  e.preventDefault();
1522
1821
  const { newRowIndex, newColumnIndex, newRange } = computeArrowNavigation({
1523
1822
  direction: e.key,
@@ -2449,7 +2748,10 @@ function useDataGridState(params) {
2449
2748
  getRowId,
2450
2749
  editable: editableProp.value,
2451
2750
  onCellValueChanged: onCellValueChanged.value,
2452
- isDragging: cellSelection.value ? isDragging.value : false
2751
+ isDragging: cellSelection.value ? isDragging.value : false,
2752
+ getFormulaValue: props.value.getFormulaValue,
2753
+ hasFormula: props.value.hasFormula,
2754
+ formulaVersion: props.value.formulaVersion
2453
2755
  }));
2454
2756
  const popoverAnchorEl = ref(null);
2455
2757
  const setPopoverAnchorEl = (el) => {
@@ -2631,9 +2933,10 @@ function useColumnResize(params) {
2631
2933
  [columnId]: { widthPx: latestWidth }
2632
2934
  });
2633
2935
  };
2936
+ const effectiveMinWidth = columnId === ROW_NUMBER_COLUMN_ID ? ROW_NUMBER_COLUMN_MIN_WIDTH : minWidth;
2634
2937
  const onMove = (moveEvent) => {
2635
2938
  const deltaX = moveEvent.clientX - startX;
2636
- latestWidth = Math.max(minWidth, startWidth + deltaX);
2939
+ latestWidth = Math.max(effectiveMinWidth, startWidth + deltaX);
2637
2940
  if (!rafId) {
2638
2941
  rafId = requestAnimationFrame(() => {
2639
2942
  rafId = 0;
@@ -3138,9 +3441,14 @@ function useColumnChooserState(params) {
3138
3441
  }
3139
3442
  function useInlineCellEditorState(params) {
3140
3443
  const { value, editorType, onCommit, onCancel } = params;
3141
- const localValue = ref(
3142
- value !== null && value !== void 0 ? String(value) : ""
3143
- );
3444
+ const localValue = ref((() => {
3445
+ if (value === null || value === void 0) return "";
3446
+ if (editorType === "date") {
3447
+ const str = String(value);
3448
+ return str.match(/^\d{4}-\d{2}-\d{2}/) ? str.substring(0, 10) : str;
3449
+ }
3450
+ return String(value);
3451
+ })());
3144
3452
  const setLocalValue = (v) => {
3145
3453
  localValue.value = v;
3146
3454
  };
@@ -3177,9 +3485,10 @@ function useInlineCellEditorState(params) {
3177
3485
  };
3178
3486
  }
3179
3487
  function useRichSelectState(params) {
3180
- const { values, formatValue, onCommit, onCancel } = params;
3488
+ const { values, formatValue, initialValue, onCommit, onCancel } = params;
3181
3489
  const searchText = ref("");
3182
- const highlightedIndex = ref(0);
3490
+ const initialIndex = values.findIndex((v) => String(v) === String(initialValue));
3491
+ const highlightedIndex = ref(Math.max(initialIndex, 0));
3183
3492
  const setSearchText = (text) => {
3184
3493
  searchText.value = text;
3185
3494
  };
@@ -3569,7 +3878,7 @@ function createDataGridTable(ui) {
3569
3878
  const cb = propsRef.value.onActiveCellChange;
3570
3879
  if (!cb) return;
3571
3880
  if (ac) {
3572
- cb(formatCellReference(ac.columnIndex, offset + ac.rowIndex + 1));
3881
+ cb(formatCellReference(ac.columnIndex - state.layout.value.colOffset, offset + ac.rowIndex + 1));
3573
3882
  } else {
3574
3883
  cb(null);
3575
3884
  }
@@ -3740,8 +4049,6 @@ function createDataGridTable(ui) {
3740
4049
  popoverAnchorEl ? h(CustomEditor, editorProps) : null
3741
4050
  ]);
3742
4051
  }
3743
- const content = resolveCellDisplayContent(col, item, descriptor.displayValue);
3744
- const cellStyle = resolveCellStyle(col, item, descriptor.displayValue);
3745
4052
  const interactionProps2 = getCellInteractionProps(descriptor, col.columnId, interactionHandlers);
3746
4053
  const cellClasses = ["ogrid-cell-content"];
3747
4054
  if (col.type === "numeric") cellClasses.push("ogrid-cell-content--numeric");
@@ -3751,12 +4058,25 @@ function createDataGridTable(ui) {
3751
4058
  if (descriptor.isActive && descriptor.isInRange) cellClasses.push("ogrid-cell-content--active-in-range");
3752
4059
  if (descriptor.isInRange && !descriptor.isActive) cellClasses.push("ogrid-cell-in-range");
3753
4060
  if (descriptor.isInCutRange) cellClasses.push("ogrid-cell-cut");
3754
- const styledContent = cellStyle ? h("span", { style: cellStyle }, content) : content;
4061
+ let displayNode;
4062
+ if (descriptor.columnType === "boolean") {
4063
+ displayNode = h("input", {
4064
+ type: "checkbox",
4065
+ checked: !!descriptor.displayValue,
4066
+ disabled: true,
4067
+ style: "margin:0;pointer-events:none",
4068
+ "aria-label": descriptor.displayValue ? "True" : "False"
4069
+ });
4070
+ } else {
4071
+ const content = resolveCellDisplayContent(col, item, descriptor.displayValue);
4072
+ const cellStyle = resolveCellStyle(col, item, descriptor.displayValue);
4073
+ displayNode = cellStyle ? h("span", { style: cellStyle }, content) : content;
4074
+ }
3755
4075
  return h("div", {
3756
4076
  ...interactionProps2,
3757
4077
  class: cellClasses.join(" ")
3758
4078
  }, [
3759
- styledContent,
4079
+ displayNode,
3760
4080
  ...descriptor.canEditAny && descriptor.isSelectionEndCell ? [
3761
4081
  h("div", {
3762
4082
  onMousedown: handleFillHandleMouseDown,
@@ -3898,32 +4218,46 @@ function createDataGridTable(ui) {
3898
4218
  })
3899
4219
  ] : [],
3900
4220
  // Row numbers header
3901
- ...rowIdx === headerRows.length - 1 && hasRowNumbersCol ? [
3902
- h("th", {
4221
+ ...rowIdx === headerRows.length - 1 && hasRowNumbersCol ? [(() => {
4222
+ const rnw = layout.columnSizingOverrides[ROW_NUMBER_COLUMN_ID]?.widthPx ?? ROW_NUMBER_COLUMN_WIDTH;
4223
+ return h("th", {
3903
4224
  class: "ogrid-row-number-header",
3904
4225
  style: {
3905
- width: `${ROW_NUMBER_COLUMN_WIDTH}px`,
3906
- minWidth: `${ROW_NUMBER_COLUMN_WIDTH}px`,
3907
- maxWidth: `${ROW_NUMBER_COLUMN_WIDTH}px`,
4226
+ width: `${rnw}px`,
4227
+ minWidth: `${rnw}px`,
4228
+ maxWidth: `${rnw}px`,
3908
4229
  position: "sticky",
3909
4230
  left: hasCheckboxCol ? `${CHECKBOX_COLUMN_WIDTH}px` : "0",
3910
4231
  zIndex: 3
3911
4232
  }
3912
- }, "#")
3913
- ] : [],
4233
+ }, [
4234
+ "#",
4235
+ h("div", {
4236
+ onMousedown: (e) => {
4237
+ setActiveCell(null);
4238
+ setSelectionRange(null);
4239
+ wrapperRef.value?.focus({ preventScroll: true });
4240
+ e.stopPropagation();
4241
+ handleResizeStart(e, { columnId: ROW_NUMBER_COLUMN_ID, name: "#" });
4242
+ },
4243
+ class: "ogrid-resize-handle"
4244
+ })
4245
+ ]);
4246
+ })()] : [],
3914
4247
  // Row numbers spacer
3915
- ...rowIdx === 0 && rowIdx < headerRows.length - 1 && hasRowNumbersCol ? [
3916
- h("th", {
4248
+ ...rowIdx === 0 && rowIdx < headerRows.length - 1 && hasRowNumbersCol ? [(() => {
4249
+ const spacerRnw = layout.columnSizingOverrides[ROW_NUMBER_COLUMN_ID]?.widthPx ?? ROW_NUMBER_COLUMN_WIDTH;
4250
+ return h("th", {
3917
4251
  rowSpan: headerRows.length - 1,
3918
4252
  class: "ogrid-row-number-spacer",
3919
4253
  style: {
3920
- width: `${ROW_NUMBER_COLUMN_WIDTH}px`,
4254
+ width: `${spacerRnw}px`,
3921
4255
  position: "sticky",
3922
4256
  left: hasCheckboxCol ? `${CHECKBOX_COLUMN_WIDTH}px` : "0",
3923
4257
  zIndex: 3
3924
4258
  }
3925
- })
3926
- ] : [],
4259
+ });
4260
+ })()] : [],
3927
4261
  // Header cells
3928
4262
  ...row.map((cell, cellIdx) => {
3929
4263
  if (cell.isGroup) {
@@ -3954,7 +4288,11 @@ function createDataGridTable(ui) {
3954
4288
  h("button", {
3955
4289
  onClick: (e) => {
3956
4290
  e.stopPropagation();
3957
- headerMenu.open(col.columnId, e.currentTarget);
4291
+ if (headerMenu.isOpen && headerMenu.openForColumn === col.columnId) {
4292
+ headerMenu.close();
4293
+ } else {
4294
+ headerMenu.open(col.columnId, e.currentTarget);
4295
+ }
3958
4296
  },
3959
4297
  "aria-label": "Column options",
3960
4298
  title: "Column options",
@@ -4029,20 +4367,21 @@ function createDataGridTable(ui) {
4029
4367
  )
4030
4368
  ] : [],
4031
4369
  // Row numbers cell
4032
- ...hasRowNumbersCol ? [
4033
- h("td", {
4370
+ ...hasRowNumbersCol ? [(() => {
4371
+ const rnw = layout.columnSizingOverrides[ROW_NUMBER_COLUMN_ID]?.widthPx ?? ROW_NUMBER_COLUMN_WIDTH;
4372
+ return h("td", {
4034
4373
  class: "ogrid-row-number-cell",
4035
4374
  style: {
4036
- width: `${ROW_NUMBER_COLUMN_WIDTH}px`,
4037
- minWidth: `${ROW_NUMBER_COLUMN_WIDTH}px`,
4038
- maxWidth: `${ROW_NUMBER_COLUMN_WIDTH}px`,
4375
+ width: `${rnw}px`,
4376
+ minWidth: `${rnw}px`,
4377
+ maxWidth: `${rnw}px`,
4039
4378
  padding: "6px",
4040
4379
  position: "sticky",
4041
4380
  left: hasCheckboxCol ? `${CHECKBOX_COLUMN_WIDTH}px` : "0",
4042
4381
  zIndex: 2
4043
4382
  }
4044
- }, String(rowNumberOffset2 + rowIndex + 1))
4045
- ] : [],
4383
+ }, String(rowNumberOffset2 + rowIndex + 1));
4384
+ })()] : [],
4046
4385
  // Left spacer for column virtualization
4047
4386
  ...leftSpacerWidth > 0 ? [
4048
4387
  h("td", { key: "__col-spacer-left", style: { width: `${leftSpacerWidth}px`, minWidth: `${leftSpacerWidth}px`, maxWidth: `${leftSpacerWidth}px`, padding: "0" } })
@@ -4112,6 +4451,14 @@ function createDataGridTable(ui) {
4112
4451
  columnSizingOverrides: layout.columnSizingOverrides,
4113
4452
  columnOrder: p.columnOrder
4114
4453
  }),
4454
+ // Formula reference overlay
4455
+ ...p.formulaReferences && p.formulaReferences.length > 0 ? [
4456
+ h(FormulaRefOverlay, {
4457
+ containerEl: tableContainerRef.value,
4458
+ references: p.formulaReferences,
4459
+ colOffset: _colOffset
4460
+ })
4461
+ ] : [],
4115
4462
  // Column header menu
4116
4463
  h(ui.ColumnHeaderMenu, {
4117
4464
  isOpen: headerMenu.isOpen,
@@ -4713,8 +5060,12 @@ function createOGrid(ui) {
4713
5060
  style: { padding: "8px 12px", borderBottom: "1px solid var(--ogrid-border, rgba(0,0,0,0.12))" }
4714
5061
  }, [layout.value.toolbarBelow])
4715
5062
  ] : [],
5063
+ // Formula bar (between toolbar and grid)
5064
+ ...layout.value.formulaBar ? [layout.value.formulaBar] : [],
4716
5065
  // Main content area (sidebar + grid)
4717
5066
  h("div", { style: { display: "flex", flex: "1", minHeight: "0" } }, mainAreaChildren),
5067
+ // Sheet tabs (between grid and footer)
5068
+ ...layout.value.sheetTabs ? [layout.value.sheetTabs] : [],
4718
5069
  // Footer strip (pagination)
4719
5070
  h("div", {
4720
5071
  style: {
@@ -4731,4 +5082,4 @@ function createOGrid(ui) {
4731
5082
  });
4732
5083
  }
4733
5084
 
4734
- export { MarchingAntsOverlay, StatusBar, createDataGridTable, createInlineCellEditor, createOGrid, getCellInteractionProps, useActiveCell, useCellEditing, useCellSelection, useClipboard, useColumnChooserState, useColumnHeaderFilterState, useColumnHeaderMenuState, useColumnPinning, useColumnReorder, useColumnResize, useContextMenu, useDataGridState, useDataGridTableSetup, useDateFilterState, useDebounce, useDebouncedCallback, useFillHandle, useFilterOptions, useInlineCellEditorState, useKeyboardNavigation, useMultiSelectFilterState, useOGrid, usePeopleFilterState, useRichSelectState, useRowSelection, useSideBarState, useTableLayout, useTextFilterState, useUndoRedo, useVirtualScroll };
5085
+ export { FormulaBar, FormulaRefOverlay, MarchingAntsOverlay, SheetTabs, StatusBar, createDataGridTable, createInlineCellEditor, createOGrid, getCellInteractionProps, useActiveCell, useCellEditing, useCellSelection, useClipboard, useColumnChooserState, useColumnHeaderFilterState, useColumnHeaderMenuState, useColumnPinning, useColumnReorder, useColumnResize, useContextMenu, useDataGridState, useDataGridTableSetup, useDateFilterState, useDebounce, useDebouncedCallback, useFillHandle, useFilterOptions, useFormulaBar, useInlineCellEditorState, useKeyboardNavigation, useMultiSelectFilterState, useOGrid, usePeopleFilterState, useRichSelectState, useRowSelection, useSideBarState, useTableLayout, useTextFilterState, useUndoRedo, useVirtualScroll };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * FormulaBar — Headless Excel-style formula bar component.
3
+ *
4
+ * Layout: [Name Box] [fx] [Formula Input]
5
+ *
6
+ * Uses --ogrid-* CSS variables for theming.
7
+ */
8
+ import { type PropType } from 'vue';
9
+ export interface FormulaBarProps {
10
+ /** Active cell reference (e.g. "A1"). */
11
+ cellRef: string | null;
12
+ /** Text displayed/edited in the formula input. */
13
+ formulaText: string;
14
+ /** Whether the input is in editing mode. */
15
+ isEditing: boolean;
16
+ }
17
+ export declare const FormulaBar: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
18
+ cellRef: {
19
+ type: PropType<string | null>;
20
+ default: null;
21
+ };
22
+ formulaText: {
23
+ type: StringConstructor;
24
+ required: true;
25
+ };
26
+ isEditing: {
27
+ type: BooleanConstructor;
28
+ required: true;
29
+ };
30
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
31
+ [key: string]: any;
32
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("cancel" | "inputChange" | "commit" | "startEditing")[], "cancel" | "inputChange" | "commit" | "startEditing", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
33
+ cellRef: {
34
+ type: PropType<string | null>;
35
+ default: null;
36
+ };
37
+ formulaText: {
38
+ type: StringConstructor;
39
+ required: true;
40
+ };
41
+ isEditing: {
42
+ type: BooleanConstructor;
43
+ required: true;
44
+ };
45
+ }>> & Readonly<{
46
+ onCancel?: ((...args: any[]) => any) | undefined;
47
+ onInputChange?: ((...args: any[]) => any) | undefined;
48
+ onCommit?: ((...args: any[]) => any) | undefined;
49
+ onStartEditing?: ((...args: any[]) => any) | undefined;
50
+ }>, {
51
+ cellRef: string | null;
52
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * FormulaRefOverlay — Renders colored border overlays on cells referenced by
3
+ * the active formula, like Excel's reference highlighting.
4
+ *
5
+ * Port of React's FormulaRefOverlay component.
6
+ */
7
+ import { type PropType } from 'vue';
8
+ import { type FormulaReference } from '@alaarab/ogrid-core';
9
+ export declare const FormulaRefOverlay: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
10
+ containerEl: {
11
+ type: PropType<HTMLElement | null>;
12
+ default: null;
13
+ };
14
+ references: {
15
+ type: PropType<FormulaReference[]>;
16
+ required: true;
17
+ };
18
+ colOffset: {
19
+ type: NumberConstructor;
20
+ required: true;
21
+ };
22
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
23
+ [key: string]: any;
24
+ }>[] | null, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
25
+ containerEl: {
26
+ type: PropType<HTMLElement | null>;
27
+ default: null;
28
+ };
29
+ references: {
30
+ type: PropType<FormulaReference[]>;
31
+ required: true;
32
+ };
33
+ colOffset: {
34
+ type: NumberConstructor;
35
+ required: true;
36
+ };
37
+ }>> & Readonly<{}>, {
38
+ containerEl: HTMLElement | null;
39
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * SheetTabs — Excel-style sheet tab bar at the bottom of the grid.
3
+ *
4
+ * Layout: [+] [Sheet1] [Sheet2] [Sheet3]
5
+ */
6
+ import { type PropType } from 'vue';
7
+ import type { ISheetDef } from '@alaarab/ogrid-core';
8
+ export interface SheetTabsProps {
9
+ sheets: ISheetDef[];
10
+ activeSheet: string;
11
+ }
12
+ export declare const SheetTabs: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
13
+ sheets: {
14
+ type: PropType<ISheetDef[]>;
15
+ required: true;
16
+ };
17
+ activeSheet: {
18
+ type: StringConstructor;
19
+ required: true;
20
+ };
21
+ showAddButton: {
22
+ type: BooleanConstructor;
23
+ default: boolean;
24
+ };
25
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
26
+ [key: string]: any;
27
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("sheetChange" | "sheetAdd")[], "sheetChange" | "sheetAdd", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
28
+ sheets: {
29
+ type: PropType<ISheetDef[]>;
30
+ required: true;
31
+ };
32
+ activeSheet: {
33
+ type: StringConstructor;
34
+ required: true;
35
+ };
36
+ showAddButton: {
37
+ type: BooleanConstructor;
38
+ default: boolean;
39
+ };
40
+ }>> & Readonly<{
41
+ onSheetChange?: ((...args: any[]) => any) | undefined;
42
+ onSheetAdd?: ((...args: any[]) => any) | undefined;
43
+ }>, {
44
+ showAddButton: boolean;
45
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -32,6 +32,8 @@ export { useTableLayout } from './useTableLayout';
32
32
  export type { UseTableLayoutParams, UseTableLayoutResult } from './useTableLayout';
33
33
  export { useFormulaEngine } from './useFormulaEngine';
34
34
  export type { UseFormulaEngineParams, UseFormulaEngineResult } from './useFormulaEngine';
35
+ export { useFormulaBar } from './useFormulaBar';
36
+ export type { UseFormulaBarParams, UseFormulaBarResult } from './useFormulaBar';
35
37
  export { useColumnHeaderFilterState } from './useColumnHeaderFilterState';
36
38
  export type { UseColumnHeaderFilterStateParams, UseColumnHeaderFilterStateResult, } from './useColumnHeaderFilterState';
37
39
  export { useTextFilterState } from './useTextFilterState';
@@ -0,0 +1,44 @@
1
+ /**
2
+ * useFormulaBar — Vue composable for formula bar state.
3
+ *
4
+ * Manages the formula bar text, editing mode, and reference extraction.
5
+ */
6
+ import { type Ref } from 'vue';
7
+ import { type FormulaReference } from '@alaarab/ogrid-core';
8
+ export interface UseFormulaBarParams {
9
+ /** Active cell column index (0-based). */
10
+ activeCol: Ref<number | null>;
11
+ /** Active cell row index (0-based). */
12
+ activeRow: Ref<number | null>;
13
+ /** Active cell reference string (e.g. "A1"). */
14
+ activeCellRef: Ref<string | null>;
15
+ /** Get formula string for a cell. */
16
+ getFormula?: (col: number, row: number) => string | undefined;
17
+ /** Get raw display value for a cell. */
18
+ getRawValue?: (col: number, row: number) => unknown;
19
+ /** Set formula for a cell. */
20
+ setFormula?: (col: number, row: number, formula: string | null) => void;
21
+ /** Commit a non-formula value change. */
22
+ onCellValueChanged?: (col: number, row: number, value: unknown) => void;
23
+ }
24
+ export interface UseFormulaBarResult {
25
+ /** Cell reference string (e.g. "A1"). */
26
+ cellRef: Ref<string | null>;
27
+ /** Text shown in the formula bar input. */
28
+ formulaText: Ref<string>;
29
+ /** Whether the formula bar input is being edited. */
30
+ isEditing: Ref<boolean>;
31
+ /** Update the formula bar input text. */
32
+ onInputChange: (text: string) => void;
33
+ /** Commit the current edit. */
34
+ onCommit: () => void;
35
+ /** Cancel the current edit. */
36
+ onCancel: () => void;
37
+ /** Start editing the formula bar. */
38
+ startEditing: () => void;
39
+ /** References extracted from the current formula text (for highlighting). */
40
+ referencedCells: Ref<FormulaReference[]>;
41
+ /** Whether the formula bar is actively being edited (for click-to-insert-ref guards). */
42
+ isFormulaBarEditing: Ref<boolean>;
43
+ }
44
+ export declare function useFormulaBar(params: UseFormulaBarParams): UseFormulaBarResult;
@@ -31,6 +31,10 @@ export interface UseOGridLayout {
31
31
  };
32
32
  sideBarProps: SideBarProps | null;
33
33
  fullScreen?: boolean;
34
+ /** Formula bar element (rendered between toolbar and grid). */
35
+ formulaBar?: unknown;
36
+ /** Sheet tabs element (rendered between grid and footer). */
37
+ sheetTabs?: unknown;
34
38
  }
35
39
  /** Filter state. */
36
40
  export interface UseOGridFilters {
@@ -4,8 +4,11 @@ export type { ColumnFilterType, IColumnFilterDef, IColumnMeta, IColumnGroupDef,
4
4
  export { toUserLike, isInSelectionRange, normalizeSelectionRange } from './types';
5
5
  export { MarchingAntsOverlay } from './components/MarchingAntsOverlay';
6
6
  export { StatusBar, type StatusBarProps } from './components/StatusBar';
7
- export { useOGrid, useDataGridState, useActiveCell, useCellEditing, useCellSelection, useClipboard, useRowSelection, useKeyboardNavigation, useFillHandle, useUndoRedo, useContextMenu, useColumnResize, useColumnReorder, useVirtualScroll, useFilterOptions, useDebounce, useDebouncedCallback, useTableLayout, useColumnHeaderFilterState, useTextFilterState, useMultiSelectFilterState, usePeopleFilterState, useDateFilterState, useColumnChooserState, useInlineCellEditorState, useRichSelectState, useSideBarState, useColumnPinning, useColumnHeaderMenuState, useDataGridTableSetup, } from './composables';
8
- export type { UseOGridResult, UseOGridPagination, UseOGridColumnChooser, UseOGridLayout, UseOGridFilters, ColumnChooserPlacement, UseDataGridStateParams, UseDataGridStateResult, DataGridLayoutState, DataGridRowSelectionState, DataGridEditingState, DataGridCellInteractionState, DataGridContextMenuState, DataGridViewModelState, DataGridPinningState, UseActiveCellResult, EditingCell, UseCellEditingParams, UseCellEditingResult, UseCellSelectionParams, UseCellSelectionResult, UseClipboardParams, UseClipboardResult, UseRowSelectionParams, UseRowSelectionResult, UseKeyboardNavigationParams, UseKeyboardNavigationResult, UseFillHandleParams, UseFillHandleResult, UseUndoRedoParams, UseUndoRedoResult, ContextMenuPosition, UseContextMenuResult, UseColumnResizeParams, UseColumnResizeResult, UseColumnReorderParams, UseColumnReorderResult, UseVirtualScrollParams, UseVirtualScrollResult, UseFilterOptionsResult, UseTableLayoutParams, UseTableLayoutResult, UseColumnHeaderFilterStateParams, UseColumnHeaderFilterStateResult, UseTextFilterStateParams, UseTextFilterStateResult, UseMultiSelectFilterStateParams, UseMultiSelectFilterStateResult, UsePeopleFilterStateParams, UsePeopleFilterStateResult, UseDateFilterStateParams, UseDateFilterStateResult, UseColumnChooserStateParams, UseColumnChooserStateResult, InlineCellEditorType, UseInlineCellEditorStateParams, UseInlineCellEditorStateResult, UseRichSelectStateParams, UseRichSelectStateResult, UseSideBarStateParams, UseSideBarStateResult, DebouncedFn, UseColumnPinningParams, UseColumnPinningResult, UseColumnHeaderMenuStateParams, UseColumnHeaderMenuStateResult, UseDataGridTableSetupParams, UseDataGridTableSetupResult, MaybeShallowRef, } from './composables';
7
+ export { FormulaBar, type FormulaBarProps } from './components/FormulaBar';
8
+ export { SheetTabs, type SheetTabsProps } from './components/SheetTabs';
9
+ export { FormulaRefOverlay } from './components/FormulaRefOverlay';
10
+ export { useOGrid, useDataGridState, useActiveCell, useCellEditing, useCellSelection, useClipboard, useRowSelection, useKeyboardNavigation, useFillHandle, useUndoRedo, useContextMenu, useColumnResize, useColumnReorder, useVirtualScroll, useFilterOptions, useDebounce, useDebouncedCallback, useTableLayout, useColumnHeaderFilterState, useTextFilterState, useMultiSelectFilterState, usePeopleFilterState, useDateFilterState, useColumnChooserState, useInlineCellEditorState, useRichSelectState, useSideBarState, useColumnPinning, useColumnHeaderMenuState, useDataGridTableSetup, useFormulaBar, } from './composables';
11
+ export type { UseOGridResult, UseOGridPagination, UseOGridColumnChooser, UseOGridLayout, UseOGridFilters, ColumnChooserPlacement, UseDataGridStateParams, UseDataGridStateResult, DataGridLayoutState, DataGridRowSelectionState, DataGridEditingState, DataGridCellInteractionState, DataGridContextMenuState, DataGridViewModelState, DataGridPinningState, UseActiveCellResult, EditingCell, UseCellEditingParams, UseCellEditingResult, UseCellSelectionParams, UseCellSelectionResult, UseClipboardParams, UseClipboardResult, UseRowSelectionParams, UseRowSelectionResult, UseKeyboardNavigationParams, UseKeyboardNavigationResult, UseFillHandleParams, UseFillHandleResult, UseUndoRedoParams, UseUndoRedoResult, ContextMenuPosition, UseContextMenuResult, UseColumnResizeParams, UseColumnResizeResult, UseColumnReorderParams, UseColumnReorderResult, UseVirtualScrollParams, UseVirtualScrollResult, UseFilterOptionsResult, UseTableLayoutParams, UseTableLayoutResult, UseColumnHeaderFilterStateParams, UseColumnHeaderFilterStateResult, UseTextFilterStateParams, UseTextFilterStateResult, UseMultiSelectFilterStateParams, UseMultiSelectFilterStateResult, UsePeopleFilterStateParams, UsePeopleFilterStateResult, UseDateFilterStateParams, UseDateFilterStateResult, UseColumnChooserStateParams, UseColumnChooserStateResult, InlineCellEditorType, UseInlineCellEditorStateParams, UseInlineCellEditorStateResult, UseRichSelectStateParams, UseRichSelectStateResult, UseSideBarStateParams, UseSideBarStateResult, DebouncedFn, UseColumnPinningParams, UseColumnPinningResult, UseColumnHeaderMenuStateParams, UseColumnHeaderMenuStateResult, UseDataGridTableSetupParams, UseDataGridTableSetupResult, MaybeShallowRef, UseFormulaBarParams, UseFormulaBarResult, } from './composables';
9
12
  export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, } from './utils';
10
13
  export type { HeaderFilterConfigInput, HeaderFilterConfig, CellRenderDescriptorInput, CellRenderDescriptor, CellRenderMode, CellInteractionHandlers, CellInteractionProps, } from './utils';
11
14
  export { createDataGridTable, type IDataGridTableUIBindings } from './components/createDataGridTable';
@@ -1,7 +1,7 @@
1
1
  import type { IColumnDef, IColumnGroupDef, ICellValueChangedEvent } from './columnTypes';
2
- export type { RowId, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, RowSelectionMode, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, SideBarPanelId, ISideBarDef, IOGridApi, IVirtualScrollConfig, IFormulaFunction, IRecalcResult, IGridDataAccessor, IAuditEntry, IAuditTrail, } from '@alaarab/ogrid-core';
2
+ export type { RowId, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, RowSelectionMode, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, SideBarPanelId, ISideBarDef, IOGridApi, IVirtualScrollConfig, IFormulaFunction, IRecalcResult, IGridDataAccessor, IAuditEntry, IAuditTrail, ISheetDef, FormulaReference, } from '@alaarab/ogrid-core';
3
3
  export { toUserLike, isInSelectionRange, normalizeSelectionRange } from '@alaarab/ogrid-core';
4
- import type { RowId, UserLike, IFilters, FilterValue, RowSelectionMode, IRowSelectionChangeEvent, IStatusBarProps, IDataSource, ISideBarDef, IVirtualScrollConfig, IFormulaFunction, IRecalcResult, IGridDataAccessor, IAuditEntry, IAuditTrail } from '@alaarab/ogrid-core';
4
+ import type { RowId, UserLike, IFilters, FilterValue, RowSelectionMode, IRowSelectionChangeEvent, IStatusBarProps, IDataSource, ISideBarDef, IVirtualScrollConfig, IFormulaFunction, IRecalcResult, IGridDataAccessor, IAuditEntry, IAuditTrail, ISheetDef, FormulaReference } from '@alaarab/ogrid-core';
5
5
  /** Base props shared by both client-side and server-side OGrid modes. */
6
6
  interface IOGridBaseProps<T> {
7
7
  columns: (IColumnDef<T> | IColumnGroupDef<T>)[];
@@ -105,6 +105,14 @@ interface IOGridBaseProps<T> {
105
105
  namedRanges?: Record<string, string>;
106
106
  /** Sheet accessors for cross-sheet formula references (e.g. { Sheet2: accessor }). */
107
107
  sheets?: Record<string, IGridDataAccessor>;
108
+ /** Sheet definitions for the tab bar at grid bottom. */
109
+ sheetDefs?: ISheetDef[];
110
+ /** Active sheet ID (controlled). */
111
+ activeSheet?: string;
112
+ /** Called when user clicks a sheet tab. */
113
+ onSheetChange?: (sheetId: string) => void;
114
+ /** Called when user clicks the "+" add sheet button. */
115
+ onSheetAdd?: () => void;
108
116
  'aria-label'?: string;
109
117
  'aria-labelledby'?: string;
110
118
  }
@@ -215,4 +223,8 @@ export interface IOGridDataGridProps<T> {
215
223
  getDependents?: (col: number, row: number) => IAuditEntry[];
216
224
  /** Get full audit trail for a cell. */
217
225
  getAuditTrail?: (col: number, row: number) => IAuditTrail | null;
226
+ /** Monotonic counter incremented on each formula recalculation — used for cache invalidation. */
227
+ formulaVersion?: number;
228
+ /** Cell references to highlight (from active formula in formula bar). */
229
+ formulaReferences?: FormulaReference[];
218
230
  }
@@ -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, 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-vue",
3
- "version": "2.3.0",
3
+ "version": "2.4.1",
4
4
  "description": "OGrid Vue – Vue 3 composables, headless components, and utilities for OGrid data grids.",
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.3.0"
39
+ "@alaarab/ogrid-core": "2.4.1"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "vue": "^3.3.0"