@alaarab/ogrid-vue 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.
- package/dist/esm/index.js +260 -40
- package/dist/esm/styles/ogrid-layout.css +26 -0
- package/dist/types/composables/index.d.ts +2 -0
- package/dist/types/composables/useFormulaEngine.d.ts +52 -0
- package/dist/types/types/dataGridTypes.d.ts +42 -2
- package/dist/types/utils/dataGridViewModel.d.ts +1 -0
- package/package.json +2 -2
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, buildHeaderRows, 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, getCellValue, calculateDropTarget, reorderColumnArray, computeAutoScrollSpeed } from '@alaarab/ogrid-core';
|
|
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';
|
|
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';
|
|
@@ -210,6 +210,126 @@ function useFilterOptions(dataSource, fields) {
|
|
|
210
210
|
}, { immediate: true });
|
|
211
211
|
return { filterOptions, loadingOptions };
|
|
212
212
|
}
|
|
213
|
+
function useLatestRef(source) {
|
|
214
|
+
let value = unref(source);
|
|
215
|
+
return customRef((track, trigger) => ({
|
|
216
|
+
get() {
|
|
217
|
+
if (isRef(source)) {
|
|
218
|
+
value = source.value;
|
|
219
|
+
}
|
|
220
|
+
return value;
|
|
221
|
+
},
|
|
222
|
+
set(newValue) {
|
|
223
|
+
value = newValue;
|
|
224
|
+
trigger();
|
|
225
|
+
}
|
|
226
|
+
}));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/composables/useFormulaEngine.ts
|
|
230
|
+
function useFormulaEngine(params) {
|
|
231
|
+
const {
|
|
232
|
+
formulas,
|
|
233
|
+
items,
|
|
234
|
+
flatColumns,
|
|
235
|
+
initialFormulas,
|
|
236
|
+
onFormulaRecalc,
|
|
237
|
+
formulaFunctions,
|
|
238
|
+
namedRanges,
|
|
239
|
+
sheets
|
|
240
|
+
} = params;
|
|
241
|
+
const itemsRef = useLatestRef(items);
|
|
242
|
+
const flatColumnsRef = useLatestRef(flatColumns);
|
|
243
|
+
const onFormulaRecalcRef = useLatestRef(onFormulaRecalc);
|
|
244
|
+
const engineRef = shallowRef(null);
|
|
245
|
+
let initialLoaded = false;
|
|
246
|
+
const enabled = computed(() => formulas?.value ?? false);
|
|
247
|
+
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
|
+
};
|
|
259
|
+
}
|
|
260
|
+
watch(
|
|
261
|
+
enabled,
|
|
262
|
+
(isEnabled) => {
|
|
263
|
+
if (isEnabled && !engineRef.value) {
|
|
264
|
+
engineRef.value = new FormulaEngine({
|
|
265
|
+
customFunctions: formulaFunctions,
|
|
266
|
+
namedRanges
|
|
267
|
+
});
|
|
268
|
+
if (sheets) {
|
|
269
|
+
for (const [name, accessor] of Object.entries(sheets)) {
|
|
270
|
+
engineRef.value.registerSheet(name, accessor);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (initialFormulas && !initialLoaded) {
|
|
274
|
+
initialLoaded = true;
|
|
275
|
+
const accessor = createAccessor();
|
|
276
|
+
const result = engineRef.value.loadFormulas(initialFormulas, accessor);
|
|
277
|
+
if (result.updatedCells.length > 0) {
|
|
278
|
+
onFormulaRecalcRef.value?.(result);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
} else if (!isEnabled && engineRef.value) {
|
|
282
|
+
engineRef.value = null;
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
{ immediate: true }
|
|
286
|
+
);
|
|
287
|
+
function getFormulaValue(col, row) {
|
|
288
|
+
return engineRef.value?.getValue(col, row);
|
|
289
|
+
}
|
|
290
|
+
function hasFormula(col, row) {
|
|
291
|
+
return engineRef.value?.hasFormula(col, row) ?? false;
|
|
292
|
+
}
|
|
293
|
+
function getFormula(col, row) {
|
|
294
|
+
return engineRef.value?.getFormula(col, row);
|
|
295
|
+
}
|
|
296
|
+
function setFormula(col, row, formula) {
|
|
297
|
+
if (!engineRef.value) return;
|
|
298
|
+
const accessor = createAccessor();
|
|
299
|
+
const result = engineRef.value.setFormula(col, row, formula, accessor);
|
|
300
|
+
if (result.updatedCells.length > 0) {
|
|
301
|
+
onFormulaRecalcRef.value?.(result);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function onCellChanged(col, row) {
|
|
305
|
+
if (!engineRef.value) return;
|
|
306
|
+
const accessor = createAccessor();
|
|
307
|
+
const result = engineRef.value.onCellChanged(col, row, accessor);
|
|
308
|
+
if (result.updatedCells.length > 0) {
|
|
309
|
+
onFormulaRecalcRef.value?.(result);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function getPrecedents(col, row) {
|
|
313
|
+
return engineRef.value?.getPrecedents(col, row) ?? [];
|
|
314
|
+
}
|
|
315
|
+
function getDependents(col, row) {
|
|
316
|
+
return engineRef.value?.getDependents(col, row) ?? [];
|
|
317
|
+
}
|
|
318
|
+
function getAuditTrail(col, row) {
|
|
319
|
+
return engineRef.value?.getAuditTrail(col, row) ?? null;
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
enabled,
|
|
323
|
+
getFormulaValue,
|
|
324
|
+
hasFormula,
|
|
325
|
+
getFormula,
|
|
326
|
+
setFormula,
|
|
327
|
+
onCellChanged,
|
|
328
|
+
getPrecedents,
|
|
329
|
+
getDependents,
|
|
330
|
+
getAuditTrail
|
|
331
|
+
};
|
|
332
|
+
}
|
|
213
333
|
var DEFAULT_PANELS = ["columns", "filters"];
|
|
214
334
|
function useSideBarState(params) {
|
|
215
335
|
const { config } = params;
|
|
@@ -494,6 +614,17 @@ function useOGrid(props) {
|
|
|
494
614
|
const displayTotalCount = computed(
|
|
495
615
|
() => isClientSide.value && resolvedClientItems.value ? resolvedClientItems.value.totalCount : serverTotalCount.value
|
|
496
616
|
);
|
|
617
|
+
const formulasRef = computed(() => !!props.value.formulas);
|
|
618
|
+
const formulaEngine = useFormulaEngine({
|
|
619
|
+
formulas: formulasRef,
|
|
620
|
+
items: displayItems,
|
|
621
|
+
flatColumns: columns,
|
|
622
|
+
initialFormulas: props.value.initialFormulas,
|
|
623
|
+
onFormulaRecalc: props.value.onFormulaRecalc,
|
|
624
|
+
formulaFunctions: props.value.formulaFunctions,
|
|
625
|
+
namedRanges: props.value.namedRanges,
|
|
626
|
+
sheets: props.value.sheets
|
|
627
|
+
});
|
|
497
628
|
let firstDataRendered = false;
|
|
498
629
|
let rowIdsValidated = false;
|
|
499
630
|
watch(displayItems, (items) => {
|
|
@@ -581,6 +712,10 @@ function useOGrid(props) {
|
|
|
581
712
|
});
|
|
582
713
|
const clearAllFilters = () => setFilters({});
|
|
583
714
|
const isLoadingResolved = computed(() => isServerSide.value && loading.value || displayLoading.value);
|
|
715
|
+
const activeCellRef = ref(null);
|
|
716
|
+
const onActiveCellChange = (cellRef) => {
|
|
717
|
+
activeCellRef.value = cellRef;
|
|
718
|
+
};
|
|
584
719
|
const dataGridProps = computed(() => {
|
|
585
720
|
const p = props.value;
|
|
586
721
|
const ds = dataProps.value.dataSource;
|
|
@@ -608,7 +743,10 @@ function useOGrid(props) {
|
|
|
608
743
|
rowSelection: p.rowSelection ?? "none",
|
|
609
744
|
selectedRows: effectiveSelectedRows.value,
|
|
610
745
|
onSelectionChange: handleSelectionChange,
|
|
611
|
-
showRowNumbers: p.showRowNumbers,
|
|
746
|
+
showRowNumbers: p.showRowNumbers || p.cellReferences,
|
|
747
|
+
showColumnLetters: !!p.cellReferences,
|
|
748
|
+
showNameBox: !!p.cellReferences,
|
|
749
|
+
onActiveCellChange: p.cellReferences ? onActiveCellChange : void 0,
|
|
612
750
|
currentPage: page.value,
|
|
613
751
|
pageSize: pageSize.value,
|
|
614
752
|
statusBar: statusBarConfig.value,
|
|
@@ -633,7 +771,18 @@ function useOGrid(props) {
|
|
|
633
771
|
onClearAll: clearAllFilters,
|
|
634
772
|
message: p.emptyState?.message,
|
|
635
773
|
render: p.emptyState?.render
|
|
636
|
-
}
|
|
774
|
+
},
|
|
775
|
+
formulas: p.formulas,
|
|
776
|
+
...formulaEngine.enabled.value ? {
|
|
777
|
+
getFormulaValue: formulaEngine.getFormulaValue,
|
|
778
|
+
hasFormula: formulaEngine.hasFormula,
|
|
779
|
+
getFormula: formulaEngine.getFormula,
|
|
780
|
+
setFormula: formulaEngine.setFormula,
|
|
781
|
+
onFormulaCellChanged: formulaEngine.onCellChanged,
|
|
782
|
+
getPrecedents: formulaEngine.getPrecedents,
|
|
783
|
+
getDependents: formulaEngine.getDependents,
|
|
784
|
+
getAuditTrail: formulaEngine.getAuditTrail
|
|
785
|
+
} : {}
|
|
637
786
|
};
|
|
638
787
|
});
|
|
639
788
|
const pagination = computed(() => ({
|
|
@@ -651,14 +800,38 @@ function useOGrid(props) {
|
|
|
651
800
|
onVisibilityChange: handleVisibilityChange,
|
|
652
801
|
placement: columnChooserPlacement.value
|
|
653
802
|
}));
|
|
654
|
-
const layout = computed(() =>
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
803
|
+
const layout = computed(() => {
|
|
804
|
+
const showNameBox = !!props.value.cellReferences;
|
|
805
|
+
let resolvedToolbar = props.value.toolbar;
|
|
806
|
+
if (showNameBox) {
|
|
807
|
+
const nameBoxEl = h("div", {
|
|
808
|
+
style: {
|
|
809
|
+
display: "inline-flex",
|
|
810
|
+
alignItems: "center",
|
|
811
|
+
padding: "0 8px",
|
|
812
|
+
fontFamily: "'Consolas', 'Courier New', monospace",
|
|
813
|
+
fontSize: "12px",
|
|
814
|
+
border: "1px solid rgba(0,0,0,0.12)",
|
|
815
|
+
borderRadius: "3px",
|
|
816
|
+
height: "24px",
|
|
817
|
+
marginRight: "8px",
|
|
818
|
+
background: "#fff",
|
|
819
|
+
minWidth: "40px",
|
|
820
|
+
color: "rgba(0,0,0,0.6)"
|
|
821
|
+
},
|
|
822
|
+
"aria-label": "Active cell reference"
|
|
823
|
+
}, activeCellRef.value ?? "\u2014");
|
|
824
|
+
resolvedToolbar = [nameBoxEl, resolvedToolbar];
|
|
825
|
+
}
|
|
826
|
+
return {
|
|
827
|
+
toolbar: resolvedToolbar,
|
|
828
|
+
toolbarBelow: props.value.toolbarBelow,
|
|
829
|
+
className: props.value.className,
|
|
830
|
+
emptyState: props.value.emptyState,
|
|
831
|
+
sideBarProps: sideBarProps.value,
|
|
832
|
+
fullScreen: props.value.fullScreen
|
|
833
|
+
};
|
|
834
|
+
});
|
|
662
835
|
const filtersResult = computed(() => ({
|
|
663
836
|
hasActiveFilters: hasActiveFilters.value,
|
|
664
837
|
setFilters
|
|
@@ -880,23 +1053,6 @@ function useActiveCell(wrapperRef, editingCell) {
|
|
|
880
1053
|
});
|
|
881
1054
|
return { activeCell, setActiveCell };
|
|
882
1055
|
}
|
|
883
|
-
function useLatestRef(source) {
|
|
884
|
-
let value = unref(source);
|
|
885
|
-
return customRef((track, trigger) => ({
|
|
886
|
-
get() {
|
|
887
|
-
if (isRef(source)) {
|
|
888
|
-
value = source.value;
|
|
889
|
-
}
|
|
890
|
-
return value;
|
|
891
|
-
},
|
|
892
|
-
set(newValue) {
|
|
893
|
-
value = newValue;
|
|
894
|
-
trigger();
|
|
895
|
-
}
|
|
896
|
-
}));
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
// src/composables/useCellSelection.ts
|
|
900
1056
|
var DRAG_ATTR = "data-drag-range";
|
|
901
1057
|
var DRAG_ANCHOR_ATTR = "data-drag-anchor";
|
|
902
1058
|
var AUTO_SCROLL_EDGE = 40;
|
|
@@ -1313,7 +1469,7 @@ function useKeyboardNavigation(params) {
|
|
|
1313
1469
|
const maxColIndex = visibleColumnCount - 1 + colOffset;
|
|
1314
1470
|
if (items.length === 0) return;
|
|
1315
1471
|
if (activeCell === null) {
|
|
1316
|
-
if (["ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight", "Tab", "Enter", "Home", "End"].includes(e.key)) {
|
|
1472
|
+
if (["ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight", "Tab", "Enter", "Home", "End", "PageDown", "PageUp"].includes(e.key)) {
|
|
1317
1473
|
setActiveCell({ rowIndex: 0, columnIndex: colOffset });
|
|
1318
1474
|
e.preventDefault();
|
|
1319
1475
|
}
|
|
@@ -1413,6 +1569,36 @@ function useKeyboardNavigation(params) {
|
|
|
1413
1569
|
setActiveCell({ rowIndex: newRowEnd, columnIndex: maxColIndex });
|
|
1414
1570
|
break;
|
|
1415
1571
|
}
|
|
1572
|
+
case "PageDown":
|
|
1573
|
+
case "PageUp": {
|
|
1574
|
+
e.preventDefault();
|
|
1575
|
+
const wrapperEl = wrapperRef.value;
|
|
1576
|
+
let pageSize = 10;
|
|
1577
|
+
if (wrapperEl) {
|
|
1578
|
+
const row = wrapperEl.querySelector("tbody tr");
|
|
1579
|
+
if (row && row.offsetHeight > 0) pageSize = Math.max(1, Math.floor(wrapperEl.clientHeight / row.offsetHeight));
|
|
1580
|
+
}
|
|
1581
|
+
const pgDirection = e.key === "PageDown" ? 1 : -1;
|
|
1582
|
+
const newRowPage = Math.max(0, Math.min(rowIndex + pgDirection * pageSize, maxRowIndex));
|
|
1583
|
+
if (shift) {
|
|
1584
|
+
setSelectionRange({
|
|
1585
|
+
startRow: selectionRange?.startRow ?? rowIndex,
|
|
1586
|
+
startCol: selectionRange?.startCol ?? dataColIndex,
|
|
1587
|
+
endRow: newRowPage,
|
|
1588
|
+
endCol: selectionRange?.endCol ?? dataColIndex
|
|
1589
|
+
});
|
|
1590
|
+
} else {
|
|
1591
|
+
setSelectionRange({
|
|
1592
|
+
startRow: newRowPage,
|
|
1593
|
+
startCol: dataColIndex,
|
|
1594
|
+
endRow: newRowPage,
|
|
1595
|
+
endCol: dataColIndex
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
setActiveCell({ rowIndex: newRowPage, columnIndex });
|
|
1599
|
+
scrollToRow?.(newRowPage, "center");
|
|
1600
|
+
break;
|
|
1601
|
+
}
|
|
1416
1602
|
case "Enter":
|
|
1417
1603
|
case "F2": {
|
|
1418
1604
|
e.preventDefault();
|
|
@@ -2970,14 +3156,14 @@ function useInlineCellEditorState(params) {
|
|
|
2970
3156
|
e.stopPropagation();
|
|
2971
3157
|
cancel();
|
|
2972
3158
|
}
|
|
2973
|
-
if (e.key === "Enter" && editorType === "text") {
|
|
3159
|
+
if (e.key === "Enter" && (editorType === "text" || editorType === "date")) {
|
|
2974
3160
|
e.preventDefault();
|
|
2975
3161
|
e.stopPropagation();
|
|
2976
3162
|
commit(localValue.value);
|
|
2977
3163
|
}
|
|
2978
3164
|
};
|
|
2979
3165
|
const handleBlur = () => {
|
|
2980
|
-
if (editorType === "text") {
|
|
3166
|
+
if (editorType === "text" || editorType === "date") {
|
|
2981
3167
|
commit(localValue.value);
|
|
2982
3168
|
}
|
|
2983
3169
|
};
|
|
@@ -3336,6 +3522,7 @@ function getCellInteractionProps(descriptor, columnId, handlers) {
|
|
|
3336
3522
|
const base = {
|
|
3337
3523
|
"data-row-index": descriptor.rowIndex,
|
|
3338
3524
|
"data-col-index": descriptor.globalColIndex,
|
|
3525
|
+
...descriptor.isActive ? { "data-active-cell": "true" } : {},
|
|
3339
3526
|
...descriptor.isInRange ? { "data-in-range": "true" } : {},
|
|
3340
3527
|
tabindex: descriptor.isActive ? 0 : -1,
|
|
3341
3528
|
onMousedown: (e) => handlers.handleCellMouseDown(e, descriptor.rowIndex, descriptor.globalColIndex),
|
|
@@ -3371,6 +3558,24 @@ function createDataGridTable(ui) {
|
|
|
3371
3558
|
columnPartition,
|
|
3372
3559
|
globalColIndexMap
|
|
3373
3560
|
} = useDataGridTableSetup({ props: propsRef });
|
|
3561
|
+
const rowNumberOffset = computed(() => {
|
|
3562
|
+
const p = propsRef.value;
|
|
3563
|
+
const hasRowNumbers = p.showRowNumbers || p.showColumnLetters;
|
|
3564
|
+
return hasRowNumbers ? ((p.currentPage ?? 1) - 1) * (p.pageSize ?? 25) : 0;
|
|
3565
|
+
});
|
|
3566
|
+
watch(
|
|
3567
|
+
[() => state.interaction.value.activeCell, rowNumberOffset],
|
|
3568
|
+
([ac, offset]) => {
|
|
3569
|
+
const cb = propsRef.value.onActiveCellChange;
|
|
3570
|
+
if (!cb) return;
|
|
3571
|
+
if (ac) {
|
|
3572
|
+
cb(formatCellReference(ac.columnIndex, offset + ac.rowIndex + 1));
|
|
3573
|
+
} else {
|
|
3574
|
+
cb(null);
|
|
3575
|
+
}
|
|
3576
|
+
},
|
|
3577
|
+
{ immediate: true }
|
|
3578
|
+
);
|
|
3374
3579
|
const onWrapperMousedown = (e) => {
|
|
3375
3580
|
lastMouseShift.value = e.shiftKey;
|
|
3376
3581
|
};
|
|
@@ -3446,7 +3651,7 @@ function createDataGridTable(ui) {
|
|
|
3446
3651
|
} = layout;
|
|
3447
3652
|
const currentPage = p.currentPage ?? 1;
|
|
3448
3653
|
const pageSize = p.pageSize ?? 25;
|
|
3449
|
-
const
|
|
3654
|
+
const rowNumberOffset2 = hasRowNumbersCol ? (currentPage - 1) * pageSize : 0;
|
|
3450
3655
|
const { selectedRowIds, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected } = rowSel;
|
|
3451
3656
|
const { editingCell: _editingCell, setEditingCell, pendingEditorValue, setPendingEditorValue, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl } = editing;
|
|
3452
3657
|
const {
|
|
@@ -3536,13 +3741,14 @@ function createDataGridTable(ui) {
|
|
|
3536
3741
|
]);
|
|
3537
3742
|
}
|
|
3538
3743
|
const content = resolveCellDisplayContent(col, item, descriptor.displayValue);
|
|
3539
|
-
const cellStyle = resolveCellStyle(col, item);
|
|
3744
|
+
const cellStyle = resolveCellStyle(col, item, descriptor.displayValue);
|
|
3540
3745
|
const interactionProps2 = getCellInteractionProps(descriptor, col.columnId, interactionHandlers);
|
|
3541
3746
|
const cellClasses = ["ogrid-cell-content"];
|
|
3542
3747
|
if (col.type === "numeric") cellClasses.push("ogrid-cell-content--numeric");
|
|
3543
3748
|
else if (col.type === "boolean") cellClasses.push("ogrid-cell-content--boolean");
|
|
3544
3749
|
if (descriptor.canEditAny) cellClasses.push("ogrid-cell-content--editable");
|
|
3545
3750
|
if (descriptor.isActive) cellClasses.push("ogrid-cell-content--active");
|
|
3751
|
+
if (descriptor.isActive && descriptor.isInRange) cellClasses.push("ogrid-cell-content--active-in-range");
|
|
3546
3752
|
if (descriptor.isInRange && !descriptor.isActive) cellClasses.push("ogrid-cell-in-range");
|
|
3547
3753
|
if (descriptor.isInCutRange) cellClasses.push("ogrid-cell-cut");
|
|
3548
3754
|
const styledContent = cellStyle ? h("span", { style: cellStyle }, content) : content;
|
|
@@ -3644,10 +3850,23 @@ function createDataGridTable(ui) {
|
|
|
3644
3850
|
...virtualScrollEnabled.value ? { "data-virtual-scroll": "" } : {}
|
|
3645
3851
|
}, [
|
|
3646
3852
|
// Header
|
|
3647
|
-
h(
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3853
|
+
h("thead", { class: stickyHeader ? "ogrid-thead ogrid-sticky-header" : "ogrid-thead" }, [
|
|
3854
|
+
// Column letter row (A, B, C...) for cell references
|
|
3855
|
+
...p.showColumnLetters ? [
|
|
3856
|
+
h("tr", { class: "ogrid-column-letter-row" }, [
|
|
3857
|
+
...hasCheckboxCol ? [h("th", { class: "ogrid-column-letter-cell" })] : [],
|
|
3858
|
+
...hasRowNumbersCol ? [h("th", { class: "ogrid-column-letter-cell" })] : [],
|
|
3859
|
+
...visibleCols.map((col, colIdx) => {
|
|
3860
|
+
const { classes: hdrCls, style: hdrSty } = getHeaderClassAndStyle(col);
|
|
3861
|
+
return h("th", {
|
|
3862
|
+
key: col.columnId,
|
|
3863
|
+
class: `ogrid-column-letter-cell ${hdrCls}`,
|
|
3864
|
+
style: hdrSty
|
|
3865
|
+
}, indexToColumnLetter(colIdx));
|
|
3866
|
+
})
|
|
3867
|
+
])
|
|
3868
|
+
] : [],
|
|
3869
|
+
...headerRows.map(
|
|
3651
3870
|
(row, rowIdx) => h("tr", { key: rowIdx, class: "ogrid-header-row" }, [
|
|
3652
3871
|
// Checkbox header cell
|
|
3653
3872
|
...rowIdx === headerRows.length - 1 && hasCheckboxCol ? [
|
|
@@ -3757,7 +3976,7 @@ function createDataGridTable(ui) {
|
|
|
3757
3976
|
})
|
|
3758
3977
|
])
|
|
3759
3978
|
)
|
|
3760
|
-
),
|
|
3979
|
+
]),
|
|
3761
3980
|
// Body
|
|
3762
3981
|
...!showEmptyInGrid ? [
|
|
3763
3982
|
h("tbody", {}, (() => {
|
|
@@ -3822,7 +4041,7 @@ function createDataGridTable(ui) {
|
|
|
3822
4041
|
left: hasCheckboxCol ? `${CHECKBOX_COLUMN_WIDTH}px` : "0",
|
|
3823
4042
|
zIndex: 2
|
|
3824
4043
|
}
|
|
3825
|
-
}, String(
|
|
4044
|
+
}, String(rowNumberOffset2 + rowIndex + 1))
|
|
3826
4045
|
] : [],
|
|
3827
4046
|
// Left spacer for column virtualization
|
|
3828
4047
|
...leftSpacerWidth > 0 ? [
|
|
@@ -3978,6 +4197,7 @@ function createInlineCellEditor(options) {
|
|
|
3978
4197
|
dropdown.style.maxHeight = `${maxH}px`;
|
|
3979
4198
|
dropdown.style.zIndex = "9999";
|
|
3980
4199
|
dropdown.style.right = "auto";
|
|
4200
|
+
dropdown.style.textAlign = "left";
|
|
3981
4201
|
if (flipUp) {
|
|
3982
4202
|
dropdown.style.top = "auto";
|
|
3983
4203
|
dropdown.style.bottom = `${window.innerHeight - rect.top}px`;
|
|
@@ -4083,7 +4303,7 @@ function createInlineCellEditor(options) {
|
|
|
4083
4303
|
selectDropdownRef.value = el;
|
|
4084
4304
|
},
|
|
4085
4305
|
role: "listbox",
|
|
4086
|
-
style: { position: "absolute", top: "100%", left: "0", right: "0", maxHeight: "200px", overflowY: "auto", background: "var(--ogrid-bg, #fff)", border: "1px solid var(--ogrid-border, rgba(0,0,0,0.12))", zIndex: "10", boxShadow: "0 4px 16px rgba(0,0,0,0.2)" }
|
|
4306
|
+
style: { position: "absolute", top: "100%", left: "0", right: "0", maxHeight: "200px", overflowY: "auto", background: "var(--ogrid-bg, #fff)", border: "1px solid var(--ogrid-border, rgba(0,0,0,0.12))", zIndex: "10", boxShadow: "0 4px 16px rgba(0,0,0,0.2)", textAlign: "left" }
|
|
4087
4307
|
}, values.map(
|
|
4088
4308
|
(v, i) => h("div", {
|
|
4089
4309
|
key: String(v),
|
|
@@ -140,6 +140,24 @@
|
|
|
140
140
|
color: var(--ogrid-fg);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/* === Column letter row (cell references) === */
|
|
144
|
+
|
|
145
|
+
.ogrid-column-letter-row {
|
|
146
|
+
background: var(--ogrid-column-letter-bg, var(--ogrid-header-bg));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.ogrid-column-letter-cell {
|
|
150
|
+
text-align: center;
|
|
151
|
+
font-size: 11px;
|
|
152
|
+
font-weight: 500;
|
|
153
|
+
color: var(--ogrid-fg-muted, rgba(0, 0, 0, 0.5));
|
|
154
|
+
padding: 2px 4px;
|
|
155
|
+
background: var(--ogrid-column-letter-bg, var(--ogrid-header-bg));
|
|
156
|
+
border-bottom: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12));
|
|
157
|
+
user-select: none;
|
|
158
|
+
font-variant-numeric: tabular-nums;
|
|
159
|
+
}
|
|
160
|
+
|
|
143
161
|
/* === Checkbox column === */
|
|
144
162
|
|
|
145
163
|
.ogrid-checkbox-header,
|
|
@@ -254,6 +272,14 @@
|
|
|
254
272
|
overflow: visible;
|
|
255
273
|
}
|
|
256
274
|
|
|
275
|
+
/* Active cell inside a selection range: suppress outline (Excel behavior).
|
|
276
|
+
The range overlay border is sufficient; the active cell is distinguished
|
|
277
|
+
by its white background vs. the dimmed range cells. */
|
|
278
|
+
.ogrid-cell-content--active-in-range {
|
|
279
|
+
outline: none;
|
|
280
|
+
background: var(--ogrid-bg, #fff);
|
|
281
|
+
}
|
|
282
|
+
|
|
257
283
|
/* === Editing cell wrapper === */
|
|
258
284
|
|
|
259
285
|
.ogrid-editing-cell {
|
|
@@ -30,6 +30,8 @@ export { useLatestRef } from './useLatestRef';
|
|
|
30
30
|
export type { MaybeShallowRef } from './useLatestRef';
|
|
31
31
|
export { useTableLayout } from './useTableLayout';
|
|
32
32
|
export type { UseTableLayoutParams, UseTableLayoutResult } from './useTableLayout';
|
|
33
|
+
export { useFormulaEngine } from './useFormulaEngine';
|
|
34
|
+
export type { UseFormulaEngineParams, UseFormulaEngineResult } from './useFormulaEngine';
|
|
33
35
|
export { useColumnHeaderFilterState } from './useColumnHeaderFilterState';
|
|
34
36
|
export type { UseColumnHeaderFilterStateParams, UseColumnHeaderFilterStateResult, } from './useColumnHeaderFilterState';
|
|
35
37
|
export { useTextFilterState } from './useTextFilterState';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useFormulaEngine — Vue composable for integrating the formula engine with the grid.
|
|
3
|
+
*
|
|
4
|
+
* Lazily creates a FormulaEngine instance when `formulas` ref is true.
|
|
5
|
+
* Provides accessor bridge between grid data and formula coordinates.
|
|
6
|
+
* Tree-shakeable: if `formulas` is false, no formula code is loaded.
|
|
7
|
+
*/
|
|
8
|
+
import { type Ref } from 'vue';
|
|
9
|
+
import { type IGridDataAccessor, type IFormulaFunction, type IRecalcResult, type IColumnDef, type IAuditEntry, type IAuditTrail } from '@alaarab/ogrid-core';
|
|
10
|
+
export interface UseFormulaEngineParams<T> {
|
|
11
|
+
/** Enable formula support. */
|
|
12
|
+
formulas?: Ref<boolean>;
|
|
13
|
+
/** Grid data items. */
|
|
14
|
+
items: Ref<T[]>;
|
|
15
|
+
/** Flat leaf columns (for mapping column index <-> columnId). */
|
|
16
|
+
flatColumns: Ref<IColumnDef<T>[]>;
|
|
17
|
+
/** Initial formulas to load. */
|
|
18
|
+
initialFormulas?: Array<{
|
|
19
|
+
col: number;
|
|
20
|
+
row: number;
|
|
21
|
+
formula: string;
|
|
22
|
+
}>;
|
|
23
|
+
/** Called when recalculation produces cascading updates. */
|
|
24
|
+
onFormulaRecalc?: (result: IRecalcResult) => void;
|
|
25
|
+
/** Custom formula functions. */
|
|
26
|
+
formulaFunctions?: Record<string, IFormulaFunction>;
|
|
27
|
+
/** Named ranges: name → cell/range reference string. */
|
|
28
|
+
namedRanges?: Record<string, string>;
|
|
29
|
+
/** Sheet accessors for cross-sheet references. */
|
|
30
|
+
sheets?: Record<string, IGridDataAccessor>;
|
|
31
|
+
}
|
|
32
|
+
export interface UseFormulaEngineResult {
|
|
33
|
+
/** Whether formula support is active. */
|
|
34
|
+
enabled: Ref<boolean>;
|
|
35
|
+
/** Get the formula engine's computed value for a cell coordinate. */
|
|
36
|
+
getFormulaValue: (col: number, row: number) => unknown;
|
|
37
|
+
/** Check if a cell has a formula. */
|
|
38
|
+
hasFormula: (col: number, row: number) => boolean;
|
|
39
|
+
/** Get the formula string for a cell. */
|
|
40
|
+
getFormula: (col: number, row: number) => string | undefined;
|
|
41
|
+
/** Set or clear a formula for a cell. Triggers recalculation. */
|
|
42
|
+
setFormula: (col: number, row: number, formula: string | null) => void;
|
|
43
|
+
/** Notify the engine that a non-formula cell value changed. Triggers dependent recalc. */
|
|
44
|
+
onCellChanged: (col: number, row: number) => void;
|
|
45
|
+
/** Get all cells that a cell depends on (deep, transitive). */
|
|
46
|
+
getPrecedents: (col: number, row: number) => IAuditEntry[];
|
|
47
|
+
/** Get all cells that depend on a cell (deep, transitive). */
|
|
48
|
+
getDependents: (col: number, row: number) => IAuditEntry[];
|
|
49
|
+
/** Get full audit trail for a cell. */
|
|
50
|
+
getAuditTrail: (col: number, row: number) => IAuditTrail | null;
|
|
51
|
+
}
|
|
52
|
+
export declare function useFormulaEngine<T>(params: UseFormulaEngineParams<T>): UseFormulaEngineResult;
|
|
@@ -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, } 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, } 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 } 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';
|
|
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>)[];
|
|
@@ -42,6 +42,8 @@ interface IOGridBaseProps<T> {
|
|
|
42
42
|
onSelectionChange?: (event: IRowSelectionChangeEvent<T>) => void;
|
|
43
43
|
/** Show Excel-style row numbers column at the start of the grid (1, 2, 3...). Default: false. */
|
|
44
44
|
showRowNumbers?: boolean;
|
|
45
|
+
/** Enable Excel-style cell references: column letter headers, row numbers, and name box. Implies showRowNumbers. */
|
|
46
|
+
cellReferences?: boolean;
|
|
45
47
|
statusBar?: boolean | IStatusBarProps;
|
|
46
48
|
defaultPageSize?: number;
|
|
47
49
|
defaultSortBy?: string;
|
|
@@ -87,6 +89,22 @@ interface IOGridBaseProps<T> {
|
|
|
87
89
|
rowHeight?: number;
|
|
88
90
|
/** Cell spacing/density preset. Controls cell padding throughout the grid. Default: 'normal'. */
|
|
89
91
|
density?: 'compact' | 'normal' | 'comfortable';
|
|
92
|
+
/** Enable Excel-like formula support. When true, cells starting with '=' are treated as formulas. Default: false. */
|
|
93
|
+
formulas?: boolean;
|
|
94
|
+
/** Initial formulas to load when the formula engine initializes. */
|
|
95
|
+
initialFormulas?: Array<{
|
|
96
|
+
col: number;
|
|
97
|
+
row: number;
|
|
98
|
+
formula: string;
|
|
99
|
+
}>;
|
|
100
|
+
/** Called when formula recalculation produces updated cell values (e.g. cascade from an edited cell). */
|
|
101
|
+
onFormulaRecalc?: (result: IRecalcResult) => void;
|
|
102
|
+
/** Custom formula functions to register with the formula engine (e.g. { MYFUNC: { minArgs: 1, maxArgs: 1, evaluate: ... } }). */
|
|
103
|
+
formulaFunctions?: Record<string, IFormulaFunction>;
|
|
104
|
+
/** Named ranges for the formula engine: name → cell/range ref string (e.g. { Revenue: 'A1:A10' }). */
|
|
105
|
+
namedRanges?: Record<string, string>;
|
|
106
|
+
/** Sheet accessors for cross-sheet formula references (e.g. { Sheet2: accessor }). */
|
|
107
|
+
sheets?: Record<string, IGridDataAccessor>;
|
|
90
108
|
'aria-label'?: string;
|
|
91
109
|
'aria-labelledby'?: string;
|
|
92
110
|
}
|
|
@@ -144,6 +162,10 @@ export interface IOGridDataGridProps<T> {
|
|
|
144
162
|
selectedRows?: Set<RowId>;
|
|
145
163
|
onSelectionChange?: (event: IRowSelectionChangeEvent<T>) => void;
|
|
146
164
|
showRowNumbers?: boolean;
|
|
165
|
+
showColumnLetters?: boolean;
|
|
166
|
+
showNameBox?: boolean;
|
|
167
|
+
/** Callback when the active cell changes. Used by the name box to display the current cell reference. */
|
|
168
|
+
onActiveCellChange?: (ref: string | null) => void;
|
|
147
169
|
currentPage?: number;
|
|
148
170
|
pageSize?: number;
|
|
149
171
|
statusBar?: IStatusBarProps;
|
|
@@ -175,4 +197,22 @@ export interface IOGridDataGridProps<T> {
|
|
|
175
197
|
'aria-labelledby'?: string;
|
|
176
198
|
/** Custom keydown handler. Called before grid's built-in handling. Call event.preventDefault() to suppress grid default. */
|
|
177
199
|
onKeyDown?: (event: KeyboardEvent) => void;
|
|
200
|
+
/** Enable formula support. When true, cell values starting with '=' are treated as formulas. */
|
|
201
|
+
formulas?: boolean;
|
|
202
|
+
/** Get the formula engine's computed value for a cell, or undefined if no formula. */
|
|
203
|
+
getFormulaValue?: (col: number, row: number) => unknown;
|
|
204
|
+
/** Check if a cell has a formula. */
|
|
205
|
+
hasFormula?: (col: number, row: number) => boolean;
|
|
206
|
+
/** Get the formula string for a cell. */
|
|
207
|
+
getFormula?: (col: number, row: number) => string | undefined;
|
|
208
|
+
/** Set a formula for a cell (called from edit commit when value starts with '='). */
|
|
209
|
+
setFormula?: (col: number, row: number, formula: string | null) => void;
|
|
210
|
+
/** Notify the formula engine that a non-formula cell changed. */
|
|
211
|
+
onFormulaCellChanged?: (col: number, row: number) => void;
|
|
212
|
+
/** Get all cells that a cell depends on (deep, transitive). */
|
|
213
|
+
getPrecedents?: (col: number, row: number) => IAuditEntry[];
|
|
214
|
+
/** Get all cells that depend on a cell (deep, transitive). */
|
|
215
|
+
getDependents?: (col: number, row: number) => IAuditEntry[];
|
|
216
|
+
/** Get full audit trail for a cell. */
|
|
217
|
+
getAuditTrail?: (col: number, row: number) => IAuditTrail | null;
|
|
178
218
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alaarab/ogrid-vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
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.
|
|
39
|
+
"@alaarab/ogrid-core": "2.3.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"vue": "^3.3.0"
|