@alaarab/ogrid-vue 2.0.11 → 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 +6 -4
- package/dist/esm/components/StatusBar.js +4 -4
- package/dist/esm/components/createDataGridTable.js +481 -0
- package/dist/esm/composables/useColumnHeaderMenuState.js +15 -9
- package/dist/esm/composables/useDataGridState.js +23 -5
- package/dist/esm/composables/useOGrid.js +8 -17
- package/dist/esm/index.js +2 -0
- package/dist/types/components/createDataGridTable.d.ts +53 -0
- package/dist/types/composables/useColumnHeaderMenuState.d.ts +4 -2
- package/dist/types/composables/useDataGridState.d.ts +2 -6
- package/dist/types/index.d.ts +1 -0
- package/dist/types/types/dataGridTypes.d.ts +5 -5
- package/package.json +2 -2
|
@@ -6,7 +6,7 @@
|
|
|
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';
|
|
9
|
+
import { defineComponent, ref, computed, watch, onMounted, onUnmounted, h, toValue } from 'vue';
|
|
10
10
|
import { Z_INDEX, measureRange, injectGlobalStyles } from '@alaarab/ogrid-core';
|
|
11
11
|
export const MarchingAntsOverlay = defineComponent({
|
|
12
12
|
name: 'MarchingAntsOverlay',
|
|
@@ -37,7 +37,9 @@ export const MarchingAntsOverlay = defineComponent({
|
|
|
37
37
|
let ro;
|
|
38
38
|
const clipRange = computed(() => props.copyRange ?? props.cutRange);
|
|
39
39
|
const measureAll = () => {
|
|
40
|
-
|
|
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);
|
|
41
43
|
if (!container) {
|
|
42
44
|
selRect.value = null;
|
|
43
45
|
clipRect.value = null;
|
|
@@ -51,7 +53,7 @@ export const MarchingAntsOverlay = defineComponent({
|
|
|
51
53
|
injectGlobalStyles('ogrid-marching-ants-keyframes', '@keyframes ogrid-marching-ants{to{stroke-dashoffset:-8}}');
|
|
52
54
|
});
|
|
53
55
|
// Measure when any range changes; re-measure on resize, column changes, data changes
|
|
54
|
-
watch([() => props.selectionRange, clipRange, () => props.containerRef
|
|
56
|
+
watch([() => props.selectionRange, clipRange, () => toValue(props.containerRef), () => props.items, () => props.visibleColumns, () => props.columnSizingOverrides, () => props.columnOrder], () => {
|
|
55
57
|
if (!props.selectionRange && !clipRange.value) {
|
|
56
58
|
selRect.value = null;
|
|
57
59
|
clipRect.value = null;
|
|
@@ -59,7 +61,7 @@ export const MarchingAntsOverlay = defineComponent({
|
|
|
59
61
|
}
|
|
60
62
|
// Delay one frame so cells are rendered
|
|
61
63
|
rafId = requestAnimationFrame(measureAll);
|
|
62
|
-
const container = props.containerRef
|
|
64
|
+
const container = toValue(props.containerRef);
|
|
63
65
|
if (container) {
|
|
64
66
|
ro?.disconnect();
|
|
65
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
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 { columns, pinnedColumns, onPinColumn, onUnpinColumn, onSort,
|
|
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);
|
|
@@ -74,16 +75,21 @@ export function useColumnHeaderMenuState(params) {
|
|
|
74
75
|
}
|
|
75
76
|
};
|
|
76
77
|
const handleAutosizeThis = () => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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();
|
|
81
84
|
};
|
|
82
85
|
const handleAutosizeAll = () => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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();
|
|
87
93
|
};
|
|
88
94
|
return {
|
|
89
95
|
isOpen,
|
|
@@ -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,17 +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({
|
|
179
184
|
columns: flatColumns,
|
|
180
185
|
pinnedColumns: pinningResult.pinnedColumns,
|
|
181
186
|
onPinColumn: pinningResult.pinColumn,
|
|
182
187
|
onUnpinColumn: pinningResult.unpinColumn,
|
|
183
188
|
onSort: props.value.onColumnSort,
|
|
184
|
-
|
|
185
|
-
|
|
189
|
+
onColumnResized: onColumnResizedProp.value,
|
|
190
|
+
onAutosizeColumn: handleAutosizeColumn,
|
|
186
191
|
sortBy: computed(() => props.value.sortBy),
|
|
187
192
|
sortDirection: computed(() => props.value.sortDirection),
|
|
188
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;
|
|
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));
|
|
189
207
|
const aggregation = computed(() => computeAggregations(items.value, visibleCols.value, cellSelection.value ? selectionRange.value : null));
|
|
190
208
|
const statusBarConfig = computed(() => {
|
|
191
209
|
const base = getDataGridStatusBarConfig(statusBarProp.value, items.value.length, rowSelectionResult.selectedRowIds.value.size);
|
|
@@ -330,8 +348,8 @@ export function useDataGridState(params) {
|
|
|
330
348
|
pinColumn: pinningResult.pinColumn,
|
|
331
349
|
unpinColumn: pinningResult.unpinColumn,
|
|
332
350
|
isPinned: pinningResult.isPinned,
|
|
333
|
-
|
|
334
|
-
|
|
351
|
+
leftOffsets: leftOffsets.value,
|
|
352
|
+
rightOffsets: rightOffsets.value,
|
|
335
353
|
headerMenu: {
|
|
336
354
|
isOpen: headerMenuResult.isOpen.value,
|
|
337
355
|
openForColumn: headerMenuResult.openForColumn.value,
|
|
@@ -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,22 +126,8 @@ export function useOGrid(props) {
|
|
|
126
126
|
internalVisibleColumns.value = cols;
|
|
127
127
|
callbacks.value.onVisibleColumnsChange?.(cols);
|
|
128
128
|
};
|
|
129
|
-
const handleSort = (columnKey) => {
|
|
130
|
-
|
|
131
|
-
if (currentSort.field === columnKey) {
|
|
132
|
-
// Cycle: asc → desc → clear
|
|
133
|
-
if (currentSort.direction === 'asc') {
|
|
134
|
-
setSort({ field: columnKey, direction: 'desc' });
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
// Clear sort (empty field means no column is sorted)
|
|
138
|
-
setSort({ field: '', direction: 'asc' });
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
// Start new sort
|
|
143
|
-
setSort({ field: columnKey, direction: 'asc' });
|
|
144
|
-
}
|
|
129
|
+
const handleSort = (columnKey, direction) => {
|
|
130
|
+
setSort(computeNextSortState(sort.value, columnKey, direction));
|
|
145
131
|
};
|
|
146
132
|
const handleFilterChange = (key, value) => {
|
|
147
133
|
setFilters(mergeFilter(filters.value, key, value));
|
|
@@ -340,6 +326,9 @@ export function useOGrid(props) {
|
|
|
340
326
|
rowSelection: p.rowSelection ?? 'none',
|
|
341
327
|
selectedRows: effectiveSelectedRows.value,
|
|
342
328
|
onSelectionChange: handleSelectionChange,
|
|
329
|
+
showRowNumbers: p.showRowNumbers,
|
|
330
|
+
currentPage: page.value,
|
|
331
|
+
pageSize: pageSize.value,
|
|
343
332
|
statusBar: statusBarConfig.value,
|
|
344
333
|
isLoading: isLoadingResolved.value,
|
|
345
334
|
filters: filters.value,
|
|
@@ -350,7 +339,9 @@ export function useOGrid(props) {
|
|
|
350
339
|
getUserByEmail: ds?.getUserByEmail,
|
|
351
340
|
layoutMode: p.layoutMode,
|
|
352
341
|
suppressHorizontalScroll: p.suppressHorizontalScroll,
|
|
342
|
+
columnReorder: p.columnReorder,
|
|
353
343
|
virtualScroll: p.virtualScroll,
|
|
344
|
+
density: p.density ?? 'normal',
|
|
354
345
|
'aria-label': p['aria-label'],
|
|
355
346
|
'aria-labelledby': p['aria-labelledby'],
|
|
356
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';
|
|
@@ -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>;
|
|
@@ -6,10 +6,12 @@ export interface UseColumnHeaderMenuStateParams<T = unknown> {
|
|
|
6
6
|
onPinColumn: (columnId: string, side: 'left' | 'right') => void;
|
|
7
7
|
onUnpinColumn: (columnId: string) => void;
|
|
8
8
|
onSort?: (columnId: string, direction: 'asc' | 'desc' | null) => void;
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
onColumnResized?: (columnId: string, width: number) => void;
|
|
10
|
+
onAutosizeColumn?: (columnId: string, width: number) => void;
|
|
11
11
|
sortBy?: Ref<string | undefined>;
|
|
12
12
|
sortDirection?: Ref<'asc' | 'desc' | undefined>;
|
|
13
|
+
data?: Ref<unknown[]>;
|
|
14
|
+
getRowId?: (item: unknown) => string | number;
|
|
13
15
|
}
|
|
14
16
|
export interface UseColumnHeaderMenuStateResult {
|
|
15
17
|
isOpen: Ref<boolean>;
|
|
@@ -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;
|
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,17 +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;
|
|
112
|
-
/** Called when user requests autosize for a single column. */
|
|
113
|
-
onAutosizeColumn?: (columnId: string) => void;
|
|
114
|
-
/** Called when user requests autosize for all columns. */
|
|
115
|
-
onAutosizeAllColumns?: () => void;
|
|
114
|
+
/** Called when user requests autosize for a single column (with measured width). */
|
|
115
|
+
onAutosizeColumn?: (columnId: string, width: number) => void;
|
|
116
116
|
/** Called when a column is pinned or unpinned. */
|
|
117
117
|
onColumnPinned?: (columnId: string, pinned: 'left' | 'right' | null) => void;
|
|
118
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"
|