@alaarab/ogrid-vue 2.0.9 → 2.0.12
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/components/MarchingAntsOverlay.js +17 -37
- package/dist/esm/components/StatusBar.js +4 -4
- package/dist/esm/components/createDataGridTable.js +481 -0
- package/dist/esm/composables/useColumnHeaderMenuState.js +61 -2
- package/dist/esm/composables/useDataGridState.js +35 -3
- package/dist/esm/composables/useOGrid.js +8 -6
- package/dist/esm/index.js +2 -0
- package/dist/types/components/MarchingAntsOverlay.d.ts +46 -0
- package/dist/types/components/createDataGridTable.d.ts +53 -0
- package/dist/types/composables/useColumnHeaderMenuState.d.ts +20 -3
- package/dist/types/composables/useDataGridState.d.ts +10 -6
- package/dist/types/index.d.ts +1 -0
- package/dist/types/types/dataGridTypes.d.ts +5 -1
- package/package.json +2 -2
|
@@ -6,38 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Uses SVG rects positioned via cell data-attribute measurements.
|
|
8
8
|
*/
|
|
9
|
-
import { defineComponent, ref, computed, watch, onMounted, onUnmounted, h } from 'vue';
|
|
10
|
-
import { Z_INDEX } from '@alaarab/ogrid-core';
|
|
11
|
-
// Inject the @keyframes rule once into <head> (deduplicates across multiple OGrid instances)
|
|
12
|
-
function ensureKeyframes() {
|
|
13
|
-
if (typeof document === 'undefined')
|
|
14
|
-
return;
|
|
15
|
-
if (document.getElementById('ogrid-marching-ants-keyframes'))
|
|
16
|
-
return;
|
|
17
|
-
const style = document.createElement('style');
|
|
18
|
-
style.id = 'ogrid-marching-ants-keyframes';
|
|
19
|
-
style.textContent =
|
|
20
|
-
'@keyframes ogrid-marching-ants{to{stroke-dashoffset:-8}}';
|
|
21
|
-
document.head.appendChild(style);
|
|
22
|
-
}
|
|
23
|
-
/** Measure the bounding rect of a range within a container. */
|
|
24
|
-
function measureRange(container, range, colOffset) {
|
|
25
|
-
const startGlobalCol = range.startCol + colOffset;
|
|
26
|
-
const endGlobalCol = range.endCol + colOffset;
|
|
27
|
-
const topLeft = container.querySelector(`[data-row-index="${range.startRow}"][data-col-index="${startGlobalCol}"]`);
|
|
28
|
-
const bottomRight = container.querySelector(`[data-row-index="${range.endRow}"][data-col-index="${endGlobalCol}"]`);
|
|
29
|
-
if (!topLeft || !bottomRight)
|
|
30
|
-
return null;
|
|
31
|
-
const cRect = container.getBoundingClientRect();
|
|
32
|
-
const tlRect = topLeft.getBoundingClientRect();
|
|
33
|
-
const brRect = bottomRight.getBoundingClientRect();
|
|
34
|
-
return {
|
|
35
|
-
top: tlRect.top - cRect.top,
|
|
36
|
-
left: tlRect.left - cRect.left,
|
|
37
|
-
width: brRect.right - tlRect.left,
|
|
38
|
-
height: brRect.bottom - tlRect.top,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
9
|
+
import { defineComponent, ref, computed, watch, onMounted, onUnmounted, h, toValue } from 'vue';
|
|
10
|
+
import { Z_INDEX, measureRange, injectGlobalStyles } from '@alaarab/ogrid-core';
|
|
41
11
|
export const MarchingAntsOverlay = defineComponent({
|
|
42
12
|
name: 'MarchingAntsOverlay',
|
|
43
13
|
props: {
|
|
@@ -51,6 +21,14 @@ export const MarchingAntsOverlay = defineComponent({
|
|
|
51
21
|
cutRange: { type: Object, default: null },
|
|
52
22
|
/** Column offset — 1 when checkbox column is present, else 0 */
|
|
53
23
|
colOffset: { type: Number, required: true },
|
|
24
|
+
/** Items array — triggers re-measurement when data changes (e.g., sorting) */
|
|
25
|
+
items: { type: Array, required: true },
|
|
26
|
+
/** Visible columns — triggers re-measurement when columns are hidden/shown */
|
|
27
|
+
visibleColumns: { type: Array, default: undefined },
|
|
28
|
+
/** Column sizing overrides — triggers re-measurement when columns are resized */
|
|
29
|
+
columnSizingOverrides: { type: Object, required: true },
|
|
30
|
+
/** Column order — triggers re-measurement when columns are reordered */
|
|
31
|
+
columnOrder: { type: Array, default: undefined },
|
|
54
32
|
},
|
|
55
33
|
setup(props) {
|
|
56
34
|
const selRect = ref(null);
|
|
@@ -59,7 +37,9 @@ export const MarchingAntsOverlay = defineComponent({
|
|
|
59
37
|
let ro;
|
|
60
38
|
const clipRange = computed(() => props.copyRange ?? props.cutRange);
|
|
61
39
|
const measureAll = () => {
|
|
62
|
-
|
|
40
|
+
// Use toValue to handle both Ref<HTMLElement> and raw HTMLElement
|
|
41
|
+
// (Vue templates may auto-unwrap refs passed as props)
|
|
42
|
+
const container = toValue(props.containerRef);
|
|
63
43
|
if (!container) {
|
|
64
44
|
selRect.value = null;
|
|
65
45
|
clipRect.value = null;
|
|
@@ -70,10 +50,10 @@ export const MarchingAntsOverlay = defineComponent({
|
|
|
70
50
|
};
|
|
71
51
|
// Inject keyframes on mount
|
|
72
52
|
onMounted(() => {
|
|
73
|
-
|
|
53
|
+
injectGlobalStyles('ogrid-marching-ants-keyframes', '@keyframes ogrid-marching-ants{to{stroke-dashoffset:-8}}');
|
|
74
54
|
});
|
|
75
|
-
// Measure when any range changes; re-measure on resize
|
|
76
|
-
watch([() => props.selectionRange, clipRange, () => props.containerRef.
|
|
55
|
+
// Measure when any range changes; re-measure on resize, column changes, data changes
|
|
56
|
+
watch([() => props.selectionRange, clipRange, () => toValue(props.containerRef), () => props.items, () => props.visibleColumns, () => props.columnSizingOverrides, () => props.columnOrder], () => {
|
|
77
57
|
if (!props.selectionRange && !clipRange.value) {
|
|
78
58
|
selRect.value = null;
|
|
79
59
|
clipRect.value = null;
|
|
@@ -81,7 +61,7 @@ export const MarchingAntsOverlay = defineComponent({
|
|
|
81
61
|
}
|
|
82
62
|
// Delay one frame so cells are rendered
|
|
83
63
|
rafId = requestAnimationFrame(measureAll);
|
|
84
|
-
const container = props.containerRef
|
|
64
|
+
const container = toValue(props.containerRef);
|
|
85
65
|
if (container) {
|
|
86
66
|
ro?.disconnect();
|
|
87
67
|
ro = new ResizeObserver(measureAll);
|
|
@@ -23,8 +23,8 @@ export const StatusBar = defineComponent({
|
|
|
23
23
|
style: {
|
|
24
24
|
marginTop: 'auto',
|
|
25
25
|
padding: '6px 12px',
|
|
26
|
-
borderTop: '1px solid rgba(0,0,0,0.12)',
|
|
27
|
-
backgroundColor: 'rgba(0,0,0,0.04)',
|
|
26
|
+
borderTop: '1px solid var(--ogrid-border, rgba(0,0,0,0.12))',
|
|
27
|
+
backgroundColor: 'var(--ogrid-header-bg, rgba(0,0,0,0.04))',
|
|
28
28
|
display: 'flex',
|
|
29
29
|
alignItems: 'center',
|
|
30
30
|
gap: '16px',
|
|
@@ -37,11 +37,11 @@ export const StatusBar = defineComponent({
|
|
|
37
37
|
alignItems: 'center',
|
|
38
38
|
gap: '4px',
|
|
39
39
|
...(i < parts.length - 1
|
|
40
|
-
? { marginRight: '16px', borderRight: '1px solid rgba(0,0,0,0.12)', paddingRight: '16px' }
|
|
40
|
+
? { marginRight: '16px', borderRight: '1px solid var(--ogrid-border, rgba(0,0,0,0.12))', paddingRight: '16px' }
|
|
41
41
|
: {}),
|
|
42
42
|
},
|
|
43
43
|
}, [
|
|
44
|
-
h('span', { style: { color: 'rgba(0,0,0,0.6)' } }, p.label),
|
|
44
|
+
h('span', { style: { color: 'var(--ogrid-fg-secondary, rgba(0,0,0,0.6))' } }, p.label),
|
|
45
45
|
h('span', { style: { fontWeight: '600' } }, p.value.toLocaleString()),
|
|
46
46
|
])));
|
|
47
47
|
};
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared DataGridTable factory for Vue UI packages.
|
|
3
|
+
*
|
|
4
|
+
* Both vue-vuetify and vue-primevue DataGridTable components are 97% identical —
|
|
5
|
+
* they only differ in which checkbox and spinner components they render.
|
|
6
|
+
* This factory extracts all shared logic into one place.
|
|
7
|
+
*/
|
|
8
|
+
import { defineComponent, computed, h, Teleport } from 'vue';
|
|
9
|
+
import { useDataGridTableSetup, } from '../composables';
|
|
10
|
+
import { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, } from '../utils';
|
|
11
|
+
import { buildHeaderRows, CHECKBOX_COLUMN_WIDTH, ROW_NUMBER_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH } from '@alaarab/ogrid-core';
|
|
12
|
+
import { StatusBar } from './StatusBar';
|
|
13
|
+
import { MarchingAntsOverlay } from './MarchingAntsOverlay';
|
|
14
|
+
const NOOP = () => { };
|
|
15
|
+
/**
|
|
16
|
+
* Creates a DataGridTable component with framework-specific UI bindings.
|
|
17
|
+
* All grid logic, layout, and interaction handling is shared.
|
|
18
|
+
*/
|
|
19
|
+
export function createDataGridTable(ui) {
|
|
20
|
+
return defineComponent({
|
|
21
|
+
name: 'DataGridTable',
|
|
22
|
+
props: {
|
|
23
|
+
gridProps: { type: Object, required: true },
|
|
24
|
+
},
|
|
25
|
+
setup(props) {
|
|
26
|
+
const propsRef = computed(() => props.gridProps);
|
|
27
|
+
const { wrapperRef, tableContainerRef, tableRef, lastMouseShift, state, columnReorder: { isDragging: isReorderDragging, dropIndicatorX, handleHeaderMouseDown: handleReorderMouseDown }, virtualScroll: { containerRef: vsContainerRef, visibleRange, totalHeight: _totalHeight, scrollToRow: _scrollToRow }, virtualScrollEnabled, columnResize: { handleResizeStart, getColumnWidth }, } = useDataGridTableSetup({ props: propsRef });
|
|
28
|
+
// Stable handlers — avoid creating new closures per render
|
|
29
|
+
const onWrapperMousedown = (e) => { lastMouseShift.value = e.shiftKey; };
|
|
30
|
+
const onContextmenu = (e) => e.preventDefault();
|
|
31
|
+
const stopPropagation = (e) => e.stopPropagation();
|
|
32
|
+
return () => {
|
|
33
|
+
const p = props.gridProps;
|
|
34
|
+
const layout = state.layout.value;
|
|
35
|
+
const rowSel = state.rowSelection.value;
|
|
36
|
+
const editing = state.editing.value;
|
|
37
|
+
const interaction = state.interaction.value;
|
|
38
|
+
const ctxMenu = state.contextMenu.value;
|
|
39
|
+
const viewModels = state.viewModels.value;
|
|
40
|
+
const pinning = state.pinning.value;
|
|
41
|
+
const { headerMenu } = pinning;
|
|
42
|
+
const { visibleCols, hasCheckboxCol, hasRowNumbersCol, colOffset: _colOffset, containerWidth, minTableWidth, desiredTableWidth, } = layout;
|
|
43
|
+
const currentPage = p.currentPage ?? 1;
|
|
44
|
+
const pageSize = p.pageSize ?? 25;
|
|
45
|
+
const rowNumberOffset = hasRowNumbersCol ? (currentPage - 1) * pageSize : 0;
|
|
46
|
+
const { selectedRowIds, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected } = rowSel;
|
|
47
|
+
const { editingCell: _editingCell, setEditingCell, pendingEditorValue, setPendingEditorValue, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl } = editing;
|
|
48
|
+
const { setActiveCell, handleCellMouseDown, handleSelectAllCells, selectionRange, hasCellSelection, handleGridKeyDown, handleFillHandleMouseDown, handleCopy, handleCut, handlePaste, cutRange: _cutRange, copyRange: _copyRange, canUndo, canRedo, onUndo, onRedo, isDragging: _isDragging, } = interaction;
|
|
49
|
+
const { menuPosition, handleCellContextMenu, closeContextMenu } = ctxMenu;
|
|
50
|
+
const { headerFilterInput, cellDescriptorInput, statusBarConfig, showEmptyInGrid, onCellError: _onCellError } = viewModels;
|
|
51
|
+
const items = p.items;
|
|
52
|
+
const getRowId = p.getRowId;
|
|
53
|
+
const layoutMode = p.layoutMode ?? 'fill';
|
|
54
|
+
const rowSelection = p.rowSelection ?? 'none';
|
|
55
|
+
const freezeRows = p.freezeRows;
|
|
56
|
+
const freezeCols = p.freezeCols;
|
|
57
|
+
const suppressHorizontalScroll = p.suppressHorizontalScroll;
|
|
58
|
+
const isLoading = p.isLoading ?? false;
|
|
59
|
+
const loadingMessage = p.loadingMessage ?? 'Loading\u2026';
|
|
60
|
+
const ariaLabel = p['aria-label'];
|
|
61
|
+
const ariaLabelledBy = p['aria-labelledby'];
|
|
62
|
+
const fitToContent = layoutMode === 'content';
|
|
63
|
+
const allowOverflowX = !suppressHorizontalScroll && containerWidth > 0 && (minTableWidth > containerWidth || desiredTableWidth > containerWidth);
|
|
64
|
+
const headerRows = buildHeaderRows(p.columns, p.visibleColumns);
|
|
65
|
+
const editCallbacks = { commitCellEdit, setEditingCell, setPendingEditorValue, cancelPopoverEdit };
|
|
66
|
+
const interactionHandlers = { handleCellMouseDown, setActiveCell, setEditingCell, handleCellContextMenu };
|
|
67
|
+
const handleSingleRowClick = (e) => {
|
|
68
|
+
if (rowSelection !== 'single')
|
|
69
|
+
return;
|
|
70
|
+
const tr = e.currentTarget;
|
|
71
|
+
const rowId = tr.dataset.rowId;
|
|
72
|
+
if (!rowId)
|
|
73
|
+
return;
|
|
74
|
+
rowSel.updateSelection(selectedRowIds.has(rowId) ? new Set() : new Set([rowId]));
|
|
75
|
+
};
|
|
76
|
+
// Render a cell's content
|
|
77
|
+
const renderCellContent = (item, col, rowIndex, colIdx) => {
|
|
78
|
+
const descriptor = getCellRenderDescriptor(item, col, rowIndex, colIdx, cellDescriptorInput);
|
|
79
|
+
if (descriptor.mode === 'editing-inline') {
|
|
80
|
+
const editorProps = buildInlineEditorProps(item, col, descriptor, editCallbacks);
|
|
81
|
+
return h(ui.InlineCellEditor, {
|
|
82
|
+
value: editorProps.value,
|
|
83
|
+
item: editorProps.item,
|
|
84
|
+
column: editorProps.column,
|
|
85
|
+
rowIndex: editorProps.rowIndex,
|
|
86
|
+
editorType: editorProps.editorType,
|
|
87
|
+
onCommit: editorProps.onCommit,
|
|
88
|
+
onCancel: editorProps.onCancel,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (descriptor.mode === 'editing-popover' && typeof col.cellEditor === 'function') {
|
|
92
|
+
const editorProps = buildPopoverEditorProps(item, col, descriptor, pendingEditorValue, editCallbacks);
|
|
93
|
+
const CustomEditor = col.cellEditor;
|
|
94
|
+
return h('div', [
|
|
95
|
+
h('div', {
|
|
96
|
+
ref: (el) => { if (el)
|
|
97
|
+
setPopoverAnchorEl(el); },
|
|
98
|
+
class: 'ogrid-popover-anchor',
|
|
99
|
+
'aria-hidden': 'true',
|
|
100
|
+
}),
|
|
101
|
+
popoverAnchorEl
|
|
102
|
+
? h(CustomEditor, editorProps)
|
|
103
|
+
: null,
|
|
104
|
+
]);
|
|
105
|
+
}
|
|
106
|
+
// Display mode
|
|
107
|
+
const content = resolveCellDisplayContent(col, item, descriptor.displayValue);
|
|
108
|
+
const cellStyle = resolveCellStyle(col, item);
|
|
109
|
+
const interactionProps2 = getCellInteractionProps(descriptor, col.columnId, interactionHandlers);
|
|
110
|
+
const cellClasses = ['ogrid-cell-content'];
|
|
111
|
+
if (col.type === 'numeric')
|
|
112
|
+
cellClasses.push('ogrid-cell-content--numeric');
|
|
113
|
+
else if (col.type === 'boolean')
|
|
114
|
+
cellClasses.push('ogrid-cell-content--boolean');
|
|
115
|
+
if (descriptor.canEditAny)
|
|
116
|
+
cellClasses.push('ogrid-cell-content--editable');
|
|
117
|
+
if (descriptor.isActive && !descriptor.isInRange)
|
|
118
|
+
cellClasses.push('ogrid-cell-content--active');
|
|
119
|
+
if (descriptor.isInRange)
|
|
120
|
+
cellClasses.push('ogrid-cell-in-range');
|
|
121
|
+
if (descriptor.isInCutRange)
|
|
122
|
+
cellClasses.push('ogrid-cell-cut');
|
|
123
|
+
const styledContent = cellStyle
|
|
124
|
+
? h('span', { style: cellStyle }, content)
|
|
125
|
+
: content;
|
|
126
|
+
return h('div', {
|
|
127
|
+
...interactionProps2,
|
|
128
|
+
class: cellClasses.join(' '),
|
|
129
|
+
}, [
|
|
130
|
+
styledContent,
|
|
131
|
+
...(descriptor.canEditAny && descriptor.isSelectionEndCell ? [
|
|
132
|
+
h('div', {
|
|
133
|
+
onMousedown: handleFillHandleMouseDown,
|
|
134
|
+
'aria-label': 'Fill handle',
|
|
135
|
+
class: 'ogrid-fill-handle',
|
|
136
|
+
}),
|
|
137
|
+
] : []),
|
|
138
|
+
]);
|
|
139
|
+
};
|
|
140
|
+
// Pre-computed pinning offsets
|
|
141
|
+
const leftOffsets = pinning.leftOffsets;
|
|
142
|
+
const rightOffsets = pinning.rightOffsets;
|
|
143
|
+
// Build column layouts
|
|
144
|
+
const columnLayouts = visibleCols.map((col, colIdx) => {
|
|
145
|
+
const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx < freezeCols;
|
|
146
|
+
const isPinnedLeft = col.pinned === 'left';
|
|
147
|
+
const isPinnedRight = col.pinned === 'right';
|
|
148
|
+
const columnWidth = getColumnWidth(col);
|
|
149
|
+
const tdClasses = ['ogrid-data-cell'];
|
|
150
|
+
const tdDynamicStyle = {
|
|
151
|
+
minWidth: `${col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH}px`,
|
|
152
|
+
width: `${columnWidth}px`,
|
|
153
|
+
maxWidth: `${columnWidth}px`,
|
|
154
|
+
};
|
|
155
|
+
if (isPinnedLeft || (isFreezeCol && colIdx === 0)) {
|
|
156
|
+
tdClasses.push('ogrid-data-cell--pinned-left');
|
|
157
|
+
tdDynamicStyle.left = `${leftOffsets[col.columnId] ?? 0}px`;
|
|
158
|
+
}
|
|
159
|
+
else if (isPinnedRight) {
|
|
160
|
+
tdClasses.push('ogrid-data-cell--pinned-right');
|
|
161
|
+
tdDynamicStyle.right = `${rightOffsets[col.columnId] ?? 0}px`;
|
|
162
|
+
}
|
|
163
|
+
return { col, tdClasses: tdClasses.join(' '), tdDynamicStyle };
|
|
164
|
+
});
|
|
165
|
+
// Build header cell classes + dynamic styles
|
|
166
|
+
const getHeaderClassAndStyle = (col, colIdx) => {
|
|
167
|
+
const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx < freezeCols;
|
|
168
|
+
const isPinnedLeft = col.pinned === 'left';
|
|
169
|
+
const isPinnedRight = col.pinned === 'right';
|
|
170
|
+
const columnWidth = getColumnWidth(col);
|
|
171
|
+
const classes = ['ogrid-header-cell'];
|
|
172
|
+
const style = {
|
|
173
|
+
cursor: isReorderDragging.value ? 'grabbing' : 'grab',
|
|
174
|
+
minWidth: `${col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH}px`,
|
|
175
|
+
width: `${columnWidth}px`,
|
|
176
|
+
maxWidth: `${columnWidth}px`,
|
|
177
|
+
};
|
|
178
|
+
if (isPinnedLeft || (isFreezeCol && colIdx === 0)) {
|
|
179
|
+
classes.push('ogrid-header-cell--pinned-left');
|
|
180
|
+
style.left = `${leftOffsets[col.columnId] ?? 0}px`;
|
|
181
|
+
}
|
|
182
|
+
else if (isPinnedRight) {
|
|
183
|
+
classes.push('ogrid-header-cell--pinned-right');
|
|
184
|
+
style.right = `${rightOffsets[col.columnId] ?? 0}px`;
|
|
185
|
+
}
|
|
186
|
+
return { classes: classes.join(' '), style };
|
|
187
|
+
};
|
|
188
|
+
// Dynamic wrapper style
|
|
189
|
+
const wrapperStyle = {
|
|
190
|
+
position: 'relative',
|
|
191
|
+
flex: '1',
|
|
192
|
+
minHeight: '0',
|
|
193
|
+
width: fitToContent ? 'fit-content' : '100%',
|
|
194
|
+
maxWidth: '100%',
|
|
195
|
+
overflowX: suppressHorizontalScroll ? 'hidden' : allowOverflowX ? 'auto' : 'hidden',
|
|
196
|
+
overflowY: 'auto',
|
|
197
|
+
backgroundColor: '#fff',
|
|
198
|
+
willChange: 'scroll-position',
|
|
199
|
+
};
|
|
200
|
+
return h('div', { class: 'ogrid-outer-container' }, [
|
|
201
|
+
// Scrollable wrapper
|
|
202
|
+
h('div', {
|
|
203
|
+
ref: (el) => { wrapperRef.value = el; vsContainerRef.value = el; },
|
|
204
|
+
tabindex: 0,
|
|
205
|
+
role: 'region',
|
|
206
|
+
'aria-label': ariaLabel ?? (ariaLabelledBy ? undefined : 'Data grid'),
|
|
207
|
+
'aria-labelledby': ariaLabelledBy,
|
|
208
|
+
onMousedown: onWrapperMousedown,
|
|
209
|
+
onKeydown: handleGridKeyDown,
|
|
210
|
+
onContextmenu,
|
|
211
|
+
'data-overflow-x': allowOverflowX ? 'true' : 'false',
|
|
212
|
+
style: wrapperStyle,
|
|
213
|
+
}, [
|
|
214
|
+
h('div', { class: 'ogrid-scroll-wrapper' }, [
|
|
215
|
+
h('div', { style: { minWidth: allowOverflowX ? `${minTableWidth}px` : undefined } }, [
|
|
216
|
+
h('div', {
|
|
217
|
+
ref: (el) => { tableContainerRef.value = el; },
|
|
218
|
+
class: ['ogrid-table-container', isLoading && items.length > 0 ? 'ogrid-table-container--loading' : ''],
|
|
219
|
+
}, [
|
|
220
|
+
// Drop indicator for column reorder
|
|
221
|
+
...(isReorderDragging.value && dropIndicatorX.value !== null ? [
|
|
222
|
+
h('div', {
|
|
223
|
+
class: 'ogrid-drop-indicator',
|
|
224
|
+
style: { left: `${dropIndicatorX.value}px` },
|
|
225
|
+
}),
|
|
226
|
+
] : []),
|
|
227
|
+
// Table
|
|
228
|
+
h('table', {
|
|
229
|
+
ref: (el) => { tableRef.value = el; },
|
|
230
|
+
class: 'ogrid-table',
|
|
231
|
+
style: { minWidth: `${minTableWidth}px` },
|
|
232
|
+
'data-freeze-rows': freezeRows != null && freezeRows >= 1 ? freezeRows : undefined,
|
|
233
|
+
'data-freeze-cols': freezeCols != null && freezeCols >= 1 ? freezeCols : undefined,
|
|
234
|
+
}, [
|
|
235
|
+
// Header
|
|
236
|
+
h('thead', { class: 'ogrid-thead' }, headerRows.map((row, rowIdx) => h('tr', { key: rowIdx, class: 'ogrid-header-row' }, [
|
|
237
|
+
// Checkbox header cell
|
|
238
|
+
...(rowIdx === headerRows.length - 1 && hasCheckboxCol ? [
|
|
239
|
+
h('th', {
|
|
240
|
+
class: 'ogrid-checkbox-header',
|
|
241
|
+
style: {
|
|
242
|
+
width: `${CHECKBOX_COLUMN_WIDTH}px`,
|
|
243
|
+
minWidth: `${CHECKBOX_COLUMN_WIDTH}px`,
|
|
244
|
+
maxWidth: `${CHECKBOX_COLUMN_WIDTH}px`,
|
|
245
|
+
},
|
|
246
|
+
}, ui.renderCheckbox({
|
|
247
|
+
modelValue: allSelected,
|
|
248
|
+
indeterminate: someSelected,
|
|
249
|
+
ariaLabel: 'Select all rows',
|
|
250
|
+
onChange: (c) => handleSelectAll(!!c),
|
|
251
|
+
})),
|
|
252
|
+
] : []),
|
|
253
|
+
// Checkbox spacer in group header row
|
|
254
|
+
...(rowIdx === 0 && rowIdx < headerRows.length - 1 && hasCheckboxCol ? [
|
|
255
|
+
h('th', {
|
|
256
|
+
rowSpan: headerRows.length - 1,
|
|
257
|
+
class: 'ogrid-checkbox-spacer',
|
|
258
|
+
style: { width: `${CHECKBOX_COLUMN_WIDTH}px`, minWidth: `${CHECKBOX_COLUMN_WIDTH}px` },
|
|
259
|
+
}),
|
|
260
|
+
] : []),
|
|
261
|
+
// Row numbers header
|
|
262
|
+
...(rowIdx === headerRows.length - 1 && hasRowNumbersCol ? [
|
|
263
|
+
h('th', {
|
|
264
|
+
class: 'ogrid-row-number-header',
|
|
265
|
+
style: {
|
|
266
|
+
width: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
267
|
+
minWidth: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
268
|
+
maxWidth: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
269
|
+
position: 'sticky',
|
|
270
|
+
left: hasCheckboxCol ? `${CHECKBOX_COLUMN_WIDTH}px` : '0',
|
|
271
|
+
zIndex: 3,
|
|
272
|
+
},
|
|
273
|
+
}, '#'),
|
|
274
|
+
] : []),
|
|
275
|
+
// Row numbers spacer
|
|
276
|
+
...(rowIdx === 0 && rowIdx < headerRows.length - 1 && hasRowNumbersCol ? [
|
|
277
|
+
h('th', {
|
|
278
|
+
rowSpan: headerRows.length - 1,
|
|
279
|
+
class: 'ogrid-row-number-spacer',
|
|
280
|
+
style: {
|
|
281
|
+
width: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
282
|
+
position: 'sticky',
|
|
283
|
+
left: hasCheckboxCol ? `${CHECKBOX_COLUMN_WIDTH}px` : '0',
|
|
284
|
+
zIndex: 3,
|
|
285
|
+
},
|
|
286
|
+
}),
|
|
287
|
+
] : []),
|
|
288
|
+
// Header cells
|
|
289
|
+
...row.map((cell, cellIdx) => {
|
|
290
|
+
if (cell.isGroup) {
|
|
291
|
+
return h('th', {
|
|
292
|
+
key: cellIdx,
|
|
293
|
+
colSpan: cell.colSpan,
|
|
294
|
+
scope: 'colgroup',
|
|
295
|
+
class: 'ogrid-column-group-header',
|
|
296
|
+
}, cell.label);
|
|
297
|
+
}
|
|
298
|
+
const col = cell.columnDef;
|
|
299
|
+
const colIdx = visibleCols.indexOf(col);
|
|
300
|
+
const { classes: headerClasses, style: headerStyle } = getHeaderClassAndStyle(col, colIdx);
|
|
301
|
+
return h('th', {
|
|
302
|
+
key: col.columnId,
|
|
303
|
+
scope: 'col',
|
|
304
|
+
'data-column-id': col.columnId,
|
|
305
|
+
rowSpan: headerRows.length > 1 ? headerRows.length - rowIdx : undefined,
|
|
306
|
+
class: headerClasses,
|
|
307
|
+
style: headerStyle,
|
|
308
|
+
onMousedown: (e) => handleReorderMouseDown(col.columnId, e),
|
|
309
|
+
}, [
|
|
310
|
+
h('div', { class: 'ogrid-header-content' }, [
|
|
311
|
+
h(ui.ColumnHeaderFilter, getHeaderFilterConfig(col, headerFilterInput)),
|
|
312
|
+
h('button', {
|
|
313
|
+
onClick: (e) => {
|
|
314
|
+
e.stopPropagation();
|
|
315
|
+
headerMenu.open(col.columnId, e.currentTarget);
|
|
316
|
+
},
|
|
317
|
+
'aria-label': 'Column options',
|
|
318
|
+
title: 'Column options',
|
|
319
|
+
class: 'ogrid-column-menu-btn',
|
|
320
|
+
}, '\u22EE'),
|
|
321
|
+
]),
|
|
322
|
+
h('div', {
|
|
323
|
+
onMousedown: (e) => { e.stopPropagation(); handleResizeStart(e, col); },
|
|
324
|
+
class: 'ogrid-resize-handle',
|
|
325
|
+
}),
|
|
326
|
+
]);
|
|
327
|
+
}),
|
|
328
|
+
]))),
|
|
329
|
+
// Body
|
|
330
|
+
...(!showEmptyInGrid ? [
|
|
331
|
+
h('tbody', {}, (() => {
|
|
332
|
+
const vsEnabled = virtualScrollEnabled.value;
|
|
333
|
+
const vr = visibleRange.value;
|
|
334
|
+
const startIdx = vsEnabled ? vr.startIndex : 0;
|
|
335
|
+
const endIdx = vsEnabled ? Math.min(vr.endIndex, items.length - 1) : items.length - 1;
|
|
336
|
+
const rows = [];
|
|
337
|
+
if (vsEnabled && vr.offsetTop > 0) {
|
|
338
|
+
rows.push(h('tr', { key: '__vs-top', style: { height: `${vr.offsetTop}px` } }));
|
|
339
|
+
}
|
|
340
|
+
for (let rowIndex = startIdx; rowIndex <= endIdx; rowIndex++) {
|
|
341
|
+
const item = items[rowIndex];
|
|
342
|
+
if (!item)
|
|
343
|
+
continue;
|
|
344
|
+
const rowIdStr = getRowId(item);
|
|
345
|
+
const isSelected = selectedRowIds.has(rowIdStr);
|
|
346
|
+
rows.push(h('tr', {
|
|
347
|
+
key: rowIdStr,
|
|
348
|
+
'data-row-id': rowIdStr,
|
|
349
|
+
onClick: handleSingleRowClick,
|
|
350
|
+
style: { cursor: rowSelection === 'single' ? 'pointer' : undefined },
|
|
351
|
+
}, [
|
|
352
|
+
// Checkbox cell
|
|
353
|
+
...(hasCheckboxCol ? [
|
|
354
|
+
h('td', {
|
|
355
|
+
class: 'ogrid-checkbox-cell',
|
|
356
|
+
style: {
|
|
357
|
+
width: `${CHECKBOX_COLUMN_WIDTH}px`,
|
|
358
|
+
minWidth: `${CHECKBOX_COLUMN_WIDTH}px`,
|
|
359
|
+
maxWidth: `${CHECKBOX_COLUMN_WIDTH}px`,
|
|
360
|
+
},
|
|
361
|
+
}, h('div', {
|
|
362
|
+
'data-row-index': rowIndex,
|
|
363
|
+
'data-col-index': 0,
|
|
364
|
+
onClick: stopPropagation,
|
|
365
|
+
class: 'ogrid-checkbox-wrapper',
|
|
366
|
+
}, ui.renderCheckbox({
|
|
367
|
+
modelValue: isSelected,
|
|
368
|
+
ariaLabel: `Select row ${rowIndex + 1}`,
|
|
369
|
+
onChange: (checked) => handleRowCheckboxChange(rowIdStr, checked, rowIndex, lastMouseShift.value),
|
|
370
|
+
}))),
|
|
371
|
+
] : []),
|
|
372
|
+
// Row numbers cell
|
|
373
|
+
...(hasRowNumbersCol ? [
|
|
374
|
+
h('td', {
|
|
375
|
+
class: 'ogrid-row-number-cell',
|
|
376
|
+
style: {
|
|
377
|
+
width: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
378
|
+
minWidth: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
379
|
+
maxWidth: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
380
|
+
padding: '6px',
|
|
381
|
+
position: 'sticky',
|
|
382
|
+
left: hasCheckboxCol ? `${CHECKBOX_COLUMN_WIDTH}px` : '0',
|
|
383
|
+
zIndex: 2,
|
|
384
|
+
},
|
|
385
|
+
}, String(rowNumberOffset + rowIndex + 1)),
|
|
386
|
+
] : []),
|
|
387
|
+
// Data cells
|
|
388
|
+
...columnLayouts.map((cl, colIdx) => h('td', {
|
|
389
|
+
key: cl.col.columnId,
|
|
390
|
+
'data-column-id': cl.col.columnId,
|
|
391
|
+
class: cl.tdClasses,
|
|
392
|
+
style: cl.tdDynamicStyle,
|
|
393
|
+
}, [renderCellContent(item, cl.col, rowIndex, colIdx)])),
|
|
394
|
+
]));
|
|
395
|
+
}
|
|
396
|
+
if (vsEnabled && vr.offsetBottom > 0) {
|
|
397
|
+
rows.push(h('tr', { key: '__vs-bottom', style: { height: `${vr.offsetBottom}px` } }));
|
|
398
|
+
}
|
|
399
|
+
return rows;
|
|
400
|
+
})()),
|
|
401
|
+
] : []),
|
|
402
|
+
]),
|
|
403
|
+
// Empty state
|
|
404
|
+
...(showEmptyInGrid && p.emptyState ? [
|
|
405
|
+
ui.renderEmptyState(p.emptyState),
|
|
406
|
+
] : []),
|
|
407
|
+
]),
|
|
408
|
+
]),
|
|
409
|
+
]),
|
|
410
|
+
]),
|
|
411
|
+
// Context menu (teleported to body)
|
|
412
|
+
...(menuPosition ? [
|
|
413
|
+
h(Teleport, { to: 'body' }, h(ui.GridContextMenu, {
|
|
414
|
+
x: menuPosition.x,
|
|
415
|
+
y: menuPosition.y,
|
|
416
|
+
hasSelection: hasCellSelection,
|
|
417
|
+
canUndo,
|
|
418
|
+
canRedo,
|
|
419
|
+
onUndo: onUndo ?? NOOP,
|
|
420
|
+
onRedo: onRedo ?? NOOP,
|
|
421
|
+
onCopy: handleCopy,
|
|
422
|
+
onCut: handleCut,
|
|
423
|
+
onPaste: () => { void handlePaste(); },
|
|
424
|
+
onSelectAll: handleSelectAllCells,
|
|
425
|
+
onClose: closeContextMenu,
|
|
426
|
+
})),
|
|
427
|
+
] : []),
|
|
428
|
+
// Marching ants overlay
|
|
429
|
+
h(MarchingAntsOverlay, {
|
|
430
|
+
containerRef: tableContainerRef,
|
|
431
|
+
selectionRange,
|
|
432
|
+
copyRange: _copyRange,
|
|
433
|
+
cutRange: _cutRange,
|
|
434
|
+
colOffset: _colOffset,
|
|
435
|
+
items,
|
|
436
|
+
visibleColumns: p.visibleColumns instanceof Set ? Array.from(p.visibleColumns) : p.visibleColumns,
|
|
437
|
+
columnSizingOverrides: layout.columnSizingOverrides,
|
|
438
|
+
columnOrder: p.columnOrder,
|
|
439
|
+
}),
|
|
440
|
+
// Column header menu
|
|
441
|
+
h(ui.ColumnHeaderMenu, {
|
|
442
|
+
isOpen: headerMenu.isOpen,
|
|
443
|
+
anchorElement: headerMenu.anchorElement,
|
|
444
|
+
onClose: headerMenu.close,
|
|
445
|
+
onPinLeft: headerMenu.handlePinLeft,
|
|
446
|
+
onPinRight: headerMenu.handlePinRight,
|
|
447
|
+
onUnpin: headerMenu.handleUnpin,
|
|
448
|
+
onSortAsc: headerMenu.handleSortAsc,
|
|
449
|
+
onSortDesc: headerMenu.handleSortDesc,
|
|
450
|
+
onClearSort: headerMenu.handleClearSort,
|
|
451
|
+
onAutosizeThis: headerMenu.handleAutosizeThis,
|
|
452
|
+
onAutosizeAll: headerMenu.handleAutosizeAll,
|
|
453
|
+
canPinLeft: headerMenu.canPinLeft,
|
|
454
|
+
canPinRight: headerMenu.canPinRight,
|
|
455
|
+
canUnpin: headerMenu.canUnpin,
|
|
456
|
+
currentSort: headerMenu.currentSort,
|
|
457
|
+
isSortable: headerMenu.isSortable,
|
|
458
|
+
isResizable: headerMenu.isResizable,
|
|
459
|
+
}),
|
|
460
|
+
// Status bar
|
|
461
|
+
...(statusBarConfig ? [
|
|
462
|
+
h(StatusBar, {
|
|
463
|
+
totalCount: statusBarConfig.totalCount,
|
|
464
|
+
filteredCount: statusBarConfig.filteredCount,
|
|
465
|
+
selectedCount: statusBarConfig.selectedCount ?? selectedRowIds.size,
|
|
466
|
+
selectedCellCount: selectionRange
|
|
467
|
+
? (Math.abs(selectionRange.endRow - selectionRange.startRow) + 1) * (Math.abs(selectionRange.endCol - selectionRange.startCol) + 1)
|
|
468
|
+
: undefined,
|
|
469
|
+
aggregation: statusBarConfig.aggregation,
|
|
470
|
+
suppressRowCount: statusBarConfig.suppressRowCount,
|
|
471
|
+
}),
|
|
472
|
+
] : []),
|
|
473
|
+
// Loading overlay
|
|
474
|
+
...(isLoading ? [
|
|
475
|
+
h('div', { class: 'ogrid-loading-overlay' }, ui.renderSpinner(loadingMessage)),
|
|
476
|
+
] : []),
|
|
477
|
+
]);
|
|
478
|
+
};
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { ref, computed } from 'vue';
|
|
2
|
+
import { measureColumnContentWidth } from '@alaarab/ogrid-core';
|
|
2
3
|
/**
|
|
3
|
-
* Manages state for the column header menu (pin
|
|
4
|
+
* Manages state for the column header menu (pin/unpin, sort, autosize actions).
|
|
4
5
|
* Tracks which column's menu is open, anchor element, and action handlers.
|
|
5
6
|
*/
|
|
6
7
|
export function useColumnHeaderMenuState(params) {
|
|
7
|
-
const { pinnedColumns, onPinColumn, onUnpinColumn } = params;
|
|
8
|
+
const { columns, pinnedColumns, onPinColumn, onUnpinColumn, onSort, onColumnResized, onAutosizeColumn, sortBy, sortDirection, } = params;
|
|
8
9
|
const isOpen = ref(false);
|
|
9
10
|
const openForColumn = ref(null);
|
|
10
11
|
const anchorElement = ref(null);
|
|
@@ -18,10 +19,25 @@ export function useColumnHeaderMenuState(params) {
|
|
|
18
19
|
openForColumn.value = null;
|
|
19
20
|
anchorElement.value = null;
|
|
20
21
|
};
|
|
22
|
+
const currentColumn = computed(() => openForColumn.value ? columns.value.find((c) => c.columnId === openForColumn.value) : undefined);
|
|
21
23
|
const currentPinState = computed(() => openForColumn.value ? pinnedColumns.value[openForColumn.value] : undefined);
|
|
22
24
|
const canPinLeft = computed(() => currentPinState.value !== 'left');
|
|
23
25
|
const canPinRight = computed(() => currentPinState.value !== 'right');
|
|
24
26
|
const canUnpin = computed(() => !!currentPinState.value);
|
|
27
|
+
const currentSort = computed(() => {
|
|
28
|
+
if (!openForColumn.value || !sortBy?.value || sortBy.value !== openForColumn.value) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return sortDirection?.value ?? null;
|
|
32
|
+
});
|
|
33
|
+
const isSortable = computed(() => {
|
|
34
|
+
const col = currentColumn.value;
|
|
35
|
+
return col?.sortable !== false;
|
|
36
|
+
});
|
|
37
|
+
const isResizable = computed(() => {
|
|
38
|
+
// All columns are resizable by default (no per-column resizable flag in core)
|
|
39
|
+
return true;
|
|
40
|
+
});
|
|
25
41
|
const handlePinLeft = () => {
|
|
26
42
|
if (openForColumn.value && canPinLeft.value) {
|
|
27
43
|
onPinColumn(openForColumn.value, 'left');
|
|
@@ -40,6 +56,41 @@ export function useColumnHeaderMenuState(params) {
|
|
|
40
56
|
close();
|
|
41
57
|
}
|
|
42
58
|
};
|
|
59
|
+
const handleSortAsc = () => {
|
|
60
|
+
if (openForColumn.value && onSort) {
|
|
61
|
+
onSort(openForColumn.value, 'asc');
|
|
62
|
+
close();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const handleSortDesc = () => {
|
|
66
|
+
if (openForColumn.value && onSort) {
|
|
67
|
+
onSort(openForColumn.value, 'desc');
|
|
68
|
+
close();
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const handleClearSort = () => {
|
|
72
|
+
if (openForColumn.value && onSort) {
|
|
73
|
+
onSort(openForColumn.value, null);
|
|
74
|
+
close();
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const handleAutosizeThis = () => {
|
|
78
|
+
const resizer = onAutosizeColumn ?? onColumnResized;
|
|
79
|
+
if (!openForColumn.value || !resizer || !isResizable.value)
|
|
80
|
+
return;
|
|
81
|
+
const col = currentColumn.value;
|
|
82
|
+
resizer(openForColumn.value, measureColumnContentWidth(openForColumn.value, col?.minWidth));
|
|
83
|
+
close();
|
|
84
|
+
};
|
|
85
|
+
const handleAutosizeAll = () => {
|
|
86
|
+
const resizer = onAutosizeColumn ?? onColumnResized;
|
|
87
|
+
if (!resizer)
|
|
88
|
+
return;
|
|
89
|
+
columns.value.forEach((col) => {
|
|
90
|
+
resizer(col.columnId, measureColumnContentWidth(col.columnId, col.minWidth));
|
|
91
|
+
});
|
|
92
|
+
close();
|
|
93
|
+
};
|
|
43
94
|
return {
|
|
44
95
|
isOpen,
|
|
45
96
|
openForColumn,
|
|
@@ -49,8 +100,16 @@ export function useColumnHeaderMenuState(params) {
|
|
|
49
100
|
handlePinLeft,
|
|
50
101
|
handlePinRight,
|
|
51
102
|
handleUnpin,
|
|
103
|
+
handleSortAsc,
|
|
104
|
+
handleSortDesc,
|
|
105
|
+
handleClearSort,
|
|
106
|
+
handleAutosizeThis,
|
|
107
|
+
handleAutosizeAll,
|
|
52
108
|
canPinLeft,
|
|
53
109
|
canPinRight,
|
|
54
110
|
canUnpin,
|
|
111
|
+
currentSort,
|
|
112
|
+
isSortable,
|
|
113
|
+
isResizable,
|
|
55
114
|
};
|
|
56
115
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ref, computed } from 'vue';
|
|
2
|
-
import { flattenColumns, getDataGridStatusBarConfig, parseValue, computeAggregations } from '@alaarab/ogrid-core';
|
|
2
|
+
import { flattenColumns, getDataGridStatusBarConfig, parseValue, computeAggregations, CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH } from '@alaarab/ogrid-core';
|
|
3
3
|
import { useRowSelection } from './useRowSelection';
|
|
4
4
|
import { useCellEditing } from './useCellEditing';
|
|
5
5
|
import { useActiveCell } from './useActiveCell';
|
|
@@ -175,11 +175,35 @@ export function useDataGridState(params) {
|
|
|
175
175
|
pinnedColumns: pinnedColumnsProp,
|
|
176
176
|
onColumnPinned: onColumnPinnedProp.value,
|
|
177
177
|
});
|
|
178
|
+
// Autosize callback — updates internal column sizing state + notifies external listener
|
|
179
|
+
const handleAutosizeColumn = (columnId, width) => {
|
|
180
|
+
setColumnSizingOverrides({ ...columnSizingOverrides.value, [columnId]: { widthPx: width } });
|
|
181
|
+
onColumnResizedProp.value?.(columnId, width);
|
|
182
|
+
};
|
|
178
183
|
const headerMenuResult = useColumnHeaderMenuState({
|
|
184
|
+
columns: flatColumns,
|
|
179
185
|
pinnedColumns: pinningResult.pinnedColumns,
|
|
180
186
|
onPinColumn: pinningResult.pinColumn,
|
|
181
187
|
onUnpinColumn: pinningResult.unpinColumn,
|
|
188
|
+
onSort: props.value.onColumnSort,
|
|
189
|
+
onColumnResized: onColumnResizedProp.value,
|
|
190
|
+
onAutosizeColumn: handleAutosizeColumn,
|
|
191
|
+
sortBy: computed(() => props.value.sortBy),
|
|
192
|
+
sortDirection: computed(() => props.value.sortDirection),
|
|
193
|
+
});
|
|
194
|
+
// Build column width map for pinning offset computation
|
|
195
|
+
const columnWidthMap = computed(() => {
|
|
196
|
+
const map = {};
|
|
197
|
+
for (const col of visibleCols.value) {
|
|
198
|
+
const override = columnSizingOverrides.value[col.columnId];
|
|
199
|
+
map[col.columnId] = override
|
|
200
|
+
? override.widthPx
|
|
201
|
+
: (col.idealWidth ?? col.defaultWidth ?? col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH);
|
|
202
|
+
}
|
|
203
|
+
return map;
|
|
182
204
|
});
|
|
205
|
+
const leftOffsets = computed(() => pinningResult.computeLeftOffsets(visibleCols.value, columnWidthMap.value, DEFAULT_MIN_COLUMN_WIDTH, hasCheckboxCol.value, CHECKBOX_COLUMN_WIDTH));
|
|
206
|
+
const rightOffsets = computed(() => pinningResult.computeRightOffsets(visibleCols.value, columnWidthMap.value, DEFAULT_MIN_COLUMN_WIDTH));
|
|
183
207
|
const aggregation = computed(() => computeAggregations(items.value, visibleCols.value, cellSelection.value ? selectionRange.value : null));
|
|
184
208
|
const statusBarConfig = computed(() => {
|
|
185
209
|
const base = getDataGridStatusBarConfig(statusBarProp.value, items.value.length, rowSelectionResult.selectedRowIds.value.size);
|
|
@@ -324,8 +348,8 @@ export function useDataGridState(params) {
|
|
|
324
348
|
pinColumn: pinningResult.pinColumn,
|
|
325
349
|
unpinColumn: pinningResult.unpinColumn,
|
|
326
350
|
isPinned: pinningResult.isPinned,
|
|
327
|
-
|
|
328
|
-
|
|
351
|
+
leftOffsets: leftOffsets.value,
|
|
352
|
+
rightOffsets: rightOffsets.value,
|
|
329
353
|
headerMenu: {
|
|
330
354
|
isOpen: headerMenuResult.isOpen.value,
|
|
331
355
|
openForColumn: headerMenuResult.openForColumn.value,
|
|
@@ -335,9 +359,17 @@ export function useDataGridState(params) {
|
|
|
335
359
|
handlePinLeft: headerMenuResult.handlePinLeft,
|
|
336
360
|
handlePinRight: headerMenuResult.handlePinRight,
|
|
337
361
|
handleUnpin: headerMenuResult.handleUnpin,
|
|
362
|
+
handleSortAsc: headerMenuResult.handleSortAsc,
|
|
363
|
+
handleSortDesc: headerMenuResult.handleSortDesc,
|
|
364
|
+
handleClearSort: headerMenuResult.handleClearSort,
|
|
365
|
+
handleAutosizeThis: headerMenuResult.handleAutosizeThis,
|
|
366
|
+
handleAutosizeAll: headerMenuResult.handleAutosizeAll,
|
|
338
367
|
canPinLeft: headerMenuResult.canPinLeft.value,
|
|
339
368
|
canPinRight: headerMenuResult.canPinRight.value,
|
|
340
369
|
canUnpin: headerMenuResult.canUnpin.value,
|
|
370
|
+
currentSort: headerMenuResult.currentSort.value,
|
|
371
|
+
isSortable: headerMenuResult.isSortable.value,
|
|
372
|
+
isResizable: headerMenuResult.isResizable.value,
|
|
341
373
|
},
|
|
342
374
|
}));
|
|
343
375
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ref, computed, watch } from 'vue';
|
|
2
|
-
import { mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, flattenColumns, processClientSideData, } from '@alaarab/ogrid-core';
|
|
2
|
+
import { mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, flattenColumns, processClientSideData, computeNextSortState, } from '@alaarab/ogrid-core';
|
|
3
3
|
import { useFilterOptions } from './useFilterOptions';
|
|
4
4
|
import { useSideBarState } from './useSideBarState';
|
|
5
5
|
const DEFAULT_PAGE_SIZE = 25;
|
|
@@ -126,11 +126,8 @@ export function useOGrid(props) {
|
|
|
126
126
|
internalVisibleColumns.value = cols;
|
|
127
127
|
callbacks.value.onVisibleColumnsChange?.(cols);
|
|
128
128
|
};
|
|
129
|
-
const handleSort = (columnKey) => {
|
|
130
|
-
setSort(
|
|
131
|
-
field: columnKey,
|
|
132
|
-
direction: sort.value.field === columnKey && sort.value.direction === 'asc' ? 'desc' : 'asc',
|
|
133
|
-
});
|
|
129
|
+
const handleSort = (columnKey, direction) => {
|
|
130
|
+
setSort(computeNextSortState(sort.value, columnKey, direction));
|
|
134
131
|
};
|
|
135
132
|
const handleFilterChange = (key, value) => {
|
|
136
133
|
setFilters(mergeFilter(filters.value, key, value));
|
|
@@ -329,6 +326,9 @@ export function useOGrid(props) {
|
|
|
329
326
|
rowSelection: p.rowSelection ?? 'none',
|
|
330
327
|
selectedRows: effectiveSelectedRows.value,
|
|
331
328
|
onSelectionChange: handleSelectionChange,
|
|
329
|
+
showRowNumbers: p.showRowNumbers,
|
|
330
|
+
currentPage: page.value,
|
|
331
|
+
pageSize: pageSize.value,
|
|
332
332
|
statusBar: statusBarConfig.value,
|
|
333
333
|
isLoading: isLoadingResolved.value,
|
|
334
334
|
filters: filters.value,
|
|
@@ -339,7 +339,9 @@ export function useOGrid(props) {
|
|
|
339
339
|
getUserByEmail: ds?.getUserByEmail,
|
|
340
340
|
layoutMode: p.layoutMode,
|
|
341
341
|
suppressHorizontalScroll: p.suppressHorizontalScroll,
|
|
342
|
+
columnReorder: p.columnReorder,
|
|
342
343
|
virtualScroll: p.virtualScroll,
|
|
344
|
+
density: p.density ?? 'normal',
|
|
343
345
|
'aria-label': p['aria-label'],
|
|
344
346
|
'aria-labelledby': p['aria-labelledby'],
|
|
345
347
|
emptyState: {
|
package/dist/esm/index.js
CHANGED
|
@@ -8,3 +8,5 @@ export { StatusBar } from './components/StatusBar';
|
|
|
8
8
|
export { useOGrid, useDataGridState, useActiveCell, useCellEditing, useCellSelection, useClipboard, useRowSelection, useKeyboardNavigation, useFillHandle, useUndoRedo, useContextMenu, useColumnResize, useColumnReorder, useVirtualScroll, useFilterOptions, useDebounce, useDebouncedCallback, useTableLayout, useColumnHeaderFilterState, useTextFilterState, useMultiSelectFilterState, usePeopleFilterState, useDateFilterState, useColumnChooserState, useInlineCellEditorState, useRichSelectState, useSideBarState, useColumnPinning, useColumnHeaderMenuState, useDataGridTableSetup, } from './composables';
|
|
9
9
|
// View model utilities (for UI packages)
|
|
10
10
|
export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, } from './utils';
|
|
11
|
+
// DataGridTable factory (for UI packages)
|
|
12
|
+
export { createDataGridTable } from './components/createDataGridTable';
|
|
@@ -34,6 +34,28 @@ export declare const MarchingAntsOverlay: import("vue").DefineComponent<import("
|
|
|
34
34
|
type: NumberConstructor;
|
|
35
35
|
required: true;
|
|
36
36
|
};
|
|
37
|
+
/** Items array — triggers re-measurement when data changes (e.g., sorting) */
|
|
38
|
+
items: {
|
|
39
|
+
type: PropType<readonly unknown[]>;
|
|
40
|
+
required: true;
|
|
41
|
+
};
|
|
42
|
+
/** Visible columns — triggers re-measurement when columns are hidden/shown */
|
|
43
|
+
visibleColumns: {
|
|
44
|
+
type: PropType<readonly string[] | undefined>;
|
|
45
|
+
default: undefined;
|
|
46
|
+
};
|
|
47
|
+
/** Column sizing overrides — triggers re-measurement when columns are resized */
|
|
48
|
+
columnSizingOverrides: {
|
|
49
|
+
type: PropType<Record<string, {
|
|
50
|
+
widthPx: number;
|
|
51
|
+
}>>;
|
|
52
|
+
required: true;
|
|
53
|
+
};
|
|
54
|
+
/** Column order — triggers re-measurement when columns are reordered */
|
|
55
|
+
columnOrder: {
|
|
56
|
+
type: PropType<readonly string[] | undefined>;
|
|
57
|
+
default: undefined;
|
|
58
|
+
};
|
|
37
59
|
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
38
60
|
[key: string]: any;
|
|
39
61
|
}> | null, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
@@ -62,8 +84,32 @@ export declare const MarchingAntsOverlay: import("vue").DefineComponent<import("
|
|
|
62
84
|
type: NumberConstructor;
|
|
63
85
|
required: true;
|
|
64
86
|
};
|
|
87
|
+
/** Items array — triggers re-measurement when data changes (e.g., sorting) */
|
|
88
|
+
items: {
|
|
89
|
+
type: PropType<readonly unknown[]>;
|
|
90
|
+
required: true;
|
|
91
|
+
};
|
|
92
|
+
/** Visible columns — triggers re-measurement when columns are hidden/shown */
|
|
93
|
+
visibleColumns: {
|
|
94
|
+
type: PropType<readonly string[] | undefined>;
|
|
95
|
+
default: undefined;
|
|
96
|
+
};
|
|
97
|
+
/** Column sizing overrides — triggers re-measurement when columns are resized */
|
|
98
|
+
columnSizingOverrides: {
|
|
99
|
+
type: PropType<Record<string, {
|
|
100
|
+
widthPx: number;
|
|
101
|
+
}>>;
|
|
102
|
+
required: true;
|
|
103
|
+
};
|
|
104
|
+
/** Column order — triggers re-measurement when columns are reordered */
|
|
105
|
+
columnOrder: {
|
|
106
|
+
type: PropType<readonly string[] | undefined>;
|
|
107
|
+
default: undefined;
|
|
108
|
+
};
|
|
65
109
|
}>> & Readonly<{}>, {
|
|
66
110
|
selectionRange: ISelectionRange | null;
|
|
67
111
|
copyRange: ISelectionRange | null;
|
|
68
112
|
cutRange: ISelectionRange | null;
|
|
113
|
+
visibleColumns: readonly string[] | undefined;
|
|
114
|
+
columnOrder: readonly string[] | undefined;
|
|
69
115
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared DataGridTable factory for Vue UI packages.
|
|
3
|
+
*
|
|
4
|
+
* Both vue-vuetify and vue-primevue DataGridTable components are 97% identical —
|
|
5
|
+
* they only differ in which checkbox and spinner components they render.
|
|
6
|
+
* This factory extracts all shared logic into one place.
|
|
7
|
+
*/
|
|
8
|
+
import { type PropType, type VNode, type Component } from 'vue';
|
|
9
|
+
import type { IOGridDataGridProps } from '../types';
|
|
10
|
+
/** Framework-specific component bindings passed by each UI package */
|
|
11
|
+
export interface IDataGridTableUIBindings {
|
|
12
|
+
/** Render a checkbox (Vuetify VCheckbox vs PrimeVue Checkbox) */
|
|
13
|
+
renderCheckbox: (props: {
|
|
14
|
+
modelValue: boolean;
|
|
15
|
+
indeterminate?: boolean;
|
|
16
|
+
ariaLabel: string;
|
|
17
|
+
onChange: (checked: boolean) => void;
|
|
18
|
+
}) => VNode;
|
|
19
|
+
/** Render a loading spinner with message */
|
|
20
|
+
renderSpinner: (message: string) => VNode;
|
|
21
|
+
/** Package-local ColumnHeaderFilter component */
|
|
22
|
+
ColumnHeaderFilter: Component;
|
|
23
|
+
/** Package-local ColumnHeaderMenu component */
|
|
24
|
+
ColumnHeaderMenu: Component;
|
|
25
|
+
/** Package-local InlineCellEditor component */
|
|
26
|
+
InlineCellEditor: Component;
|
|
27
|
+
/** Package-local GridContextMenu component */
|
|
28
|
+
GridContextMenu: Component;
|
|
29
|
+
/** Package-local renderEmptyState function */
|
|
30
|
+
renderEmptyState: (emptyState: {
|
|
31
|
+
render?: () => unknown;
|
|
32
|
+
message?: string | null;
|
|
33
|
+
hasActiveFilters?: boolean;
|
|
34
|
+
onClearAll?: () => void;
|
|
35
|
+
}) => VNode;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Creates a DataGridTable component with framework-specific UI bindings.
|
|
39
|
+
* All grid logic, layout, and interaction handling is shared.
|
|
40
|
+
*/
|
|
41
|
+
export declare function createDataGridTable(ui: IDataGridTableUIBindings): import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
42
|
+
gridProps: {
|
|
43
|
+
type: PropType<IOGridDataGridProps<unknown>>;
|
|
44
|
+
required: true;
|
|
45
|
+
};
|
|
46
|
+
}>, () => VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
47
|
+
[key: string]: any;
|
|
48
|
+
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
49
|
+
gridProps: {
|
|
50
|
+
type: PropType<IOGridDataGridProps<unknown>>;
|
|
51
|
+
required: true;
|
|
52
|
+
};
|
|
53
|
+
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { type Ref } from 'vue';
|
|
2
|
-
|
|
2
|
+
import type { IColumnDef } from '../types';
|
|
3
|
+
export interface UseColumnHeaderMenuStateParams<T = unknown> {
|
|
4
|
+
columns: Ref<IColumnDef<T>[]>;
|
|
3
5
|
pinnedColumns: Ref<Record<string, 'left' | 'right'>>;
|
|
4
6
|
onPinColumn: (columnId: string, side: 'left' | 'right') => void;
|
|
5
7
|
onUnpinColumn: (columnId: string) => void;
|
|
8
|
+
onSort?: (columnId: string, direction: 'asc' | 'desc' | null) => void;
|
|
9
|
+
onColumnResized?: (columnId: string, width: number) => void;
|
|
10
|
+
onAutosizeColumn?: (columnId: string, width: number) => void;
|
|
11
|
+
sortBy?: Ref<string | undefined>;
|
|
12
|
+
sortDirection?: Ref<'asc' | 'desc' | undefined>;
|
|
13
|
+
data?: Ref<unknown[]>;
|
|
14
|
+
getRowId?: (item: unknown) => string | number;
|
|
6
15
|
}
|
|
7
16
|
export interface UseColumnHeaderMenuStateResult {
|
|
8
17
|
isOpen: Ref<boolean>;
|
|
@@ -13,12 +22,20 @@ export interface UseColumnHeaderMenuStateResult {
|
|
|
13
22
|
handlePinLeft: () => void;
|
|
14
23
|
handlePinRight: () => void;
|
|
15
24
|
handleUnpin: () => void;
|
|
25
|
+
handleSortAsc: () => void;
|
|
26
|
+
handleSortDesc: () => void;
|
|
27
|
+
handleClearSort: () => void;
|
|
28
|
+
handleAutosizeThis: () => void;
|
|
29
|
+
handleAutosizeAll: () => void;
|
|
16
30
|
canPinLeft: Ref<boolean>;
|
|
17
31
|
canPinRight: Ref<boolean>;
|
|
18
32
|
canUnpin: Ref<boolean>;
|
|
33
|
+
currentSort: Ref<'asc' | 'desc' | null>;
|
|
34
|
+
isSortable: Ref<boolean>;
|
|
35
|
+
isResizable: Ref<boolean>;
|
|
19
36
|
}
|
|
20
37
|
/**
|
|
21
|
-
* Manages state for the column header menu (pin
|
|
38
|
+
* Manages state for the column header menu (pin/unpin, sort, autosize actions).
|
|
22
39
|
* Tracks which column's menu is open, anchor element, and action handlers.
|
|
23
40
|
*/
|
|
24
|
-
export declare function useColumnHeaderMenuState(params: UseColumnHeaderMenuStateParams): UseColumnHeaderMenuStateResult;
|
|
41
|
+
export declare function useColumnHeaderMenuState<T = unknown>(params: UseColumnHeaderMenuStateParams<T>): UseColumnHeaderMenuStateResult;
|
|
@@ -121,12 +121,8 @@ export interface DataGridPinningState {
|
|
|
121
121
|
pinColumn: (columnId: string, side: 'left' | 'right') => void;
|
|
122
122
|
unpinColumn: (columnId: string) => void;
|
|
123
123
|
isPinned: (columnId: string) => 'left' | 'right' | undefined;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}[], columnWidths: Record<string, number>, defaultWidth: number, hasCheckboxColumn: boolean, checkboxColumnWidth: number) => Record<string, number>;
|
|
127
|
-
computeRightOffsets: (visibleCols: {
|
|
128
|
-
columnId: string;
|
|
129
|
-
}[], columnWidths: Record<string, number>, defaultWidth: number) => Record<string, number>;
|
|
124
|
+
leftOffsets: Record<string, number>;
|
|
125
|
+
rightOffsets: Record<string, number>;
|
|
130
126
|
headerMenu: {
|
|
131
127
|
isOpen: boolean;
|
|
132
128
|
openForColumn: string | null;
|
|
@@ -136,9 +132,17 @@ export interface DataGridPinningState {
|
|
|
136
132
|
handlePinLeft: () => void;
|
|
137
133
|
handlePinRight: () => void;
|
|
138
134
|
handleUnpin: () => void;
|
|
135
|
+
handleSortAsc: () => void;
|
|
136
|
+
handleSortDesc: () => void;
|
|
137
|
+
handleClearSort: () => void;
|
|
138
|
+
handleAutosizeThis: () => void;
|
|
139
|
+
handleAutosizeAll: () => void;
|
|
139
140
|
canPinLeft: boolean;
|
|
140
141
|
canPinRight: boolean;
|
|
141
142
|
canUnpin: boolean;
|
|
143
|
+
currentSort: 'asc' | 'desc' | null;
|
|
144
|
+
isSortable: boolean;
|
|
145
|
+
isResizable: boolean;
|
|
142
146
|
};
|
|
143
147
|
}
|
|
144
148
|
export interface UseDataGridStateResult<T> {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -8,4 +8,5 @@ export { useOGrid, useDataGridState, useActiveCell, useCellEditing, useCellSelec
|
|
|
8
8
|
export type { UseOGridResult, UseOGridPagination, UseOGridColumnChooser, UseOGridLayout, UseOGridFilters, ColumnChooserPlacement, UseDataGridStateParams, UseDataGridStateResult, DataGridLayoutState, DataGridRowSelectionState, DataGridEditingState, DataGridCellInteractionState, DataGridContextMenuState, DataGridViewModelState, DataGridPinningState, UseActiveCellResult, EditingCell, UseCellEditingParams, UseCellEditingResult, UseCellSelectionParams, UseCellSelectionResult, UseClipboardParams, UseClipboardResult, UseRowSelectionParams, UseRowSelectionResult, UseKeyboardNavigationParams, UseKeyboardNavigationResult, UseFillHandleParams, UseFillHandleResult, UseUndoRedoParams, UseUndoRedoResult, ContextMenuPosition, UseContextMenuResult, UseColumnResizeParams, UseColumnResizeResult, UseColumnReorderParams, UseColumnReorderResult, UseVirtualScrollParams, UseVirtualScrollResult, UseFilterOptionsResult, UseTableLayoutParams, UseTableLayoutResult, UseColumnHeaderFilterStateParams, UseColumnHeaderFilterStateResult, UseTextFilterStateParams, UseTextFilterStateResult, UseMultiSelectFilterStateParams, UseMultiSelectFilterStateResult, UsePeopleFilterStateParams, UsePeopleFilterStateResult, UseDateFilterStateParams, UseDateFilterStateResult, UseColumnChooserStateParams, UseColumnChooserStateResult, InlineCellEditorType, UseInlineCellEditorStateParams, UseInlineCellEditorStateResult, UseRichSelectStateParams, UseRichSelectStateResult, UseSideBarStateParams, UseSideBarStateResult, DebouncedFn, UseColumnPinningParams, UseColumnPinningResult, UseColumnHeaderMenuStateParams, UseColumnHeaderMenuStateResult, UseDataGridTableSetupParams, UseDataGridTableSetupResult, } from './composables';
|
|
9
9
|
export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, } from './utils';
|
|
10
10
|
export type { HeaderFilterConfigInput, HeaderFilterConfig, CellRenderDescriptorInput, CellRenderDescriptor, CellRenderMode, CellInteractionHandlers, CellInteractionProps, } from './utils';
|
|
11
|
+
export { createDataGridTable, type IDataGridTableUIBindings } from './components/createDataGridTable';
|
|
11
12
|
export type { SideBarProps, SideBarFilterColumn } from './components/SideBar';
|
|
@@ -42,6 +42,8 @@ interface IOGridBaseProps<T> {
|
|
|
42
42
|
rowSelection?: RowSelectionMode;
|
|
43
43
|
selectedRows?: Set<RowId>;
|
|
44
44
|
onSelectionChange?: (event: IRowSelectionChangeEvent<T>) => void;
|
|
45
|
+
/** Show Excel-style row numbers column at the start of the grid (1, 2, 3...). Default: false. */
|
|
46
|
+
showRowNumbers?: boolean;
|
|
45
47
|
statusBar?: boolean | IStatusBarProps;
|
|
46
48
|
defaultPageSize?: number;
|
|
47
49
|
defaultSortBy?: string;
|
|
@@ -102,13 +104,15 @@ export interface IOGridDataGridProps<T> {
|
|
|
102
104
|
getRowId: (item: T) => RowId;
|
|
103
105
|
sortBy?: string;
|
|
104
106
|
sortDirection: 'asc' | 'desc';
|
|
105
|
-
onColumnSort: (columnKey: string) => void;
|
|
107
|
+
onColumnSort: (columnKey: string, direction?: 'asc' | 'desc' | null) => void;
|
|
106
108
|
visibleColumns: Set<string>;
|
|
107
109
|
/** Optional column display order (column ids). When set, visible columns are ordered by this array. */
|
|
108
110
|
columnOrder?: string[];
|
|
109
111
|
onColumnOrderChange?: (order: string[]) => void;
|
|
110
112
|
/** Called when a column is resized by the user. */
|
|
111
113
|
onColumnResized?: (columnId: string, width: number) => void;
|
|
114
|
+
/** Called when user requests autosize for a single column (with measured width). */
|
|
115
|
+
onAutosizeColumn?: (columnId: string, width: number) => void;
|
|
112
116
|
/** Called when a column is pinned or unpinned. */
|
|
113
117
|
onColumnPinned?: (columnId: string, pinned: 'left' | 'right' | null) => void;
|
|
114
118
|
/** Runtime pin overrides (from restored state or programmatic changes). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alaarab/ogrid-vue",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.12",
|
|
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",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"node": ">=18"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@alaarab/ogrid-core": "2.0.
|
|
38
|
+
"@alaarab/ogrid-core": "2.0.12"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"vue": "^3.3.0"
|