@alaarab/ogrid-vue 2.1.2 → 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/index.js +4336 -15
- package/package.json +4 -4
- package/dist/esm/components/MarchingAntsOverlay.js +0 -144
- package/dist/esm/components/SideBar.js +0 -1
- package/dist/esm/components/StatusBar.js +0 -49
- package/dist/esm/components/createDataGridTable.js +0 -514
- package/dist/esm/components/createInlineCellEditor.js +0 -194
- package/dist/esm/components/createOGrid.js +0 -383
- package/dist/esm/composables/index.js +0 -33
- package/dist/esm/composables/useActiveCell.js +0 -77
- package/dist/esm/composables/useCellEditing.js +0 -27
- package/dist/esm/composables/useCellSelection.js +0 -359
- package/dist/esm/composables/useClipboard.js +0 -87
- package/dist/esm/composables/useColumnChooserState.js +0 -74
- package/dist/esm/composables/useColumnHeaderFilterState.js +0 -189
- package/dist/esm/composables/useColumnHeaderMenuState.js +0 -113
- package/dist/esm/composables/useColumnPinning.js +0 -64
- package/dist/esm/composables/useColumnReorder.js +0 -110
- package/dist/esm/composables/useColumnResize.js +0 -73
- package/dist/esm/composables/useContextMenu.js +0 -23
- package/dist/esm/composables/useDataGridState.js +0 -425
- package/dist/esm/composables/useDataGridTableSetup.js +0 -66
- package/dist/esm/composables/useDateFilterState.js +0 -36
- package/dist/esm/composables/useDebounce.js +0 -60
- package/dist/esm/composables/useFillHandle.js +0 -205
- package/dist/esm/composables/useFilterOptions.js +0 -39
- package/dist/esm/composables/useInlineCellEditorState.js +0 -42
- package/dist/esm/composables/useKeyboardNavigation.js +0 -232
- package/dist/esm/composables/useLatestRef.js +0 -27
- package/dist/esm/composables/useMultiSelectFilterState.js +0 -59
- package/dist/esm/composables/useOGrid.js +0 -491
- package/dist/esm/composables/usePeopleFilterState.js +0 -66
- package/dist/esm/composables/useRichSelectState.js +0 -59
- package/dist/esm/composables/useRowSelection.js +0 -75
- package/dist/esm/composables/useSideBarState.js +0 -41
- package/dist/esm/composables/useTableLayout.js +0 -85
- package/dist/esm/composables/useTextFilterState.js +0 -26
- package/dist/esm/composables/useUndoRedo.js +0 -65
- package/dist/esm/composables/useVirtualScroll.js +0 -87
- package/dist/esm/types/columnTypes.js +0 -1
- package/dist/esm/types/dataGridTypes.js +0 -1
- package/dist/esm/types/index.js +0 -1
- package/dist/esm/utils/dataGridViewModel.js +0 -23
- package/dist/esm/utils/index.js +0 -1
|
@@ -1,514 +0,0 @@
|
|
|
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
|
-
// Pre-compute header rows so buildHeaderRows is not called on every render
|
|
33
|
-
const headerRowsComputed = computed(() => buildHeaderRows(propsRef.value.columns, propsRef.value.visibleColumns));
|
|
34
|
-
// Pre-compute per-column layout metadata so it's only recalculated when
|
|
35
|
-
// column config, sizing, pinning, or measured widths change — not on every
|
|
36
|
-
// render (parity with React's columnMeta useMemo).
|
|
37
|
-
const columnMetaCache = computed(() => {
|
|
38
|
-
const layout = state.layout.value;
|
|
39
|
-
const pinning = state.pinning.value;
|
|
40
|
-
const { visibleCols, columnSizingOverrides, measuredColumnWidths } = layout;
|
|
41
|
-
const { leftOffsets, rightOffsets } = pinning;
|
|
42
|
-
const cellStyles = {};
|
|
43
|
-
const cellClasses = {};
|
|
44
|
-
const hdrStyles = {};
|
|
45
|
-
const hdrClasses = {};
|
|
46
|
-
for (let colIdx = 0; colIdx < visibleCols.length; colIdx++) {
|
|
47
|
-
const col = visibleCols[colIdx];
|
|
48
|
-
const isPinnedLeft = col.pinned === 'left';
|
|
49
|
-
const isPinnedRight = col.pinned === 'right';
|
|
50
|
-
const columnWidth = getColumnWidth(col);
|
|
51
|
-
const hasResizeOverride = !!columnSizingOverrides[col.columnId];
|
|
52
|
-
const measuredW = measuredColumnWidths[col.columnId];
|
|
53
|
-
const baseMinWidth = col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
|
|
54
|
-
const effectiveMinWidth = hasResizeOverride ? columnWidth : Math.max(baseMinWidth, measuredW ?? 0);
|
|
55
|
-
const tdStyle = {
|
|
56
|
-
minWidth: `${effectiveMinWidth}px`,
|
|
57
|
-
width: `${columnWidth}px`,
|
|
58
|
-
maxWidth: `${columnWidth}px`,
|
|
59
|
-
};
|
|
60
|
-
const hdrStyle = {
|
|
61
|
-
minWidth: `${effectiveMinWidth}px`,
|
|
62
|
-
width: `${columnWidth}px`,
|
|
63
|
-
maxWidth: `${columnWidth}px`,
|
|
64
|
-
};
|
|
65
|
-
const tdClassParts = ['ogrid-data-cell'];
|
|
66
|
-
const hdrClassParts = ['ogrid-header-cell'];
|
|
67
|
-
if (isPinnedLeft) {
|
|
68
|
-
tdClassParts.push('ogrid-data-cell--pinned-left');
|
|
69
|
-
tdStyle.left = `${leftOffsets[col.columnId] ?? 0}px`;
|
|
70
|
-
hdrClassParts.push('ogrid-header-cell--pinned-left');
|
|
71
|
-
hdrStyle.left = `${leftOffsets[col.columnId] ?? 0}px`;
|
|
72
|
-
}
|
|
73
|
-
else if (isPinnedRight) {
|
|
74
|
-
tdClassParts.push('ogrid-data-cell--pinned-right');
|
|
75
|
-
tdStyle.right = `${rightOffsets[col.columnId] ?? 0}px`;
|
|
76
|
-
hdrClassParts.push('ogrid-header-cell--pinned-right');
|
|
77
|
-
hdrStyle.right = `${rightOffsets[col.columnId] ?? 0}px`;
|
|
78
|
-
}
|
|
79
|
-
cellStyles[col.columnId] = tdStyle;
|
|
80
|
-
cellClasses[col.columnId] = tdClassParts.join(' ');
|
|
81
|
-
hdrStyles[col.columnId] = hdrStyle;
|
|
82
|
-
hdrClasses[col.columnId] = hdrClassParts.join(' ');
|
|
83
|
-
}
|
|
84
|
-
return { cellStyles, cellClasses, hdrStyles, hdrClasses };
|
|
85
|
-
});
|
|
86
|
-
return () => {
|
|
87
|
-
const p = props.gridProps;
|
|
88
|
-
const layout = state.layout.value;
|
|
89
|
-
const rowSel = state.rowSelection.value;
|
|
90
|
-
const editing = state.editing.value;
|
|
91
|
-
const interaction = state.interaction.value;
|
|
92
|
-
const ctxMenu = state.contextMenu.value;
|
|
93
|
-
const viewModels = state.viewModels.value;
|
|
94
|
-
const pinning = state.pinning.value;
|
|
95
|
-
const { headerMenu } = pinning;
|
|
96
|
-
const { visibleCols, hasCheckboxCol, hasRowNumbersCol, colOffset: _colOffset, containerWidth, minTableWidth, desiredTableWidth, } = layout;
|
|
97
|
-
const currentPage = p.currentPage ?? 1;
|
|
98
|
-
const pageSize = p.pageSize ?? 25;
|
|
99
|
-
const rowNumberOffset = hasRowNumbersCol ? (currentPage - 1) * pageSize : 0;
|
|
100
|
-
const { selectedRowIds, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected } = rowSel;
|
|
101
|
-
const { editingCell: _editingCell, setEditingCell, pendingEditorValue, setPendingEditorValue, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl } = editing;
|
|
102
|
-
const { setActiveCell, setSelectionRange, handleCellMouseDown, handleSelectAllCells, selectionRange, hasCellSelection, handleGridKeyDown, handleFillHandleMouseDown, handleCopy, handleCut, handlePaste, cutRange: _cutRange, copyRange: _copyRange, canUndo, canRedo, onUndo, onRedo, isDragging: _isDragging, } = interaction;
|
|
103
|
-
const { menuPosition, handleCellContextMenu, closeContextMenu } = ctxMenu;
|
|
104
|
-
const { headerFilterInput, cellDescriptorInput, statusBarConfig, showEmptyInGrid, onCellError: _onCellError } = viewModels;
|
|
105
|
-
const items = p.items;
|
|
106
|
-
const getRowId = p.getRowId;
|
|
107
|
-
const layoutMode = p.layoutMode ?? 'fill';
|
|
108
|
-
const rowSelection = p.rowSelection ?? 'none';
|
|
109
|
-
const suppressHorizontalScroll = p.suppressHorizontalScroll;
|
|
110
|
-
const stickyHeader = p.stickyHeader ?? true;
|
|
111
|
-
const isLoading = p.isLoading ?? false;
|
|
112
|
-
const loadingMessage = p.loadingMessage ?? 'Loading\u2026';
|
|
113
|
-
const ariaLabel = p['aria-label'];
|
|
114
|
-
const ariaLabelledBy = p['aria-labelledby'];
|
|
115
|
-
const fitToContent = layoutMode === 'content';
|
|
116
|
-
const allowOverflowX = !suppressHorizontalScroll && containerWidth > 0 && (minTableWidth > containerWidth || desiredTableWidth > containerWidth);
|
|
117
|
-
const headerRows = headerRowsComputed.value;
|
|
118
|
-
const editCallbacks = { commitCellEdit, setEditingCell, setPendingEditorValue, cancelPopoverEdit };
|
|
119
|
-
const interactionHandlers = { handleCellMouseDown, setActiveCell, setEditingCell, handleCellContextMenu };
|
|
120
|
-
const handleSingleRowClick = (e) => {
|
|
121
|
-
if (rowSelection !== 'single')
|
|
122
|
-
return;
|
|
123
|
-
const tr = e.currentTarget;
|
|
124
|
-
const rowId = tr.dataset.rowId;
|
|
125
|
-
if (!rowId)
|
|
126
|
-
return;
|
|
127
|
-
rowSel.updateSelection(selectedRowIds.has(rowId) ? new Set() : new Set([rowId]));
|
|
128
|
-
};
|
|
129
|
-
// Render a cell's content
|
|
130
|
-
const renderCellContent = (item, col, rowIndex, colIdx) => {
|
|
131
|
-
const descriptor = getCellRenderDescriptor(item, col, rowIndex, colIdx, cellDescriptorInput);
|
|
132
|
-
if (descriptor.mode === 'editing-inline') {
|
|
133
|
-
const editorProps = buildInlineEditorProps(item, col, descriptor, editCallbacks);
|
|
134
|
-
return h('div', { class: 'ogrid-editing-cell' }, h(ui.InlineCellEditor, {
|
|
135
|
-
value: editorProps.value,
|
|
136
|
-
item: editorProps.item,
|
|
137
|
-
column: editorProps.column,
|
|
138
|
-
rowIndex: editorProps.rowIndex,
|
|
139
|
-
editorType: editorProps.editorType,
|
|
140
|
-
onCommit: editorProps.onCommit,
|
|
141
|
-
onCancel: editorProps.onCancel,
|
|
142
|
-
}));
|
|
143
|
-
}
|
|
144
|
-
if (descriptor.mode === 'editing-popover' && typeof col.cellEditor === 'function') {
|
|
145
|
-
const editorProps = buildPopoverEditorProps(item, col, descriptor, pendingEditorValue, editCallbacks);
|
|
146
|
-
const CustomEditor = col.cellEditor;
|
|
147
|
-
return h('div', [
|
|
148
|
-
h('div', {
|
|
149
|
-
ref: (el) => { if (el)
|
|
150
|
-
setPopoverAnchorEl(el); },
|
|
151
|
-
class: 'ogrid-popover-anchor',
|
|
152
|
-
'aria-hidden': 'true',
|
|
153
|
-
}),
|
|
154
|
-
popoverAnchorEl
|
|
155
|
-
? h(CustomEditor, editorProps)
|
|
156
|
-
: null,
|
|
157
|
-
]);
|
|
158
|
-
}
|
|
159
|
-
// Display mode
|
|
160
|
-
const content = resolveCellDisplayContent(col, item, descriptor.displayValue);
|
|
161
|
-
const cellStyle = resolveCellStyle(col, item);
|
|
162
|
-
const interactionProps2 = getCellInteractionProps(descriptor, col.columnId, interactionHandlers);
|
|
163
|
-
const cellClasses = ['ogrid-cell-content'];
|
|
164
|
-
if (col.type === 'numeric')
|
|
165
|
-
cellClasses.push('ogrid-cell-content--numeric');
|
|
166
|
-
else if (col.type === 'boolean')
|
|
167
|
-
cellClasses.push('ogrid-cell-content--boolean');
|
|
168
|
-
if (descriptor.canEditAny)
|
|
169
|
-
cellClasses.push('ogrid-cell-content--editable');
|
|
170
|
-
if (descriptor.isActive && !descriptor.isInRange)
|
|
171
|
-
cellClasses.push('ogrid-cell-content--active');
|
|
172
|
-
if (descriptor.isInRange)
|
|
173
|
-
cellClasses.push('ogrid-cell-in-range');
|
|
174
|
-
if (descriptor.isInCutRange)
|
|
175
|
-
cellClasses.push('ogrid-cell-cut');
|
|
176
|
-
const styledContent = cellStyle
|
|
177
|
-
? h('span', { style: cellStyle }, content)
|
|
178
|
-
: content;
|
|
179
|
-
return h('div', {
|
|
180
|
-
...interactionProps2,
|
|
181
|
-
class: cellClasses.join(' '),
|
|
182
|
-
}, [
|
|
183
|
-
styledContent,
|
|
184
|
-
...(descriptor.canEditAny && descriptor.isSelectionEndCell ? [
|
|
185
|
-
h('div', {
|
|
186
|
-
onMousedown: handleFillHandleMouseDown,
|
|
187
|
-
'aria-label': 'Fill handle',
|
|
188
|
-
class: 'ogrid-fill-handle',
|
|
189
|
-
}),
|
|
190
|
-
] : []),
|
|
191
|
-
]);
|
|
192
|
-
};
|
|
193
|
-
// Use the pre-computed column metadata cache (computed in setup() for memoization)
|
|
194
|
-
const { cellStyles: colCellStyles, cellClasses: colCellClasses, hdrStyles: colHdrStyles, hdrClasses: colHdrClasses } = columnMetaCache.value;
|
|
195
|
-
// Build column layouts using cached metadata
|
|
196
|
-
const columnLayouts = visibleCols.map((col) => ({
|
|
197
|
-
col,
|
|
198
|
-
tdClasses: colCellClasses[col.columnId] || 'ogrid-data-cell',
|
|
199
|
-
tdDynamicStyle: colCellStyles[col.columnId] || {},
|
|
200
|
-
}));
|
|
201
|
-
// Header class+style lookup using cached metadata
|
|
202
|
-
const getHeaderClassAndStyle = (col) => {
|
|
203
|
-
const base = colHdrStyles[col.columnId] || {};
|
|
204
|
-
// cursor depends on drag state — add it at render time (not cached)
|
|
205
|
-
return {
|
|
206
|
-
classes: colHdrClasses[col.columnId] || 'ogrid-header-cell',
|
|
207
|
-
style: { ...base, cursor: isReorderDragging.value ? 'grabbing' : 'grab' },
|
|
208
|
-
};
|
|
209
|
-
};
|
|
210
|
-
// Dynamic wrapper style
|
|
211
|
-
const wrapperStyle = {
|
|
212
|
-
position: 'relative',
|
|
213
|
-
flex: '1',
|
|
214
|
-
minHeight: isLoading && items.length === 0 ? '200px' : '0',
|
|
215
|
-
width: fitToContent ? 'fit-content' : '100%',
|
|
216
|
-
maxWidth: '100%',
|
|
217
|
-
overflowX: suppressHorizontalScroll ? 'hidden' : allowOverflowX ? 'auto' : 'hidden',
|
|
218
|
-
overflowY: 'auto',
|
|
219
|
-
backgroundColor: '#fff',
|
|
220
|
-
willChange: 'scroll-position',
|
|
221
|
-
};
|
|
222
|
-
if (p.rowHeight) {
|
|
223
|
-
wrapperStyle['--ogrid-row-height'] = `${p.rowHeight}px`;
|
|
224
|
-
}
|
|
225
|
-
return h('div', { class: 'ogrid-outer-container' }, [
|
|
226
|
-
// Scrollable wrapper
|
|
227
|
-
h('div', {
|
|
228
|
-
ref: (el) => { wrapperRef.value = el; vsContainerRef.value = el; },
|
|
229
|
-
tabindex: 0,
|
|
230
|
-
role: 'region',
|
|
231
|
-
'aria-label': ariaLabel ?? (ariaLabelledBy ? undefined : 'Data grid'),
|
|
232
|
-
'aria-labelledby': ariaLabelledBy,
|
|
233
|
-
onMousedown: onWrapperMousedown,
|
|
234
|
-
onKeydown: handleGridKeyDown,
|
|
235
|
-
onContextmenu,
|
|
236
|
-
'data-overflow-x': allowOverflowX ? 'true' : 'false',
|
|
237
|
-
style: wrapperStyle,
|
|
238
|
-
}, [
|
|
239
|
-
h('div', { class: 'ogrid-scroll-wrapper' }, [
|
|
240
|
-
h('div', { style: { minWidth: allowOverflowX ? `${minTableWidth}px` : undefined } }, [
|
|
241
|
-
h('div', {
|
|
242
|
-
ref: (el) => { tableContainerRef.value = el; },
|
|
243
|
-
class: ['ogrid-table-container', isLoading && items.length > 0 ? 'ogrid-table-container--loading' : ''],
|
|
244
|
-
}, [
|
|
245
|
-
// Drop indicator for column reorder
|
|
246
|
-
...(isReorderDragging.value && dropIndicatorX.value !== null ? [
|
|
247
|
-
h('div', {
|
|
248
|
-
class: 'ogrid-drop-indicator',
|
|
249
|
-
style: { left: `${dropIndicatorX.value}px` },
|
|
250
|
-
}),
|
|
251
|
-
] : []),
|
|
252
|
-
// Table
|
|
253
|
-
h('table', {
|
|
254
|
-
ref: (el) => { tableRef.value = el; },
|
|
255
|
-
class: 'ogrid-table',
|
|
256
|
-
style: { minWidth: `${minTableWidth}px` },
|
|
257
|
-
}, [
|
|
258
|
-
// Header
|
|
259
|
-
h('thead', { class: stickyHeader ? 'ogrid-thead ogrid-sticky-header' : 'ogrid-thead' }, headerRows.map((row, rowIdx) => h('tr', { key: rowIdx, class: 'ogrid-header-row' }, [
|
|
260
|
-
// Checkbox header cell
|
|
261
|
-
...(rowIdx === headerRows.length - 1 && hasCheckboxCol ? [
|
|
262
|
-
h('th', {
|
|
263
|
-
class: 'ogrid-checkbox-header',
|
|
264
|
-
style: {
|
|
265
|
-
width: `${CHECKBOX_COLUMN_WIDTH}px`,
|
|
266
|
-
minWidth: `${CHECKBOX_COLUMN_WIDTH}px`,
|
|
267
|
-
maxWidth: `${CHECKBOX_COLUMN_WIDTH}px`,
|
|
268
|
-
},
|
|
269
|
-
}, ui.renderCheckbox({
|
|
270
|
-
modelValue: allSelected,
|
|
271
|
-
// Indeterminate only when some (but not all) rows are selected
|
|
272
|
-
indeterminate: someSelected && !allSelected,
|
|
273
|
-
ariaLabel: 'Select all rows',
|
|
274
|
-
onChange: (c) => handleSelectAll(!!c),
|
|
275
|
-
})),
|
|
276
|
-
] : []),
|
|
277
|
-
// Checkbox spacer in group header row
|
|
278
|
-
...(rowIdx === 0 && rowIdx < headerRows.length - 1 && hasCheckboxCol ? [
|
|
279
|
-
h('th', {
|
|
280
|
-
rowSpan: headerRows.length - 1,
|
|
281
|
-
class: 'ogrid-checkbox-spacer',
|
|
282
|
-
style: { width: `${CHECKBOX_COLUMN_WIDTH}px`, minWidth: `${CHECKBOX_COLUMN_WIDTH}px` },
|
|
283
|
-
}),
|
|
284
|
-
] : []),
|
|
285
|
-
// Row numbers header
|
|
286
|
-
...(rowIdx === headerRows.length - 1 && hasRowNumbersCol ? [
|
|
287
|
-
h('th', {
|
|
288
|
-
class: 'ogrid-row-number-header',
|
|
289
|
-
style: {
|
|
290
|
-
width: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
291
|
-
minWidth: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
292
|
-
maxWidth: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
293
|
-
position: 'sticky',
|
|
294
|
-
left: hasCheckboxCol ? `${CHECKBOX_COLUMN_WIDTH}px` : '0',
|
|
295
|
-
zIndex: 3,
|
|
296
|
-
},
|
|
297
|
-
}, '#'),
|
|
298
|
-
] : []),
|
|
299
|
-
// Row numbers spacer
|
|
300
|
-
...(rowIdx === 0 && rowIdx < headerRows.length - 1 && hasRowNumbersCol ? [
|
|
301
|
-
h('th', {
|
|
302
|
-
rowSpan: headerRows.length - 1,
|
|
303
|
-
class: 'ogrid-row-number-spacer',
|
|
304
|
-
style: {
|
|
305
|
-
width: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
306
|
-
position: 'sticky',
|
|
307
|
-
left: hasCheckboxCol ? `${CHECKBOX_COLUMN_WIDTH}px` : '0',
|
|
308
|
-
zIndex: 3,
|
|
309
|
-
},
|
|
310
|
-
}),
|
|
311
|
-
] : []),
|
|
312
|
-
// Header cells
|
|
313
|
-
...row.map((cell, cellIdx) => {
|
|
314
|
-
if (cell.isGroup) {
|
|
315
|
-
return h('th', {
|
|
316
|
-
key: cellIdx,
|
|
317
|
-
colSpan: cell.colSpan,
|
|
318
|
-
scope: 'colgroup',
|
|
319
|
-
class: 'ogrid-column-group-header',
|
|
320
|
-
}, cell.label);
|
|
321
|
-
}
|
|
322
|
-
if (!cell.columnDef)
|
|
323
|
-
return null;
|
|
324
|
-
const col = cell.columnDef;
|
|
325
|
-
const { classes: headerClasses, style: headerStyle } = getHeaderClassAndStyle(col);
|
|
326
|
-
return h('th', {
|
|
327
|
-
key: col.columnId,
|
|
328
|
-
scope: 'col',
|
|
329
|
-
'data-column-id': col.columnId,
|
|
330
|
-
rowSpan: headerRows.length > 1 ? headerRows.length - rowIdx : undefined,
|
|
331
|
-
class: headerClasses,
|
|
332
|
-
style: headerStyle,
|
|
333
|
-
onMousedown: (e) => handleReorderMouseDown(col.columnId, e),
|
|
334
|
-
}, [
|
|
335
|
-
h('div', { class: 'ogrid-header-content' }, [
|
|
336
|
-
h(ui.ColumnHeaderFilter, getHeaderFilterConfig(col, headerFilterInput)),
|
|
337
|
-
h('button', {
|
|
338
|
-
onClick: (e) => {
|
|
339
|
-
e.stopPropagation();
|
|
340
|
-
headerMenu.open(col.columnId, e.currentTarget);
|
|
341
|
-
},
|
|
342
|
-
'aria-label': 'Column options',
|
|
343
|
-
title: 'Column options',
|
|
344
|
-
class: 'ogrid-column-menu-btn',
|
|
345
|
-
}, '\u22EE'),
|
|
346
|
-
]),
|
|
347
|
-
h('div', {
|
|
348
|
-
onMousedown: (e) => {
|
|
349
|
-
// Clear cell selection/focus before resize so outlines
|
|
350
|
-
// and focus rings don't persist during drag (parity with React).
|
|
351
|
-
setActiveCell(null);
|
|
352
|
-
setSelectionRange(null);
|
|
353
|
-
wrapperRef.value?.focus({ preventScroll: true });
|
|
354
|
-
e.stopPropagation();
|
|
355
|
-
handleResizeStart(e, col);
|
|
356
|
-
},
|
|
357
|
-
class: 'ogrid-resize-handle',
|
|
358
|
-
}),
|
|
359
|
-
]);
|
|
360
|
-
}),
|
|
361
|
-
]))),
|
|
362
|
-
// Body
|
|
363
|
-
...(!showEmptyInGrid ? [
|
|
364
|
-
h('tbody', {}, (() => {
|
|
365
|
-
const vsEnabled = virtualScrollEnabled.value;
|
|
366
|
-
const vr = visibleRange.value;
|
|
367
|
-
const startIdx = vsEnabled ? vr.startIndex : 0;
|
|
368
|
-
const endIdx = vsEnabled ? Math.min(vr.endIndex, items.length - 1) : items.length - 1;
|
|
369
|
-
const rows = [];
|
|
370
|
-
if (vsEnabled && vr.offsetTop > 0) {
|
|
371
|
-
rows.push(h('tr', { key: '__vs-top', style: { height: `${vr.offsetTop}px` } }));
|
|
372
|
-
}
|
|
373
|
-
for (let rowIndex = startIdx; rowIndex <= endIdx; rowIndex++) {
|
|
374
|
-
const item = items[rowIndex];
|
|
375
|
-
if (!item)
|
|
376
|
-
continue;
|
|
377
|
-
const rowIdStr = getRowId(item);
|
|
378
|
-
const isSelected = selectedRowIds.has(rowIdStr);
|
|
379
|
-
rows.push(h('tr', {
|
|
380
|
-
key: rowIdStr,
|
|
381
|
-
'data-row-id': rowIdStr,
|
|
382
|
-
onClick: handleSingleRowClick,
|
|
383
|
-
style: { cursor: rowSelection === 'single' ? 'pointer' : undefined },
|
|
384
|
-
}, [
|
|
385
|
-
// Checkbox cell
|
|
386
|
-
...(hasCheckboxCol ? [
|
|
387
|
-
h('td', {
|
|
388
|
-
class: 'ogrid-checkbox-cell',
|
|
389
|
-
style: {
|
|
390
|
-
width: `${CHECKBOX_COLUMN_WIDTH}px`,
|
|
391
|
-
minWidth: `${CHECKBOX_COLUMN_WIDTH}px`,
|
|
392
|
-
maxWidth: `${CHECKBOX_COLUMN_WIDTH}px`,
|
|
393
|
-
},
|
|
394
|
-
}, h('div', {
|
|
395
|
-
'data-row-index': rowIndex,
|
|
396
|
-
'data-col-index': 0,
|
|
397
|
-
onClick: stopPropagation,
|
|
398
|
-
class: 'ogrid-checkbox-wrapper',
|
|
399
|
-
}, ui.renderCheckbox({
|
|
400
|
-
modelValue: isSelected,
|
|
401
|
-
ariaLabel: `Select row ${rowIndex + 1}`,
|
|
402
|
-
onChange: (checked) => handleRowCheckboxChange(rowIdStr, checked, rowIndex, lastMouseShift.value),
|
|
403
|
-
}))),
|
|
404
|
-
] : []),
|
|
405
|
-
// Row numbers cell
|
|
406
|
-
...(hasRowNumbersCol ? [
|
|
407
|
-
h('td', {
|
|
408
|
-
class: 'ogrid-row-number-cell',
|
|
409
|
-
style: {
|
|
410
|
-
width: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
411
|
-
minWidth: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
412
|
-
maxWidth: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
413
|
-
padding: '6px',
|
|
414
|
-
position: 'sticky',
|
|
415
|
-
left: hasCheckboxCol ? `${CHECKBOX_COLUMN_WIDTH}px` : '0',
|
|
416
|
-
zIndex: 2,
|
|
417
|
-
},
|
|
418
|
-
}, String(rowNumberOffset + rowIndex + 1)),
|
|
419
|
-
] : []),
|
|
420
|
-
// Data cells
|
|
421
|
-
...columnLayouts.map((cl, colIdx) => h('td', {
|
|
422
|
-
key: cl.col.columnId,
|
|
423
|
-
'data-column-id': cl.col.columnId,
|
|
424
|
-
class: cl.tdClasses,
|
|
425
|
-
style: cl.tdDynamicStyle,
|
|
426
|
-
}, [renderCellContent(item, cl.col, rowIndex, colIdx)])),
|
|
427
|
-
]));
|
|
428
|
-
}
|
|
429
|
-
if (vsEnabled && vr.offsetBottom > 0) {
|
|
430
|
-
rows.push(h('tr', { key: '__vs-bottom', style: { height: `${vr.offsetBottom}px` } }));
|
|
431
|
-
}
|
|
432
|
-
return rows;
|
|
433
|
-
})()),
|
|
434
|
-
] : []),
|
|
435
|
-
]),
|
|
436
|
-
// Empty state
|
|
437
|
-
...(showEmptyInGrid && p.emptyState ? [
|
|
438
|
-
ui.renderEmptyState(p.emptyState),
|
|
439
|
-
] : []),
|
|
440
|
-
]),
|
|
441
|
-
]),
|
|
442
|
-
]),
|
|
443
|
-
]),
|
|
444
|
-
// Context menu (teleported to body)
|
|
445
|
-
...(menuPosition ? [
|
|
446
|
-
h(Teleport, { to: 'body' }, h(ui.GridContextMenu, {
|
|
447
|
-
x: menuPosition.x,
|
|
448
|
-
y: menuPosition.y,
|
|
449
|
-
hasSelection: hasCellSelection,
|
|
450
|
-
canUndo,
|
|
451
|
-
canRedo,
|
|
452
|
-
onUndo: onUndo ?? NOOP,
|
|
453
|
-
onRedo: onRedo ?? NOOP,
|
|
454
|
-
onCopy: handleCopy,
|
|
455
|
-
onCut: handleCut,
|
|
456
|
-
onPaste: () => { void handlePaste(); },
|
|
457
|
-
onSelectAll: handleSelectAllCells,
|
|
458
|
-
onClose: closeContextMenu,
|
|
459
|
-
})),
|
|
460
|
-
] : []),
|
|
461
|
-
// Marching ants overlay
|
|
462
|
-
h(MarchingAntsOverlay, {
|
|
463
|
-
containerRef: tableContainerRef,
|
|
464
|
-
selectionRange,
|
|
465
|
-
copyRange: _copyRange,
|
|
466
|
-
cutRange: _cutRange,
|
|
467
|
-
colOffset: _colOffset,
|
|
468
|
-
items,
|
|
469
|
-
visibleColumns: p.visibleColumns instanceof Set ? Array.from(p.visibleColumns) : p.visibleColumns,
|
|
470
|
-
columnSizingOverrides: layout.columnSizingOverrides,
|
|
471
|
-
columnOrder: p.columnOrder,
|
|
472
|
-
}),
|
|
473
|
-
// Column header menu
|
|
474
|
-
h(ui.ColumnHeaderMenu, {
|
|
475
|
-
isOpen: headerMenu.isOpen,
|
|
476
|
-
anchorElement: headerMenu.anchorElement,
|
|
477
|
-
onClose: headerMenu.close,
|
|
478
|
-
onPinLeft: headerMenu.handlePinLeft,
|
|
479
|
-
onPinRight: headerMenu.handlePinRight,
|
|
480
|
-
onUnpin: headerMenu.handleUnpin,
|
|
481
|
-
onSortAsc: headerMenu.handleSortAsc,
|
|
482
|
-
onSortDesc: headerMenu.handleSortDesc,
|
|
483
|
-
onClearSort: headerMenu.handleClearSort,
|
|
484
|
-
onAutosizeThis: headerMenu.handleAutosizeThis,
|
|
485
|
-
onAutosizeAll: headerMenu.handleAutosizeAll,
|
|
486
|
-
canPinLeft: headerMenu.canPinLeft,
|
|
487
|
-
canPinRight: headerMenu.canPinRight,
|
|
488
|
-
canUnpin: headerMenu.canUnpin,
|
|
489
|
-
currentSort: headerMenu.currentSort,
|
|
490
|
-
isSortable: headerMenu.isSortable,
|
|
491
|
-
isResizable: headerMenu.isResizable,
|
|
492
|
-
}),
|
|
493
|
-
// Status bar
|
|
494
|
-
...(statusBarConfig ? [
|
|
495
|
-
h(StatusBar, {
|
|
496
|
-
totalCount: statusBarConfig.totalCount,
|
|
497
|
-
filteredCount: statusBarConfig.filteredCount,
|
|
498
|
-
selectedCount: statusBarConfig.selectedCount ?? selectedRowIds.size,
|
|
499
|
-
selectedCellCount: selectionRange
|
|
500
|
-
? (Math.abs(selectionRange.endRow - selectionRange.startRow) + 1) * (Math.abs(selectionRange.endCol - selectionRange.startCol) + 1)
|
|
501
|
-
: undefined,
|
|
502
|
-
aggregation: statusBarConfig.aggregation,
|
|
503
|
-
suppressRowCount: statusBarConfig.suppressRowCount,
|
|
504
|
-
}),
|
|
505
|
-
] : []),
|
|
506
|
-
// Loading overlay
|
|
507
|
-
...(isLoading ? [
|
|
508
|
-
h('div', { class: 'ogrid-loading-overlay' }, ui.renderSpinner(loadingMessage)),
|
|
509
|
-
] : []),
|
|
510
|
-
]);
|
|
511
|
-
};
|
|
512
|
-
},
|
|
513
|
-
});
|
|
514
|
-
}
|