@alaarab/ogrid-vue 2.1.14 → 2.2.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
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, 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';
|
|
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';
|
|
@@ -71,8 +71,8 @@ var MarchingAntsOverlay = defineComponent({
|
|
|
71
71
|
return () => {
|
|
72
72
|
if (!selRect.value && !clipRect.value) return null;
|
|
73
73
|
return h("div", { style: { position: "relative" } }, [
|
|
74
|
-
// Selection range: solid green border (hidden when clipboard range overlaps)
|
|
75
|
-
selRect.value && !clipRangeMatchesSel.value ? h("svg", {
|
|
74
|
+
// Selection range: solid green border (hidden when clipboard range overlaps or single-cell — CSS outline handles that)
|
|
75
|
+
selRect.value && !clipRangeMatchesSel.value && !(props.selectionRange && props.selectionRange.startRow === props.selectionRange.endRow && props.selectionRange.startCol === props.selectionRange.endCol) ? h("svg", {
|
|
76
76
|
style: {
|
|
77
77
|
position: "absolute",
|
|
78
78
|
top: `${selRect.value.top}px`,
|
|
@@ -151,7 +151,10 @@ var StatusBar = defineComponent({
|
|
|
151
151
|
display: "flex",
|
|
152
152
|
alignItems: "center",
|
|
153
153
|
gap: "16px",
|
|
154
|
-
fontSize: "0.875rem"
|
|
154
|
+
fontSize: "0.875rem",
|
|
155
|
+
lineHeight: "20px",
|
|
156
|
+
minHeight: "33px",
|
|
157
|
+
boxSizing: "border-box"
|
|
155
158
|
}
|
|
156
159
|
}, parts.map(
|
|
157
160
|
(p, i) => h("span", {
|
|
@@ -328,7 +331,11 @@ function useOGrid(props) {
|
|
|
328
331
|
return new Set(visible.length > 0 ? visible : columns.value.map((c) => c.columnId));
|
|
329
332
|
})());
|
|
330
333
|
const columnWidthOverrides = ref({});
|
|
331
|
-
const
|
|
334
|
+
const initialPinned = {};
|
|
335
|
+
for (const col of flattenColumns(props.value.columns)) {
|
|
336
|
+
if (col.pinned) initialPinned[col.columnId] = col.pinned;
|
|
337
|
+
}
|
|
338
|
+
const pinnedOverrides = ref(initialPinned);
|
|
332
339
|
const page = computed(() => controlledState.value.page ?? internalPage.value);
|
|
333
340
|
const pageSize = computed(() => controlledState.value.pageSize ?? internalPageSize.value);
|
|
334
341
|
const sort = computed(() => controlledState.value.sort ?? internalSort.value);
|
|
@@ -386,8 +393,9 @@ function useOGrid(props) {
|
|
|
386
393
|
if (hasServerFilterOptions.value) return serverFilterOptions.value;
|
|
387
394
|
return deriveFilterOptionsFromData(displayData.value, columns.value);
|
|
388
395
|
});
|
|
396
|
+
const workerSortEnabled = computed(() => !!props.value.workerSort);
|
|
389
397
|
const clientItemsAndTotal = computed(() => {
|
|
390
|
-
if (!isClientSide.value) return null;
|
|
398
|
+
if (!isClientSide.value || workerSortEnabled.value) return null;
|
|
391
399
|
const rows = processClientSideData(
|
|
392
400
|
displayData.value,
|
|
393
401
|
columns.value,
|
|
@@ -400,6 +408,42 @@ function useOGrid(props) {
|
|
|
400
408
|
const paged = rows.slice(start, start + pageSize.value);
|
|
401
409
|
return { items: paged, totalCount: total };
|
|
402
410
|
});
|
|
411
|
+
const asyncClientItems = ref(null);
|
|
412
|
+
let workerSortAbortId = 0;
|
|
413
|
+
watch(
|
|
414
|
+
[isClientSide, workerSortEnabled, displayData, columns, filters, () => sort.value.field, () => sort.value.direction, page, pageSize],
|
|
415
|
+
() => {
|
|
416
|
+
if (!isClientSide.value || !workerSortEnabled.value) return;
|
|
417
|
+
const data = displayData.value;
|
|
418
|
+
const cols = columns.value;
|
|
419
|
+
const f = filters.value;
|
|
420
|
+
const sf = sort.value.field;
|
|
421
|
+
const sd = sort.value.direction;
|
|
422
|
+
const p = page.value;
|
|
423
|
+
const ps = pageSize.value;
|
|
424
|
+
const abortId = ++workerSortAbortId;
|
|
425
|
+
processClientSideDataAsync(data, cols, f, sf, sd).then((rows) => {
|
|
426
|
+
if (abortId !== workerSortAbortId || isDestroyed) return;
|
|
427
|
+
const total = rows.length;
|
|
428
|
+
const start = (p - 1) * ps;
|
|
429
|
+
const paged = rows.slice(start, start + ps);
|
|
430
|
+
asyncClientItems.value = { items: paged, totalCount: total };
|
|
431
|
+
}).catch(() => {
|
|
432
|
+
if (abortId !== workerSortAbortId || isDestroyed) return;
|
|
433
|
+
const rows = processClientSideData(data, cols, f, sf, sd);
|
|
434
|
+
const total = rows.length;
|
|
435
|
+
const start = (p - 1) * ps;
|
|
436
|
+
const paged = rows.slice(start, start + ps);
|
|
437
|
+
asyncClientItems.value = { items: paged, totalCount: total };
|
|
438
|
+
});
|
|
439
|
+
},
|
|
440
|
+
{ immediate: true }
|
|
441
|
+
);
|
|
442
|
+
const resolvedClientItems = computed(() => {
|
|
443
|
+
const syncResult = clientItemsAndTotal.value;
|
|
444
|
+
if (syncResult) return syncResult;
|
|
445
|
+
return asyncClientItems.value;
|
|
446
|
+
});
|
|
403
447
|
const serverItems = ref([]);
|
|
404
448
|
const serverTotalCount = ref(0);
|
|
405
449
|
const loading = ref(false);
|
|
@@ -445,10 +489,10 @@ function useOGrid(props) {
|
|
|
445
489
|
isDestroyed = true;
|
|
446
490
|
});
|
|
447
491
|
const displayItems = computed(
|
|
448
|
-
() => isClientSide.value &&
|
|
492
|
+
() => isClientSide.value && resolvedClientItems.value ? resolvedClientItems.value.items : serverItems.value
|
|
449
493
|
);
|
|
450
494
|
const displayTotalCount = computed(
|
|
451
|
-
() => isClientSide.value &&
|
|
495
|
+
() => isClientSide.value && resolvedClientItems.value ? resolvedClientItems.value.totalCount : serverTotalCount.value
|
|
452
496
|
);
|
|
453
497
|
let firstDataRendered = false;
|
|
454
498
|
let rowIdsValidated = false;
|
|
@@ -961,15 +1005,17 @@ function useCellSelection(params) {
|
|
|
961
1005
|
}
|
|
962
1006
|
}
|
|
963
1007
|
if (!cellIndex) cellIndex = buildCellIndex(wrapperRef.value);
|
|
1008
|
+
let rebuilt = false;
|
|
964
1009
|
for (let r = minR; r <= maxR; r++) {
|
|
965
1010
|
for (let c = minC; c <= maxC; c++) {
|
|
966
1011
|
const key = `${r},${c + colOff}`;
|
|
967
1012
|
let el = cellIndex?.get(key);
|
|
968
|
-
if (el && !el.isConnected) {
|
|
1013
|
+
if (el && !el.isConnected && !rebuilt) {
|
|
1014
|
+
rebuilt = true;
|
|
969
1015
|
cellIndex = buildCellIndex(wrapperRef.value);
|
|
970
1016
|
el = cellIndex?.get(key);
|
|
971
1017
|
}
|
|
972
|
-
if (el) {
|
|
1018
|
+
if (el && el.isConnected) {
|
|
973
1019
|
styleCellInRange(el, r, c, minR, maxR, minC, maxC, anchor);
|
|
974
1020
|
}
|
|
975
1021
|
}
|
|
@@ -1097,10 +1143,13 @@ function useCellSelection(params) {
|
|
|
1097
1143
|
const finalRange = liveDragRange;
|
|
1098
1144
|
if (finalRange) {
|
|
1099
1145
|
setSelectionRange(finalRange);
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1146
|
+
const anchor = dragStart;
|
|
1147
|
+
if (anchor) {
|
|
1148
|
+
setActiveCell({
|
|
1149
|
+
rowIndex: anchor.row,
|
|
1150
|
+
columnIndex: anchor.col + getColOffset()
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1104
1153
|
}
|
|
1105
1154
|
}
|
|
1106
1155
|
clearDragAttrs();
|
|
@@ -1607,7 +1656,7 @@ function useFillHandle(params) {
|
|
|
1607
1656
|
norm.endRow = Math.min(norm.endRow, vr.endIndex);
|
|
1608
1657
|
}
|
|
1609
1658
|
setSelectionRange(norm);
|
|
1610
|
-
setActiveCell({ rowIndex:
|
|
1659
|
+
setActiveCell({ rowIndex: drag.startRow, columnIndex: drag.startCol + getColOffset() });
|
|
1611
1660
|
const currentItems = items.value;
|
|
1612
1661
|
const currentCols = visibleCols.value;
|
|
1613
1662
|
const callback = onCellValueChanged.value;
|
|
@@ -3096,13 +3145,24 @@ function useColumnReorder(params) {
|
|
|
3096
3145
|
}
|
|
3097
3146
|
var DEFAULT_PASSTHROUGH_THRESHOLD = 100;
|
|
3098
3147
|
function useVirtualScroll(params) {
|
|
3099
|
-
const {
|
|
3148
|
+
const {
|
|
3149
|
+
totalRows,
|
|
3150
|
+
rowHeight,
|
|
3151
|
+
enabled,
|
|
3152
|
+
overscan = 5,
|
|
3153
|
+
threshold = DEFAULT_PASSTHROUGH_THRESHOLD,
|
|
3154
|
+
columnsEnabled,
|
|
3155
|
+
columnWidths,
|
|
3156
|
+
columnOverscan = 2
|
|
3157
|
+
} = params;
|
|
3100
3158
|
onMounted(() => {
|
|
3101
3159
|
validateVirtualScrollConfig({ enabled: enabled.value, rowHeight });
|
|
3102
3160
|
});
|
|
3103
3161
|
const containerRef = ref(null);
|
|
3104
3162
|
const scrollTop = ref(0);
|
|
3163
|
+
const scrollLeft = ref(0);
|
|
3105
3164
|
const containerHeight = ref(0);
|
|
3165
|
+
const containerWidth = ref(0);
|
|
3106
3166
|
let rafId = 0;
|
|
3107
3167
|
let resizeObserver;
|
|
3108
3168
|
let prevObservedEl = null;
|
|
@@ -3123,6 +3183,17 @@ function useVirtualScroll(params) {
|
|
|
3123
3183
|
if (!enabled.value) return 0;
|
|
3124
3184
|
return computeTotalHeight(totalRows.value, rowHeight);
|
|
3125
3185
|
});
|
|
3186
|
+
const columnRange = computed(() => {
|
|
3187
|
+
if (!columnsEnabled?.value) return null;
|
|
3188
|
+
const widths = columnWidths?.value;
|
|
3189
|
+
if (!widths || widths.length === 0) return null;
|
|
3190
|
+
return computeVisibleColumnRange(
|
|
3191
|
+
scrollLeft.value,
|
|
3192
|
+
widths,
|
|
3193
|
+
containerWidth.value,
|
|
3194
|
+
columnOverscan
|
|
3195
|
+
);
|
|
3196
|
+
});
|
|
3126
3197
|
const onScroll = () => {
|
|
3127
3198
|
if (!rafId) {
|
|
3128
3199
|
rafId = requestAnimationFrame(() => {
|
|
@@ -3130,6 +3201,7 @@ function useVirtualScroll(params) {
|
|
|
3130
3201
|
const el = containerRef.value;
|
|
3131
3202
|
if (el) {
|
|
3132
3203
|
scrollTop.value = el.scrollTop;
|
|
3204
|
+
scrollLeft.value = el.scrollLeft;
|
|
3133
3205
|
}
|
|
3134
3206
|
});
|
|
3135
3207
|
}
|
|
@@ -3138,6 +3210,7 @@ function useVirtualScroll(params) {
|
|
|
3138
3210
|
const el = containerRef.value;
|
|
3139
3211
|
if (!el) return;
|
|
3140
3212
|
containerHeight.value = el.clientHeight;
|
|
3213
|
+
containerWidth.value = el.clientWidth;
|
|
3141
3214
|
};
|
|
3142
3215
|
watch(containerRef, (el) => {
|
|
3143
3216
|
if (el === prevObservedEl) return;
|
|
@@ -3157,6 +3230,7 @@ function useVirtualScroll(params) {
|
|
|
3157
3230
|
}
|
|
3158
3231
|
measure();
|
|
3159
3232
|
scrollTop.value = el.scrollTop;
|
|
3233
|
+
scrollLeft.value = el.scrollLeft;
|
|
3160
3234
|
}
|
|
3161
3235
|
});
|
|
3162
3236
|
onUnmounted(() => {
|
|
@@ -3175,7 +3249,7 @@ function useVirtualScroll(params) {
|
|
|
3175
3249
|
if (!el) return;
|
|
3176
3250
|
el.scrollTop = getScrollTopForRow(index, rowHeight, containerHeight.value, align);
|
|
3177
3251
|
};
|
|
3178
|
-
return { containerRef, visibleRange, totalHeight, scrollToRow };
|
|
3252
|
+
return { containerRef, visibleRange, totalHeight, scrollToRow, columnRange, scrollLeft };
|
|
3179
3253
|
}
|
|
3180
3254
|
function useDataGridTableSetup(params) {
|
|
3181
3255
|
const { props: propsRef } = params;
|
|
@@ -3199,11 +3273,45 @@ function useDataGridTableSetup(params) {
|
|
|
3199
3273
|
const totalRowsRef = computed(() => propsRef.value.items.length);
|
|
3200
3274
|
const rowHeight = propsRef.value.virtualScroll?.rowHeight ?? 36;
|
|
3201
3275
|
const overscan = propsRef.value.virtualScroll?.overscan ?? 5;
|
|
3276
|
+
const columnsVirtEnabled = computed(() => propsRef.value.virtualScroll?.columns === true);
|
|
3277
|
+
const columnOverscan = propsRef.value.virtualScroll?.columnOverscan ?? 2;
|
|
3278
|
+
const unpinnedColumnWidths = computed(() => {
|
|
3279
|
+
const layout = state.layout.value;
|
|
3280
|
+
const { visibleCols, columnSizingOverrides } = layout;
|
|
3281
|
+
const pinnedCols = propsRef.value.pinnedColumns ?? {};
|
|
3282
|
+
const widths = [];
|
|
3283
|
+
for (const col of visibleCols) {
|
|
3284
|
+
if (pinnedCols[col.columnId] || col.pinned) continue;
|
|
3285
|
+
const override = columnSizingOverrides[col.columnId];
|
|
3286
|
+
widths.push(override ? override.widthPx : col.defaultWidth ?? col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH);
|
|
3287
|
+
}
|
|
3288
|
+
return widths;
|
|
3289
|
+
});
|
|
3202
3290
|
const virtualScroll = useVirtualScroll({
|
|
3203
3291
|
totalRows: totalRowsRef,
|
|
3204
3292
|
rowHeight,
|
|
3205
3293
|
enabled: virtualScrollEnabled,
|
|
3206
|
-
overscan
|
|
3294
|
+
overscan,
|
|
3295
|
+
columnsEnabled: columnsVirtEnabled,
|
|
3296
|
+
columnWidths: unpinnedColumnWidths,
|
|
3297
|
+
columnOverscan
|
|
3298
|
+
});
|
|
3299
|
+
const columnPartition = computed(() => {
|
|
3300
|
+
if (!columnsVirtEnabled.value) return null;
|
|
3301
|
+
const layout = state.layout.value;
|
|
3302
|
+
const cols = layout.visibleCols;
|
|
3303
|
+
const range = virtualScroll.columnRange.value;
|
|
3304
|
+
const pinnedCols = propsRef.value.pinnedColumns;
|
|
3305
|
+
return partitionColumnsForVirtualization(cols, range, pinnedCols);
|
|
3306
|
+
});
|
|
3307
|
+
const globalColIndexMap = computed(() => {
|
|
3308
|
+
const layout = state.layout.value;
|
|
3309
|
+
const cols = layout.visibleCols;
|
|
3310
|
+
const map = /* @__PURE__ */ new Map();
|
|
3311
|
+
for (let i = 0; i < cols.length; i++) {
|
|
3312
|
+
map.set(cols[i].columnId, i);
|
|
3313
|
+
}
|
|
3314
|
+
return map;
|
|
3207
3315
|
});
|
|
3208
3316
|
const columnSizingOverridesRef = computed(() => state.layout.value.columnSizingOverrides);
|
|
3209
3317
|
const columnResize = useColumnResize({
|
|
@@ -3219,7 +3327,9 @@ function useDataGridTableSetup(params) {
|
|
|
3219
3327
|
columnReorder,
|
|
3220
3328
|
virtualScroll,
|
|
3221
3329
|
virtualScrollEnabled,
|
|
3222
|
-
columnResize
|
|
3330
|
+
columnResize,
|
|
3331
|
+
columnPartition,
|
|
3332
|
+
globalColIndexMap
|
|
3223
3333
|
};
|
|
3224
3334
|
}
|
|
3225
3335
|
function getCellInteractionProps(descriptor, columnId, handlers) {
|
|
@@ -3257,7 +3367,9 @@ function createDataGridTable(ui) {
|
|
|
3257
3367
|
columnReorder: { isDragging: isReorderDragging, dropIndicatorX, handleHeaderMouseDown: handleReorderMouseDown },
|
|
3258
3368
|
virtualScroll: { containerRef: vsContainerRef, visibleRange, totalHeight: _totalHeight, scrollToRow: _scrollToRow },
|
|
3259
3369
|
virtualScrollEnabled,
|
|
3260
|
-
columnResize: { handleResizeStart, handleResizeDoubleClick, getColumnWidth }
|
|
3370
|
+
columnResize: { handleResizeStart, handleResizeDoubleClick, getColumnWidth },
|
|
3371
|
+
columnPartition,
|
|
3372
|
+
globalColIndexMap
|
|
3261
3373
|
} = useDataGridTableSetup({ props: propsRef });
|
|
3262
3374
|
const onWrapperMousedown = (e) => {
|
|
3263
3375
|
lastMouseShift.value = e.shiftKey;
|
|
@@ -3430,8 +3542,8 @@ function createDataGridTable(ui) {
|
|
|
3430
3542
|
if (col.type === "numeric") cellClasses.push("ogrid-cell-content--numeric");
|
|
3431
3543
|
else if (col.type === "boolean") cellClasses.push("ogrid-cell-content--boolean");
|
|
3432
3544
|
if (descriptor.canEditAny) cellClasses.push("ogrid-cell-content--editable");
|
|
3433
|
-
if (descriptor.isActive
|
|
3434
|
-
if (descriptor.isInRange) cellClasses.push("ogrid-cell-in-range");
|
|
3545
|
+
if (descriptor.isActive) cellClasses.push("ogrid-cell-content--active");
|
|
3546
|
+
if (descriptor.isInRange && !descriptor.isActive) cellClasses.push("ogrid-cell-in-range");
|
|
3435
3547
|
if (descriptor.isInCutRange) cellClasses.push("ogrid-cell-cut");
|
|
3436
3548
|
const styledContent = cellStyle ? h("span", { style: cellStyle }, content) : content;
|
|
3437
3549
|
return h("div", {
|
|
@@ -3449,11 +3561,25 @@ function createDataGridTable(ui) {
|
|
|
3449
3561
|
]);
|
|
3450
3562
|
};
|
|
3451
3563
|
const { cellStyles: colCellStyles, cellClasses: colCellClasses, hdrStyles: colHdrStyles, hdrClasses: colHdrClasses } = columnMetaCache.value;
|
|
3452
|
-
const
|
|
3564
|
+
const allColumnLayouts = visibleCols.map((col) => ({
|
|
3453
3565
|
col,
|
|
3454
3566
|
tdClasses: colCellClasses[col.columnId] || "ogrid-data-cell",
|
|
3455
3567
|
tdDynamicStyle: colCellStyles[col.columnId] || {}
|
|
3456
3568
|
}));
|
|
3569
|
+
const partition = columnPartition.value;
|
|
3570
|
+
let columnLayouts = allColumnLayouts;
|
|
3571
|
+
let leftSpacerWidth = 0;
|
|
3572
|
+
let rightSpacerWidth = 0;
|
|
3573
|
+
if (partition) {
|
|
3574
|
+
const visibleIds = /* @__PURE__ */ new Set();
|
|
3575
|
+
for (const col of partition.pinnedLeft) visibleIds.add(col.columnId);
|
|
3576
|
+
for (const col of partition.virtualizedUnpinned) visibleIds.add(col.columnId);
|
|
3577
|
+
for (const col of partition.pinnedRight) visibleIds.add(col.columnId);
|
|
3578
|
+
columnLayouts = allColumnLayouts.filter((cl) => visibleIds.has(cl.col.columnId));
|
|
3579
|
+
leftSpacerWidth = partition.leftSpacerWidth;
|
|
3580
|
+
rightSpacerWidth = partition.rightSpacerWidth;
|
|
3581
|
+
}
|
|
3582
|
+
const colIndexMap = globalColIndexMap.value;
|
|
3457
3583
|
const getHeaderClassAndStyle = (col) => {
|
|
3458
3584
|
const base = colHdrStyles[col.columnId] || {};
|
|
3459
3585
|
return {
|
|
@@ -3514,7 +3640,8 @@ function createDataGridTable(ui) {
|
|
|
3514
3640
|
},
|
|
3515
3641
|
class: "ogrid-table",
|
|
3516
3642
|
role: "grid",
|
|
3517
|
-
style: { minWidth: `${minTableWidth}px` }
|
|
3643
|
+
style: { minWidth: `${minTableWidth}px` },
|
|
3644
|
+
...virtualScrollEnabled.value ? { "data-virtual-scroll": "" } : {}
|
|
3518
3645
|
}, [
|
|
3519
3646
|
// Header
|
|
3520
3647
|
h(
|
|
@@ -3697,15 +3824,23 @@ function createDataGridTable(ui) {
|
|
|
3697
3824
|
}
|
|
3698
3825
|
}, String(rowNumberOffset + rowIndex + 1))
|
|
3699
3826
|
] : [],
|
|
3827
|
+
// Left spacer for column virtualization
|
|
3828
|
+
...leftSpacerWidth > 0 ? [
|
|
3829
|
+
h("td", { key: "__col-spacer-left", style: { width: `${leftSpacerWidth}px`, minWidth: `${leftSpacerWidth}px`, maxWidth: `${leftSpacerWidth}px`, padding: "0" } })
|
|
3830
|
+
] : [],
|
|
3700
3831
|
// Data cells
|
|
3701
3832
|
...columnLayouts.map(
|
|
3702
|
-
(cl
|
|
3833
|
+
(cl) => h("td", {
|
|
3703
3834
|
key: cl.col.columnId,
|
|
3704
3835
|
"data-column-id": cl.col.columnId,
|
|
3705
3836
|
class: cl.tdClasses,
|
|
3706
3837
|
style: cl.tdDynamicStyle
|
|
3707
|
-
}, [renderCellContent(item, cl.col, rowIndex,
|
|
3708
|
-
)
|
|
3838
|
+
}, [renderCellContent(item, cl.col, rowIndex, colIndexMap.get(cl.col.columnId) ?? 0)])
|
|
3839
|
+
),
|
|
3840
|
+
// Right spacer for column virtualization
|
|
3841
|
+
...rightSpacerWidth > 0 ? [
|
|
3842
|
+
h("td", { key: "__col-spacer-right", style: { width: `${rightSpacerWidth}px`, minWidth: `${rightSpacerWidth}px`, maxWidth: `${rightSpacerWidth}px`, padding: "0" } })
|
|
3843
|
+
] : []
|
|
3709
3844
|
]));
|
|
3710
3845
|
}
|
|
3711
3846
|
if (vsEnabled && vr.offsetBottom > 0) {
|
|
@@ -189,8 +189,16 @@
|
|
|
189
189
|
position: relative;
|
|
190
190
|
padding: 0;
|
|
191
191
|
height: 1px;
|
|
192
|
+
contain: content;
|
|
192
193
|
}
|
|
193
194
|
|
|
195
|
+
/* Pinned columns need contain: none because contain breaks position: sticky */
|
|
196
|
+
.ogrid-data-cell--pinned-left,
|
|
197
|
+
.ogrid-data-cell--pinned-right { contain: none; }
|
|
198
|
+
|
|
199
|
+
/* content-visibility: auto on rows for non-virtualized grids */
|
|
200
|
+
.ogrid-table:not([data-virtual-scroll]) tbody tr { content-visibility: auto; }
|
|
201
|
+
|
|
194
202
|
.ogrid-data-cell--pinned-left {
|
|
195
203
|
position: sticky;
|
|
196
204
|
z-index: var(--ogrid-z-pinned, 6);
|
|
@@ -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.
|
|
@@ -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
|
|
@@ -81,6 +81,8 @@ interface IOGridBaseProps<T> {
|
|
|
81
81
|
columnReorder?: boolean;
|
|
82
82
|
/** Virtual scrolling configuration. Set `enabled: true` with a fixed `rowHeight` to virtualize large datasets. */
|
|
83
83
|
virtualScroll?: IVirtualScrollConfig;
|
|
84
|
+
/** Offload sort/filter to a Web Worker for large datasets. Falls back to sync when sort column has a custom compare. */
|
|
85
|
+
workerSort?: boolean;
|
|
84
86
|
/** Fixed row height in pixels. Overrides default row height (36px). */
|
|
85
87
|
rowHeight?: number;
|
|
86
88
|
/** Cell spacing/density preset. Controls cell padding throughout the grid. Default: 'normal'. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alaarab/ogrid-vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.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.2.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"vue": "^3.3.0"
|