@alaarab/ogrid-vue 2.1.15 → 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 +401 -54
- package/dist/esm/styles/ogrid-layout.css +34 -0
- package/dist/types/composables/index.d.ts +2 -0
- package/dist/types/composables/useDataGridTableSetup.d.ts +11 -1
- package/dist/types/composables/useFormulaEngine.d.ts +52 -0
- package/dist/types/composables/useVirtualScroll.d.ts +11 -1
- package/dist/types/types/dataGridTypes.d.ts +44 -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, validateColumns, validateRowIds, computeRowSelectionState, buildCellIndex, UndoRedoStack, CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, computeAggregations, getDataGridStatusBarConfig, validateVirtualScrollConfig, computeVisibleRange, computeTotalHeight, 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;
|
|
@@ -331,7 +451,11 @@ function useOGrid(props) {
|
|
|
331
451
|
return new Set(visible.length > 0 ? visible : columns.value.map((c) => c.columnId));
|
|
332
452
|
})());
|
|
333
453
|
const columnWidthOverrides = ref({});
|
|
334
|
-
const
|
|
454
|
+
const initialPinned = {};
|
|
455
|
+
for (const col of flattenColumns(props.value.columns)) {
|
|
456
|
+
if (col.pinned) initialPinned[col.columnId] = col.pinned;
|
|
457
|
+
}
|
|
458
|
+
const pinnedOverrides = ref(initialPinned);
|
|
335
459
|
const page = computed(() => controlledState.value.page ?? internalPage.value);
|
|
336
460
|
const pageSize = computed(() => controlledState.value.pageSize ?? internalPageSize.value);
|
|
337
461
|
const sort = computed(() => controlledState.value.sort ?? internalSort.value);
|
|
@@ -389,8 +513,9 @@ function useOGrid(props) {
|
|
|
389
513
|
if (hasServerFilterOptions.value) return serverFilterOptions.value;
|
|
390
514
|
return deriveFilterOptionsFromData(displayData.value, columns.value);
|
|
391
515
|
});
|
|
516
|
+
const workerSortEnabled = computed(() => !!props.value.workerSort);
|
|
392
517
|
const clientItemsAndTotal = computed(() => {
|
|
393
|
-
if (!isClientSide.value) return null;
|
|
518
|
+
if (!isClientSide.value || workerSortEnabled.value) return null;
|
|
394
519
|
const rows = processClientSideData(
|
|
395
520
|
displayData.value,
|
|
396
521
|
columns.value,
|
|
@@ -403,6 +528,42 @@ function useOGrid(props) {
|
|
|
403
528
|
const paged = rows.slice(start, start + pageSize.value);
|
|
404
529
|
return { items: paged, totalCount: total };
|
|
405
530
|
});
|
|
531
|
+
const asyncClientItems = ref(null);
|
|
532
|
+
let workerSortAbortId = 0;
|
|
533
|
+
watch(
|
|
534
|
+
[isClientSide, workerSortEnabled, displayData, columns, filters, () => sort.value.field, () => sort.value.direction, page, pageSize],
|
|
535
|
+
() => {
|
|
536
|
+
if (!isClientSide.value || !workerSortEnabled.value) return;
|
|
537
|
+
const data = displayData.value;
|
|
538
|
+
const cols = columns.value;
|
|
539
|
+
const f = filters.value;
|
|
540
|
+
const sf = sort.value.field;
|
|
541
|
+
const sd = sort.value.direction;
|
|
542
|
+
const p = page.value;
|
|
543
|
+
const ps = pageSize.value;
|
|
544
|
+
const abortId = ++workerSortAbortId;
|
|
545
|
+
processClientSideDataAsync(data, cols, f, sf, sd).then((rows) => {
|
|
546
|
+
if (abortId !== workerSortAbortId || isDestroyed) return;
|
|
547
|
+
const total = rows.length;
|
|
548
|
+
const start = (p - 1) * ps;
|
|
549
|
+
const paged = rows.slice(start, start + ps);
|
|
550
|
+
asyncClientItems.value = { items: paged, totalCount: total };
|
|
551
|
+
}).catch(() => {
|
|
552
|
+
if (abortId !== workerSortAbortId || isDestroyed) return;
|
|
553
|
+
const rows = processClientSideData(data, cols, f, sf, sd);
|
|
554
|
+
const total = rows.length;
|
|
555
|
+
const start = (p - 1) * ps;
|
|
556
|
+
const paged = rows.slice(start, start + ps);
|
|
557
|
+
asyncClientItems.value = { items: paged, totalCount: total };
|
|
558
|
+
});
|
|
559
|
+
},
|
|
560
|
+
{ immediate: true }
|
|
561
|
+
);
|
|
562
|
+
const resolvedClientItems = computed(() => {
|
|
563
|
+
const syncResult = clientItemsAndTotal.value;
|
|
564
|
+
if (syncResult) return syncResult;
|
|
565
|
+
return asyncClientItems.value;
|
|
566
|
+
});
|
|
406
567
|
const serverItems = ref([]);
|
|
407
568
|
const serverTotalCount = ref(0);
|
|
408
569
|
const loading = ref(false);
|
|
@@ -448,11 +609,22 @@ function useOGrid(props) {
|
|
|
448
609
|
isDestroyed = true;
|
|
449
610
|
});
|
|
450
611
|
const displayItems = computed(
|
|
451
|
-
() => isClientSide.value &&
|
|
612
|
+
() => isClientSide.value && resolvedClientItems.value ? resolvedClientItems.value.items : serverItems.value
|
|
452
613
|
);
|
|
453
614
|
const displayTotalCount = computed(
|
|
454
|
-
() => isClientSide.value &&
|
|
615
|
+
() => isClientSide.value && resolvedClientItems.value ? resolvedClientItems.value.totalCount : serverTotalCount.value
|
|
455
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
|
+
});
|
|
456
628
|
let firstDataRendered = false;
|
|
457
629
|
let rowIdsValidated = false;
|
|
458
630
|
watch(displayItems, (items) => {
|
|
@@ -540,6 +712,10 @@ function useOGrid(props) {
|
|
|
540
712
|
});
|
|
541
713
|
const clearAllFilters = () => setFilters({});
|
|
542
714
|
const isLoadingResolved = computed(() => isServerSide.value && loading.value || displayLoading.value);
|
|
715
|
+
const activeCellRef = ref(null);
|
|
716
|
+
const onActiveCellChange = (cellRef) => {
|
|
717
|
+
activeCellRef.value = cellRef;
|
|
718
|
+
};
|
|
543
719
|
const dataGridProps = computed(() => {
|
|
544
720
|
const p = props.value;
|
|
545
721
|
const ds = dataProps.value.dataSource;
|
|
@@ -567,7 +743,10 @@ function useOGrid(props) {
|
|
|
567
743
|
rowSelection: p.rowSelection ?? "none",
|
|
568
744
|
selectedRows: effectiveSelectedRows.value,
|
|
569
745
|
onSelectionChange: handleSelectionChange,
|
|
570
|
-
showRowNumbers: p.showRowNumbers,
|
|
746
|
+
showRowNumbers: p.showRowNumbers || p.cellReferences,
|
|
747
|
+
showColumnLetters: !!p.cellReferences,
|
|
748
|
+
showNameBox: !!p.cellReferences,
|
|
749
|
+
onActiveCellChange: p.cellReferences ? onActiveCellChange : void 0,
|
|
571
750
|
currentPage: page.value,
|
|
572
751
|
pageSize: pageSize.value,
|
|
573
752
|
statusBar: statusBarConfig.value,
|
|
@@ -592,7 +771,18 @@ function useOGrid(props) {
|
|
|
592
771
|
onClearAll: clearAllFilters,
|
|
593
772
|
message: p.emptyState?.message,
|
|
594
773
|
render: p.emptyState?.render
|
|
595
|
-
}
|
|
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
|
+
} : {}
|
|
596
786
|
};
|
|
597
787
|
});
|
|
598
788
|
const pagination = computed(() => ({
|
|
@@ -610,14 +800,38 @@ function useOGrid(props) {
|
|
|
610
800
|
onVisibilityChange: handleVisibilityChange,
|
|
611
801
|
placement: columnChooserPlacement.value
|
|
612
802
|
}));
|
|
613
|
-
const layout = computed(() =>
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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
|
+
});
|
|
621
835
|
const filtersResult = computed(() => ({
|
|
622
836
|
hasActiveFilters: hasActiveFilters.value,
|
|
623
837
|
setFilters
|
|
@@ -839,23 +1053,6 @@ function useActiveCell(wrapperRef, editingCell) {
|
|
|
839
1053
|
});
|
|
840
1054
|
return { activeCell, setActiveCell };
|
|
841
1055
|
}
|
|
842
|
-
function useLatestRef(source) {
|
|
843
|
-
let value = unref(source);
|
|
844
|
-
return customRef((track, trigger) => ({
|
|
845
|
-
get() {
|
|
846
|
-
if (isRef(source)) {
|
|
847
|
-
value = source.value;
|
|
848
|
-
}
|
|
849
|
-
return value;
|
|
850
|
-
},
|
|
851
|
-
set(newValue) {
|
|
852
|
-
value = newValue;
|
|
853
|
-
trigger();
|
|
854
|
-
}
|
|
855
|
-
}));
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
// src/composables/useCellSelection.ts
|
|
859
1056
|
var DRAG_ATTR = "data-drag-range";
|
|
860
1057
|
var DRAG_ANCHOR_ATTR = "data-drag-anchor";
|
|
861
1058
|
var AUTO_SCROLL_EDGE = 40;
|
|
@@ -1272,7 +1469,7 @@ function useKeyboardNavigation(params) {
|
|
|
1272
1469
|
const maxColIndex = visibleColumnCount - 1 + colOffset;
|
|
1273
1470
|
if (items.length === 0) return;
|
|
1274
1471
|
if (activeCell === null) {
|
|
1275
|
-
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)) {
|
|
1276
1473
|
setActiveCell({ rowIndex: 0, columnIndex: colOffset });
|
|
1277
1474
|
e.preventDefault();
|
|
1278
1475
|
}
|
|
@@ -1372,6 +1569,36 @@ function useKeyboardNavigation(params) {
|
|
|
1372
1569
|
setActiveCell({ rowIndex: newRowEnd, columnIndex: maxColIndex });
|
|
1373
1570
|
break;
|
|
1374
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
|
+
}
|
|
1375
1602
|
case "Enter":
|
|
1376
1603
|
case "F2": {
|
|
1377
1604
|
e.preventDefault();
|
|
@@ -2929,14 +3156,14 @@ function useInlineCellEditorState(params) {
|
|
|
2929
3156
|
e.stopPropagation();
|
|
2930
3157
|
cancel();
|
|
2931
3158
|
}
|
|
2932
|
-
if (e.key === "Enter" && editorType === "text") {
|
|
3159
|
+
if (e.key === "Enter" && (editorType === "text" || editorType === "date")) {
|
|
2933
3160
|
e.preventDefault();
|
|
2934
3161
|
e.stopPropagation();
|
|
2935
3162
|
commit(localValue.value);
|
|
2936
3163
|
}
|
|
2937
3164
|
};
|
|
2938
3165
|
const handleBlur = () => {
|
|
2939
|
-
if (editorType === "text") {
|
|
3166
|
+
if (editorType === "text" || editorType === "date") {
|
|
2940
3167
|
commit(localValue.value);
|
|
2941
3168
|
}
|
|
2942
3169
|
};
|
|
@@ -3104,13 +3331,24 @@ function useColumnReorder(params) {
|
|
|
3104
3331
|
}
|
|
3105
3332
|
var DEFAULT_PASSTHROUGH_THRESHOLD = 100;
|
|
3106
3333
|
function useVirtualScroll(params) {
|
|
3107
|
-
const {
|
|
3334
|
+
const {
|
|
3335
|
+
totalRows,
|
|
3336
|
+
rowHeight,
|
|
3337
|
+
enabled,
|
|
3338
|
+
overscan = 5,
|
|
3339
|
+
threshold = DEFAULT_PASSTHROUGH_THRESHOLD,
|
|
3340
|
+
columnsEnabled,
|
|
3341
|
+
columnWidths,
|
|
3342
|
+
columnOverscan = 2
|
|
3343
|
+
} = params;
|
|
3108
3344
|
onMounted(() => {
|
|
3109
3345
|
validateVirtualScrollConfig({ enabled: enabled.value, rowHeight });
|
|
3110
3346
|
});
|
|
3111
3347
|
const containerRef = ref(null);
|
|
3112
3348
|
const scrollTop = ref(0);
|
|
3349
|
+
const scrollLeft = ref(0);
|
|
3113
3350
|
const containerHeight = ref(0);
|
|
3351
|
+
const containerWidth = ref(0);
|
|
3114
3352
|
let rafId = 0;
|
|
3115
3353
|
let resizeObserver;
|
|
3116
3354
|
let prevObservedEl = null;
|
|
@@ -3131,6 +3369,17 @@ function useVirtualScroll(params) {
|
|
|
3131
3369
|
if (!enabled.value) return 0;
|
|
3132
3370
|
return computeTotalHeight(totalRows.value, rowHeight);
|
|
3133
3371
|
});
|
|
3372
|
+
const columnRange = computed(() => {
|
|
3373
|
+
if (!columnsEnabled?.value) return null;
|
|
3374
|
+
const widths = columnWidths?.value;
|
|
3375
|
+
if (!widths || widths.length === 0) return null;
|
|
3376
|
+
return computeVisibleColumnRange(
|
|
3377
|
+
scrollLeft.value,
|
|
3378
|
+
widths,
|
|
3379
|
+
containerWidth.value,
|
|
3380
|
+
columnOverscan
|
|
3381
|
+
);
|
|
3382
|
+
});
|
|
3134
3383
|
const onScroll = () => {
|
|
3135
3384
|
if (!rafId) {
|
|
3136
3385
|
rafId = requestAnimationFrame(() => {
|
|
@@ -3138,6 +3387,7 @@ function useVirtualScroll(params) {
|
|
|
3138
3387
|
const el = containerRef.value;
|
|
3139
3388
|
if (el) {
|
|
3140
3389
|
scrollTop.value = el.scrollTop;
|
|
3390
|
+
scrollLeft.value = el.scrollLeft;
|
|
3141
3391
|
}
|
|
3142
3392
|
});
|
|
3143
3393
|
}
|
|
@@ -3146,6 +3396,7 @@ function useVirtualScroll(params) {
|
|
|
3146
3396
|
const el = containerRef.value;
|
|
3147
3397
|
if (!el) return;
|
|
3148
3398
|
containerHeight.value = el.clientHeight;
|
|
3399
|
+
containerWidth.value = el.clientWidth;
|
|
3149
3400
|
};
|
|
3150
3401
|
watch(containerRef, (el) => {
|
|
3151
3402
|
if (el === prevObservedEl) return;
|
|
@@ -3165,6 +3416,7 @@ function useVirtualScroll(params) {
|
|
|
3165
3416
|
}
|
|
3166
3417
|
measure();
|
|
3167
3418
|
scrollTop.value = el.scrollTop;
|
|
3419
|
+
scrollLeft.value = el.scrollLeft;
|
|
3168
3420
|
}
|
|
3169
3421
|
});
|
|
3170
3422
|
onUnmounted(() => {
|
|
@@ -3183,7 +3435,7 @@ function useVirtualScroll(params) {
|
|
|
3183
3435
|
if (!el) return;
|
|
3184
3436
|
el.scrollTop = getScrollTopForRow(index, rowHeight, containerHeight.value, align);
|
|
3185
3437
|
};
|
|
3186
|
-
return { containerRef, visibleRange, totalHeight, scrollToRow };
|
|
3438
|
+
return { containerRef, visibleRange, totalHeight, scrollToRow, columnRange, scrollLeft };
|
|
3187
3439
|
}
|
|
3188
3440
|
function useDataGridTableSetup(params) {
|
|
3189
3441
|
const { props: propsRef } = params;
|
|
@@ -3207,11 +3459,45 @@ function useDataGridTableSetup(params) {
|
|
|
3207
3459
|
const totalRowsRef = computed(() => propsRef.value.items.length);
|
|
3208
3460
|
const rowHeight = propsRef.value.virtualScroll?.rowHeight ?? 36;
|
|
3209
3461
|
const overscan = propsRef.value.virtualScroll?.overscan ?? 5;
|
|
3462
|
+
const columnsVirtEnabled = computed(() => propsRef.value.virtualScroll?.columns === true);
|
|
3463
|
+
const columnOverscan = propsRef.value.virtualScroll?.columnOverscan ?? 2;
|
|
3464
|
+
const unpinnedColumnWidths = computed(() => {
|
|
3465
|
+
const layout = state.layout.value;
|
|
3466
|
+
const { visibleCols, columnSizingOverrides } = layout;
|
|
3467
|
+
const pinnedCols = propsRef.value.pinnedColumns ?? {};
|
|
3468
|
+
const widths = [];
|
|
3469
|
+
for (const col of visibleCols) {
|
|
3470
|
+
if (pinnedCols[col.columnId] || col.pinned) continue;
|
|
3471
|
+
const override = columnSizingOverrides[col.columnId];
|
|
3472
|
+
widths.push(override ? override.widthPx : col.defaultWidth ?? col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH);
|
|
3473
|
+
}
|
|
3474
|
+
return widths;
|
|
3475
|
+
});
|
|
3210
3476
|
const virtualScroll = useVirtualScroll({
|
|
3211
3477
|
totalRows: totalRowsRef,
|
|
3212
3478
|
rowHeight,
|
|
3213
3479
|
enabled: virtualScrollEnabled,
|
|
3214
|
-
overscan
|
|
3480
|
+
overscan,
|
|
3481
|
+
columnsEnabled: columnsVirtEnabled,
|
|
3482
|
+
columnWidths: unpinnedColumnWidths,
|
|
3483
|
+
columnOverscan
|
|
3484
|
+
});
|
|
3485
|
+
const columnPartition = computed(() => {
|
|
3486
|
+
if (!columnsVirtEnabled.value) return null;
|
|
3487
|
+
const layout = state.layout.value;
|
|
3488
|
+
const cols = layout.visibleCols;
|
|
3489
|
+
const range = virtualScroll.columnRange.value;
|
|
3490
|
+
const pinnedCols = propsRef.value.pinnedColumns;
|
|
3491
|
+
return partitionColumnsForVirtualization(cols, range, pinnedCols);
|
|
3492
|
+
});
|
|
3493
|
+
const globalColIndexMap = computed(() => {
|
|
3494
|
+
const layout = state.layout.value;
|
|
3495
|
+
const cols = layout.visibleCols;
|
|
3496
|
+
const map = /* @__PURE__ */ new Map();
|
|
3497
|
+
for (let i = 0; i < cols.length; i++) {
|
|
3498
|
+
map.set(cols[i].columnId, i);
|
|
3499
|
+
}
|
|
3500
|
+
return map;
|
|
3215
3501
|
});
|
|
3216
3502
|
const columnSizingOverridesRef = computed(() => state.layout.value.columnSizingOverrides);
|
|
3217
3503
|
const columnResize = useColumnResize({
|
|
@@ -3227,13 +3513,16 @@ function useDataGridTableSetup(params) {
|
|
|
3227
3513
|
columnReorder,
|
|
3228
3514
|
virtualScroll,
|
|
3229
3515
|
virtualScrollEnabled,
|
|
3230
|
-
columnResize
|
|
3516
|
+
columnResize,
|
|
3517
|
+
columnPartition,
|
|
3518
|
+
globalColIndexMap
|
|
3231
3519
|
};
|
|
3232
3520
|
}
|
|
3233
3521
|
function getCellInteractionProps(descriptor, columnId, handlers) {
|
|
3234
3522
|
const base = {
|
|
3235
3523
|
"data-row-index": descriptor.rowIndex,
|
|
3236
3524
|
"data-col-index": descriptor.globalColIndex,
|
|
3525
|
+
...descriptor.isActive ? { "data-active-cell": "true" } : {},
|
|
3237
3526
|
...descriptor.isInRange ? { "data-in-range": "true" } : {},
|
|
3238
3527
|
tabindex: descriptor.isActive ? 0 : -1,
|
|
3239
3528
|
onMousedown: (e) => handlers.handleCellMouseDown(e, descriptor.rowIndex, descriptor.globalColIndex),
|
|
@@ -3265,8 +3554,28 @@ function createDataGridTable(ui) {
|
|
|
3265
3554
|
columnReorder: { isDragging: isReorderDragging, dropIndicatorX, handleHeaderMouseDown: handleReorderMouseDown },
|
|
3266
3555
|
virtualScroll: { containerRef: vsContainerRef, visibleRange, totalHeight: _totalHeight, scrollToRow: _scrollToRow },
|
|
3267
3556
|
virtualScrollEnabled,
|
|
3268
|
-
columnResize: { handleResizeStart, handleResizeDoubleClick, getColumnWidth }
|
|
3557
|
+
columnResize: { handleResizeStart, handleResizeDoubleClick, getColumnWidth },
|
|
3558
|
+
columnPartition,
|
|
3559
|
+
globalColIndexMap
|
|
3269
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
|
+
);
|
|
3270
3579
|
const onWrapperMousedown = (e) => {
|
|
3271
3580
|
lastMouseShift.value = e.shiftKey;
|
|
3272
3581
|
};
|
|
@@ -3342,7 +3651,7 @@ function createDataGridTable(ui) {
|
|
|
3342
3651
|
} = layout;
|
|
3343
3652
|
const currentPage = p.currentPage ?? 1;
|
|
3344
3653
|
const pageSize = p.pageSize ?? 25;
|
|
3345
|
-
const
|
|
3654
|
+
const rowNumberOffset2 = hasRowNumbersCol ? (currentPage - 1) * pageSize : 0;
|
|
3346
3655
|
const { selectedRowIds, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected } = rowSel;
|
|
3347
3656
|
const { editingCell: _editingCell, setEditingCell, pendingEditorValue, setPendingEditorValue, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl } = editing;
|
|
3348
3657
|
const {
|
|
@@ -3432,13 +3741,14 @@ function createDataGridTable(ui) {
|
|
|
3432
3741
|
]);
|
|
3433
3742
|
}
|
|
3434
3743
|
const content = resolveCellDisplayContent(col, item, descriptor.displayValue);
|
|
3435
|
-
const cellStyle = resolveCellStyle(col, item);
|
|
3744
|
+
const cellStyle = resolveCellStyle(col, item, descriptor.displayValue);
|
|
3436
3745
|
const interactionProps2 = getCellInteractionProps(descriptor, col.columnId, interactionHandlers);
|
|
3437
3746
|
const cellClasses = ["ogrid-cell-content"];
|
|
3438
3747
|
if (col.type === "numeric") cellClasses.push("ogrid-cell-content--numeric");
|
|
3439
3748
|
else if (col.type === "boolean") cellClasses.push("ogrid-cell-content--boolean");
|
|
3440
3749
|
if (descriptor.canEditAny) cellClasses.push("ogrid-cell-content--editable");
|
|
3441
3750
|
if (descriptor.isActive) cellClasses.push("ogrid-cell-content--active");
|
|
3751
|
+
if (descriptor.isActive && descriptor.isInRange) cellClasses.push("ogrid-cell-content--active-in-range");
|
|
3442
3752
|
if (descriptor.isInRange && !descriptor.isActive) cellClasses.push("ogrid-cell-in-range");
|
|
3443
3753
|
if (descriptor.isInCutRange) cellClasses.push("ogrid-cell-cut");
|
|
3444
3754
|
const styledContent = cellStyle ? h("span", { style: cellStyle }, content) : content;
|
|
@@ -3457,11 +3767,25 @@ function createDataGridTable(ui) {
|
|
|
3457
3767
|
]);
|
|
3458
3768
|
};
|
|
3459
3769
|
const { cellStyles: colCellStyles, cellClasses: colCellClasses, hdrStyles: colHdrStyles, hdrClasses: colHdrClasses } = columnMetaCache.value;
|
|
3460
|
-
const
|
|
3770
|
+
const allColumnLayouts = visibleCols.map((col) => ({
|
|
3461
3771
|
col,
|
|
3462
3772
|
tdClasses: colCellClasses[col.columnId] || "ogrid-data-cell",
|
|
3463
3773
|
tdDynamicStyle: colCellStyles[col.columnId] || {}
|
|
3464
3774
|
}));
|
|
3775
|
+
const partition = columnPartition.value;
|
|
3776
|
+
let columnLayouts = allColumnLayouts;
|
|
3777
|
+
let leftSpacerWidth = 0;
|
|
3778
|
+
let rightSpacerWidth = 0;
|
|
3779
|
+
if (partition) {
|
|
3780
|
+
const visibleIds = /* @__PURE__ */ new Set();
|
|
3781
|
+
for (const col of partition.pinnedLeft) visibleIds.add(col.columnId);
|
|
3782
|
+
for (const col of partition.virtualizedUnpinned) visibleIds.add(col.columnId);
|
|
3783
|
+
for (const col of partition.pinnedRight) visibleIds.add(col.columnId);
|
|
3784
|
+
columnLayouts = allColumnLayouts.filter((cl) => visibleIds.has(cl.col.columnId));
|
|
3785
|
+
leftSpacerWidth = partition.leftSpacerWidth;
|
|
3786
|
+
rightSpacerWidth = partition.rightSpacerWidth;
|
|
3787
|
+
}
|
|
3788
|
+
const colIndexMap = globalColIndexMap.value;
|
|
3465
3789
|
const getHeaderClassAndStyle = (col) => {
|
|
3466
3790
|
const base = colHdrStyles[col.columnId] || {};
|
|
3467
3791
|
return {
|
|
@@ -3522,13 +3846,27 @@ function createDataGridTable(ui) {
|
|
|
3522
3846
|
},
|
|
3523
3847
|
class: "ogrid-table",
|
|
3524
3848
|
role: "grid",
|
|
3525
|
-
style: { minWidth: `${minTableWidth}px` }
|
|
3849
|
+
style: { minWidth: `${minTableWidth}px` },
|
|
3850
|
+
...virtualScrollEnabled.value ? { "data-virtual-scroll": "" } : {}
|
|
3526
3851
|
}, [
|
|
3527
3852
|
// Header
|
|
3528
|
-
h(
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
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(
|
|
3532
3870
|
(row, rowIdx) => h("tr", { key: rowIdx, class: "ogrid-header-row" }, [
|
|
3533
3871
|
// Checkbox header cell
|
|
3534
3872
|
...rowIdx === headerRows.length - 1 && hasCheckboxCol ? [
|
|
@@ -3638,7 +3976,7 @@ function createDataGridTable(ui) {
|
|
|
3638
3976
|
})
|
|
3639
3977
|
])
|
|
3640
3978
|
)
|
|
3641
|
-
),
|
|
3979
|
+
]),
|
|
3642
3980
|
// Body
|
|
3643
3981
|
...!showEmptyInGrid ? [
|
|
3644
3982
|
h("tbody", {}, (() => {
|
|
@@ -3703,17 +4041,25 @@ function createDataGridTable(ui) {
|
|
|
3703
4041
|
left: hasCheckboxCol ? `${CHECKBOX_COLUMN_WIDTH}px` : "0",
|
|
3704
4042
|
zIndex: 2
|
|
3705
4043
|
}
|
|
3706
|
-
}, String(
|
|
4044
|
+
}, String(rowNumberOffset2 + rowIndex + 1))
|
|
4045
|
+
] : [],
|
|
4046
|
+
// Left spacer for column virtualization
|
|
4047
|
+
...leftSpacerWidth > 0 ? [
|
|
4048
|
+
h("td", { key: "__col-spacer-left", style: { width: `${leftSpacerWidth}px`, minWidth: `${leftSpacerWidth}px`, maxWidth: `${leftSpacerWidth}px`, padding: "0" } })
|
|
3707
4049
|
] : [],
|
|
3708
4050
|
// Data cells
|
|
3709
4051
|
...columnLayouts.map(
|
|
3710
|
-
(cl
|
|
4052
|
+
(cl) => h("td", {
|
|
3711
4053
|
key: cl.col.columnId,
|
|
3712
4054
|
"data-column-id": cl.col.columnId,
|
|
3713
4055
|
class: cl.tdClasses,
|
|
3714
4056
|
style: cl.tdDynamicStyle
|
|
3715
|
-
}, [renderCellContent(item, cl.col, rowIndex,
|
|
3716
|
-
)
|
|
4057
|
+
}, [renderCellContent(item, cl.col, rowIndex, colIndexMap.get(cl.col.columnId) ?? 0)])
|
|
4058
|
+
),
|
|
4059
|
+
// Right spacer for column virtualization
|
|
4060
|
+
...rightSpacerWidth > 0 ? [
|
|
4061
|
+
h("td", { key: "__col-spacer-right", style: { width: `${rightSpacerWidth}px`, minWidth: `${rightSpacerWidth}px`, maxWidth: `${rightSpacerWidth}px`, padding: "0" } })
|
|
4062
|
+
] : []
|
|
3717
4063
|
]));
|
|
3718
4064
|
}
|
|
3719
4065
|
if (vsEnabled && vr.offsetBottom > 0) {
|
|
@@ -3851,6 +4197,7 @@ function createInlineCellEditor(options) {
|
|
|
3851
4197
|
dropdown.style.maxHeight = `${maxH}px`;
|
|
3852
4198
|
dropdown.style.zIndex = "9999";
|
|
3853
4199
|
dropdown.style.right = "auto";
|
|
4200
|
+
dropdown.style.textAlign = "left";
|
|
3854
4201
|
if (flipUp) {
|
|
3855
4202
|
dropdown.style.top = "auto";
|
|
3856
4203
|
dropdown.style.bottom = `${window.innerHeight - rect.top}px`;
|
|
@@ -3956,7 +4303,7 @@ function createInlineCellEditor(options) {
|
|
|
3956
4303
|
selectDropdownRef.value = el;
|
|
3957
4304
|
},
|
|
3958
4305
|
role: "listbox",
|
|
3959
|
-
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" }
|
|
3960
4307
|
}, values.map(
|
|
3961
4308
|
(v, i) => h("div", {
|
|
3962
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,
|
|
@@ -189,8 +207,16 @@
|
|
|
189
207
|
position: relative;
|
|
190
208
|
padding: 0;
|
|
191
209
|
height: 1px;
|
|
210
|
+
contain: content;
|
|
192
211
|
}
|
|
193
212
|
|
|
213
|
+
/* Pinned columns need contain: none because contain breaks position: sticky */
|
|
214
|
+
.ogrid-data-cell--pinned-left,
|
|
215
|
+
.ogrid-data-cell--pinned-right { contain: none; }
|
|
216
|
+
|
|
217
|
+
/* content-visibility: auto on rows for non-virtualized grids */
|
|
218
|
+
.ogrid-table:not([data-virtual-scroll]) tbody tr { content-visibility: auto; }
|
|
219
|
+
|
|
194
220
|
.ogrid-data-cell--pinned-left {
|
|
195
221
|
position: sticky;
|
|
196
222
|
z-index: var(--ogrid-z-pinned, 6);
|
|
@@ -246,6 +272,14 @@
|
|
|
246
272
|
overflow: visible;
|
|
247
273
|
}
|
|
248
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
|
+
|
|
249
283
|
/* === Editing cell wrapper === */
|
|
250
284
|
|
|
251
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';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Ref } from 'vue';
|
|
2
|
-
import type { IOGridDataGridProps } from '../types';
|
|
2
|
+
import type { IOGridDataGridProps, IColumnDef } from '../types';
|
|
3
3
|
import { type UseDataGridStateResult } from './useDataGridState';
|
|
4
4
|
import { type UseColumnResizeResult } from './useColumnResize';
|
|
5
5
|
import { type UseColumnReorderResult } from './useColumnReorder';
|
|
@@ -27,6 +27,16 @@ export interface UseDataGridTableSetupResult<T> {
|
|
|
27
27
|
virtualScrollEnabled: Ref<boolean>;
|
|
28
28
|
/** Column resize handlers (handleResizeStart, getColumnWidth). */
|
|
29
29
|
columnResize: UseColumnResizeResult<T>;
|
|
30
|
+
/** Column virtualization partition (or null when column virtualization is off). */
|
|
31
|
+
columnPartition: Ref<{
|
|
32
|
+
pinnedLeft: IColumnDef<T>[];
|
|
33
|
+
virtualizedUnpinned: IColumnDef<T>[];
|
|
34
|
+
pinnedRight: IColumnDef<T>[];
|
|
35
|
+
leftSpacerWidth: number;
|
|
36
|
+
rightSpacerWidth: number;
|
|
37
|
+
} | null>;
|
|
38
|
+
/** Map from columnId to its global index in visibleCols. */
|
|
39
|
+
globalColIndexMap: Ref<Map<string, number>>;
|
|
30
40
|
}
|
|
31
41
|
/**
|
|
32
42
|
* Shared setup composable for Vue DataGridTable components.
|
|
@@ -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,5 +1,5 @@
|
|
|
1
1
|
import { type Ref } from 'vue';
|
|
2
|
-
import type { IVisibleRange } from '@alaarab/ogrid-core';
|
|
2
|
+
import type { IVisibleRange, IVisibleColumnRange } from '@alaarab/ogrid-core';
|
|
3
3
|
export interface UseVirtualScrollParams {
|
|
4
4
|
totalRows: Ref<number>;
|
|
5
5
|
rowHeight: number;
|
|
@@ -10,12 +10,22 @@ export interface UseVirtualScrollParams {
|
|
|
10
10
|
* When totalRows < threshold, all rows render without virtualization.
|
|
11
11
|
*/
|
|
12
12
|
threshold?: number;
|
|
13
|
+
/** Enable column virtualization. */
|
|
14
|
+
columnsEnabled?: Ref<boolean>;
|
|
15
|
+
/** Column widths array for unpinned columns. */
|
|
16
|
+
columnWidths?: Ref<number[]>;
|
|
17
|
+
/** Number of extra columns to render outside the visible area. Default: 2. */
|
|
18
|
+
columnOverscan?: number;
|
|
13
19
|
}
|
|
14
20
|
export interface UseVirtualScrollResult {
|
|
15
21
|
containerRef: Ref<HTMLElement | null>;
|
|
16
22
|
visibleRange: Ref<IVisibleRange>;
|
|
17
23
|
totalHeight: Ref<number>;
|
|
18
24
|
scrollToRow: (index: number, align?: 'start' | 'center' | 'end') => void;
|
|
25
|
+
/** Visible column range for horizontal virtualization, or null when column virtualization is off. */
|
|
26
|
+
columnRange: Ref<IVisibleColumnRange | null>;
|
|
27
|
+
/** Reactive scrollLeft value. */
|
|
28
|
+
scrollLeft: Ref<number>;
|
|
19
29
|
}
|
|
20
30
|
/**
|
|
21
31
|
* Manages virtual scrolling with RAF-throttled scroll handling and ResizeObserver
|
|
@@ -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;
|
|
@@ -81,10 +83,28 @@ interface IOGridBaseProps<T> {
|
|
|
81
83
|
columnReorder?: boolean;
|
|
82
84
|
/** Virtual scrolling configuration. Set `enabled: true` with a fixed `rowHeight` to virtualize large datasets. */
|
|
83
85
|
virtualScroll?: IVirtualScrollConfig;
|
|
86
|
+
/** Offload sort/filter to a Web Worker for large datasets. Falls back to sync when sort column has a custom compare. */
|
|
87
|
+
workerSort?: boolean;
|
|
84
88
|
/** Fixed row height in pixels. Overrides default row height (36px). */
|
|
85
89
|
rowHeight?: number;
|
|
86
90
|
/** Cell spacing/density preset. Controls cell padding throughout the grid. Default: 'normal'. */
|
|
87
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>;
|
|
88
108
|
'aria-label'?: string;
|
|
89
109
|
'aria-labelledby'?: string;
|
|
90
110
|
}
|
|
@@ -142,6 +162,10 @@ export interface IOGridDataGridProps<T> {
|
|
|
142
162
|
selectedRows?: Set<RowId>;
|
|
143
163
|
onSelectionChange?: (event: IRowSelectionChangeEvent<T>) => void;
|
|
144
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;
|
|
145
169
|
currentPage?: number;
|
|
146
170
|
pageSize?: number;
|
|
147
171
|
statusBar?: IStatusBarProps;
|
|
@@ -173,4 +197,22 @@ export interface IOGridDataGridProps<T> {
|
|
|
173
197
|
'aria-labelledby'?: string;
|
|
174
198
|
/** Custom keydown handler. Called before grid's built-in handling. Call event.preventDefault() to suppress grid default. */
|
|
175
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;
|
|
176
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"
|