@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
package/dist/esm/index.js
CHANGED
|
@@ -1,16 +1,4337 @@
|
|
|
1
|
-
|
|
1
|
+
import { injectGlobalStyles, Z_INDEX, getStatusBarParts, measureRange, flattenColumns, getMultiSelectFilterFields, deriveFilterOptionsFromData, processClientSideData, validateColumns, validateRowIds, computeRowSelectionState, UndoRedoStack, CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, computeAggregations, getDataGridStatusBarConfig, computeVisibleRange, computeTotalHeight, buildHeaderRows, ROW_NUMBER_COLUMN_WIDTH, getHeaderFilterConfig, getCellRenderDescriptor, buildInlineEditorProps, buildPopoverEditorProps, resolveCellDisplayContent, resolveCellStyle, rangesEqual, normalizeSelectionRange, formatSelectionAsTsv, parseTsvClipboard, applyPastedValues, applyCutClear, measureColumnContentWidth, getPinStateForColumn, parseValue, applyFillValues, applyCellDeletion, computeTabNavigation, computeArrowNavigation, computeNextSortState, mergeFilter, applyRangeRowSelection, getScrollTopForRow, getCellValue, calculateDropTarget, reorderColumnArray, computeAutoScrollSpeed } from '@alaarab/ogrid-core';
|
|
2
2
|
export * from '@alaarab/ogrid-core';
|
|
3
|
-
export {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
3
|
+
export { buildInlineEditorProps, buildPopoverEditorProps, getCellRenderDescriptor, getHeaderFilterConfig, isInSelectionRange, normalizeSelectionRange, resolveCellDisplayContent, resolveCellStyle, toUserLike } from '@alaarab/ogrid-core';
|
|
4
|
+
import { defineComponent, ref, computed, onMounted, watch, toValue, onUnmounted, h, shallowRef, triggerRef, nextTick, Teleport, isRef, isReadonly, unref, customRef } from 'vue';
|
|
5
|
+
|
|
6
|
+
// src/index.ts
|
|
7
|
+
var MarchingAntsOverlay = defineComponent({
|
|
8
|
+
name: "MarchingAntsOverlay",
|
|
9
|
+
props: {
|
|
10
|
+
/** Ref to the positioned container that wraps the table (must have position: relative) */
|
|
11
|
+
containerRef: { type: Object, required: true },
|
|
12
|
+
/** Current selection range — solid green border */
|
|
13
|
+
selectionRange: { type: Object, default: null },
|
|
14
|
+
/** Copy range — animated dashed border */
|
|
15
|
+
copyRange: { type: Object, default: null },
|
|
16
|
+
/** Cut range — animated dashed border */
|
|
17
|
+
cutRange: { type: Object, default: null },
|
|
18
|
+
/** Column offset — 1 when checkbox column is present, else 0 */
|
|
19
|
+
colOffset: { type: Number, required: true },
|
|
20
|
+
/** Items array — triggers re-measurement when data changes (e.g., sorting) */
|
|
21
|
+
items: { type: Array, required: true },
|
|
22
|
+
/** Visible columns — triggers re-measurement when columns are hidden/shown */
|
|
23
|
+
visibleColumns: { type: Array, default: void 0 },
|
|
24
|
+
/** Column sizing overrides — triggers re-measurement when columns are resized */
|
|
25
|
+
columnSizingOverrides: { type: Object, required: true },
|
|
26
|
+
/** Column order — triggers re-measurement when columns are reordered */
|
|
27
|
+
columnOrder: { type: Array, default: void 0 }
|
|
28
|
+
},
|
|
29
|
+
setup(props) {
|
|
30
|
+
const selRect = ref(null);
|
|
31
|
+
const clipRect = ref(null);
|
|
32
|
+
let rafId = 0;
|
|
33
|
+
let ro;
|
|
34
|
+
const clipRange = computed(() => props.copyRange ?? props.cutRange);
|
|
35
|
+
const measureAll = () => {
|
|
36
|
+
const container = toValue(props.containerRef);
|
|
37
|
+
if (!container) {
|
|
38
|
+
selRect.value = null;
|
|
39
|
+
clipRect.value = null;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
selRect.value = props.selectionRange ? measureRange(container, props.selectionRange, props.colOffset) : null;
|
|
43
|
+
clipRect.value = clipRange.value ? measureRange(container, clipRange.value, props.colOffset) : null;
|
|
44
|
+
};
|
|
45
|
+
onMounted(() => {
|
|
46
|
+
injectGlobalStyles("ogrid-marching-ants-keyframes", "@keyframes ogrid-marching-ants{to{stroke-dashoffset:-8}}");
|
|
47
|
+
});
|
|
48
|
+
watch([() => props.selectionRange, clipRange, () => toValue(props.containerRef), () => props.items, () => props.visibleColumns, () => props.columnSizingOverrides, () => props.columnOrder], () => {
|
|
49
|
+
if (!props.selectionRange && !clipRange.value) {
|
|
50
|
+
selRect.value = null;
|
|
51
|
+
clipRect.value = null;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
rafId = requestAnimationFrame(measureAll);
|
|
55
|
+
const container = toValue(props.containerRef);
|
|
56
|
+
if (container) {
|
|
57
|
+
ro?.disconnect();
|
|
58
|
+
ro = new ResizeObserver(measureAll);
|
|
59
|
+
ro.observe(container);
|
|
60
|
+
}
|
|
61
|
+
}, { immediate: true });
|
|
62
|
+
onUnmounted(() => {
|
|
63
|
+
cancelAnimationFrame(rafId);
|
|
64
|
+
ro?.disconnect();
|
|
65
|
+
});
|
|
66
|
+
const clipRangeMatchesSel = computed(() => {
|
|
67
|
+
const sel = props.selectionRange;
|
|
68
|
+
const clip = clipRange.value;
|
|
69
|
+
return sel != null && clip != null && sel.startRow === clip.startRow && sel.startCol === clip.startCol && sel.endRow === clip.endRow && sel.endCol === clip.endCol;
|
|
70
|
+
});
|
|
71
|
+
return () => {
|
|
72
|
+
if (!selRect.value && !clipRect.value) return null;
|
|
73
|
+
return h("div", { style: { position: "relative" } }, [
|
|
74
|
+
// Selection range: solid green border (hidden when clipboard range overlaps)
|
|
75
|
+
selRect.value && !clipRangeMatchesSel.value ? h("svg", {
|
|
76
|
+
style: {
|
|
77
|
+
position: "absolute",
|
|
78
|
+
top: `${selRect.value.top}px`,
|
|
79
|
+
left: `${selRect.value.left}px`,
|
|
80
|
+
width: `${selRect.value.width}px`,
|
|
81
|
+
height: `${selRect.value.height}px`,
|
|
82
|
+
pointerEvents: "none",
|
|
83
|
+
zIndex: Z_INDEX.SELECTION_OVERLAY,
|
|
84
|
+
overflow: "visible"
|
|
85
|
+
},
|
|
86
|
+
"aria-hidden": "true"
|
|
87
|
+
}, [
|
|
88
|
+
h("rect", {
|
|
89
|
+
x: 1,
|
|
90
|
+
y: 1,
|
|
91
|
+
width: Math.max(0, selRect.value.width - 2),
|
|
92
|
+
height: Math.max(0, selRect.value.height - 2),
|
|
93
|
+
fill: "none",
|
|
94
|
+
stroke: "var(--ogrid-selection, #217346)",
|
|
95
|
+
"stroke-width": 2
|
|
96
|
+
})
|
|
97
|
+
]) : null,
|
|
98
|
+
// Copy/Cut range: animated marching ants
|
|
99
|
+
clipRect.value ? h("svg", {
|
|
100
|
+
style: {
|
|
101
|
+
position: "absolute",
|
|
102
|
+
top: `${clipRect.value.top}px`,
|
|
103
|
+
left: `${clipRect.value.left}px`,
|
|
104
|
+
width: `${clipRect.value.width}px`,
|
|
105
|
+
height: `${clipRect.value.height}px`,
|
|
106
|
+
pointerEvents: "none",
|
|
107
|
+
zIndex: Z_INDEX.CLIPBOARD_OVERLAY,
|
|
108
|
+
overflow: "visible"
|
|
109
|
+
},
|
|
110
|
+
"aria-hidden": "true"
|
|
111
|
+
}, [
|
|
112
|
+
h("rect", {
|
|
113
|
+
x: 1,
|
|
114
|
+
y: 1,
|
|
115
|
+
width: Math.max(0, clipRect.value.width - 2),
|
|
116
|
+
height: Math.max(0, clipRect.value.height - 2),
|
|
117
|
+
fill: "none",
|
|
118
|
+
stroke: "var(--ogrid-selection, #217346)",
|
|
119
|
+
"stroke-width": 2,
|
|
120
|
+
"stroke-dasharray": "4 4",
|
|
121
|
+
style: {
|
|
122
|
+
animation: "ogrid-marching-ants 0.5s linear infinite"
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
]) : null
|
|
126
|
+
]);
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
var StatusBar = defineComponent({
|
|
131
|
+
name: "StatusBar",
|
|
132
|
+
props: {
|
|
133
|
+
totalCount: { type: Number, required: true },
|
|
134
|
+
filteredCount: { type: Number, default: void 0 },
|
|
135
|
+
selectedCount: { type: Number, default: void 0 },
|
|
136
|
+
selectedCellCount: { type: Number, default: void 0 },
|
|
137
|
+
aggregation: { type: Object, default: void 0 },
|
|
138
|
+
suppressRowCount: { type: Boolean, default: false }
|
|
139
|
+
},
|
|
140
|
+
setup(props) {
|
|
141
|
+
return () => {
|
|
142
|
+
const parts = getStatusBarParts(props);
|
|
143
|
+
return h("div", {
|
|
144
|
+
role: "status",
|
|
145
|
+
"aria-live": "polite",
|
|
146
|
+
style: {
|
|
147
|
+
marginTop: "auto",
|
|
148
|
+
padding: "6px 12px",
|
|
149
|
+
borderTop: "1px solid var(--ogrid-border, rgba(0,0,0,0.12))",
|
|
150
|
+
backgroundColor: "var(--ogrid-header-bg, rgba(0,0,0,0.04))",
|
|
151
|
+
display: "flex",
|
|
152
|
+
alignItems: "center",
|
|
153
|
+
gap: "16px",
|
|
154
|
+
fontSize: "0.875rem"
|
|
155
|
+
}
|
|
156
|
+
}, parts.map(
|
|
157
|
+
(p, i) => h("span", {
|
|
158
|
+
key: p.key,
|
|
159
|
+
style: {
|
|
160
|
+
display: "inline-flex",
|
|
161
|
+
alignItems: "center",
|
|
162
|
+
gap: "4px",
|
|
163
|
+
...i < parts.length - 1 ? { marginRight: "16px", borderRight: "1px solid var(--ogrid-border, rgba(0,0,0,0.12))", paddingRight: "16px" } : {}
|
|
164
|
+
}
|
|
165
|
+
}, [
|
|
166
|
+
h("span", { style: { color: "var(--ogrid-fg-secondary, rgba(0,0,0,0.6))" } }, p.label),
|
|
167
|
+
h("span", { style: { fontWeight: "600" } }, p.value.toLocaleString())
|
|
168
|
+
])
|
|
169
|
+
));
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
function useFilterOptions(dataSource, fields) {
|
|
174
|
+
const filterOptions = ref({});
|
|
175
|
+
const loadingOptions = ref({});
|
|
176
|
+
const fieldsKey = computed(() => [...fields.value].sort().join(","));
|
|
177
|
+
const load = async () => {
|
|
178
|
+
const ds = dataSource.value;
|
|
179
|
+
const currentFields = fields.value;
|
|
180
|
+
const fetcher = "fetchFilterOptions" in ds && typeof ds.fetchFilterOptions === "function" ? ds.fetchFilterOptions.bind(ds) : void 0;
|
|
181
|
+
if (!fetcher) {
|
|
182
|
+
filterOptions.value = {};
|
|
183
|
+
loadingOptions.value = {};
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const loading = {};
|
|
187
|
+
currentFields.forEach((f) => {
|
|
188
|
+
loading[f] = true;
|
|
189
|
+
});
|
|
190
|
+
loadingOptions.value = loading;
|
|
191
|
+
const results = {};
|
|
192
|
+
await Promise.all(
|
|
193
|
+
currentFields.map(async (field) => {
|
|
194
|
+
try {
|
|
195
|
+
results[field] = await fetcher(field);
|
|
196
|
+
} catch {
|
|
197
|
+
results[field] = [];
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
);
|
|
201
|
+
filterOptions.value = results;
|
|
202
|
+
loadingOptions.value = {};
|
|
203
|
+
};
|
|
204
|
+
watch([dataSource, fieldsKey], () => {
|
|
205
|
+
load().catch(() => {
|
|
206
|
+
});
|
|
207
|
+
}, { immediate: true });
|
|
208
|
+
return { filterOptions, loadingOptions };
|
|
209
|
+
}
|
|
210
|
+
var DEFAULT_PANELS = ["columns", "filters"];
|
|
211
|
+
function useSideBarState(params) {
|
|
212
|
+
const { config } = params;
|
|
213
|
+
const isEnabled = config != null && config !== false;
|
|
214
|
+
const parsed = (() => {
|
|
215
|
+
if (!isEnabled || config === true) {
|
|
216
|
+
return { panels: DEFAULT_PANELS, position: "right", defaultPanel: null };
|
|
217
|
+
}
|
|
218
|
+
const def = config;
|
|
219
|
+
return {
|
|
220
|
+
panels: def.panels ?? DEFAULT_PANELS,
|
|
221
|
+
position: def.position ?? "right",
|
|
222
|
+
defaultPanel: def.defaultPanel ?? null
|
|
223
|
+
};
|
|
224
|
+
})();
|
|
225
|
+
const activePanel = ref(parsed.defaultPanel);
|
|
226
|
+
const setActivePanel = (panel) => {
|
|
227
|
+
activePanel.value = panel;
|
|
228
|
+
};
|
|
229
|
+
const toggle = (panel) => {
|
|
230
|
+
activePanel.value = activePanel.value === panel ? null : panel;
|
|
231
|
+
};
|
|
232
|
+
const close = () => {
|
|
233
|
+
activePanel.value = null;
|
|
234
|
+
};
|
|
235
|
+
const isOpen = computed(() => activePanel.value !== null);
|
|
236
|
+
return {
|
|
237
|
+
isEnabled,
|
|
238
|
+
activePanel,
|
|
239
|
+
setActivePanel,
|
|
240
|
+
panels: parsed.panels,
|
|
241
|
+
position: parsed.position,
|
|
242
|
+
isOpen,
|
|
243
|
+
toggle,
|
|
244
|
+
close
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/composables/useOGrid.ts
|
|
249
|
+
var DEFAULT_PAGE_SIZE = 25;
|
|
250
|
+
var EMPTY_LOADING_OPTIONS = {};
|
|
251
|
+
function useOGrid(props) {
|
|
252
|
+
const columnProps = computed(() => {
|
|
253
|
+
const p = props.value;
|
|
254
|
+
return {
|
|
255
|
+
columns: p.columns,
|
|
256
|
+
columnOrder: p.columnOrder,
|
|
257
|
+
onColumnOrderChange: p.onColumnOrderChange,
|
|
258
|
+
onColumnResized: p.onColumnResized,
|
|
259
|
+
onColumnPinned: p.onColumnPinned,
|
|
260
|
+
columnChooser: p.columnChooser
|
|
261
|
+
};
|
|
262
|
+
});
|
|
263
|
+
const dataProps = computed(() => {
|
|
264
|
+
const p = props.value;
|
|
265
|
+
const data = "data" in p ? p.data : void 0;
|
|
266
|
+
const dataSource = "dataSource" in p ? p.dataSource : void 0;
|
|
267
|
+
if (data && dataSource) {
|
|
268
|
+
console.warn("[OGrid] Both data and dataSource provided. dataSource takes precedence.");
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
getRowId: p.getRowId,
|
|
272
|
+
data,
|
|
273
|
+
dataSource
|
|
274
|
+
};
|
|
275
|
+
});
|
|
276
|
+
const controlledState = computed(() => {
|
|
277
|
+
const p = props.value;
|
|
278
|
+
return {
|
|
279
|
+
page: p.page,
|
|
280
|
+
pageSize: p.pageSize,
|
|
281
|
+
sort: p.sort,
|
|
282
|
+
filters: p.filters,
|
|
283
|
+
visibleColumns: p.visibleColumns,
|
|
284
|
+
isLoading: p.isLoading
|
|
285
|
+
};
|
|
286
|
+
});
|
|
287
|
+
const callbacks = computed(() => {
|
|
288
|
+
const p = props.value;
|
|
289
|
+
return {
|
|
290
|
+
onPageChange: p.onPageChange,
|
|
291
|
+
onPageSizeChange: p.onPageSizeChange,
|
|
292
|
+
onSortChange: p.onSortChange,
|
|
293
|
+
onFiltersChange: p.onFiltersChange,
|
|
294
|
+
onVisibleColumnsChange: p.onVisibleColumnsChange,
|
|
295
|
+
onFirstDataRendered: p.onFirstDataRendered,
|
|
296
|
+
onError: p.onError
|
|
297
|
+
};
|
|
298
|
+
});
|
|
299
|
+
const defaults = computed(() => {
|
|
300
|
+
const p = props.value;
|
|
301
|
+
return {
|
|
302
|
+
defaultPageSize: p.defaultPageSize ?? DEFAULT_PAGE_SIZE,
|
|
303
|
+
defaultSortBy: p.defaultSortBy,
|
|
304
|
+
defaultSortDirection: p.defaultSortDirection ?? "asc",
|
|
305
|
+
entityLabelPlural: p.entityLabelPlural ?? "items"
|
|
306
|
+
};
|
|
307
|
+
});
|
|
308
|
+
const columnChooserPlacement = computed(
|
|
309
|
+
() => columnProps.value.columnChooser === false ? "none" : columnProps.value.columnChooser === "sidebar" ? "sidebar" : "toolbar"
|
|
310
|
+
);
|
|
311
|
+
const columns = computed(() => flattenColumns(columnProps.value.columns));
|
|
312
|
+
const isServerSide = computed(() => dataProps.value.dataSource != null);
|
|
313
|
+
const isClientSide = computed(() => !isServerSide.value);
|
|
314
|
+
const internalData = ref([]);
|
|
315
|
+
const internalLoading = ref(false);
|
|
316
|
+
const displayData = computed(() => dataProps.value.data ?? internalData.value);
|
|
317
|
+
const displayLoading = computed(() => controlledState.value.isLoading ?? internalLoading.value);
|
|
318
|
+
const defaultSortField = computed(() => defaults.value.defaultSortBy ?? columns.value[0]?.columnId ?? "");
|
|
319
|
+
const internalPage = ref(1);
|
|
320
|
+
const internalPageSize = ref(defaults.value.defaultPageSize);
|
|
321
|
+
const internalSort = ref({
|
|
322
|
+
field: defaultSortField.value,
|
|
323
|
+
direction: defaults.value.defaultSortDirection
|
|
324
|
+
});
|
|
325
|
+
const internalFilters = ref({});
|
|
326
|
+
const internalVisibleColumns = ref((() => {
|
|
327
|
+
const visible = columns.value.filter((c) => c.defaultVisible !== false).map((c) => c.columnId);
|
|
328
|
+
return new Set(visible.length > 0 ? visible : columns.value.map((c) => c.columnId));
|
|
329
|
+
})());
|
|
330
|
+
const columnWidthOverrides = ref({});
|
|
331
|
+
const pinnedOverrides = ref({});
|
|
332
|
+
const page = computed(() => controlledState.value.page ?? internalPage.value);
|
|
333
|
+
const pageSize = computed(() => controlledState.value.pageSize ?? internalPageSize.value);
|
|
334
|
+
const sort = computed(() => controlledState.value.sort ?? internalSort.value);
|
|
335
|
+
const filters = computed(() => controlledState.value.filters ?? internalFilters.value);
|
|
336
|
+
const visibleColumns = computed(() => controlledState.value.visibleColumns ?? internalVisibleColumns.value);
|
|
337
|
+
const setPage = (p) => {
|
|
338
|
+
if (controlledState.value.page === void 0) internalPage.value = p;
|
|
339
|
+
callbacks.value.onPageChange?.(p);
|
|
340
|
+
};
|
|
341
|
+
const setPageSize = (size) => {
|
|
342
|
+
if (controlledState.value.pageSize === void 0) internalPageSize.value = size;
|
|
343
|
+
callbacks.value.onPageSizeChange?.(size);
|
|
344
|
+
setPage(1);
|
|
345
|
+
};
|
|
346
|
+
const setSort = (s) => {
|
|
347
|
+
if (controlledState.value.sort === void 0) internalSort.value = s;
|
|
348
|
+
callbacks.value.onSortChange?.(s);
|
|
349
|
+
setPage(1);
|
|
350
|
+
};
|
|
351
|
+
const setFilters = (f) => {
|
|
352
|
+
if (controlledState.value.filters === void 0) internalFilters.value = f;
|
|
353
|
+
callbacks.value.onFiltersChange?.(f);
|
|
354
|
+
setPage(1);
|
|
355
|
+
};
|
|
356
|
+
const setVisibleColumns = (cols) => {
|
|
357
|
+
if (controlledState.value.visibleColumns === void 0) internalVisibleColumns.value = cols;
|
|
358
|
+
callbacks.value.onVisibleColumnsChange?.(cols);
|
|
359
|
+
};
|
|
360
|
+
const handleSort = (columnKey, direction) => {
|
|
361
|
+
setSort(computeNextSortState(sort.value, columnKey, direction));
|
|
362
|
+
};
|
|
363
|
+
const handleFilterChange = (key, value) => {
|
|
364
|
+
setFilters(mergeFilter(filters.value, key, value));
|
|
365
|
+
};
|
|
366
|
+
const handleVisibilityChange = (columnKey, isVisible) => {
|
|
367
|
+
const next = new Set(visibleColumns.value);
|
|
368
|
+
if (isVisible) next.add(columnKey);
|
|
369
|
+
else next.delete(columnKey);
|
|
370
|
+
setVisibleColumns(next);
|
|
371
|
+
};
|
|
372
|
+
const internalSelectedRows = ref(/* @__PURE__ */ new Set());
|
|
373
|
+
const selectedRowsProp = computed(() => props.value.selectedRows);
|
|
374
|
+
const effectiveSelectedRows = computed(() => selectedRowsProp.value ?? internalSelectedRows.value);
|
|
375
|
+
const handleSelectionChange = (event) => {
|
|
376
|
+
if (selectedRowsProp.value === void 0) {
|
|
377
|
+
internalSelectedRows.value = new Set(event.selectedRowIds);
|
|
378
|
+
}
|
|
379
|
+
props.value.onSelectionChange?.(event);
|
|
380
|
+
};
|
|
381
|
+
const multiSelectFilterFields = computed(() => getMultiSelectFilterFields(columns.value));
|
|
382
|
+
const filterOptionsSource = computed(() => dataProps.value.dataSource ?? { fetchFilterOptions: void 0 });
|
|
383
|
+
const { filterOptions: serverFilterOptions, loadingOptions: loadingFilterOptions } = useFilterOptions(filterOptionsSource, multiSelectFilterFields);
|
|
384
|
+
const hasServerFilterOptions = computed(() => dataProps.value.dataSource?.fetchFilterOptions != null);
|
|
385
|
+
const clientFilterOptions = computed(() => {
|
|
386
|
+
if (hasServerFilterOptions.value) return serverFilterOptions.value;
|
|
387
|
+
return deriveFilterOptionsFromData(displayData.value, columns.value);
|
|
388
|
+
});
|
|
389
|
+
const clientItemsAndTotal = computed(() => {
|
|
390
|
+
if (!isClientSide.value) return null;
|
|
391
|
+
const rows = processClientSideData(
|
|
392
|
+
displayData.value,
|
|
393
|
+
columns.value,
|
|
394
|
+
filters.value,
|
|
395
|
+
sort.value.field,
|
|
396
|
+
sort.value.direction
|
|
397
|
+
);
|
|
398
|
+
const total = rows.length;
|
|
399
|
+
const start = (page.value - 1) * pageSize.value;
|
|
400
|
+
const paged = rows.slice(start, start + pageSize.value);
|
|
401
|
+
return { items: paged, totalCount: total };
|
|
402
|
+
});
|
|
403
|
+
const serverItems = ref([]);
|
|
404
|
+
const serverTotalCount = ref(0);
|
|
405
|
+
const loading = ref(false);
|
|
406
|
+
let fetchId = 0;
|
|
407
|
+
let isDestroyed = false;
|
|
408
|
+
const refreshCounter = ref(0);
|
|
409
|
+
const doFetch = () => {
|
|
410
|
+
if (!isServerSide.value || !dataProps.value.dataSource) {
|
|
411
|
+
if (!isServerSide.value) loading.value = false;
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const id = ++fetchId;
|
|
415
|
+
loading.value = true;
|
|
416
|
+
dataProps.value.dataSource.fetchPage({
|
|
417
|
+
page: page.value,
|
|
418
|
+
pageSize: pageSize.value,
|
|
419
|
+
sort: { field: sort.value.field, direction: sort.value.direction },
|
|
420
|
+
filters: filters.value
|
|
421
|
+
}).then((res) => {
|
|
422
|
+
if (id !== fetchId || isDestroyed) return;
|
|
423
|
+
serverItems.value = res.items;
|
|
424
|
+
serverTotalCount.value = res.totalCount;
|
|
425
|
+
}).catch((err) => {
|
|
426
|
+
if (id !== fetchId || isDestroyed) return;
|
|
427
|
+
callbacks.value.onError?.(err);
|
|
428
|
+
serverItems.value = [];
|
|
429
|
+
serverTotalCount.value = 0;
|
|
430
|
+
}).finally(() => {
|
|
431
|
+
if (id === fetchId && !isDestroyed) loading.value = false;
|
|
432
|
+
});
|
|
433
|
+
};
|
|
434
|
+
onMounted(() => {
|
|
435
|
+
validateColumns(columns.value);
|
|
436
|
+
doFetch();
|
|
437
|
+
});
|
|
438
|
+
watch(
|
|
439
|
+
[() => dataProps.value.dataSource, page, pageSize, () => sort.value.field, () => sort.value.direction, filters, refreshCounter],
|
|
440
|
+
() => {
|
|
441
|
+
doFetch();
|
|
442
|
+
}
|
|
443
|
+
);
|
|
444
|
+
onUnmounted(() => {
|
|
445
|
+
isDestroyed = true;
|
|
446
|
+
});
|
|
447
|
+
const displayItems = computed(
|
|
448
|
+
() => isClientSide.value && clientItemsAndTotal.value ? clientItemsAndTotal.value.items : serverItems.value
|
|
449
|
+
);
|
|
450
|
+
const displayTotalCount = computed(
|
|
451
|
+
() => isClientSide.value && clientItemsAndTotal.value ? clientItemsAndTotal.value.totalCount : serverTotalCount.value
|
|
452
|
+
);
|
|
453
|
+
let firstDataRendered = false;
|
|
454
|
+
let rowIdsValidated = false;
|
|
455
|
+
watch(displayItems, (items) => {
|
|
456
|
+
if (!firstDataRendered && items.length > 0) {
|
|
457
|
+
firstDataRendered = true;
|
|
458
|
+
callbacks.value.onFirstDataRendered?.();
|
|
459
|
+
}
|
|
460
|
+
if (!rowIdsValidated && items.length > 0) {
|
|
461
|
+
rowIdsValidated = true;
|
|
462
|
+
validateRowIds(items, dataProps.value.getRowId);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
const hasActiveFilters = computed(() => Object.values(filters.value).some((v) => v !== void 0));
|
|
466
|
+
const columnChooserColumns = computed(
|
|
467
|
+
() => columns.value.map((c) => ({
|
|
468
|
+
columnId: c.columnId,
|
|
469
|
+
name: c.name,
|
|
470
|
+
required: c.required === true
|
|
471
|
+
}))
|
|
472
|
+
);
|
|
473
|
+
const statusBarConfig = computed(() => {
|
|
474
|
+
const sb = props.value.statusBar;
|
|
475
|
+
if (!sb) return void 0;
|
|
476
|
+
if (typeof sb === "object") return sb;
|
|
477
|
+
const totalData = isClientSide.value ? dataProps.value.data?.length ?? 0 : serverTotalCount.value;
|
|
478
|
+
const filteredData = displayTotalCount.value;
|
|
479
|
+
return {
|
|
480
|
+
totalCount: totalData,
|
|
481
|
+
filteredCount: hasActiveFilters.value ? filteredData : void 0,
|
|
482
|
+
selectedCount: effectiveSelectedRows.value.size,
|
|
483
|
+
suppressRowCount: true
|
|
484
|
+
};
|
|
485
|
+
});
|
|
486
|
+
const handleColumnResized = (columnId, width) => {
|
|
487
|
+
columnWidthOverrides.value = { ...columnWidthOverrides.value, [columnId]: width };
|
|
488
|
+
columnProps.value.onColumnResized?.(columnId, width);
|
|
489
|
+
};
|
|
490
|
+
const handleColumnPinned = (columnId, pinned) => {
|
|
491
|
+
if (pinned === null) {
|
|
492
|
+
const { [columnId]: _removed, ...rest } = pinnedOverrides.value;
|
|
493
|
+
pinnedOverrides.value = rest;
|
|
494
|
+
} else {
|
|
495
|
+
pinnedOverrides.value = { ...pinnedOverrides.value, [columnId]: pinned };
|
|
496
|
+
}
|
|
497
|
+
columnProps.value.onColumnPinned?.(columnId, pinned);
|
|
498
|
+
};
|
|
499
|
+
const sideBarStateRef = shallowRef(useSideBarState({ config: props.value.sideBar }));
|
|
500
|
+
watch(() => props.value.sideBar, (newConfig) => {
|
|
501
|
+
sideBarStateRef.value = useSideBarState({ config: newConfig });
|
|
502
|
+
});
|
|
503
|
+
const filterableColumns = computed(
|
|
504
|
+
() => columns.value.filter((c) => c.filterable && c.filterable.type).map((c) => ({
|
|
505
|
+
columnId: c.columnId,
|
|
506
|
+
name: c.name,
|
|
507
|
+
filterField: c.filterable?.filterField ?? c.columnId,
|
|
508
|
+
filterType: c.filterable?.type
|
|
509
|
+
}))
|
|
510
|
+
);
|
|
511
|
+
const sideBarProps = computed(() => {
|
|
512
|
+
const sideBarState = sideBarStateRef.value;
|
|
513
|
+
if (!sideBarState.isEnabled) return null;
|
|
514
|
+
sideBarState.activePanel.value;
|
|
515
|
+
sideBarState.isOpen.value;
|
|
516
|
+
return {
|
|
517
|
+
get activePanel() {
|
|
518
|
+
return sideBarState.activePanel.value;
|
|
519
|
+
},
|
|
520
|
+
onPanelChange: sideBarState.setActivePanel,
|
|
521
|
+
panels: sideBarState.panels,
|
|
522
|
+
position: sideBarState.position,
|
|
523
|
+
get isOpen() {
|
|
524
|
+
return sideBarState.isOpen.value;
|
|
525
|
+
},
|
|
526
|
+
toggle: sideBarState.toggle,
|
|
527
|
+
close: sideBarState.close,
|
|
528
|
+
columns: columnChooserColumns.value,
|
|
529
|
+
visibleColumns: visibleColumns.value,
|
|
530
|
+
onVisibilityChange: handleVisibilityChange,
|
|
531
|
+
onSetVisibleColumns: setVisibleColumns,
|
|
532
|
+
filterableColumns: filterableColumns.value,
|
|
533
|
+
filters: filters.value,
|
|
534
|
+
onFilterChange: handleFilterChange,
|
|
535
|
+
filterOptions: clientFilterOptions.value
|
|
536
|
+
};
|
|
537
|
+
});
|
|
538
|
+
const clearAllFilters = () => setFilters({});
|
|
539
|
+
const isLoadingResolved = computed(() => isServerSide.value && loading.value || displayLoading.value);
|
|
540
|
+
const dataGridProps = computed(() => {
|
|
541
|
+
const p = props.value;
|
|
542
|
+
const ds = dataProps.value.dataSource;
|
|
543
|
+
return {
|
|
544
|
+
items: displayItems.value,
|
|
545
|
+
columns: columnProps.value.columns,
|
|
546
|
+
getRowId: dataProps.value.getRowId,
|
|
547
|
+
sortBy: sort.value.field,
|
|
548
|
+
sortDirection: sort.value.direction,
|
|
549
|
+
onColumnSort: handleSort,
|
|
550
|
+
visibleColumns: visibleColumns.value,
|
|
551
|
+
columnOrder: columnProps.value.columnOrder,
|
|
552
|
+
onColumnOrderChange: columnProps.value.onColumnOrderChange,
|
|
553
|
+
onColumnResized: handleColumnResized,
|
|
554
|
+
onColumnPinned: handleColumnPinned,
|
|
555
|
+
pinnedColumns: pinnedOverrides.value,
|
|
556
|
+
initialColumnWidths: columnWidthOverrides.value,
|
|
557
|
+
editable: p.editable,
|
|
558
|
+
cellSelection: p.cellSelection,
|
|
559
|
+
onCellValueChanged: p.onCellValueChanged,
|
|
560
|
+
onUndo: p.onUndo,
|
|
561
|
+
onRedo: p.onRedo,
|
|
562
|
+
canUndo: p.canUndo,
|
|
563
|
+
canRedo: p.canRedo,
|
|
564
|
+
rowSelection: p.rowSelection ?? "none",
|
|
565
|
+
selectedRows: effectiveSelectedRows.value,
|
|
566
|
+
onSelectionChange: handleSelectionChange,
|
|
567
|
+
showRowNumbers: p.showRowNumbers,
|
|
568
|
+
currentPage: page.value,
|
|
569
|
+
pageSize: pageSize.value,
|
|
570
|
+
statusBar: statusBarConfig.value,
|
|
571
|
+
isLoading: isLoadingResolved.value,
|
|
572
|
+
filters: filters.value,
|
|
573
|
+
onFilterChange: handleFilterChange,
|
|
574
|
+
filterOptions: clientFilterOptions.value,
|
|
575
|
+
loadingFilterOptions: ds?.fetchFilterOptions ? loadingFilterOptions.value : EMPTY_LOADING_OPTIONS,
|
|
576
|
+
peopleSearch: ds?.searchPeople,
|
|
577
|
+
getUserByEmail: ds?.getUserByEmail,
|
|
578
|
+
layoutMode: p.layoutMode,
|
|
579
|
+
suppressHorizontalScroll: p.suppressHorizontalScroll,
|
|
580
|
+
stickyHeader: p.stickyHeader ?? true,
|
|
581
|
+
columnReorder: p.columnReorder,
|
|
582
|
+
virtualScroll: p.virtualScroll,
|
|
583
|
+
rowHeight: p.rowHeight,
|
|
584
|
+
density: p.density ?? "normal",
|
|
585
|
+
"aria-label": p["aria-label"],
|
|
586
|
+
"aria-labelledby": p["aria-labelledby"],
|
|
587
|
+
emptyState: {
|
|
588
|
+
hasActiveFilters: hasActiveFilters.value,
|
|
589
|
+
onClearAll: clearAllFilters,
|
|
590
|
+
message: p.emptyState?.message,
|
|
591
|
+
render: p.emptyState?.render
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
});
|
|
595
|
+
const pagination = computed(() => ({
|
|
596
|
+
page: page.value,
|
|
597
|
+
pageSize: pageSize.value,
|
|
598
|
+
displayTotalCount: displayTotalCount.value,
|
|
599
|
+
setPage,
|
|
600
|
+
setPageSize,
|
|
601
|
+
pageSizeOptions: props.value.pageSizeOptions,
|
|
602
|
+
entityLabelPlural: defaults.value.entityLabelPlural
|
|
603
|
+
}));
|
|
604
|
+
const columnChooser = computed(() => ({
|
|
605
|
+
columns: columnChooserColumns.value,
|
|
606
|
+
visibleColumns: visibleColumns.value,
|
|
607
|
+
onVisibilityChange: handleVisibilityChange,
|
|
608
|
+
placement: columnChooserPlacement.value
|
|
609
|
+
}));
|
|
610
|
+
const layout = computed(() => ({
|
|
611
|
+
toolbar: props.value.toolbar,
|
|
612
|
+
toolbarBelow: props.value.toolbarBelow,
|
|
613
|
+
className: props.value.className,
|
|
614
|
+
emptyState: props.value.emptyState,
|
|
615
|
+
sideBarProps: sideBarProps.value,
|
|
616
|
+
fullScreen: props.value.fullScreen
|
|
617
|
+
}));
|
|
618
|
+
const filtersResult = computed(() => ({
|
|
619
|
+
hasActiveFilters: hasActiveFilters.value,
|
|
620
|
+
setFilters
|
|
621
|
+
}));
|
|
622
|
+
const api = computed(() => ({
|
|
623
|
+
setRowData: (d) => {
|
|
624
|
+
if (!isServerSide.value) internalData.value = d;
|
|
625
|
+
},
|
|
626
|
+
setLoading: (v) => {
|
|
627
|
+
internalLoading.value = v;
|
|
628
|
+
},
|
|
629
|
+
getColumnState: () => ({
|
|
630
|
+
visibleColumns: Array.from(visibleColumns.value),
|
|
631
|
+
sort: sort.value,
|
|
632
|
+
columnOrder: columnProps.value.columnOrder ?? void 0,
|
|
633
|
+
columnWidths: Object.keys(columnWidthOverrides.value).length > 0 ? columnWidthOverrides.value : void 0,
|
|
634
|
+
filters: Object.keys(filters.value).length > 0 ? filters.value : void 0,
|
|
635
|
+
pinnedColumns: Object.keys(pinnedOverrides.value).length > 0 ? pinnedOverrides.value : void 0
|
|
636
|
+
}),
|
|
637
|
+
applyColumnState: (state) => {
|
|
638
|
+
if (state.visibleColumns) setVisibleColumns(new Set(state.visibleColumns));
|
|
639
|
+
if (state.sort) setSort(state.sort);
|
|
640
|
+
if (state.columnOrder && columnProps.value.onColumnOrderChange) columnProps.value.onColumnOrderChange(state.columnOrder);
|
|
641
|
+
if (state.columnWidths) columnWidthOverrides.value = state.columnWidths;
|
|
642
|
+
if (state.filters) setFilters(state.filters);
|
|
643
|
+
if (state.pinnedColumns) pinnedOverrides.value = state.pinnedColumns;
|
|
644
|
+
},
|
|
645
|
+
setFilterModel: setFilters,
|
|
646
|
+
getSelectedRows: () => Array.from(effectiveSelectedRows.value),
|
|
647
|
+
setSelectedRows: (rowIds) => {
|
|
648
|
+
if (selectedRowsProp.value === void 0) internalSelectedRows.value = new Set(rowIds);
|
|
649
|
+
},
|
|
650
|
+
selectAll: () => {
|
|
651
|
+
const allIds = new Set(displayItems.value.map((item) => dataProps.value.getRowId(item)));
|
|
652
|
+
if (selectedRowsProp.value === void 0) internalSelectedRows.value = allIds;
|
|
653
|
+
props.value.onSelectionChange?.({ selectedRowIds: Array.from(allIds), selectedItems: displayItems.value });
|
|
654
|
+
},
|
|
655
|
+
deselectAll: () => {
|
|
656
|
+
if (selectedRowsProp.value === void 0) internalSelectedRows.value = /* @__PURE__ */ new Set();
|
|
657
|
+
props.value.onSelectionChange?.({ selectedRowIds: [], selectedItems: [] });
|
|
658
|
+
},
|
|
659
|
+
clearFilters: () => setFilters({}),
|
|
660
|
+
clearSort: () => setSort({ field: defaultSortField.value, direction: defaults.value.defaultSortDirection }),
|
|
661
|
+
resetGridState: (options) => {
|
|
662
|
+
setFilters({});
|
|
663
|
+
setSort({ field: defaultSortField.value, direction: defaults.value.defaultSortDirection });
|
|
664
|
+
if (!options?.keepSelection) {
|
|
665
|
+
if (selectedRowsProp.value === void 0) internalSelectedRows.value = /* @__PURE__ */ new Set();
|
|
666
|
+
props.value.onSelectionChange?.({ selectedRowIds: [], selectedItems: [] });
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
getDisplayedRows: () => displayItems.value,
|
|
670
|
+
refreshData: () => {
|
|
671
|
+
if (isServerSide.value) refreshCounter.value++;
|
|
672
|
+
},
|
|
673
|
+
scrollToRow: () => {
|
|
674
|
+
},
|
|
675
|
+
getColumnOrder: () => columnProps.value.columnOrder ?? columns.value.map((c) => c.columnId),
|
|
676
|
+
setColumnOrder: (order) => {
|
|
677
|
+
columnProps.value.onColumnOrderChange?.(order);
|
|
678
|
+
}
|
|
679
|
+
}));
|
|
680
|
+
return {
|
|
681
|
+
dataGridProps,
|
|
682
|
+
pagination,
|
|
683
|
+
columnChooser,
|
|
684
|
+
layout,
|
|
685
|
+
filters: filtersResult,
|
|
686
|
+
api
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
function useRowSelection(params) {
|
|
690
|
+
const {
|
|
691
|
+
items,
|
|
692
|
+
getRowId,
|
|
693
|
+
rowSelection,
|
|
694
|
+
controlledSelectedRows,
|
|
695
|
+
onSelectionChange
|
|
696
|
+
} = params;
|
|
697
|
+
const internalSelectedRows = shallowRef(/* @__PURE__ */ new Set());
|
|
698
|
+
let lastClickedRow = -1;
|
|
699
|
+
const selectedRowIds = computed(() => {
|
|
700
|
+
const controlled = controlledSelectedRows.value;
|
|
701
|
+
if (controlled != null) {
|
|
702
|
+
return controlled instanceof Set ? controlled : new Set(controlled);
|
|
703
|
+
}
|
|
704
|
+
return internalSelectedRows.value;
|
|
705
|
+
});
|
|
706
|
+
const updateSelection = (newSelectedIds) => {
|
|
707
|
+
if (controlledSelectedRows.value !== void 0) {
|
|
708
|
+
if (!isReadonly(controlledSelectedRows)) {
|
|
709
|
+
controlledSelectedRows.value = newSelectedIds;
|
|
710
|
+
}
|
|
711
|
+
} else {
|
|
712
|
+
internalSelectedRows.value = newSelectedIds;
|
|
713
|
+
}
|
|
714
|
+
onSelectionChange?.({
|
|
715
|
+
selectedRowIds: Array.from(newSelectedIds),
|
|
716
|
+
selectedItems: items.value.filter((item) => newSelectedIds.has(getRowId(item)))
|
|
717
|
+
});
|
|
718
|
+
};
|
|
719
|
+
const handleRowCheckboxChange = (rowId, checked, rowIndex, shiftKey) => {
|
|
720
|
+
if (rowSelection.value === "single") {
|
|
721
|
+
updateSelection(checked ? /* @__PURE__ */ new Set([rowId]) : /* @__PURE__ */ new Set());
|
|
722
|
+
lastClickedRow = rowIndex;
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
const currentItems = items.value;
|
|
726
|
+
let next;
|
|
727
|
+
if (shiftKey && lastClickedRow >= 0 && lastClickedRow !== rowIndex) {
|
|
728
|
+
next = applyRangeRowSelection(lastClickedRow, rowIndex, checked, currentItems, getRowId, selectedRowIds.value);
|
|
729
|
+
} else {
|
|
730
|
+
next = new Set(selectedRowIds.value);
|
|
731
|
+
if (checked) next.add(rowId);
|
|
732
|
+
else next.delete(rowId);
|
|
733
|
+
}
|
|
734
|
+
lastClickedRow = rowIndex;
|
|
735
|
+
updateSelection(next);
|
|
736
|
+
};
|
|
737
|
+
const handleSelectAll = (checked) => {
|
|
738
|
+
if (checked) {
|
|
739
|
+
updateSelection(new Set(items.value.map((item) => getRowId(item))));
|
|
740
|
+
} else {
|
|
741
|
+
updateSelection(/* @__PURE__ */ new Set());
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
const allSelected = computed(
|
|
745
|
+
() => computeRowSelectionState(selectedRowIds.value, items.value, getRowId).allSelected
|
|
746
|
+
);
|
|
747
|
+
const someSelected = computed(
|
|
748
|
+
() => computeRowSelectionState(selectedRowIds.value, items.value, getRowId).someSelected
|
|
749
|
+
);
|
|
750
|
+
return {
|
|
751
|
+
selectedRowIds,
|
|
752
|
+
updateSelection,
|
|
753
|
+
handleRowCheckboxChange,
|
|
754
|
+
handleSelectAll,
|
|
755
|
+
allSelected,
|
|
756
|
+
someSelected
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
function useCellEditing(params) {
|
|
760
|
+
const editingCell = shallowRef(null);
|
|
761
|
+
const pendingEditorValue = ref(void 0);
|
|
762
|
+
const setEditingCell = (cell) => {
|
|
763
|
+
if (cell && params?.scrollToRow && params?.getRowIndex) {
|
|
764
|
+
const rowIndex = params.getRowIndex(cell.rowId);
|
|
765
|
+
if (rowIndex >= 0) {
|
|
766
|
+
params.scrollToRow(rowIndex, "center");
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
editingCell.value = cell;
|
|
770
|
+
};
|
|
771
|
+
const setPendingEditorValue = (value) => {
|
|
772
|
+
pendingEditorValue.value = value;
|
|
773
|
+
};
|
|
774
|
+
return {
|
|
775
|
+
editingCell,
|
|
776
|
+
setEditingCell,
|
|
777
|
+
pendingEditorValue,
|
|
778
|
+
setPendingEditorValue
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
function useActiveCell(wrapperRef, editingCell) {
|
|
782
|
+
const activeCell = shallowRef(null);
|
|
783
|
+
let pendingRaf = 0;
|
|
784
|
+
const setActiveCell = (cell) => {
|
|
785
|
+
const prev = activeCell.value;
|
|
786
|
+
if (prev === cell) return;
|
|
787
|
+
if (prev && cell && prev.rowIndex === cell.rowIndex && prev.columnIndex === cell.columnIndex) return;
|
|
788
|
+
activeCell.value = cell;
|
|
789
|
+
};
|
|
790
|
+
watch(
|
|
791
|
+
[activeCell, () => editingCell?.value],
|
|
792
|
+
() => {
|
|
793
|
+
if (pendingRaf) {
|
|
794
|
+
cancelAnimationFrame(pendingRaf);
|
|
795
|
+
pendingRaf = 0;
|
|
796
|
+
}
|
|
797
|
+
if (activeCell.value == null || !wrapperRef?.value || editingCell?.value != null) return;
|
|
798
|
+
const { rowIndex, columnIndex } = activeCell.value;
|
|
799
|
+
pendingRaf = requestAnimationFrame(() => {
|
|
800
|
+
pendingRaf = 0;
|
|
801
|
+
const wrapper = wrapperRef.value;
|
|
802
|
+
if (!wrapper) return;
|
|
803
|
+
const current = activeCell.value;
|
|
804
|
+
if (!current || current.rowIndex !== rowIndex || current.columnIndex !== columnIndex) return;
|
|
805
|
+
const selector = `[data-row-index="${rowIndex}"][data-col-index="${columnIndex}"]`;
|
|
806
|
+
const cell = wrapper.querySelector(selector);
|
|
807
|
+
if (cell) {
|
|
808
|
+
const thead = wrapper.querySelector("thead");
|
|
809
|
+
const headerHeight = thead ? thead.getBoundingClientRect().height : 0;
|
|
810
|
+
const wrapperRect = wrapper.getBoundingClientRect();
|
|
811
|
+
const cellRect = cell.getBoundingClientRect();
|
|
812
|
+
const visibleTop = wrapperRect.top + headerHeight;
|
|
813
|
+
if (cellRect.top < visibleTop) {
|
|
814
|
+
wrapper.scrollTop -= visibleTop - cellRect.top;
|
|
815
|
+
} else if (cellRect.bottom > wrapperRect.bottom) {
|
|
816
|
+
wrapper.scrollTop += cellRect.bottom - wrapperRect.bottom;
|
|
817
|
+
}
|
|
818
|
+
if (cellRect.left < wrapperRect.left) {
|
|
819
|
+
wrapper.scrollLeft -= wrapperRect.left - cellRect.left;
|
|
820
|
+
} else if (cellRect.right > wrapperRect.right) {
|
|
821
|
+
wrapper.scrollLeft += cellRect.right - wrapperRect.right;
|
|
822
|
+
}
|
|
823
|
+
if (document.activeElement !== cell && typeof cell.focus === "function") {
|
|
824
|
+
cell.focus({ preventScroll: true });
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
},
|
|
829
|
+
{ flush: "post" }
|
|
830
|
+
);
|
|
831
|
+
onUnmounted(() => {
|
|
832
|
+
if (pendingRaf) {
|
|
833
|
+
cancelAnimationFrame(pendingRaf);
|
|
834
|
+
pendingRaf = 0;
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
return { activeCell, setActiveCell };
|
|
838
|
+
}
|
|
839
|
+
function useLatestRef(source) {
|
|
840
|
+
let value = unref(source);
|
|
841
|
+
return customRef((track, trigger) => ({
|
|
842
|
+
get() {
|
|
843
|
+
if (isRef(source)) {
|
|
844
|
+
value = source.value;
|
|
845
|
+
}
|
|
846
|
+
return value;
|
|
847
|
+
},
|
|
848
|
+
set(newValue) {
|
|
849
|
+
value = newValue;
|
|
850
|
+
trigger();
|
|
851
|
+
}
|
|
852
|
+
}));
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// src/composables/useCellSelection.ts
|
|
856
|
+
var DRAG_ATTR = "data-drag-range";
|
|
857
|
+
var DRAG_ANCHOR_ATTR = "data-drag-anchor";
|
|
858
|
+
var AUTO_SCROLL_EDGE = 40;
|
|
859
|
+
var AUTO_SCROLL_INTERVAL = 16;
|
|
860
|
+
function useCellSelection(params) {
|
|
861
|
+
const paramsRef = useLatestRef(params);
|
|
862
|
+
const { wrapperRef, setActiveCell } = params;
|
|
863
|
+
const getColOffset = () => isRef(params.colOffset) ? params.colOffset.value : params.colOffset;
|
|
864
|
+
const selectionRange = shallowRef(null);
|
|
865
|
+
const isDragging = ref(false);
|
|
866
|
+
const isDraggingInternal = ref(false);
|
|
867
|
+
const isUnmounted = ref(false);
|
|
868
|
+
let dragMoved = false;
|
|
869
|
+
let dragStart = null;
|
|
870
|
+
let rafId = 0;
|
|
871
|
+
let liveDragRange = null;
|
|
872
|
+
let autoScrollInterval = null;
|
|
873
|
+
let lastMousePos = null;
|
|
874
|
+
const setSelectionRange = (next) => {
|
|
875
|
+
if (rangesEqual(selectionRange.value, next)) return;
|
|
876
|
+
selectionRange.value = next;
|
|
877
|
+
};
|
|
878
|
+
const handleCellMouseDown = (e, rowIndex, globalColIndex) => {
|
|
879
|
+
if (e.button !== 0) return;
|
|
880
|
+
const colOffset = getColOffset();
|
|
881
|
+
if (globalColIndex < colOffset) return;
|
|
882
|
+
e.preventDefault();
|
|
883
|
+
const dataColIndex = globalColIndex - colOffset;
|
|
884
|
+
const currentRange = selectionRange.value;
|
|
885
|
+
if (e.shiftKey && currentRange != null) {
|
|
886
|
+
setSelectionRange(
|
|
887
|
+
normalizeSelectionRange({
|
|
888
|
+
startRow: currentRange.startRow,
|
|
889
|
+
startCol: currentRange.startCol,
|
|
890
|
+
endRow: rowIndex,
|
|
891
|
+
endCol: dataColIndex
|
|
892
|
+
})
|
|
893
|
+
);
|
|
894
|
+
setActiveCell({ rowIndex, columnIndex: globalColIndex });
|
|
895
|
+
} else {
|
|
896
|
+
dragStart = { row: rowIndex, col: dataColIndex };
|
|
897
|
+
dragMoved = false;
|
|
898
|
+
const initial = {
|
|
899
|
+
startRow: rowIndex,
|
|
900
|
+
startCol: dataColIndex,
|
|
901
|
+
endRow: rowIndex,
|
|
902
|
+
endCol: dataColIndex
|
|
903
|
+
};
|
|
904
|
+
setSelectionRange(initial);
|
|
905
|
+
liveDragRange = initial;
|
|
906
|
+
setActiveCell({ rowIndex, columnIndex: globalColIndex });
|
|
907
|
+
isDraggingInternal.value = true;
|
|
908
|
+
setTimeout(() => applyDragAttrs(initial), 0);
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
const handleSelectAllCells = () => {
|
|
912
|
+
const { rowCount, visibleColCount } = paramsRef.value;
|
|
913
|
+
if (rowCount.value === 0 || visibleColCount.value === 0) return;
|
|
914
|
+
setSelectionRange({
|
|
915
|
+
startRow: 0,
|
|
916
|
+
startCol: 0,
|
|
917
|
+
endRow: rowCount.value - 1,
|
|
918
|
+
endCol: visibleColCount.value - 1
|
|
919
|
+
});
|
|
920
|
+
setActiveCell({ rowIndex: 0, columnIndex: getColOffset() });
|
|
921
|
+
};
|
|
922
|
+
const markedCells = /* @__PURE__ */ new Set();
|
|
923
|
+
let cellIndex = null;
|
|
924
|
+
const buildCellIndex = () => {
|
|
925
|
+
const wrapper = wrapperRef.value;
|
|
926
|
+
if (!wrapper) return;
|
|
927
|
+
cellIndex = /* @__PURE__ */ new Map();
|
|
928
|
+
const cells = wrapper.querySelectorAll("[data-row-index][data-col-index]");
|
|
929
|
+
for (let i = 0; i < cells.length; i++) {
|
|
930
|
+
const el = cells[i];
|
|
931
|
+
const r = el.getAttribute("data-row-index") ?? "";
|
|
932
|
+
const c = el.getAttribute("data-col-index") ?? "";
|
|
933
|
+
cellIndex.set(`${r},${c}`, el);
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
const styleCellInRange = (el, r, c, minR, maxR, minC, maxC, anchor) => {
|
|
937
|
+
if (!el.hasAttribute(DRAG_ATTR)) el.setAttribute(DRAG_ATTR, "");
|
|
938
|
+
const isAnchor = anchor && r === anchor.row && c === anchor.col;
|
|
939
|
+
if (isAnchor) {
|
|
940
|
+
if (!el.hasAttribute(DRAG_ANCHOR_ATTR)) el.setAttribute(DRAG_ANCHOR_ATTR, "");
|
|
941
|
+
} else {
|
|
942
|
+
if (el.hasAttribute(DRAG_ANCHOR_ATTR)) el.removeAttribute(DRAG_ANCHOR_ATTR);
|
|
943
|
+
}
|
|
944
|
+
const shadows = [];
|
|
945
|
+
if (r === minR) shadows.push("inset 0 2px 0 0 var(--ogrid-selection, #217346)");
|
|
946
|
+
if (r === maxR) shadows.push("inset 0 -2px 0 0 var(--ogrid-selection, #217346)");
|
|
947
|
+
if (c === minC) shadows.push("inset 2px 0 0 0 var(--ogrid-selection, #217346)");
|
|
948
|
+
if (c === maxC) shadows.push("inset -2px 0 0 0 var(--ogrid-selection, #217346)");
|
|
949
|
+
el.style.boxShadow = shadows.length > 0 ? shadows.join(", ") : "";
|
|
950
|
+
markedCells.add(el);
|
|
951
|
+
};
|
|
952
|
+
const unstyleCell = (el) => {
|
|
953
|
+
el.removeAttribute(DRAG_ATTR);
|
|
954
|
+
el.removeAttribute(DRAG_ANCHOR_ATTR);
|
|
955
|
+
el.style.boxShadow = "";
|
|
956
|
+
};
|
|
957
|
+
const applyDragAttrs = (range) => {
|
|
958
|
+
const wrapper = wrapperRef.value;
|
|
959
|
+
if (!wrapper) return;
|
|
960
|
+
const minR = Math.min(range.startRow, range.endRow);
|
|
961
|
+
const maxR = Math.max(range.startRow, range.endRow);
|
|
962
|
+
const minC = Math.min(range.startCol, range.endCol);
|
|
963
|
+
const maxC = Math.max(range.startCol, range.endCol);
|
|
964
|
+
const anchor = dragStart;
|
|
965
|
+
const colOff = getColOffset();
|
|
966
|
+
for (const el of markedCells) {
|
|
967
|
+
const r = parseInt(el.getAttribute("data-row-index") ?? "", 10);
|
|
968
|
+
const c = parseInt(el.getAttribute("data-col-index") ?? "", 10) - colOff;
|
|
969
|
+
const stillInRange = r >= minR && r <= maxR && c >= minC && c <= maxC;
|
|
970
|
+
if (!stillInRange) {
|
|
971
|
+
unstyleCell(el);
|
|
972
|
+
markedCells.delete(el);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
if (!cellIndex) buildCellIndex();
|
|
976
|
+
for (let r = minR; r <= maxR; r++) {
|
|
977
|
+
for (let c = minC; c <= maxC; c++) {
|
|
978
|
+
const key = `${r},${c + colOff}`;
|
|
979
|
+
let el = cellIndex?.get(key);
|
|
980
|
+
if (el && !el.isConnected) {
|
|
981
|
+
buildCellIndex();
|
|
982
|
+
el = cellIndex?.get(key);
|
|
983
|
+
}
|
|
984
|
+
if (el) {
|
|
985
|
+
styleCellInRange(el, r, c, minR, maxR, minC, maxC, anchor);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
const clearDragAttrs = () => {
|
|
991
|
+
for (const el of markedCells) {
|
|
992
|
+
unstyleCell(el);
|
|
993
|
+
}
|
|
994
|
+
markedCells.clear();
|
|
995
|
+
cellIndex = null;
|
|
996
|
+
};
|
|
997
|
+
const resolveRange = (cx, cy) => {
|
|
998
|
+
if (!dragStart) return null;
|
|
999
|
+
const target = document.elementFromPoint(cx, cy);
|
|
1000
|
+
const cell = target?.closest?.("[data-row-index][data-col-index]");
|
|
1001
|
+
if (!cell) return null;
|
|
1002
|
+
const r = parseInt(cell.getAttribute("data-row-index") ?? "", 10);
|
|
1003
|
+
const c = parseInt(cell.getAttribute("data-col-index") ?? "", 10);
|
|
1004
|
+
const colOffset = getColOffset();
|
|
1005
|
+
if (Number.isNaN(r) || Number.isNaN(c) || c < colOffset) return null;
|
|
1006
|
+
const dataCol = c - colOffset;
|
|
1007
|
+
return normalizeSelectionRange({
|
|
1008
|
+
startRow: dragStart.row,
|
|
1009
|
+
startCol: dragStart.col,
|
|
1010
|
+
endRow: r,
|
|
1011
|
+
endCol: dataCol
|
|
1012
|
+
});
|
|
1013
|
+
};
|
|
1014
|
+
const stopAutoScroll = () => {
|
|
1015
|
+
if (autoScrollInterval) {
|
|
1016
|
+
clearInterval(autoScrollInterval);
|
|
1017
|
+
autoScrollInterval = null;
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
const updateAutoScroll = () => {
|
|
1021
|
+
const wrapper = wrapperRef.value;
|
|
1022
|
+
if (!wrapper || !lastMousePos || !isDraggingInternal.value) {
|
|
1023
|
+
stopAutoScroll();
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
const rect = wrapper.getBoundingClientRect();
|
|
1027
|
+
let dx = 0;
|
|
1028
|
+
let dy = 0;
|
|
1029
|
+
if (lastMousePos.cy < rect.top + AUTO_SCROLL_EDGE) {
|
|
1030
|
+
dy = -computeAutoScrollSpeed(rect.top + AUTO_SCROLL_EDGE - lastMousePos.cy);
|
|
1031
|
+
} else if (lastMousePos.cy > rect.bottom - AUTO_SCROLL_EDGE) {
|
|
1032
|
+
dy = computeAutoScrollSpeed(lastMousePos.cy - (rect.bottom - AUTO_SCROLL_EDGE));
|
|
1033
|
+
}
|
|
1034
|
+
if (lastMousePos.cx < rect.left + AUTO_SCROLL_EDGE) {
|
|
1035
|
+
dx = -computeAutoScrollSpeed(rect.left + AUTO_SCROLL_EDGE - lastMousePos.cx);
|
|
1036
|
+
} else if (lastMousePos.cx > rect.right - AUTO_SCROLL_EDGE) {
|
|
1037
|
+
dx = computeAutoScrollSpeed(lastMousePos.cx - (rect.right - AUTO_SCROLL_EDGE));
|
|
1038
|
+
}
|
|
1039
|
+
if (dx === 0 && dy === 0) {
|
|
1040
|
+
stopAutoScroll();
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
if (!autoScrollInterval) {
|
|
1044
|
+
autoScrollInterval = setInterval(() => {
|
|
1045
|
+
const w = wrapperRef.value;
|
|
1046
|
+
const p = lastMousePos;
|
|
1047
|
+
if (!w || !p || !isDraggingInternal.value) {
|
|
1048
|
+
stopAutoScroll();
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
const r = w.getBoundingClientRect();
|
|
1052
|
+
let sdx = 0;
|
|
1053
|
+
let sdy = 0;
|
|
1054
|
+
if (p.cy < r.top + AUTO_SCROLL_EDGE) sdy = -computeAutoScrollSpeed(r.top + AUTO_SCROLL_EDGE - p.cy);
|
|
1055
|
+
else if (p.cy > r.bottom - AUTO_SCROLL_EDGE) sdy = computeAutoScrollSpeed(p.cy - (r.bottom - AUTO_SCROLL_EDGE));
|
|
1056
|
+
if (p.cx < r.left + AUTO_SCROLL_EDGE) sdx = -computeAutoScrollSpeed(r.left + AUTO_SCROLL_EDGE - p.cx);
|
|
1057
|
+
else if (p.cx > r.right - AUTO_SCROLL_EDGE) sdx = computeAutoScrollSpeed(p.cx - (r.right - AUTO_SCROLL_EDGE));
|
|
1058
|
+
if (sdx === 0 && sdy === 0) {
|
|
1059
|
+
stopAutoScroll();
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
w.scrollTop += sdy;
|
|
1063
|
+
w.scrollLeft += sdx;
|
|
1064
|
+
const newRange = resolveRange(p.cx, p.cy);
|
|
1065
|
+
if (newRange) {
|
|
1066
|
+
liveDragRange = newRange;
|
|
1067
|
+
applyDragAttrs(newRange);
|
|
1068
|
+
}
|
|
1069
|
+
}, AUTO_SCROLL_INTERVAL);
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
const onMove = (e) => {
|
|
1073
|
+
if (!isDraggingInternal.value || !dragStart) return;
|
|
1074
|
+
if (!dragMoved) {
|
|
1075
|
+
dragMoved = true;
|
|
1076
|
+
isDragging.value = true;
|
|
1077
|
+
buildCellIndex();
|
|
1078
|
+
}
|
|
1079
|
+
lastMousePos = { cx: e.clientX, cy: e.clientY };
|
|
1080
|
+
updateAutoScroll();
|
|
1081
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
1082
|
+
rafId = requestAnimationFrame(() => {
|
|
1083
|
+
rafId = 0;
|
|
1084
|
+
if (!lastMousePos) return;
|
|
1085
|
+
const newRange = resolveRange(lastMousePos.cx, lastMousePos.cy);
|
|
1086
|
+
if (!newRange) return;
|
|
1087
|
+
const prev = liveDragRange;
|
|
1088
|
+
if (prev && prev.startRow === newRange.startRow && prev.startCol === newRange.startCol && prev.endRow === newRange.endRow && prev.endCol === newRange.endCol) {
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
liveDragRange = newRange;
|
|
1092
|
+
applyDragAttrs(newRange);
|
|
1093
|
+
});
|
|
1094
|
+
};
|
|
1095
|
+
const onUp = () => {
|
|
1096
|
+
if (!isDraggingInternal.value) return;
|
|
1097
|
+
stopAutoScroll();
|
|
1098
|
+
if (rafId) {
|
|
1099
|
+
cancelAnimationFrame(rafId);
|
|
1100
|
+
rafId = 0;
|
|
1101
|
+
}
|
|
1102
|
+
isDraggingInternal.value = false;
|
|
1103
|
+
const wasDrag = dragMoved;
|
|
1104
|
+
if (wasDrag) {
|
|
1105
|
+
if (lastMousePos) {
|
|
1106
|
+
const flushed = resolveRange(lastMousePos.cx, lastMousePos.cy);
|
|
1107
|
+
if (flushed) liveDragRange = flushed;
|
|
1108
|
+
}
|
|
1109
|
+
const finalRange = liveDragRange;
|
|
1110
|
+
if (finalRange) {
|
|
1111
|
+
setSelectionRange(finalRange);
|
|
1112
|
+
setActiveCell({
|
|
1113
|
+
rowIndex: finalRange.endRow,
|
|
1114
|
+
columnIndex: finalRange.endCol + getColOffset()
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
clearDragAttrs();
|
|
1119
|
+
liveDragRange = null;
|
|
1120
|
+
lastMousePos = null;
|
|
1121
|
+
dragStart = null;
|
|
1122
|
+
if (wasDrag) isDragging.value = false;
|
|
1123
|
+
};
|
|
1124
|
+
const onMoveSafe = (e) => {
|
|
1125
|
+
if (isUnmounted.value) return;
|
|
1126
|
+
onMove(e);
|
|
1127
|
+
};
|
|
1128
|
+
const onUpSafe = () => {
|
|
1129
|
+
if (isUnmounted.value) return;
|
|
1130
|
+
onUp();
|
|
1131
|
+
};
|
|
1132
|
+
onMounted(() => {
|
|
1133
|
+
window.addEventListener("mousemove", onMoveSafe, true);
|
|
1134
|
+
window.addEventListener("mouseup", onUpSafe, true);
|
|
1135
|
+
});
|
|
1136
|
+
onUnmounted(() => {
|
|
1137
|
+
isUnmounted.value = true;
|
|
1138
|
+
window.removeEventListener("mousemove", onMoveSafe, true);
|
|
1139
|
+
window.removeEventListener("mouseup", onUpSafe, true);
|
|
1140
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
1141
|
+
stopAutoScroll();
|
|
1142
|
+
});
|
|
1143
|
+
return {
|
|
1144
|
+
selectionRange,
|
|
1145
|
+
setSelectionRange,
|
|
1146
|
+
handleCellMouseDown,
|
|
1147
|
+
handleSelectAllCells,
|
|
1148
|
+
isDragging
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
function useContextMenu() {
|
|
1152
|
+
const contextMenuPosition = shallowRef(null);
|
|
1153
|
+
const setContextMenuPosition = (pos) => {
|
|
1154
|
+
contextMenuPosition.value = pos;
|
|
1155
|
+
};
|
|
1156
|
+
const handleCellContextMenu = (e) => {
|
|
1157
|
+
e.preventDefault?.();
|
|
1158
|
+
contextMenuPosition.value = { x: e.clientX, y: e.clientY };
|
|
1159
|
+
};
|
|
1160
|
+
const closeContextMenu = () => {
|
|
1161
|
+
contextMenuPosition.value = null;
|
|
1162
|
+
};
|
|
1163
|
+
return {
|
|
1164
|
+
contextMenuPosition,
|
|
1165
|
+
setContextMenuPosition,
|
|
1166
|
+
handleCellContextMenu,
|
|
1167
|
+
closeContextMenu
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
function useClipboard(params) {
|
|
1171
|
+
const {
|
|
1172
|
+
items,
|
|
1173
|
+
visibleCols,
|
|
1174
|
+
selectionRange,
|
|
1175
|
+
activeCell,
|
|
1176
|
+
editable,
|
|
1177
|
+
onCellValueChanged,
|
|
1178
|
+
beginBatch,
|
|
1179
|
+
endBatch
|
|
1180
|
+
} = params;
|
|
1181
|
+
const getColOffset = () => isRef(params.colOffset) ? params.colOffset.value : params.colOffset;
|
|
1182
|
+
const cutRange = shallowRef(null);
|
|
1183
|
+
const copyRange = shallowRef(null);
|
|
1184
|
+
const internalClipboardRef = ref(null);
|
|
1185
|
+
const getEffectiveRange = () => {
|
|
1186
|
+
const sel = selectionRange.value;
|
|
1187
|
+
const ac = activeCell.value;
|
|
1188
|
+
const colOffset = getColOffset();
|
|
1189
|
+
return sel ?? (ac != null ? { startRow: ac.rowIndex, startCol: ac.columnIndex - colOffset, endRow: ac.rowIndex, endCol: ac.columnIndex - colOffset } : null);
|
|
1190
|
+
};
|
|
1191
|
+
const handleCopy = () => {
|
|
1192
|
+
const range = getEffectiveRange();
|
|
1193
|
+
if (range == null) return;
|
|
1194
|
+
const norm = normalizeSelectionRange(range);
|
|
1195
|
+
const tsv = formatSelectionAsTsv(items.value, visibleCols.value, norm);
|
|
1196
|
+
internalClipboardRef.value = tsv;
|
|
1197
|
+
copyRange.value = norm;
|
|
1198
|
+
void navigator.clipboard.writeText(tsv).catch((err) => {
|
|
1199
|
+
if (typeof console !== "undefined") console.warn("[OGrid] Clipboard write failed:", err);
|
|
1200
|
+
});
|
|
1201
|
+
};
|
|
1202
|
+
const handleCut = () => {
|
|
1203
|
+
if (editable.value === false) return;
|
|
1204
|
+
const range = getEffectiveRange();
|
|
1205
|
+
if (range == null || onCellValueChanged.value == null) return;
|
|
1206
|
+
const norm = normalizeSelectionRange(range);
|
|
1207
|
+
cutRange.value = norm;
|
|
1208
|
+
copyRange.value = null;
|
|
1209
|
+
handleCopy();
|
|
1210
|
+
copyRange.value = null;
|
|
1211
|
+
};
|
|
1212
|
+
const handlePaste = async () => {
|
|
1213
|
+
if (editable.value === false) return;
|
|
1214
|
+
const callback = onCellValueChanged.value;
|
|
1215
|
+
if (callback == null) return;
|
|
1216
|
+
let text;
|
|
1217
|
+
try {
|
|
1218
|
+
text = await navigator.clipboard.readText();
|
|
1219
|
+
} catch {
|
|
1220
|
+
text = "";
|
|
1221
|
+
}
|
|
1222
|
+
if (!text.trim() && internalClipboardRef.value != null) {
|
|
1223
|
+
text = internalClipboardRef.value;
|
|
1224
|
+
}
|
|
1225
|
+
if (!text.trim()) return;
|
|
1226
|
+
const norm = getEffectiveRange();
|
|
1227
|
+
const anchorRow = norm ? norm.startRow : 0;
|
|
1228
|
+
const anchorCol = norm ? norm.startCol : 0;
|
|
1229
|
+
const currentItems = items.value;
|
|
1230
|
+
const currentCols = visibleCols.value;
|
|
1231
|
+
const parsedRows = parseTsvClipboard(text);
|
|
1232
|
+
beginBatch?.();
|
|
1233
|
+
const pasteEvents = applyPastedValues(parsedRows, anchorRow, anchorCol, currentItems, currentCols);
|
|
1234
|
+
for (const evt of pasteEvents) callback(evt);
|
|
1235
|
+
if (cutRange.value) {
|
|
1236
|
+
const cutEvents = applyCutClear(cutRange.value, currentItems, currentCols);
|
|
1237
|
+
for (const evt of cutEvents) callback(evt);
|
|
1238
|
+
cutRange.value = null;
|
|
1239
|
+
}
|
|
1240
|
+
endBatch?.();
|
|
1241
|
+
copyRange.value = null;
|
|
1242
|
+
};
|
|
1243
|
+
const clearClipboardRanges = () => {
|
|
1244
|
+
copyRange.value = null;
|
|
1245
|
+
cutRange.value = null;
|
|
1246
|
+
};
|
|
1247
|
+
return { handleCopy, handleCut, handlePaste, cutRange, copyRange, clearClipboardRanges };
|
|
1248
|
+
}
|
|
1249
|
+
function useKeyboardNavigation(params) {
|
|
1250
|
+
const paramsRef = useLatestRef(params);
|
|
1251
|
+
const handleGridKeyDown = (e) => {
|
|
1252
|
+
const { data, state, handlers, features } = paramsRef.value;
|
|
1253
|
+
const items = data.items.value;
|
|
1254
|
+
const visibleCols = data.visibleCols.value;
|
|
1255
|
+
const { getRowId } = data;
|
|
1256
|
+
const colOffset = isRef(data.colOffset) ? data.colOffset.value : data.colOffset;
|
|
1257
|
+
const hasCheckboxCol = data.hasCheckboxCol.value;
|
|
1258
|
+
const visibleColumnCount = data.visibleColumnCount.value;
|
|
1259
|
+
const activeCell = state.activeCell.value;
|
|
1260
|
+
const selectionRange = state.selectionRange.value;
|
|
1261
|
+
const editingCell = state.editingCell.value;
|
|
1262
|
+
const selectedRowIds = state.selectedRowIds.value;
|
|
1263
|
+
const { setActiveCell, setSelectionRange, setEditingCell, handleRowCheckboxChange, handleCopy, handleCut, handlePaste, setContextMenu, onUndo, onRedo, clearClipboardRanges } = handlers;
|
|
1264
|
+
const editable = features.editable.value;
|
|
1265
|
+
const onCellValueChanged = features.onCellValueChanged.value;
|
|
1266
|
+
const rowSelection = features.rowSelection.value;
|
|
1267
|
+
const wrapperRef = features.wrapperRef;
|
|
1268
|
+
const scrollToRow = features.scrollToRow;
|
|
1269
|
+
const maxRowIndex = items.length - 1;
|
|
1270
|
+
const maxColIndex = visibleColumnCount - 1 + colOffset;
|
|
1271
|
+
if (items.length === 0) return;
|
|
1272
|
+
if (activeCell === null) {
|
|
1273
|
+
if (["ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight", "Tab", "Enter", "Home", "End"].includes(e.key)) {
|
|
1274
|
+
setActiveCell({ rowIndex: 0, columnIndex: colOffset });
|
|
1275
|
+
e.preventDefault();
|
|
1276
|
+
}
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
const { rowIndex, columnIndex } = activeCell;
|
|
1280
|
+
const dataColIndex = columnIndex - colOffset;
|
|
1281
|
+
const shift = e.shiftKey;
|
|
1282
|
+
const isEmptyAt = (r, c) => {
|
|
1283
|
+
if (r < 0 || r >= items.length || c < 0 || c >= visibleCols.length) return true;
|
|
1284
|
+
const v = getCellValue(items[r], visibleCols[c]);
|
|
1285
|
+
return v == null || v === "";
|
|
1286
|
+
};
|
|
1287
|
+
switch (e.key) {
|
|
1288
|
+
case "c":
|
|
1289
|
+
if (e.ctrlKey || e.metaKey) {
|
|
1290
|
+
if (editingCell != null) break;
|
|
1291
|
+
e.preventDefault();
|
|
1292
|
+
handleCopy();
|
|
1293
|
+
}
|
|
1294
|
+
break;
|
|
1295
|
+
case "x":
|
|
1296
|
+
if (e.ctrlKey || e.metaKey) {
|
|
1297
|
+
if (editingCell != null) break;
|
|
1298
|
+
e.preventDefault();
|
|
1299
|
+
handleCut();
|
|
1300
|
+
}
|
|
1301
|
+
break;
|
|
1302
|
+
case "v":
|
|
1303
|
+
if (e.ctrlKey || e.metaKey) {
|
|
1304
|
+
if (editingCell != null) break;
|
|
1305
|
+
e.preventDefault();
|
|
1306
|
+
void handlePaste();
|
|
1307
|
+
}
|
|
1308
|
+
break;
|
|
1309
|
+
case "ArrowDown":
|
|
1310
|
+
case "ArrowUp":
|
|
1311
|
+
case "ArrowRight":
|
|
1312
|
+
case "ArrowLeft": {
|
|
1313
|
+
e.preventDefault();
|
|
1314
|
+
const { newRowIndex, newColumnIndex, newRange } = computeArrowNavigation({
|
|
1315
|
+
direction: e.key,
|
|
1316
|
+
rowIndex,
|
|
1317
|
+
columnIndex,
|
|
1318
|
+
dataColIndex,
|
|
1319
|
+
colOffset,
|
|
1320
|
+
maxRowIndex,
|
|
1321
|
+
maxColIndex,
|
|
1322
|
+
visibleColCount: visibleCols.length,
|
|
1323
|
+
isCtrl: e.ctrlKey || e.metaKey,
|
|
1324
|
+
isShift: shift,
|
|
1325
|
+
selectionRange,
|
|
1326
|
+
isEmptyAt
|
|
1327
|
+
});
|
|
1328
|
+
setSelectionRange(newRange);
|
|
1329
|
+
setActiveCell({ rowIndex: newRowIndex, columnIndex: newColumnIndex });
|
|
1330
|
+
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
1331
|
+
scrollToRow?.(newRowIndex, "center");
|
|
1332
|
+
}
|
|
1333
|
+
break;
|
|
1334
|
+
}
|
|
1335
|
+
case "Tab": {
|
|
1336
|
+
e.preventDefault();
|
|
1337
|
+
const { rowIndex: newRowTab, columnIndex: newColTab } = computeTabNavigation(
|
|
1338
|
+
rowIndex,
|
|
1339
|
+
columnIndex,
|
|
1340
|
+
maxRowIndex,
|
|
1341
|
+
maxColIndex,
|
|
1342
|
+
colOffset,
|
|
1343
|
+
e.shiftKey
|
|
1344
|
+
);
|
|
1345
|
+
const newDataColTab = newColTab - colOffset;
|
|
1346
|
+
setSelectionRange({ startRow: newRowTab, startCol: newDataColTab, endRow: newRowTab, endCol: newDataColTab });
|
|
1347
|
+
setActiveCell({ rowIndex: newRowTab, columnIndex: newColTab });
|
|
1348
|
+
break;
|
|
1349
|
+
}
|
|
1350
|
+
case "Home": {
|
|
1351
|
+
e.preventDefault();
|
|
1352
|
+
const newRowHome = e.ctrlKey ? 0 : rowIndex;
|
|
1353
|
+
setSelectionRange({ startRow: newRowHome, startCol: 0, endRow: newRowHome, endCol: 0 });
|
|
1354
|
+
setActiveCell({ rowIndex: newRowHome, columnIndex: colOffset });
|
|
1355
|
+
break;
|
|
1356
|
+
}
|
|
1357
|
+
case "End": {
|
|
1358
|
+
e.preventDefault();
|
|
1359
|
+
const newRowEnd = e.ctrlKey ? maxRowIndex : rowIndex;
|
|
1360
|
+
setSelectionRange({ startRow: newRowEnd, startCol: visibleColumnCount - 1, endRow: newRowEnd, endCol: visibleColumnCount - 1 });
|
|
1361
|
+
setActiveCell({ rowIndex: newRowEnd, columnIndex: maxColIndex });
|
|
1362
|
+
break;
|
|
1363
|
+
}
|
|
1364
|
+
case "Enter":
|
|
1365
|
+
case "F2": {
|
|
1366
|
+
e.preventDefault();
|
|
1367
|
+
if (dataColIndex >= 0 && dataColIndex < visibleCols.length) {
|
|
1368
|
+
const col = visibleCols[dataColIndex];
|
|
1369
|
+
const item = items[rowIndex];
|
|
1370
|
+
if (item && col) {
|
|
1371
|
+
const colEditable = col.editable === true || typeof col.editable === "function" && col.editable(item);
|
|
1372
|
+
if (editable !== false && colEditable && onCellValueChanged != null) {
|
|
1373
|
+
setEditingCell({ rowId: getRowId(item), columnId: col.columnId });
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
break;
|
|
1378
|
+
}
|
|
1379
|
+
case "Escape":
|
|
1380
|
+
e.preventDefault();
|
|
1381
|
+
if (editingCell != null) {
|
|
1382
|
+
setEditingCell(null);
|
|
1383
|
+
} else {
|
|
1384
|
+
clearClipboardRanges?.();
|
|
1385
|
+
setActiveCell(null);
|
|
1386
|
+
setSelectionRange(null);
|
|
1387
|
+
}
|
|
1388
|
+
break;
|
|
1389
|
+
case " ":
|
|
1390
|
+
if (rowSelection !== "none" && columnIndex === 0 && hasCheckboxCol) {
|
|
1391
|
+
e.preventDefault();
|
|
1392
|
+
const item = items[rowIndex];
|
|
1393
|
+
if (item) {
|
|
1394
|
+
const id = getRowId(item);
|
|
1395
|
+
const isSelected = selectedRowIds.has(id);
|
|
1396
|
+
handleRowCheckboxChange(id, !isSelected, rowIndex, e.shiftKey);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
break;
|
|
1400
|
+
case "z":
|
|
1401
|
+
if (e.ctrlKey || e.metaKey) {
|
|
1402
|
+
if (editingCell == null) {
|
|
1403
|
+
if (e.shiftKey && onRedo) {
|
|
1404
|
+
e.preventDefault();
|
|
1405
|
+
onRedo();
|
|
1406
|
+
} else if (!e.shiftKey && onUndo) {
|
|
1407
|
+
e.preventDefault();
|
|
1408
|
+
onUndo();
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
break;
|
|
1413
|
+
case "y":
|
|
1414
|
+
if (e.ctrlKey || e.metaKey) {
|
|
1415
|
+
if (editingCell == null && onRedo) {
|
|
1416
|
+
e.preventDefault();
|
|
1417
|
+
onRedo();
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
break;
|
|
1421
|
+
case "a":
|
|
1422
|
+
if (e.ctrlKey || e.metaKey) {
|
|
1423
|
+
if (editingCell != null) break;
|
|
1424
|
+
e.preventDefault();
|
|
1425
|
+
if (items.length > 0 && visibleColumnCount > 0) {
|
|
1426
|
+
setSelectionRange({ startRow: 0, startCol: 0, endRow: items.length - 1, endCol: visibleColumnCount - 1 });
|
|
1427
|
+
setActiveCell({ rowIndex: 0, columnIndex: colOffset });
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
break;
|
|
1431
|
+
case "Delete":
|
|
1432
|
+
case "Backspace": {
|
|
1433
|
+
if (editingCell != null) break;
|
|
1434
|
+
if (editable === false) break;
|
|
1435
|
+
if (onCellValueChanged == null) break;
|
|
1436
|
+
const range = selectionRange ?? (activeCell != null ? { startRow: activeCell.rowIndex, startCol: activeCell.columnIndex - colOffset, endRow: activeCell.rowIndex, endCol: activeCell.columnIndex - colOffset } : null);
|
|
1437
|
+
if (range == null) break;
|
|
1438
|
+
e.preventDefault();
|
|
1439
|
+
const deleteEvents = applyCellDeletion(range, items, visibleCols);
|
|
1440
|
+
for (const evt of deleteEvents) onCellValueChanged(evt);
|
|
1441
|
+
break;
|
|
1442
|
+
}
|
|
1443
|
+
case "F10":
|
|
1444
|
+
if (e.shiftKey) {
|
|
1445
|
+
e.preventDefault();
|
|
1446
|
+
if (activeCell != null && wrapperRef.value) {
|
|
1447
|
+
const sel = `[data-row-index="${activeCell.rowIndex}"][data-col-index="${activeCell.columnIndex}"]`;
|
|
1448
|
+
const cell = wrapperRef.value.querySelector(sel);
|
|
1449
|
+
if (cell) {
|
|
1450
|
+
const rect = cell.getBoundingClientRect();
|
|
1451
|
+
setContextMenu({ x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 });
|
|
1452
|
+
} else {
|
|
1453
|
+
setContextMenu({ x: 100, y: 100 });
|
|
1454
|
+
}
|
|
1455
|
+
} else {
|
|
1456
|
+
setContextMenu({ x: 100, y: 100 });
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
break;
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1462
|
+
return { handleGridKeyDown };
|
|
1463
|
+
}
|
|
1464
|
+
var DRAG_ATTR2 = "data-drag-range";
|
|
1465
|
+
function useFillHandle(params) {
|
|
1466
|
+
const {
|
|
1467
|
+
items,
|
|
1468
|
+
visibleCols,
|
|
1469
|
+
editable,
|
|
1470
|
+
onCellValueChanged,
|
|
1471
|
+
selectionRange,
|
|
1472
|
+
setSelectionRange,
|
|
1473
|
+
setActiveCell,
|
|
1474
|
+
wrapperRef,
|
|
1475
|
+
beginBatch,
|
|
1476
|
+
endBatch,
|
|
1477
|
+
visibleRange
|
|
1478
|
+
} = params;
|
|
1479
|
+
const getColOffset = () => isRef(params.colOffset) ? params.colOffset.value : params.colOffset;
|
|
1480
|
+
const fillDrag = shallowRef(null);
|
|
1481
|
+
let fillDragEnd = { endRow: 0, endCol: 0 };
|
|
1482
|
+
let rafId = 0;
|
|
1483
|
+
let liveFillRange = null;
|
|
1484
|
+
let moveListener = null;
|
|
1485
|
+
let upListener = null;
|
|
1486
|
+
const setFillDrag = (value) => {
|
|
1487
|
+
fillDrag.value = value;
|
|
1488
|
+
};
|
|
1489
|
+
const cleanup = () => {
|
|
1490
|
+
if (moveListener) {
|
|
1491
|
+
window.removeEventListener("mousemove", moveListener, true);
|
|
1492
|
+
moveListener = null;
|
|
1493
|
+
}
|
|
1494
|
+
if (upListener) {
|
|
1495
|
+
window.removeEventListener("mouseup", upListener, true);
|
|
1496
|
+
upListener = null;
|
|
1497
|
+
}
|
|
1498
|
+
if (rafId) {
|
|
1499
|
+
cancelAnimationFrame(rafId);
|
|
1500
|
+
rafId = 0;
|
|
1501
|
+
}
|
|
1502
|
+
};
|
|
1503
|
+
watch(fillDrag, (drag, _oldDrag, onCleanup) => {
|
|
1504
|
+
if (!drag || editable.value === false || !onCellValueChanged.value || !wrapperRef.value) {
|
|
1505
|
+
cleanup();
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
fillDragEnd = { endRow: drag.startRow, endCol: drag.startCol };
|
|
1509
|
+
liveFillRange = null;
|
|
1510
|
+
const markedCells = /* @__PURE__ */ new Set();
|
|
1511
|
+
let fillCellIndex = null;
|
|
1512
|
+
const buildFillCellIndex = () => {
|
|
1513
|
+
const wrapper = wrapperRef.value;
|
|
1514
|
+
if (!wrapper) return;
|
|
1515
|
+
fillCellIndex = /* @__PURE__ */ new Map();
|
|
1516
|
+
const cells = wrapper.querySelectorAll("[data-row-index][data-col-index]");
|
|
1517
|
+
for (let i = 0; i < cells.length; i++) {
|
|
1518
|
+
const el = cells[i];
|
|
1519
|
+
const r = el.getAttribute("data-row-index") ?? "";
|
|
1520
|
+
const c = el.getAttribute("data-col-index") ?? "";
|
|
1521
|
+
fillCellIndex.set(`${r},${c}`, el);
|
|
1522
|
+
}
|
|
1523
|
+
};
|
|
1524
|
+
buildFillCellIndex();
|
|
1525
|
+
const applyDragAttrs = (range) => {
|
|
1526
|
+
const wrapper = wrapperRef.value;
|
|
1527
|
+
if (!wrapper) return;
|
|
1528
|
+
const minR = Math.min(range.startRow, range.endRow);
|
|
1529
|
+
const maxR = Math.max(range.startRow, range.endRow);
|
|
1530
|
+
const minC = Math.min(range.startCol, range.endCol);
|
|
1531
|
+
const maxC = Math.max(range.startCol, range.endCol);
|
|
1532
|
+
const colOff = getColOffset();
|
|
1533
|
+
for (const el of markedCells) {
|
|
1534
|
+
const r = parseInt(el.getAttribute("data-row-index") ?? "", 10);
|
|
1535
|
+
const c = parseInt(el.getAttribute("data-col-index") ?? "", 10) - colOff;
|
|
1536
|
+
if (!(r >= minR && r <= maxR && c >= minC && c <= maxC)) {
|
|
1537
|
+
el.removeAttribute(DRAG_ATTR2);
|
|
1538
|
+
markedCells.delete(el);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
for (let r = minR; r <= maxR; r++) {
|
|
1542
|
+
for (let c = minC; c <= maxC; c++) {
|
|
1543
|
+
const key = `${r},${c + colOff}`;
|
|
1544
|
+
let el = fillCellIndex?.get(key);
|
|
1545
|
+
if (el && !el.isConnected) {
|
|
1546
|
+
buildFillCellIndex();
|
|
1547
|
+
el = fillCellIndex?.get(key);
|
|
1548
|
+
}
|
|
1549
|
+
if (el) {
|
|
1550
|
+
if (!el.hasAttribute(DRAG_ATTR2)) el.setAttribute(DRAG_ATTR2, "");
|
|
1551
|
+
markedCells.add(el);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
};
|
|
1556
|
+
const clearDragAttrs = () => {
|
|
1557
|
+
for (const el of markedCells) {
|
|
1558
|
+
el.removeAttribute(DRAG_ATTR2);
|
|
1559
|
+
}
|
|
1560
|
+
markedCells.clear();
|
|
1561
|
+
fillCellIndex = null;
|
|
1562
|
+
};
|
|
1563
|
+
let lastFillMousePos = null;
|
|
1564
|
+
const resolveRange = (cx, cy) => {
|
|
1565
|
+
const target = document.elementFromPoint(cx, cy);
|
|
1566
|
+
const cell = target?.closest?.("[data-row-index][data-col-index]");
|
|
1567
|
+
if (!cell || !wrapperRef.value?.contains(cell)) return null;
|
|
1568
|
+
const r = parseInt(cell.getAttribute("data-row-index") ?? "", 10);
|
|
1569
|
+
const c = parseInt(cell.getAttribute("data-col-index") ?? "", 10);
|
|
1570
|
+
const colOffset = getColOffset();
|
|
1571
|
+
if (Number.isNaN(r) || Number.isNaN(c) || c < colOffset) return null;
|
|
1572
|
+
const dataCol = c - colOffset;
|
|
1573
|
+
return normalizeSelectionRange({
|
|
1574
|
+
startRow: drag.startRow,
|
|
1575
|
+
startCol: drag.startCol,
|
|
1576
|
+
endRow: r,
|
|
1577
|
+
endCol: dataCol
|
|
1578
|
+
});
|
|
1579
|
+
};
|
|
1580
|
+
moveListener = (e) => {
|
|
1581
|
+
lastFillMousePos = { cx: e.clientX, cy: e.clientY };
|
|
1582
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
1583
|
+
rafId = requestAnimationFrame(() => {
|
|
1584
|
+
rafId = 0;
|
|
1585
|
+
if (!lastFillMousePos) return;
|
|
1586
|
+
const newRange = resolveRange(lastFillMousePos.cx, lastFillMousePos.cy);
|
|
1587
|
+
if (!newRange) return;
|
|
1588
|
+
const prev = liveFillRange;
|
|
1589
|
+
if (prev && prev.startRow === newRange.startRow && prev.startCol === newRange.startCol && prev.endRow === newRange.endRow && prev.endCol === newRange.endCol) return;
|
|
1590
|
+
liveFillRange = newRange;
|
|
1591
|
+
fillDragEnd = { endRow: newRange.endRow, endCol: newRange.endCol };
|
|
1592
|
+
applyDragAttrs(newRange);
|
|
1593
|
+
});
|
|
1594
|
+
};
|
|
1595
|
+
upListener = () => {
|
|
1596
|
+
if (rafId) {
|
|
1597
|
+
cancelAnimationFrame(rafId);
|
|
1598
|
+
rafId = 0;
|
|
1599
|
+
}
|
|
1600
|
+
if (lastFillMousePos) {
|
|
1601
|
+
const flushed = resolveRange(lastFillMousePos.cx, lastFillMousePos.cy);
|
|
1602
|
+
if (flushed) {
|
|
1603
|
+
liveFillRange = flushed;
|
|
1604
|
+
fillDragEnd = { endRow: flushed.endRow, endCol: flushed.endCol };
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
clearDragAttrs();
|
|
1608
|
+
const end = fillDragEnd;
|
|
1609
|
+
const norm = normalizeSelectionRange({
|
|
1610
|
+
startRow: drag.startRow,
|
|
1611
|
+
startCol: drag.startCol,
|
|
1612
|
+
endRow: end.endRow,
|
|
1613
|
+
endCol: end.endCol
|
|
1614
|
+
});
|
|
1615
|
+
const vr = visibleRange?.value;
|
|
1616
|
+
if (vr) {
|
|
1617
|
+
norm.startRow = Math.max(norm.startRow, vr.startIndex);
|
|
1618
|
+
norm.endRow = Math.min(norm.endRow, vr.endIndex);
|
|
1619
|
+
}
|
|
1620
|
+
setSelectionRange(norm);
|
|
1621
|
+
setActiveCell({ rowIndex: end.endRow, columnIndex: end.endCol + getColOffset() });
|
|
1622
|
+
const currentItems = items.value;
|
|
1623
|
+
const currentCols = visibleCols.value;
|
|
1624
|
+
const callback = onCellValueChanged.value;
|
|
1625
|
+
if (callback) {
|
|
1626
|
+
const fillEvents = applyFillValues(norm, drag.startRow, drag.startCol, currentItems, currentCols);
|
|
1627
|
+
if (fillEvents.length > 0) {
|
|
1628
|
+
beginBatch?.();
|
|
1629
|
+
for (const evt of fillEvents) callback(evt);
|
|
1630
|
+
endBatch?.();
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
fillDrag.value = null;
|
|
1634
|
+
liveFillRange = null;
|
|
1635
|
+
cleanup();
|
|
1636
|
+
};
|
|
1637
|
+
window.addEventListener("mousemove", moveListener, true);
|
|
1638
|
+
window.addEventListener("mouseup", upListener, true);
|
|
1639
|
+
onCleanup(() => {
|
|
1640
|
+
cleanup();
|
|
1641
|
+
});
|
|
1642
|
+
});
|
|
1643
|
+
onUnmounted(() => cleanup());
|
|
1644
|
+
const handleFillHandleMouseDown = (e) => {
|
|
1645
|
+
e.preventDefault();
|
|
1646
|
+
e.stopPropagation();
|
|
1647
|
+
const range = selectionRange.value;
|
|
1648
|
+
if (!range) return;
|
|
1649
|
+
fillDrag.value = { startRow: range.startRow, startCol: range.startCol };
|
|
1650
|
+
};
|
|
1651
|
+
return { fillDrag, setFillDrag, handleFillHandleMouseDown };
|
|
1652
|
+
}
|
|
1653
|
+
function useUndoRedo(params) {
|
|
1654
|
+
const { onCellValueChanged, maxUndoDepth = 100 } = params;
|
|
1655
|
+
const stack = new UndoRedoStack(maxUndoDepth);
|
|
1656
|
+
const canUndo = ref(false);
|
|
1657
|
+
const canRedo = ref(false);
|
|
1658
|
+
const updateFlags = () => {
|
|
1659
|
+
canUndo.value = stack.canUndo;
|
|
1660
|
+
canRedo.value = stack.canRedo;
|
|
1661
|
+
};
|
|
1662
|
+
const wrapped = onCellValueChanged ? (event) => {
|
|
1663
|
+
stack.record(event);
|
|
1664
|
+
if (!stack.isBatching) {
|
|
1665
|
+
updateFlags();
|
|
1666
|
+
}
|
|
1667
|
+
onCellValueChanged(event);
|
|
1668
|
+
} : void 0;
|
|
1669
|
+
const beginBatch = () => {
|
|
1670
|
+
stack.beginBatch();
|
|
1671
|
+
};
|
|
1672
|
+
const endBatch = () => {
|
|
1673
|
+
stack.endBatch();
|
|
1674
|
+
updateFlags();
|
|
1675
|
+
};
|
|
1676
|
+
const undo = () => {
|
|
1677
|
+
if (!onCellValueChanged) return;
|
|
1678
|
+
const lastBatch = stack.undo();
|
|
1679
|
+
if (!lastBatch) return;
|
|
1680
|
+
updateFlags();
|
|
1681
|
+
for (let i = lastBatch.length - 1; i >= 0; i--) {
|
|
1682
|
+
const ev = lastBatch[i];
|
|
1683
|
+
onCellValueChanged({ ...ev, oldValue: ev.newValue, newValue: ev.oldValue });
|
|
1684
|
+
}
|
|
1685
|
+
};
|
|
1686
|
+
const redo = () => {
|
|
1687
|
+
if (!onCellValueChanged) return;
|
|
1688
|
+
const nextBatch = stack.redo();
|
|
1689
|
+
if (!nextBatch) return;
|
|
1690
|
+
updateFlags();
|
|
1691
|
+
for (const ev of nextBatch) {
|
|
1692
|
+
onCellValueChanged(ev);
|
|
1693
|
+
}
|
|
1694
|
+
};
|
|
1695
|
+
return {
|
|
1696
|
+
onCellValueChanged: wrapped,
|
|
1697
|
+
undo,
|
|
1698
|
+
redo,
|
|
1699
|
+
canUndo,
|
|
1700
|
+
canRedo,
|
|
1701
|
+
beginBatch,
|
|
1702
|
+
endBatch,
|
|
1703
|
+
maxUndoDepth
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
function useTableLayout(params) {
|
|
1707
|
+
const {
|
|
1708
|
+
wrapperRef,
|
|
1709
|
+
visibleCols,
|
|
1710
|
+
flatColumns,
|
|
1711
|
+
hasCheckboxCol,
|
|
1712
|
+
initialColumnWidths,
|
|
1713
|
+
onColumnResized
|
|
1714
|
+
} = params;
|
|
1715
|
+
const containerWidth = ref(0);
|
|
1716
|
+
let resizeObserver;
|
|
1717
|
+
const measure = () => {
|
|
1718
|
+
const el = wrapperRef.value;
|
|
1719
|
+
if (!el) return;
|
|
1720
|
+
const rect = el.getBoundingClientRect();
|
|
1721
|
+
const cs = window.getComputedStyle(el);
|
|
1722
|
+
const borderX = (parseFloat(cs.borderLeftWidth || "0") || 0) + (parseFloat(cs.borderRightWidth || "0") || 0);
|
|
1723
|
+
containerWidth.value = Math.max(0, rect.width - borderX);
|
|
1724
|
+
};
|
|
1725
|
+
onMounted(() => {
|
|
1726
|
+
const el = wrapperRef.value;
|
|
1727
|
+
if (el) {
|
|
1728
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
1729
|
+
resizeObserver = new ResizeObserver(measure);
|
|
1730
|
+
resizeObserver.observe(el);
|
|
1731
|
+
}
|
|
1732
|
+
measure();
|
|
1733
|
+
}
|
|
1734
|
+
});
|
|
1735
|
+
onUnmounted(() => {
|
|
1736
|
+
resizeObserver?.disconnect();
|
|
1737
|
+
});
|
|
1738
|
+
const columnSizingOverrides = ref((() => {
|
|
1739
|
+
if (!initialColumnWidths) return {};
|
|
1740
|
+
const result = {};
|
|
1741
|
+
for (const [id, width] of Object.entries(initialColumnWidths)) {
|
|
1742
|
+
result[id] = { widthPx: width };
|
|
1743
|
+
}
|
|
1744
|
+
return result;
|
|
1745
|
+
})());
|
|
1746
|
+
const setColumnSizingOverrides = (value) => {
|
|
1747
|
+
columnSizingOverrides.value = value;
|
|
1748
|
+
};
|
|
1749
|
+
const minTableWidth = computed(() => {
|
|
1750
|
+
const checkboxW = hasCheckboxCol.value ? CHECKBOX_COLUMN_WIDTH : 0;
|
|
1751
|
+
return visibleCols.value.reduce(
|
|
1752
|
+
(sum, c) => sum + (c.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH) + CELL_PADDING,
|
|
1753
|
+
checkboxW
|
|
1754
|
+
);
|
|
1755
|
+
});
|
|
1756
|
+
watch(flatColumns, (cols) => {
|
|
1757
|
+
const colIds = new Set(cols.map((c) => c.columnId));
|
|
1758
|
+
const prev = columnSizingOverrides.value;
|
|
1759
|
+
const keys = Object.keys(prev);
|
|
1760
|
+
const kept = keys.filter((id) => colIds.has(id));
|
|
1761
|
+
if (kept.length < keys.length) {
|
|
1762
|
+
const next = {};
|
|
1763
|
+
for (const id of kept) next[id] = prev[id];
|
|
1764
|
+
columnSizingOverrides.value = next;
|
|
1765
|
+
}
|
|
1766
|
+
});
|
|
1767
|
+
const desiredTableWidth = computed(() => {
|
|
1768
|
+
const checkboxW = hasCheckboxCol.value ? CHECKBOX_COLUMN_WIDTH : 0;
|
|
1769
|
+
return visibleCols.value.reduce((sum, c) => {
|
|
1770
|
+
const override = columnSizingOverrides.value[c.columnId];
|
|
1771
|
+
const w = override ? override.widthPx : c.idealWidth ?? c.defaultWidth ?? c.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
|
|
1772
|
+
return sum + Math.max(c.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH, w) + CELL_PADDING;
|
|
1773
|
+
}, checkboxW);
|
|
1774
|
+
});
|
|
1775
|
+
return {
|
|
1776
|
+
containerWidth,
|
|
1777
|
+
minTableWidth,
|
|
1778
|
+
desiredTableWidth,
|
|
1779
|
+
columnSizingOverrides,
|
|
1780
|
+
setColumnSizingOverrides,
|
|
1781
|
+
onColumnResized
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
function useColumnPinning(params) {
|
|
1785
|
+
const { columns, pinnedColumns: controlledPinnedColumns, onColumnPinned } = params;
|
|
1786
|
+
const initialPinnedColumns = {};
|
|
1787
|
+
for (const col of columns.value) {
|
|
1788
|
+
if (col.pinned) {
|
|
1789
|
+
initialPinnedColumns[col.columnId] = col.pinned;
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
const internalPinnedColumns = ref(initialPinnedColumns);
|
|
1793
|
+
const pinnedColumns = computed(() => controlledPinnedColumns?.value ?? internalPinnedColumns.value);
|
|
1794
|
+
const pinColumn = (columnId, side) => {
|
|
1795
|
+
const next = { ...pinnedColumns.value, [columnId]: side };
|
|
1796
|
+
internalPinnedColumns.value = next;
|
|
1797
|
+
onColumnPinned?.(columnId, side);
|
|
1798
|
+
};
|
|
1799
|
+
const unpinColumn = (columnId) => {
|
|
1800
|
+
const { [columnId]: _removed, ...next } = pinnedColumns.value;
|
|
1801
|
+
internalPinnedColumns.value = next;
|
|
1802
|
+
onColumnPinned?.(columnId, null);
|
|
1803
|
+
};
|
|
1804
|
+
const isPinned = (columnId) => {
|
|
1805
|
+
return pinnedColumns.value[columnId];
|
|
1806
|
+
};
|
|
1807
|
+
const computeLeftOffsets = (visibleCols, columnWidths, defaultWidth, hasCheckboxColumn, checkboxColumnWidth) => {
|
|
1808
|
+
const offsets = {};
|
|
1809
|
+
let left = hasCheckboxColumn ? checkboxColumnWidth : 0;
|
|
1810
|
+
for (const col of visibleCols) {
|
|
1811
|
+
if (pinnedColumns.value[col.columnId] === "left") {
|
|
1812
|
+
offsets[col.columnId] = left;
|
|
1813
|
+
left += columnWidths[col.columnId] ?? defaultWidth;
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
return offsets;
|
|
1817
|
+
};
|
|
1818
|
+
const computeRightOffsets = (visibleCols, columnWidths, defaultWidth) => {
|
|
1819
|
+
const offsets = {};
|
|
1820
|
+
let right = 0;
|
|
1821
|
+
for (let i = visibleCols.length - 1; i >= 0; i--) {
|
|
1822
|
+
const col = visibleCols[i];
|
|
1823
|
+
if (pinnedColumns.value[col.columnId] === "right") {
|
|
1824
|
+
offsets[col.columnId] = right;
|
|
1825
|
+
right += columnWidths[col.columnId] ?? defaultWidth;
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
return offsets;
|
|
1829
|
+
};
|
|
1830
|
+
return {
|
|
1831
|
+
pinnedColumns,
|
|
1832
|
+
pinColumn,
|
|
1833
|
+
unpinColumn,
|
|
1834
|
+
isPinned,
|
|
1835
|
+
computeLeftOffsets,
|
|
1836
|
+
computeRightOffsets
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
function useColumnHeaderMenuState(params) {
|
|
1840
|
+
const {
|
|
1841
|
+
columns,
|
|
1842
|
+
pinnedColumns,
|
|
1843
|
+
onPinColumn,
|
|
1844
|
+
onUnpinColumn,
|
|
1845
|
+
onSort,
|
|
1846
|
+
onColumnResized,
|
|
1847
|
+
onAutosizeColumn,
|
|
1848
|
+
sortBy,
|
|
1849
|
+
sortDirection
|
|
1850
|
+
} = params;
|
|
1851
|
+
const isOpen = ref(false);
|
|
1852
|
+
const openForColumn = ref(null);
|
|
1853
|
+
const anchorElement = ref(null);
|
|
1854
|
+
const open = (columnId, anchorEl) => {
|
|
1855
|
+
openForColumn.value = columnId;
|
|
1856
|
+
anchorElement.value = anchorEl;
|
|
1857
|
+
isOpen.value = true;
|
|
1858
|
+
};
|
|
1859
|
+
const close = () => {
|
|
1860
|
+
isOpen.value = false;
|
|
1861
|
+
openForColumn.value = null;
|
|
1862
|
+
anchorElement.value = null;
|
|
1863
|
+
};
|
|
1864
|
+
const currentColumn = computed(
|
|
1865
|
+
() => openForColumn.value ? columns.value.find((c) => c.columnId === openForColumn.value) : void 0
|
|
1866
|
+
);
|
|
1867
|
+
const currentPinState = computed(
|
|
1868
|
+
() => openForColumn.value ? pinnedColumns.value[openForColumn.value] : void 0
|
|
1869
|
+
);
|
|
1870
|
+
const canPinLeft = computed(() => currentPinState.value !== "left");
|
|
1871
|
+
const canPinRight = computed(() => currentPinState.value !== "right");
|
|
1872
|
+
const canUnpin = computed(() => !!currentPinState.value);
|
|
1873
|
+
const currentSort = computed(() => {
|
|
1874
|
+
if (!openForColumn.value || !sortBy?.value || sortBy.value !== openForColumn.value) {
|
|
1875
|
+
return null;
|
|
1876
|
+
}
|
|
1877
|
+
return sortDirection?.value ?? null;
|
|
1878
|
+
});
|
|
1879
|
+
const isSortable = computed(() => {
|
|
1880
|
+
const col = currentColumn.value;
|
|
1881
|
+
return col?.sortable !== false;
|
|
1882
|
+
});
|
|
1883
|
+
const isResizable = ref(true);
|
|
1884
|
+
const handlePinLeft = () => {
|
|
1885
|
+
if (openForColumn.value && canPinLeft.value) {
|
|
1886
|
+
onPinColumn(openForColumn.value, "left");
|
|
1887
|
+
close();
|
|
1888
|
+
}
|
|
1889
|
+
};
|
|
1890
|
+
const handlePinRight = () => {
|
|
1891
|
+
if (openForColumn.value && canPinRight.value) {
|
|
1892
|
+
onPinColumn(openForColumn.value, "right");
|
|
1893
|
+
close();
|
|
1894
|
+
}
|
|
1895
|
+
};
|
|
1896
|
+
const handleUnpin = () => {
|
|
1897
|
+
if (openForColumn.value && canUnpin.value) {
|
|
1898
|
+
onUnpinColumn(openForColumn.value);
|
|
1899
|
+
close();
|
|
1900
|
+
}
|
|
1901
|
+
};
|
|
1902
|
+
const handleSortAsc = () => {
|
|
1903
|
+
if (openForColumn.value && onSort) {
|
|
1904
|
+
onSort(openForColumn.value, "asc");
|
|
1905
|
+
close();
|
|
1906
|
+
}
|
|
1907
|
+
};
|
|
1908
|
+
const handleSortDesc = () => {
|
|
1909
|
+
if (openForColumn.value && onSort) {
|
|
1910
|
+
onSort(openForColumn.value, "desc");
|
|
1911
|
+
close();
|
|
1912
|
+
}
|
|
1913
|
+
};
|
|
1914
|
+
const handleClearSort = () => {
|
|
1915
|
+
if (openForColumn.value && onSort) {
|
|
1916
|
+
onSort(openForColumn.value, null);
|
|
1917
|
+
close();
|
|
1918
|
+
}
|
|
1919
|
+
};
|
|
1920
|
+
const handleAutosizeThis = () => {
|
|
1921
|
+
const resizer = onAutosizeColumn ?? onColumnResized;
|
|
1922
|
+
if (!openForColumn.value || !resizer || !isResizable.value) return;
|
|
1923
|
+
const col = currentColumn.value;
|
|
1924
|
+
resizer(openForColumn.value, measureColumnContentWidth(openForColumn.value, col?.minWidth));
|
|
1925
|
+
close();
|
|
1926
|
+
};
|
|
1927
|
+
const handleAutosizeAll = () => {
|
|
1928
|
+
const resizer = onAutosizeColumn ?? onColumnResized;
|
|
1929
|
+
if (!resizer) return;
|
|
1930
|
+
columns.value.forEach((col) => {
|
|
1931
|
+
resizer(col.columnId, measureColumnContentWidth(col.columnId, col.minWidth));
|
|
1932
|
+
});
|
|
1933
|
+
close();
|
|
1934
|
+
};
|
|
1935
|
+
return {
|
|
1936
|
+
isOpen,
|
|
1937
|
+
openForColumn,
|
|
1938
|
+
anchorElement,
|
|
1939
|
+
open,
|
|
1940
|
+
close,
|
|
1941
|
+
handlePinLeft,
|
|
1942
|
+
handlePinRight,
|
|
1943
|
+
handleUnpin,
|
|
1944
|
+
handleSortAsc,
|
|
1945
|
+
handleSortDesc,
|
|
1946
|
+
handleClearSort,
|
|
1947
|
+
handleAutosizeThis,
|
|
1948
|
+
handleAutosizeAll,
|
|
1949
|
+
canPinLeft,
|
|
1950
|
+
canPinRight,
|
|
1951
|
+
canUnpin,
|
|
1952
|
+
currentSort,
|
|
1953
|
+
isSortable,
|
|
1954
|
+
isResizable
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
// src/composables/useDataGridState.ts
|
|
1959
|
+
var NOOP = () => {
|
|
1960
|
+
};
|
|
1961
|
+
var NOOP_ASYNC = async () => {
|
|
1962
|
+
};
|
|
1963
|
+
var NOOP_MOUSE = (_e, _r, _c) => {
|
|
1964
|
+
};
|
|
1965
|
+
var NOOP_KEY = (_e) => {
|
|
1966
|
+
};
|
|
1967
|
+
var NOOP_CTX = (_e) => {
|
|
1968
|
+
};
|
|
1969
|
+
function useDataGridState(params) {
|
|
1970
|
+
const { props, wrapperRef } = params;
|
|
1971
|
+
const items = computed(() => props.value.items);
|
|
1972
|
+
const getRowId = props.value.getRowId;
|
|
1973
|
+
const rowSelectionProp = computed(() => props.value.rowSelection ?? "none");
|
|
1974
|
+
const controlledSelectedRows = computed(() => props.value.selectedRows);
|
|
1975
|
+
const editableProp = computed(() => props.value.editable);
|
|
1976
|
+
const cellSelection = computed(() => props.value.cellSelection !== false);
|
|
1977
|
+
const pinnedColumnsProp = computed(() => props.value.pinnedColumns);
|
|
1978
|
+
const undoRedo = useUndoRedo({ onCellValueChanged: props.value.onCellValueChanged });
|
|
1979
|
+
const onCellValueChanged = computed(() => undoRedo.onCellValueChanged);
|
|
1980
|
+
const flatColumnsRaw = computed(() => flattenColumns(props.value.columns));
|
|
1981
|
+
const flatColumns = computed(() => {
|
|
1982
|
+
const pinned = pinnedColumnsProp.value;
|
|
1983
|
+
if (!pinned || Object.keys(pinned).length === 0) return flatColumnsRaw.value;
|
|
1984
|
+
return flatColumnsRaw.value.map((col) => {
|
|
1985
|
+
const override = pinned[col.columnId];
|
|
1986
|
+
if (override && col.pinned !== override) return { ...col, pinned: override };
|
|
1987
|
+
return col;
|
|
1988
|
+
});
|
|
1989
|
+
});
|
|
1990
|
+
const visibleCols = computed(() => {
|
|
1991
|
+
const vis = props.value.visibleColumns;
|
|
1992
|
+
const order = props.value.columnOrder;
|
|
1993
|
+
const filtered = vis ? flatColumns.value.filter((c) => vis.has(c.columnId)) : flatColumns.value;
|
|
1994
|
+
if (!order?.length) return filtered;
|
|
1995
|
+
const orderMap = /* @__PURE__ */ new Map();
|
|
1996
|
+
for (let i = 0; i < order.length; i++) {
|
|
1997
|
+
orderMap.set(order[i], i);
|
|
1998
|
+
}
|
|
1999
|
+
return [...filtered].sort((a, b) => {
|
|
2000
|
+
const ia = orderMap.get(a.columnId) ?? -1;
|
|
2001
|
+
const ib = orderMap.get(b.columnId) ?? -1;
|
|
2002
|
+
if (ia === -1 && ib === -1) return 0;
|
|
2003
|
+
if (ia === -1) return 1;
|
|
2004
|
+
if (ib === -1) return -1;
|
|
2005
|
+
return ia - ib;
|
|
2006
|
+
});
|
|
2007
|
+
});
|
|
2008
|
+
const visibleColumnCount = computed(() => visibleCols.value.length);
|
|
2009
|
+
const hasCheckboxCol = computed(() => rowSelectionProp.value === "multiple");
|
|
2010
|
+
const hasRowNumbersCol = computed(() => !!props.value.showRowNumbers);
|
|
2011
|
+
const specialColsCount = computed(() => (hasCheckboxCol.value ? 1 : 0) + (hasRowNumbersCol.value ? 1 : 0));
|
|
2012
|
+
const totalColCount = computed(() => visibleColumnCount.value + specialColsCount.value);
|
|
2013
|
+
const colOffset = specialColsCount;
|
|
2014
|
+
const rowIndexByRowId = shallowRef(/* @__PURE__ */ new Map());
|
|
2015
|
+
watch(items, (newItems) => {
|
|
2016
|
+
const m = rowIndexByRowId.value;
|
|
2017
|
+
m.clear();
|
|
2018
|
+
newItems.forEach((item, idx) => m.set(getRowId(item), idx));
|
|
2019
|
+
triggerRef(rowIndexByRowId);
|
|
2020
|
+
}, { immediate: true });
|
|
2021
|
+
const rowSelectionResult = useRowSelection({
|
|
2022
|
+
items,
|
|
2023
|
+
getRowId,
|
|
2024
|
+
rowSelection: rowSelectionProp,
|
|
2025
|
+
controlledSelectedRows,
|
|
2026
|
+
onSelectionChange: props.value.onSelectionChange
|
|
2027
|
+
});
|
|
2028
|
+
const { editingCell, setEditingCell, pendingEditorValue, setPendingEditorValue } = useCellEditing();
|
|
2029
|
+
const { activeCell, setActiveCell } = useActiveCell(wrapperRef, editingCell);
|
|
2030
|
+
const rowCount = computed(() => items.value.length);
|
|
2031
|
+
const visColCount = computed(() => visibleCols.value.length);
|
|
2032
|
+
const {
|
|
2033
|
+
selectionRange,
|
|
2034
|
+
setSelectionRange,
|
|
2035
|
+
handleCellMouseDown: handleCellMouseDownBase,
|
|
2036
|
+
handleSelectAllCells,
|
|
2037
|
+
isDragging
|
|
2038
|
+
} = useCellSelection({
|
|
2039
|
+
colOffset,
|
|
2040
|
+
rowCount,
|
|
2041
|
+
visibleColCount: visColCount,
|
|
2042
|
+
setActiveCell,
|
|
2043
|
+
wrapperRef
|
|
2044
|
+
});
|
|
2045
|
+
const { contextMenuPosition, setContextMenuPosition, handleCellContextMenu, closeContextMenu } = useContextMenu();
|
|
2046
|
+
const { handleCopy, handleCut, handlePaste, cutRange, copyRange, clearClipboardRanges } = useClipboard({
|
|
2047
|
+
items,
|
|
2048
|
+
visibleCols,
|
|
2049
|
+
colOffset,
|
|
2050
|
+
selectionRange,
|
|
2051
|
+
activeCell,
|
|
2052
|
+
editable: editableProp,
|
|
2053
|
+
onCellValueChanged,
|
|
2054
|
+
beginBatch: undoRedo.beginBatch,
|
|
2055
|
+
endBatch: undoRedo.endBatch
|
|
2056
|
+
});
|
|
2057
|
+
const handleCellMouseDown = (e, rowIndex, globalColIndex) => {
|
|
2058
|
+
if (e.button !== 0) return;
|
|
2059
|
+
wrapperRef.value?.focus({ preventScroll: true });
|
|
2060
|
+
clearClipboardRanges();
|
|
2061
|
+
handleCellMouseDownBase(e, rowIndex, globalColIndex);
|
|
2062
|
+
};
|
|
2063
|
+
const { handleGridKeyDown } = useKeyboardNavigation({
|
|
2064
|
+
data: { items, visibleCols, colOffset, hasCheckboxCol, visibleColumnCount, getRowId },
|
|
2065
|
+
state: { activeCell, selectionRange, editingCell, selectedRowIds: rowSelectionResult.selectedRowIds },
|
|
2066
|
+
handlers: {
|
|
2067
|
+
setActiveCell,
|
|
2068
|
+
setSelectionRange,
|
|
2069
|
+
setEditingCell,
|
|
2070
|
+
handleRowCheckboxChange: rowSelectionResult.handleRowCheckboxChange,
|
|
2071
|
+
handleCopy,
|
|
2072
|
+
handleCut,
|
|
2073
|
+
handlePaste,
|
|
2074
|
+
setContextMenu: setContextMenuPosition,
|
|
2075
|
+
onUndo: undoRedo.undo,
|
|
2076
|
+
onRedo: undoRedo.redo,
|
|
2077
|
+
clearClipboardRanges
|
|
2078
|
+
},
|
|
2079
|
+
features: {
|
|
2080
|
+
editable: editableProp,
|
|
2081
|
+
onCellValueChanged,
|
|
2082
|
+
rowSelection: rowSelectionProp,
|
|
2083
|
+
wrapperRef
|
|
2084
|
+
}
|
|
2085
|
+
});
|
|
2086
|
+
const { handleFillHandleMouseDown } = useFillHandle({
|
|
2087
|
+
items,
|
|
2088
|
+
visibleCols,
|
|
2089
|
+
editable: editableProp,
|
|
2090
|
+
onCellValueChanged,
|
|
2091
|
+
selectionRange,
|
|
2092
|
+
setSelectionRange,
|
|
2093
|
+
setActiveCell,
|
|
2094
|
+
colOffset,
|
|
2095
|
+
wrapperRef,
|
|
2096
|
+
beginBatch: undoRedo.beginBatch,
|
|
2097
|
+
endBatch: undoRedo.endBatch
|
|
2098
|
+
});
|
|
2099
|
+
const {
|
|
2100
|
+
containerWidth,
|
|
2101
|
+
minTableWidth,
|
|
2102
|
+
desiredTableWidth,
|
|
2103
|
+
columnSizingOverrides,
|
|
2104
|
+
setColumnSizingOverrides
|
|
2105
|
+
} = useTableLayout({
|
|
2106
|
+
wrapperRef,
|
|
2107
|
+
visibleCols,
|
|
2108
|
+
flatColumns,
|
|
2109
|
+
hasCheckboxCol,
|
|
2110
|
+
initialColumnWidths: props.value.initialColumnWidths,
|
|
2111
|
+
onColumnResized: (columnId, width) => props.value.onColumnResized?.(columnId, width)
|
|
2112
|
+
});
|
|
2113
|
+
const pinningResult = useColumnPinning({
|
|
2114
|
+
columns: flatColumns,
|
|
2115
|
+
pinnedColumns: pinnedColumnsProp,
|
|
2116
|
+
onColumnPinned: props.value.onColumnPinned
|
|
2117
|
+
});
|
|
2118
|
+
const handleAutosizeColumn = (columnId, width) => {
|
|
2119
|
+
setColumnSizingOverrides({ ...columnSizingOverrides.value, [columnId]: { widthPx: width } });
|
|
2120
|
+
props.value.onColumnResized?.(columnId, width);
|
|
2121
|
+
};
|
|
2122
|
+
const headerMenuResult = useColumnHeaderMenuState({
|
|
2123
|
+
columns: flatColumns,
|
|
2124
|
+
pinnedColumns: pinningResult.pinnedColumns,
|
|
2125
|
+
onPinColumn: pinningResult.pinColumn,
|
|
2126
|
+
onUnpinColumn: pinningResult.unpinColumn,
|
|
2127
|
+
onSort: props.value.onColumnSort,
|
|
2128
|
+
onColumnResized: props.value.onColumnResized,
|
|
2129
|
+
onAutosizeColumn: handleAutosizeColumn,
|
|
2130
|
+
sortBy: computed(() => props.value.sortBy),
|
|
2131
|
+
sortDirection: computed(() => props.value.sortDirection)
|
|
2132
|
+
});
|
|
2133
|
+
const measuredColumnWidths = ref({});
|
|
2134
|
+
watch(
|
|
2135
|
+
[visibleCols, containerWidth, columnSizingOverrides],
|
|
2136
|
+
() => {
|
|
2137
|
+
void nextTick(() => {
|
|
2138
|
+
const wrapper = wrapperRef.value;
|
|
2139
|
+
if (!wrapper) return;
|
|
2140
|
+
const headerCells = wrapper.querySelectorAll("th[data-column-id]");
|
|
2141
|
+
if (headerCells.length === 0) return;
|
|
2142
|
+
const measured = {};
|
|
2143
|
+
headerCells.forEach((cell) => {
|
|
2144
|
+
const colId = cell.getAttribute("data-column-id");
|
|
2145
|
+
if (colId) measured[colId] = cell.offsetWidth;
|
|
2146
|
+
});
|
|
2147
|
+
const prev = measuredColumnWidths.value;
|
|
2148
|
+
const keys = Object.keys(measured);
|
|
2149
|
+
let changed = keys.length !== Object.keys(prev).length;
|
|
2150
|
+
if (!changed) {
|
|
2151
|
+
for (const key of keys) {
|
|
2152
|
+
if (prev[key] !== measured[key]) {
|
|
2153
|
+
changed = true;
|
|
2154
|
+
break;
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
if (changed) measuredColumnWidths.value = measured;
|
|
2159
|
+
});
|
|
2160
|
+
},
|
|
2161
|
+
{ flush: "post" }
|
|
2162
|
+
);
|
|
2163
|
+
const columnWidthMap = computed(() => {
|
|
2164
|
+
const map = {};
|
|
2165
|
+
for (const col of visibleCols.value) {
|
|
2166
|
+
const override = columnSizingOverrides.value[col.columnId];
|
|
2167
|
+
map[col.columnId] = override ? override.widthPx : col.idealWidth ?? col.defaultWidth ?? col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
|
|
2168
|
+
}
|
|
2169
|
+
return map;
|
|
2170
|
+
});
|
|
2171
|
+
const leftOffsets = computed(
|
|
2172
|
+
() => pinningResult.computeLeftOffsets(visibleCols.value, columnWidthMap.value, DEFAULT_MIN_COLUMN_WIDTH, hasCheckboxCol.value, CHECKBOX_COLUMN_WIDTH)
|
|
2173
|
+
);
|
|
2174
|
+
const rightOffsets = computed(
|
|
2175
|
+
() => pinningResult.computeRightOffsets(visibleCols.value, columnWidthMap.value, DEFAULT_MIN_COLUMN_WIDTH)
|
|
2176
|
+
);
|
|
2177
|
+
const aggregation = computed(
|
|
2178
|
+
() => computeAggregations(items.value, visibleCols.value, cellSelection.value ? selectionRange.value : null)
|
|
2179
|
+
);
|
|
2180
|
+
const statusBarConfig = computed(() => {
|
|
2181
|
+
const base = getDataGridStatusBarConfig(
|
|
2182
|
+
props.value.statusBar,
|
|
2183
|
+
items.value.length,
|
|
2184
|
+
rowSelectionResult.selectedRowIds.value.size
|
|
2185
|
+
);
|
|
2186
|
+
if (!base) return null;
|
|
2187
|
+
return { ...base, aggregation: aggregation.value ?? void 0 };
|
|
2188
|
+
});
|
|
2189
|
+
const showEmptyInGrid = computed(() => items.value.length === 0 && !!props.value.emptyState && !props.value.isLoading);
|
|
2190
|
+
const hasCellSelection = computed(() => selectionRange.value != null || activeCell.value != null);
|
|
2191
|
+
const headerFilterInput = computed(() => ({
|
|
2192
|
+
sortBy: props.value.sortBy,
|
|
2193
|
+
sortDirection: props.value.sortDirection,
|
|
2194
|
+
onColumnSort: props.value.onColumnSort,
|
|
2195
|
+
filters: props.value.filters,
|
|
2196
|
+
onFilterChange: props.value.onFilterChange,
|
|
2197
|
+
filterOptions: props.value.filterOptions,
|
|
2198
|
+
loadingFilterOptions: props.value.loadingFilterOptions,
|
|
2199
|
+
peopleSearch: props.value.peopleSearch
|
|
2200
|
+
}));
|
|
2201
|
+
const cellDescriptorInput = computed(() => ({
|
|
2202
|
+
editingCell: editingCell.value,
|
|
2203
|
+
activeCell: cellSelection.value ? activeCell.value : null,
|
|
2204
|
+
selectionRange: cellSelection.value ? selectionRange.value : null,
|
|
2205
|
+
cutRange: cellSelection.value ? cutRange.value : null,
|
|
2206
|
+
copyRange: cellSelection.value ? copyRange.value : null,
|
|
2207
|
+
colOffset: colOffset.value,
|
|
2208
|
+
itemsLength: items.value.length,
|
|
2209
|
+
getRowId,
|
|
2210
|
+
editable: editableProp.value,
|
|
2211
|
+
onCellValueChanged: onCellValueChanged.value,
|
|
2212
|
+
isDragging: cellSelection.value ? isDragging.value : false
|
|
2213
|
+
}));
|
|
2214
|
+
const popoverAnchorEl = ref(null);
|
|
2215
|
+
const setPopoverAnchorEl = (el) => {
|
|
2216
|
+
popoverAnchorEl.value = el;
|
|
2217
|
+
};
|
|
2218
|
+
const commitCellEdit = (item, columnId, oldValue, newValue, rowIndex, globalColIndex) => {
|
|
2219
|
+
const col = visibleCols.value.find((c) => c.columnId === columnId);
|
|
2220
|
+
if (col) {
|
|
2221
|
+
const result = parseValue(newValue, oldValue, item, col);
|
|
2222
|
+
if (!result.valid) {
|
|
2223
|
+
setEditingCell(null);
|
|
2224
|
+
setPopoverAnchorEl(null);
|
|
2225
|
+
setPendingEditorValue(void 0);
|
|
2226
|
+
return;
|
|
2227
|
+
}
|
|
2228
|
+
newValue = result.value;
|
|
2229
|
+
}
|
|
2230
|
+
onCellValueChanged.value?.({
|
|
2231
|
+
item,
|
|
2232
|
+
columnId,
|
|
2233
|
+
oldValue,
|
|
2234
|
+
newValue,
|
|
2235
|
+
rowIndex
|
|
2236
|
+
});
|
|
2237
|
+
setEditingCell(null);
|
|
2238
|
+
setPopoverAnchorEl(null);
|
|
2239
|
+
setPendingEditorValue(void 0);
|
|
2240
|
+
if (rowIndex < items.value.length - 1) {
|
|
2241
|
+
setActiveCell({ rowIndex: rowIndex + 1, columnIndex: globalColIndex });
|
|
2242
|
+
}
|
|
2243
|
+
};
|
|
2244
|
+
const cancelPopoverEdit = () => {
|
|
2245
|
+
setEditingCell(null);
|
|
2246
|
+
setPopoverAnchorEl(null);
|
|
2247
|
+
setPendingEditorValue(void 0);
|
|
2248
|
+
};
|
|
2249
|
+
const layoutState = computed(() => ({
|
|
2250
|
+
flatColumns: flatColumns.value,
|
|
2251
|
+
visibleCols: visibleCols.value,
|
|
2252
|
+
visibleColumnCount: visibleColumnCount.value,
|
|
2253
|
+
totalColCount: totalColCount.value,
|
|
2254
|
+
colOffset: colOffset.value,
|
|
2255
|
+
hasCheckboxCol: hasCheckboxCol.value,
|
|
2256
|
+
hasRowNumbersCol: hasRowNumbersCol.value,
|
|
2257
|
+
rowIndexByRowId: rowIndexByRowId.value,
|
|
2258
|
+
containerWidth: containerWidth.value,
|
|
2259
|
+
minTableWidth: minTableWidth.value,
|
|
2260
|
+
desiredTableWidth: desiredTableWidth.value,
|
|
2261
|
+
columnSizingOverrides: columnSizingOverrides.value,
|
|
2262
|
+
setColumnSizingOverrides,
|
|
2263
|
+
onColumnResized: props.value.onColumnResized,
|
|
2264
|
+
measuredColumnWidths: measuredColumnWidths.value,
|
|
2265
|
+
stickyHeader: props.value.stickyHeader ?? true
|
|
2266
|
+
}));
|
|
2267
|
+
const rowSelectionState = computed(() => ({
|
|
2268
|
+
selectedRowIds: rowSelectionResult.selectedRowIds.value,
|
|
2269
|
+
updateSelection: rowSelectionResult.updateSelection,
|
|
2270
|
+
handleRowCheckboxChange: rowSelectionResult.handleRowCheckboxChange,
|
|
2271
|
+
handleSelectAll: rowSelectionResult.handleSelectAll,
|
|
2272
|
+
allSelected: rowSelectionResult.allSelected.value,
|
|
2273
|
+
someSelected: rowSelectionResult.someSelected.value
|
|
2274
|
+
}));
|
|
2275
|
+
const editingState = computed(() => ({
|
|
2276
|
+
editingCell: editingCell.value,
|
|
2277
|
+
setEditingCell,
|
|
2278
|
+
pendingEditorValue: pendingEditorValue.value,
|
|
2279
|
+
setPendingEditorValue,
|
|
2280
|
+
commitCellEdit,
|
|
2281
|
+
cancelPopoverEdit,
|
|
2282
|
+
popoverAnchorEl: popoverAnchorEl.value,
|
|
2283
|
+
setPopoverAnchorEl
|
|
2284
|
+
}));
|
|
2285
|
+
const interactionState = computed(() => ({
|
|
2286
|
+
activeCell: cellSelection.value ? activeCell.value : null,
|
|
2287
|
+
setActiveCell: cellSelection.value ? setActiveCell : NOOP,
|
|
2288
|
+
selectionRange: cellSelection.value ? selectionRange.value : null,
|
|
2289
|
+
setSelectionRange: cellSelection.value ? setSelectionRange : NOOP,
|
|
2290
|
+
handleCellMouseDown: cellSelection.value ? handleCellMouseDown : NOOP_MOUSE,
|
|
2291
|
+
handleSelectAllCells: cellSelection.value ? handleSelectAllCells : NOOP,
|
|
2292
|
+
hasCellSelection: cellSelection.value ? hasCellSelection.value : false,
|
|
2293
|
+
handleGridKeyDown: cellSelection.value ? handleGridKeyDown : NOOP_KEY,
|
|
2294
|
+
handleFillHandleMouseDown: cellSelection.value ? handleFillHandleMouseDown : NOOP,
|
|
2295
|
+
handleCopy: cellSelection.value ? handleCopy : NOOP,
|
|
2296
|
+
handleCut: cellSelection.value ? handleCut : NOOP,
|
|
2297
|
+
handlePaste: cellSelection.value ? handlePaste : NOOP_ASYNC,
|
|
2298
|
+
cutRange: cellSelection.value ? cutRange.value : null,
|
|
2299
|
+
copyRange: cellSelection.value ? copyRange.value : null,
|
|
2300
|
+
clearClipboardRanges: cellSelection.value ? clearClipboardRanges : NOOP,
|
|
2301
|
+
canUndo: undoRedo.canUndo.value,
|
|
2302
|
+
canRedo: undoRedo.canRedo.value,
|
|
2303
|
+
onUndo: undoRedo.undo,
|
|
2304
|
+
onRedo: undoRedo.redo,
|
|
2305
|
+
isDragging: cellSelection.value ? isDragging.value : false
|
|
2306
|
+
}));
|
|
2307
|
+
const contextMenuState = computed(() => ({
|
|
2308
|
+
menuPosition: cellSelection.value ? contextMenuPosition.value : null,
|
|
2309
|
+
setMenuPosition: cellSelection.value ? setContextMenuPosition : NOOP,
|
|
2310
|
+
handleCellContextMenu: cellSelection.value ? handleCellContextMenu : NOOP_CTX,
|
|
2311
|
+
closeContextMenu: cellSelection.value ? closeContextMenu : NOOP
|
|
2312
|
+
}));
|
|
2313
|
+
const viewModelsState = computed(() => ({
|
|
2314
|
+
headerFilterInput: headerFilterInput.value,
|
|
2315
|
+
cellDescriptorInput: cellDescriptorInput.value,
|
|
2316
|
+
statusBarConfig: statusBarConfig.value,
|
|
2317
|
+
showEmptyInGrid: showEmptyInGrid.value,
|
|
2318
|
+
onCellError: props.value.onCellError
|
|
2319
|
+
}));
|
|
2320
|
+
const pinningState = computed(() => ({
|
|
2321
|
+
pinnedColumns: pinningResult.pinnedColumns.value,
|
|
2322
|
+
pinColumn: pinningResult.pinColumn,
|
|
2323
|
+
unpinColumn: pinningResult.unpinColumn,
|
|
2324
|
+
isPinned: pinningResult.isPinned,
|
|
2325
|
+
leftOffsets: leftOffsets.value,
|
|
2326
|
+
rightOffsets: rightOffsets.value,
|
|
2327
|
+
headerMenu: {
|
|
2328
|
+
isOpen: headerMenuResult.isOpen.value,
|
|
2329
|
+
openForColumn: headerMenuResult.openForColumn.value,
|
|
2330
|
+
anchorElement: headerMenuResult.anchorElement.value,
|
|
2331
|
+
open: headerMenuResult.open,
|
|
2332
|
+
close: headerMenuResult.close,
|
|
2333
|
+
handlePinLeft: headerMenuResult.handlePinLeft,
|
|
2334
|
+
handlePinRight: headerMenuResult.handlePinRight,
|
|
2335
|
+
handleUnpin: headerMenuResult.handleUnpin,
|
|
2336
|
+
handleSortAsc: headerMenuResult.handleSortAsc,
|
|
2337
|
+
handleSortDesc: headerMenuResult.handleSortDesc,
|
|
2338
|
+
handleClearSort: headerMenuResult.handleClearSort,
|
|
2339
|
+
handleAutosizeThis: headerMenuResult.handleAutosizeThis,
|
|
2340
|
+
handleAutosizeAll: headerMenuResult.handleAutosizeAll,
|
|
2341
|
+
canPinLeft: headerMenuResult.canPinLeft.value,
|
|
2342
|
+
canPinRight: headerMenuResult.canPinRight.value,
|
|
2343
|
+
canUnpin: headerMenuResult.canUnpin.value,
|
|
2344
|
+
currentSort: headerMenuResult.currentSort.value,
|
|
2345
|
+
isSortable: headerMenuResult.isSortable.value,
|
|
2346
|
+
isResizable: headerMenuResult.isResizable.value
|
|
2347
|
+
}
|
|
2348
|
+
}));
|
|
2349
|
+
return {
|
|
2350
|
+
layout: layoutState,
|
|
2351
|
+
rowSelection: rowSelectionState,
|
|
2352
|
+
editing: editingState,
|
|
2353
|
+
interaction: interactionState,
|
|
2354
|
+
contextMenu: contextMenuState,
|
|
2355
|
+
viewModels: viewModelsState,
|
|
2356
|
+
pinning: pinningState
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
function useColumnResize(params) {
|
|
2360
|
+
const {
|
|
2361
|
+
columnSizingOverrides,
|
|
2362
|
+
setColumnSizingOverrides,
|
|
2363
|
+
minWidth = 80,
|
|
2364
|
+
defaultWidth = 120,
|
|
2365
|
+
onColumnResized
|
|
2366
|
+
} = params;
|
|
2367
|
+
let rafId = 0;
|
|
2368
|
+
let cleanupFn = null;
|
|
2369
|
+
onUnmounted(() => {
|
|
2370
|
+
cleanupFn?.();
|
|
2371
|
+
cleanupFn = null;
|
|
2372
|
+
});
|
|
2373
|
+
const handleResizeStart = (e, col) => {
|
|
2374
|
+
e.preventDefault();
|
|
2375
|
+
e.stopPropagation();
|
|
2376
|
+
const startX = e.clientX;
|
|
2377
|
+
const columnId = col.columnId;
|
|
2378
|
+
const thEl = e.currentTarget.parentElement;
|
|
2379
|
+
const startWidth = thEl ? thEl.getBoundingClientRect().width : columnSizingOverrides.value[columnId]?.widthPx ?? col.idealWidth ?? col.defaultWidth ?? defaultWidth;
|
|
2380
|
+
let latestWidth = startWidth;
|
|
2381
|
+
const prevCursor = document.body.style.cursor;
|
|
2382
|
+
const prevUserSelect = document.body.style.userSelect;
|
|
2383
|
+
document.body.style.cursor = "col-resize";
|
|
2384
|
+
document.body.style.userSelect = "none";
|
|
2385
|
+
const flushWidth = () => {
|
|
2386
|
+
setColumnSizingOverrides({
|
|
2387
|
+
...columnSizingOverrides.value,
|
|
2388
|
+
[columnId]: { widthPx: latestWidth }
|
|
2389
|
+
});
|
|
2390
|
+
};
|
|
2391
|
+
const onMove = (moveEvent) => {
|
|
2392
|
+
const deltaX = moveEvent.clientX - startX;
|
|
2393
|
+
latestWidth = Math.max(minWidth, startWidth + deltaX);
|
|
2394
|
+
if (!rafId) {
|
|
2395
|
+
rafId = requestAnimationFrame(() => {
|
|
2396
|
+
rafId = 0;
|
|
2397
|
+
flushWidth();
|
|
2398
|
+
});
|
|
2399
|
+
}
|
|
2400
|
+
};
|
|
2401
|
+
const cleanup = () => {
|
|
2402
|
+
document.removeEventListener("mousemove", onMove);
|
|
2403
|
+
document.removeEventListener("mouseup", onUp);
|
|
2404
|
+
cleanupFn = null;
|
|
2405
|
+
document.body.style.cursor = prevCursor;
|
|
2406
|
+
document.body.style.userSelect = prevUserSelect;
|
|
2407
|
+
if (rafId) {
|
|
2408
|
+
cancelAnimationFrame(rafId);
|
|
2409
|
+
rafId = 0;
|
|
2410
|
+
}
|
|
2411
|
+
};
|
|
2412
|
+
const onUp = () => {
|
|
2413
|
+
cleanup();
|
|
2414
|
+
flushWidth();
|
|
2415
|
+
onColumnResized?.(columnId, latestWidth);
|
|
2416
|
+
};
|
|
2417
|
+
document.addEventListener("mousemove", onMove);
|
|
2418
|
+
document.addEventListener("mouseup", onUp);
|
|
2419
|
+
cleanupFn = cleanup;
|
|
2420
|
+
};
|
|
2421
|
+
const getColumnWidth = (col) => {
|
|
2422
|
+
return columnSizingOverrides.value[col.columnId]?.widthPx ?? col.idealWidth ?? col.defaultWidth ?? defaultWidth;
|
|
2423
|
+
};
|
|
2424
|
+
return { handleResizeStart, getColumnWidth };
|
|
2425
|
+
}
|
|
2426
|
+
function useDebounce(value, delayMs) {
|
|
2427
|
+
const debouncedValue = ref(value.value);
|
|
2428
|
+
let timeoutId;
|
|
2429
|
+
watch(value, (newVal) => {
|
|
2430
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
2431
|
+
timeoutId = setTimeout(() => {
|
|
2432
|
+
debouncedValue.value = newVal;
|
|
2433
|
+
}, delayMs);
|
|
2434
|
+
});
|
|
2435
|
+
onUnmounted(() => {
|
|
2436
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
2437
|
+
});
|
|
2438
|
+
return debouncedValue;
|
|
2439
|
+
}
|
|
2440
|
+
function useDebouncedCallback(fn, delayMs) {
|
|
2441
|
+
let timeoutId;
|
|
2442
|
+
let latestFn = fn;
|
|
2443
|
+
let latestArgs;
|
|
2444
|
+
const debounced = ((...args) => {
|
|
2445
|
+
latestFn = fn;
|
|
2446
|
+
latestArgs = args;
|
|
2447
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
2448
|
+
timeoutId = setTimeout(() => {
|
|
2449
|
+
latestFn(...args);
|
|
2450
|
+
latestArgs = void 0;
|
|
2451
|
+
timeoutId = void 0;
|
|
2452
|
+
}, delayMs);
|
|
2453
|
+
});
|
|
2454
|
+
debounced.cancel = () => {
|
|
2455
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
2456
|
+
timeoutId = void 0;
|
|
2457
|
+
latestArgs = void 0;
|
|
2458
|
+
};
|
|
2459
|
+
debounced.flush = () => {
|
|
2460
|
+
if (timeoutId !== void 0 && latestArgs !== void 0) {
|
|
2461
|
+
clearTimeout(timeoutId);
|
|
2462
|
+
timeoutId = void 0;
|
|
2463
|
+
const args = latestArgs;
|
|
2464
|
+
latestArgs = void 0;
|
|
2465
|
+
latestFn(...args);
|
|
2466
|
+
}
|
|
2467
|
+
};
|
|
2468
|
+
onUnmounted(() => {
|
|
2469
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
2470
|
+
});
|
|
2471
|
+
return debounced;
|
|
2472
|
+
}
|
|
2473
|
+
function useTextFilterState(params) {
|
|
2474
|
+
const { textValue = "", onTextChange } = params;
|
|
2475
|
+
const tempTextValue = ref(textValue);
|
|
2476
|
+
watch(params.isFilterOpen, (open) => {
|
|
2477
|
+
if (open) {
|
|
2478
|
+
tempTextValue.value = params.textValue ?? "";
|
|
2479
|
+
}
|
|
2480
|
+
});
|
|
2481
|
+
const setTempTextValue = (v) => {
|
|
2482
|
+
tempTextValue.value = v;
|
|
2483
|
+
};
|
|
2484
|
+
const handleTextApply = () => {
|
|
2485
|
+
onTextChange?.(tempTextValue.value.trim());
|
|
2486
|
+
};
|
|
2487
|
+
const handleTextClear = () => {
|
|
2488
|
+
tempTextValue.value = "";
|
|
2489
|
+
};
|
|
2490
|
+
return {
|
|
2491
|
+
tempTextValue,
|
|
2492
|
+
setTempTextValue,
|
|
2493
|
+
handleTextApply,
|
|
2494
|
+
handleTextClear
|
|
2495
|
+
};
|
|
2496
|
+
}
|
|
2497
|
+
var SEARCH_DEBOUNCE_MS = 150;
|
|
2498
|
+
var EMPTY_OPTIONS = [];
|
|
2499
|
+
function useMultiSelectFilterState(params) {
|
|
2500
|
+
const { onFilterChange } = params;
|
|
2501
|
+
const tempSelected = ref(new Set(params.selectedValues ?? EMPTY_OPTIONS));
|
|
2502
|
+
const searchText = ref("");
|
|
2503
|
+
const debouncedSearchText = useDebounce(searchText, SEARCH_DEBOUNCE_MS);
|
|
2504
|
+
watch(params.isFilterOpen, (open) => {
|
|
2505
|
+
if (open) {
|
|
2506
|
+
tempSelected.value = new Set(params.selectedValues ?? EMPTY_OPTIONS);
|
|
2507
|
+
searchText.value = "";
|
|
2508
|
+
}
|
|
2509
|
+
});
|
|
2510
|
+
const filteredOptions = computed(() => {
|
|
2511
|
+
const safeOptions = params.options ?? EMPTY_OPTIONS;
|
|
2512
|
+
if (!debouncedSearchText.value.trim()) return safeOptions;
|
|
2513
|
+
const searchLower = debouncedSearchText.value.toLowerCase().trim();
|
|
2514
|
+
return safeOptions.filter((opt) => opt.toLowerCase().includes(searchLower));
|
|
2515
|
+
});
|
|
2516
|
+
const setTempSelected = (v) => {
|
|
2517
|
+
tempSelected.value = v;
|
|
2518
|
+
};
|
|
2519
|
+
const setSearchText = (v) => {
|
|
2520
|
+
searchText.value = v;
|
|
2521
|
+
};
|
|
2522
|
+
const handleCheckboxChange = (option, checked) => {
|
|
2523
|
+
const next = new Set(tempSelected.value);
|
|
2524
|
+
if (checked) next.add(option);
|
|
2525
|
+
else next.delete(option);
|
|
2526
|
+
tempSelected.value = next;
|
|
2527
|
+
};
|
|
2528
|
+
const handleSelectAll = () => {
|
|
2529
|
+
tempSelected.value = new Set(filteredOptions.value);
|
|
2530
|
+
};
|
|
2531
|
+
const handleClearSelection = () => {
|
|
2532
|
+
tempSelected.value = /* @__PURE__ */ new Set();
|
|
2533
|
+
};
|
|
2534
|
+
const handleApplyMultiSelect = () => {
|
|
2535
|
+
onFilterChange?.(Array.from(tempSelected.value));
|
|
2536
|
+
};
|
|
2537
|
+
return {
|
|
2538
|
+
tempSelected,
|
|
2539
|
+
setTempSelected,
|
|
2540
|
+
searchText,
|
|
2541
|
+
setSearchText,
|
|
2542
|
+
debouncedSearchText,
|
|
2543
|
+
filteredOptions,
|
|
2544
|
+
handleCheckboxChange,
|
|
2545
|
+
handleSelectAll,
|
|
2546
|
+
handleClearSelection,
|
|
2547
|
+
handleApplyMultiSelect
|
|
2548
|
+
};
|
|
2549
|
+
}
|
|
2550
|
+
var PEOPLE_SEARCH_DEBOUNCE_MS = 300;
|
|
2551
|
+
function usePeopleFilterState(params) {
|
|
2552
|
+
const { onUserChange, filterType } = params;
|
|
2553
|
+
const peopleInputRef = ref(null);
|
|
2554
|
+
let peopleSearchTimeout;
|
|
2555
|
+
const peopleSuggestions = ref([]);
|
|
2556
|
+
const isPeopleLoading = ref(false);
|
|
2557
|
+
const peopleSearchText = ref("");
|
|
2558
|
+
const setPeopleSearchText = (v) => {
|
|
2559
|
+
peopleSearchText.value = v;
|
|
2560
|
+
};
|
|
2561
|
+
watch(params.isFilterOpen, (open) => {
|
|
2562
|
+
if (open) {
|
|
2563
|
+
peopleSearchText.value = "";
|
|
2564
|
+
peopleSuggestions.value = [];
|
|
2565
|
+
if (filterType === "people") {
|
|
2566
|
+
setTimeout(() => peopleInputRef.value?.focus(), 50);
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
});
|
|
2570
|
+
watch(
|
|
2571
|
+
[peopleSearchText, () => params.peopleSearch, params.isFilterOpen],
|
|
2572
|
+
([searchText, search, isOpen]) => {
|
|
2573
|
+
if (peopleSearchTimeout) clearTimeout(peopleSearchTimeout);
|
|
2574
|
+
if (!search || !isOpen || filterType !== "people") return;
|
|
2575
|
+
if (!searchText.trim()) {
|
|
2576
|
+
peopleSuggestions.value = [];
|
|
2577
|
+
return;
|
|
2578
|
+
}
|
|
2579
|
+
isPeopleLoading.value = true;
|
|
2580
|
+
peopleSearchTimeout = setTimeout(async () => {
|
|
2581
|
+
try {
|
|
2582
|
+
const results = await search(searchText);
|
|
2583
|
+
peopleSuggestions.value = results.slice(0, 10);
|
|
2584
|
+
} catch {
|
|
2585
|
+
peopleSuggestions.value = [];
|
|
2586
|
+
} finally {
|
|
2587
|
+
isPeopleLoading.value = false;
|
|
2588
|
+
}
|
|
2589
|
+
}, PEOPLE_SEARCH_DEBOUNCE_MS);
|
|
2590
|
+
}
|
|
2591
|
+
);
|
|
2592
|
+
onUnmounted(() => {
|
|
2593
|
+
if (peopleSearchTimeout) clearTimeout(peopleSearchTimeout);
|
|
2594
|
+
});
|
|
2595
|
+
const handleUserSelect = (user) => {
|
|
2596
|
+
onUserChange?.(user);
|
|
2597
|
+
};
|
|
2598
|
+
const handleClearUser = () => {
|
|
2599
|
+
onUserChange?.(void 0);
|
|
2600
|
+
};
|
|
2601
|
+
return {
|
|
2602
|
+
peopleSuggestions,
|
|
2603
|
+
isPeopleLoading,
|
|
2604
|
+
peopleSearchText,
|
|
2605
|
+
setPeopleSearchText,
|
|
2606
|
+
peopleInputRef,
|
|
2607
|
+
handleUserSelect,
|
|
2608
|
+
handleClearUser
|
|
2609
|
+
};
|
|
2610
|
+
}
|
|
2611
|
+
function useDateFilterState(params) {
|
|
2612
|
+
const { onDateChange } = params;
|
|
2613
|
+
const tempDateFrom = ref(params.dateValue?.from ?? "");
|
|
2614
|
+
const tempDateTo = ref(params.dateValue?.to ?? "");
|
|
2615
|
+
watch(params.isFilterOpen, (open) => {
|
|
2616
|
+
if (open) {
|
|
2617
|
+
tempDateFrom.value = params.dateValue?.from ?? "";
|
|
2618
|
+
tempDateTo.value = params.dateValue?.to ?? "";
|
|
2619
|
+
}
|
|
2620
|
+
});
|
|
2621
|
+
const setTempDateFrom = (v) => {
|
|
2622
|
+
tempDateFrom.value = v;
|
|
2623
|
+
};
|
|
2624
|
+
const setTempDateTo = (v) => {
|
|
2625
|
+
tempDateTo.value = v;
|
|
2626
|
+
};
|
|
2627
|
+
const handleDateApply = () => {
|
|
2628
|
+
const from = tempDateFrom.value || void 0;
|
|
2629
|
+
const to = tempDateTo.value || void 0;
|
|
2630
|
+
onDateChange?.(from || to ? { from, to } : void 0);
|
|
2631
|
+
};
|
|
2632
|
+
const handleDateClear = () => {
|
|
2633
|
+
tempDateFrom.value = "";
|
|
2634
|
+
tempDateTo.value = "";
|
|
2635
|
+
};
|
|
2636
|
+
return {
|
|
2637
|
+
tempDateFrom,
|
|
2638
|
+
setTempDateFrom,
|
|
2639
|
+
tempDateTo,
|
|
2640
|
+
setTempDateTo,
|
|
2641
|
+
handleDateApply,
|
|
2642
|
+
handleDateClear
|
|
2643
|
+
};
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
// src/composables/useColumnHeaderFilterState.ts
|
|
2647
|
+
var EMPTY_OPTIONS2 = [];
|
|
2648
|
+
function useColumnHeaderFilterState(params) {
|
|
2649
|
+
const {
|
|
2650
|
+
filterType,
|
|
2651
|
+
onSort
|
|
2652
|
+
} = params;
|
|
2653
|
+
const safeSelectedValues = () => params.selectedValues ?? EMPTY_OPTIONS2;
|
|
2654
|
+
const headerRef = ref(null);
|
|
2655
|
+
const popoverRef = ref(null);
|
|
2656
|
+
const isFilterOpen = ref(false);
|
|
2657
|
+
const popoverPosition = ref(null);
|
|
2658
|
+
const setFilterOpen = (open) => {
|
|
2659
|
+
isFilterOpen.value = open;
|
|
2660
|
+
};
|
|
2661
|
+
const textFilterState = useTextFilterState({
|
|
2662
|
+
textValue: params.textValue,
|
|
2663
|
+
onTextChange: params.onTextChange,
|
|
2664
|
+
isFilterOpen
|
|
2665
|
+
});
|
|
2666
|
+
const multiSelectFilterState = useMultiSelectFilterState({
|
|
2667
|
+
selectedValues: params.selectedValues,
|
|
2668
|
+
onFilterChange: params.onFilterChange,
|
|
2669
|
+
options: params.options,
|
|
2670
|
+
isFilterOpen
|
|
2671
|
+
});
|
|
2672
|
+
const peopleFilterState = usePeopleFilterState({
|
|
2673
|
+
selectedUser: params.selectedUser,
|
|
2674
|
+
onUserChange: params.onUserChange,
|
|
2675
|
+
peopleSearch: params.peopleSearch,
|
|
2676
|
+
isFilterOpen,
|
|
2677
|
+
filterType
|
|
2678
|
+
});
|
|
2679
|
+
const dateFilterState = useDateFilterState({
|
|
2680
|
+
dateValue: params.dateValue,
|
|
2681
|
+
onDateChange: params.onDateChange,
|
|
2682
|
+
isFilterOpen
|
|
2683
|
+
});
|
|
2684
|
+
watch(isFilterOpen, (open) => {
|
|
2685
|
+
if (!open) {
|
|
2686
|
+
popoverPosition.value = null;
|
|
2687
|
+
}
|
|
2688
|
+
});
|
|
2689
|
+
let clickOutsideHandler = null;
|
|
2690
|
+
let keyDownHandler = null;
|
|
2691
|
+
let clickOutsideTimeout;
|
|
2692
|
+
const setupListeners = () => {
|
|
2693
|
+
cleanupListeners();
|
|
2694
|
+
clickOutsideHandler = (e) => {
|
|
2695
|
+
const target = e.target;
|
|
2696
|
+
if (popoverRef.value && !popoverRef.value.contains(target) && headerRef.value && !headerRef.value.contains(target)) {
|
|
2697
|
+
isFilterOpen.value = false;
|
|
2698
|
+
}
|
|
2699
|
+
};
|
|
2700
|
+
keyDownHandler = (e) => {
|
|
2701
|
+
if (e.key === "Escape" || e.key === "Esc") {
|
|
2702
|
+
e.preventDefault();
|
|
2703
|
+
e.stopPropagation();
|
|
2704
|
+
isFilterOpen.value = false;
|
|
2705
|
+
}
|
|
2706
|
+
};
|
|
2707
|
+
clickOutsideTimeout = setTimeout(() => {
|
|
2708
|
+
if (clickOutsideHandler) document.addEventListener("mousedown", clickOutsideHandler);
|
|
2709
|
+
}, 0);
|
|
2710
|
+
document.addEventListener("keydown", keyDownHandler, true);
|
|
2711
|
+
};
|
|
2712
|
+
const cleanupListeners = () => {
|
|
2713
|
+
if (clickOutsideTimeout) clearTimeout(clickOutsideTimeout);
|
|
2714
|
+
if (clickOutsideHandler) document.removeEventListener("mousedown", clickOutsideHandler);
|
|
2715
|
+
if (keyDownHandler) document.removeEventListener("keydown", keyDownHandler, true);
|
|
2716
|
+
clickOutsideHandler = null;
|
|
2717
|
+
keyDownHandler = null;
|
|
2718
|
+
};
|
|
2719
|
+
watch(isFilterOpen, (open) => {
|
|
2720
|
+
if (open) setupListeners();
|
|
2721
|
+
else cleanupListeners();
|
|
2722
|
+
});
|
|
2723
|
+
onUnmounted(() => cleanupListeners());
|
|
2724
|
+
const handleFilterIconClick = (e) => {
|
|
2725
|
+
e.stopPropagation();
|
|
2726
|
+
e.preventDefault();
|
|
2727
|
+
if (!isFilterOpen.value && headerRef.value) {
|
|
2728
|
+
const rect = headerRef.value.getBoundingClientRect();
|
|
2729
|
+
popoverPosition.value = { top: rect.bottom + 4, left: rect.left };
|
|
2730
|
+
}
|
|
2731
|
+
isFilterOpen.value = !isFilterOpen.value;
|
|
2732
|
+
};
|
|
2733
|
+
const handleSortClick = (e) => {
|
|
2734
|
+
e.stopPropagation();
|
|
2735
|
+
onSort?.();
|
|
2736
|
+
};
|
|
2737
|
+
const handleApplyMultiSelect = () => {
|
|
2738
|
+
multiSelectFilterState.handleApplyMultiSelect();
|
|
2739
|
+
isFilterOpen.value = false;
|
|
2740
|
+
};
|
|
2741
|
+
const handleTextApply = () => {
|
|
2742
|
+
textFilterState.handleTextApply();
|
|
2743
|
+
isFilterOpen.value = false;
|
|
2744
|
+
};
|
|
2745
|
+
const handleUserSelect = (user) => {
|
|
2746
|
+
peopleFilterState.handleUserSelect(user);
|
|
2747
|
+
isFilterOpen.value = false;
|
|
2748
|
+
};
|
|
2749
|
+
const handleClearUser = () => {
|
|
2750
|
+
peopleFilterState.handleClearUser();
|
|
2751
|
+
isFilterOpen.value = false;
|
|
2752
|
+
};
|
|
2753
|
+
const handleDateApply = () => {
|
|
2754
|
+
dateFilterState.handleDateApply();
|
|
2755
|
+
isFilterOpen.value = false;
|
|
2756
|
+
};
|
|
2757
|
+
const handlePopoverClick = (e) => e.stopPropagation();
|
|
2758
|
+
const handleInputFocus = (e) => e.stopPropagation();
|
|
2759
|
+
const handleInputMouseDown = (e) => e.stopPropagation();
|
|
2760
|
+
const handleInputClick = (e) => e.stopPropagation();
|
|
2761
|
+
const handleInputKeyDown = (e) => {
|
|
2762
|
+
if (e.key !== "Escape" && e.key !== "Esc") e.stopPropagation();
|
|
2763
|
+
};
|
|
2764
|
+
const hasActiveFilter = computed(() => {
|
|
2765
|
+
if (filterType === "multiSelect") return safeSelectedValues().length > 0;
|
|
2766
|
+
if (filterType === "text") return !!(params.textValue ?? "").trim();
|
|
2767
|
+
if (filterType === "people") return !!params.selectedUser;
|
|
2768
|
+
if (filterType === "date") return !!(params.dateValue?.from || params.dateValue?.to);
|
|
2769
|
+
return false;
|
|
2770
|
+
});
|
|
2771
|
+
return {
|
|
2772
|
+
headerRef,
|
|
2773
|
+
popoverRef,
|
|
2774
|
+
peopleInputRef: peopleFilterState.peopleInputRef,
|
|
2775
|
+
isFilterOpen,
|
|
2776
|
+
setFilterOpen,
|
|
2777
|
+
tempSelected: multiSelectFilterState.tempSelected,
|
|
2778
|
+
setTempSelected: multiSelectFilterState.setTempSelected,
|
|
2779
|
+
tempTextValue: textFilterState.tempTextValue,
|
|
2780
|
+
setTempTextValue: textFilterState.setTempTextValue,
|
|
2781
|
+
searchText: multiSelectFilterState.searchText,
|
|
2782
|
+
setSearchText: multiSelectFilterState.setSearchText,
|
|
2783
|
+
debouncedSearchText: multiSelectFilterState.debouncedSearchText,
|
|
2784
|
+
filteredOptions: multiSelectFilterState.filteredOptions,
|
|
2785
|
+
peopleSuggestions: peopleFilterState.peopleSuggestions,
|
|
2786
|
+
isPeopleLoading: peopleFilterState.isPeopleLoading,
|
|
2787
|
+
peopleSearchText: peopleFilterState.peopleSearchText,
|
|
2788
|
+
setPeopleSearchText: peopleFilterState.setPeopleSearchText,
|
|
2789
|
+
tempDateFrom: dateFilterState.tempDateFrom,
|
|
2790
|
+
setTempDateFrom: dateFilterState.setTempDateFrom,
|
|
2791
|
+
tempDateTo: dateFilterState.tempDateTo,
|
|
2792
|
+
setTempDateTo: dateFilterState.setTempDateTo,
|
|
2793
|
+
hasActiveFilter,
|
|
2794
|
+
popoverPosition,
|
|
2795
|
+
handlers: {
|
|
2796
|
+
handleFilterIconClick,
|
|
2797
|
+
handleApplyMultiSelect,
|
|
2798
|
+
handleTextApply,
|
|
2799
|
+
handleTextClear: textFilterState.handleTextClear,
|
|
2800
|
+
handleUserSelect,
|
|
2801
|
+
handleClearUser,
|
|
2802
|
+
handleCheckboxChange: multiSelectFilterState.handleCheckboxChange,
|
|
2803
|
+
handleSelectAll: multiSelectFilterState.handleSelectAll,
|
|
2804
|
+
handleClearSelection: multiSelectFilterState.handleClearSelection,
|
|
2805
|
+
handlePopoverClick,
|
|
2806
|
+
handleInputFocus,
|
|
2807
|
+
handleInputMouseDown,
|
|
2808
|
+
handleInputClick,
|
|
2809
|
+
handleInputKeyDown,
|
|
2810
|
+
handleDateApply,
|
|
2811
|
+
handleDateClear: dateFilterState.handleDateClear,
|
|
2812
|
+
handleSortClick
|
|
2813
|
+
}
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2816
|
+
function useColumnChooserState(params) {
|
|
2817
|
+
const { columns, visibleColumns, onVisibilityChange } = params;
|
|
2818
|
+
const open = ref(false);
|
|
2819
|
+
let keyDownHandler = null;
|
|
2820
|
+
const setupEscapeHandler = () => {
|
|
2821
|
+
cleanupEscapeHandler();
|
|
2822
|
+
keyDownHandler = (event) => {
|
|
2823
|
+
if (event.key === "Escape") {
|
|
2824
|
+
event.preventDefault();
|
|
2825
|
+
open.value = false;
|
|
2826
|
+
}
|
|
2827
|
+
};
|
|
2828
|
+
document.addEventListener("keydown", keyDownHandler, true);
|
|
2829
|
+
};
|
|
2830
|
+
const cleanupEscapeHandler = () => {
|
|
2831
|
+
if (keyDownHandler) {
|
|
2832
|
+
document.removeEventListener("keydown", keyDownHandler, true);
|
|
2833
|
+
keyDownHandler = null;
|
|
2834
|
+
}
|
|
2835
|
+
};
|
|
2836
|
+
watch(open, (isOpen) => {
|
|
2837
|
+
if (isOpen) setupEscapeHandler();
|
|
2838
|
+
else cleanupEscapeHandler();
|
|
2839
|
+
});
|
|
2840
|
+
onUnmounted(() => cleanupEscapeHandler());
|
|
2841
|
+
const setOpen = (value) => {
|
|
2842
|
+
open.value = value;
|
|
2843
|
+
};
|
|
2844
|
+
const handleToggle = () => {
|
|
2845
|
+
open.value = !open.value;
|
|
2846
|
+
};
|
|
2847
|
+
const handleClose = () => {
|
|
2848
|
+
open.value = false;
|
|
2849
|
+
};
|
|
2850
|
+
const handleCheckboxChange = (columnKey) => {
|
|
2851
|
+
return (visible) => {
|
|
2852
|
+
onVisibilityChange(columnKey, visible);
|
|
2853
|
+
};
|
|
2854
|
+
};
|
|
2855
|
+
const handleSelectAll = () => {
|
|
2856
|
+
columns.value.forEach((col) => {
|
|
2857
|
+
if (!visibleColumns.value.has(col.columnId)) {
|
|
2858
|
+
onVisibilityChange(col.columnId, true);
|
|
2859
|
+
}
|
|
2860
|
+
});
|
|
2861
|
+
};
|
|
2862
|
+
const handleClearAll = () => {
|
|
2863
|
+
columns.value.forEach((col) => {
|
|
2864
|
+
if (!col.required && visibleColumns.value.has(col.columnId)) {
|
|
2865
|
+
onVisibilityChange(col.columnId, false);
|
|
2866
|
+
}
|
|
2867
|
+
});
|
|
2868
|
+
};
|
|
2869
|
+
const visibleCount = computed(() => visibleColumns.value.size);
|
|
2870
|
+
const totalCount = computed(() => columns.value.length);
|
|
2871
|
+
return {
|
|
2872
|
+
open,
|
|
2873
|
+
setOpen,
|
|
2874
|
+
handleToggle,
|
|
2875
|
+
handleClose,
|
|
2876
|
+
handleCheckboxChange,
|
|
2877
|
+
handleSelectAll,
|
|
2878
|
+
handleClearAll,
|
|
2879
|
+
visibleCount,
|
|
2880
|
+
totalCount
|
|
2881
|
+
};
|
|
2882
|
+
}
|
|
2883
|
+
function useInlineCellEditorState(params) {
|
|
2884
|
+
const { value, editorType, onCommit, onCancel } = params;
|
|
2885
|
+
const localValue = ref(
|
|
2886
|
+
value !== null && value !== void 0 ? String(value) : ""
|
|
2887
|
+
);
|
|
2888
|
+
const setLocalValue = (v) => {
|
|
2889
|
+
localValue.value = v;
|
|
2890
|
+
};
|
|
2891
|
+
const commit = (v) => {
|
|
2892
|
+
onCommit(v);
|
|
2893
|
+
};
|
|
2894
|
+
const cancel = () => {
|
|
2895
|
+
onCancel();
|
|
2896
|
+
};
|
|
2897
|
+
const handleKeyDown = (e) => {
|
|
2898
|
+
if (e.key === "Escape") {
|
|
2899
|
+
e.preventDefault();
|
|
2900
|
+
e.stopPropagation();
|
|
2901
|
+
cancel();
|
|
2902
|
+
}
|
|
2903
|
+
if (e.key === "Enter" && editorType === "text") {
|
|
2904
|
+
e.preventDefault();
|
|
2905
|
+
e.stopPropagation();
|
|
2906
|
+
commit(localValue.value);
|
|
2907
|
+
}
|
|
2908
|
+
};
|
|
2909
|
+
const handleBlur = () => {
|
|
2910
|
+
if (editorType === "text") {
|
|
2911
|
+
commit(localValue.value);
|
|
2912
|
+
}
|
|
2913
|
+
};
|
|
2914
|
+
return {
|
|
2915
|
+
localValue,
|
|
2916
|
+
setLocalValue,
|
|
2917
|
+
handleKeyDown,
|
|
2918
|
+
handleBlur,
|
|
2919
|
+
commit,
|
|
2920
|
+
cancel
|
|
2921
|
+
};
|
|
2922
|
+
}
|
|
2923
|
+
function useRichSelectState(params) {
|
|
2924
|
+
const { values, formatValue, onCommit, onCancel } = params;
|
|
2925
|
+
const searchText = ref("");
|
|
2926
|
+
const highlightedIndex = ref(0);
|
|
2927
|
+
const setSearchText = (text) => {
|
|
2928
|
+
searchText.value = text;
|
|
2929
|
+
};
|
|
2930
|
+
const getDisplayText = (value) => {
|
|
2931
|
+
if (formatValue) return formatValue(value);
|
|
2932
|
+
return value != null ? String(value) : "";
|
|
2933
|
+
};
|
|
2934
|
+
const filteredValues = computed(() => {
|
|
2935
|
+
if (!searchText.value.trim()) return values;
|
|
2936
|
+
const lower = searchText.value.toLowerCase();
|
|
2937
|
+
return values.filter((v) => getDisplayText(v).toLowerCase().includes(lower));
|
|
2938
|
+
});
|
|
2939
|
+
const selectValue = (value) => {
|
|
2940
|
+
onCommit(value);
|
|
2941
|
+
};
|
|
2942
|
+
const handleKeyDown = (e) => {
|
|
2943
|
+
switch (e.key) {
|
|
2944
|
+
case "ArrowDown":
|
|
2945
|
+
e.preventDefault();
|
|
2946
|
+
highlightedIndex.value = Math.min(highlightedIndex.value + 1, filteredValues.value.length - 1);
|
|
2947
|
+
break;
|
|
2948
|
+
case "ArrowUp":
|
|
2949
|
+
e.preventDefault();
|
|
2950
|
+
highlightedIndex.value = Math.max(highlightedIndex.value - 1, 0);
|
|
2951
|
+
break;
|
|
2952
|
+
case "Enter":
|
|
2953
|
+
e.preventDefault();
|
|
2954
|
+
e.stopPropagation();
|
|
2955
|
+
if (filteredValues.value.length > 0 && highlightedIndex.value < filteredValues.value.length) {
|
|
2956
|
+
selectValue(filteredValues.value[highlightedIndex.value]);
|
|
2957
|
+
}
|
|
2958
|
+
break;
|
|
2959
|
+
case "Escape":
|
|
2960
|
+
e.preventDefault();
|
|
2961
|
+
e.stopPropagation();
|
|
2962
|
+
onCancel();
|
|
2963
|
+
break;
|
|
2964
|
+
}
|
|
2965
|
+
};
|
|
2966
|
+
return {
|
|
2967
|
+
searchText,
|
|
2968
|
+
setSearchText,
|
|
2969
|
+
filteredValues,
|
|
2970
|
+
highlightedIndex,
|
|
2971
|
+
handleKeyDown,
|
|
2972
|
+
selectValue,
|
|
2973
|
+
getDisplayText
|
|
2974
|
+
};
|
|
2975
|
+
}
|
|
2976
|
+
var RESIZE_HANDLE_ZONE = 8;
|
|
2977
|
+
var MIN_DRAG_DISTANCE = 5;
|
|
2978
|
+
function useColumnReorder(params) {
|
|
2979
|
+
const { columnOrder, onColumnOrderChange, tableRef, pinnedColumns } = params;
|
|
2980
|
+
const isDragging = ref(false);
|
|
2981
|
+
const dropIndicatorX = ref(null);
|
|
2982
|
+
let draggedColumnId = null;
|
|
2983
|
+
let draggedPinState = "unpinned";
|
|
2984
|
+
let rafId = 0;
|
|
2985
|
+
let cleanupFn = null;
|
|
2986
|
+
onUnmounted(() => {
|
|
2987
|
+
cleanupFn?.();
|
|
2988
|
+
cleanupFn = null;
|
|
2989
|
+
});
|
|
2990
|
+
const handleHeaderMouseDown = (columnId, event) => {
|
|
2991
|
+
if (event.button !== 0) return;
|
|
2992
|
+
const th = event.target.closest("th");
|
|
2993
|
+
if (th) {
|
|
2994
|
+
const rect = th.getBoundingClientRect();
|
|
2995
|
+
if (event.clientX > rect.right - RESIZE_HANDLE_ZONE) return;
|
|
2996
|
+
}
|
|
2997
|
+
event.preventDefault();
|
|
2998
|
+
const table = tableRef.value;
|
|
2999
|
+
if (!table) return;
|
|
3000
|
+
if (!onColumnOrderChange.value) return;
|
|
3001
|
+
draggedColumnId = columnId;
|
|
3002
|
+
draggedPinState = getPinStateForColumn(
|
|
3003
|
+
columnId,
|
|
3004
|
+
pinnedColumns?.value
|
|
3005
|
+
);
|
|
3006
|
+
dropIndicatorX.value = null;
|
|
3007
|
+
const startX = event.clientX;
|
|
3008
|
+
let hasMoved = false;
|
|
3009
|
+
let latestMouseX = event.clientX;
|
|
3010
|
+
let targetIndex = -1;
|
|
3011
|
+
const prevCursor = document.body.style.cursor;
|
|
3012
|
+
const prevUserSelect = document.body.style.userSelect;
|
|
3013
|
+
document.body.style.cursor = "grabbing";
|
|
3014
|
+
document.body.style.userSelect = "none";
|
|
3015
|
+
const onMove = (moveEvent) => {
|
|
3016
|
+
if (!hasMoved && Math.abs(moveEvent.clientX - startX) < MIN_DRAG_DISTANCE) return;
|
|
3017
|
+
if (!hasMoved) {
|
|
3018
|
+
hasMoved = true;
|
|
3019
|
+
isDragging.value = true;
|
|
3020
|
+
}
|
|
3021
|
+
latestMouseX = moveEvent.clientX;
|
|
3022
|
+
if (!rafId) {
|
|
3023
|
+
rafId = requestAnimationFrame(() => {
|
|
3024
|
+
rafId = 0;
|
|
3025
|
+
const tableEl = tableRef.value;
|
|
3026
|
+
if (!tableEl || !draggedColumnId) return;
|
|
3027
|
+
const result = calculateDropTarget({
|
|
3028
|
+
mouseX: latestMouseX,
|
|
3029
|
+
columnOrder: columnOrder.value,
|
|
3030
|
+
draggedColumnId,
|
|
3031
|
+
draggedPinState,
|
|
3032
|
+
tableElement: tableEl,
|
|
3033
|
+
pinnedColumns: pinnedColumns?.value
|
|
3034
|
+
});
|
|
3035
|
+
if (result) {
|
|
3036
|
+
targetIndex = result.targetIndex;
|
|
3037
|
+
dropIndicatorX.value = result.indicatorX;
|
|
3038
|
+
} else {
|
|
3039
|
+
dropIndicatorX.value = null;
|
|
3040
|
+
}
|
|
3041
|
+
});
|
|
3042
|
+
}
|
|
3043
|
+
};
|
|
3044
|
+
const cleanup = () => {
|
|
3045
|
+
window.removeEventListener("mousemove", onMove, true);
|
|
3046
|
+
window.removeEventListener("mouseup", onUp, true);
|
|
3047
|
+
cleanupFn = null;
|
|
3048
|
+
document.body.style.cursor = prevCursor;
|
|
3049
|
+
document.body.style.userSelect = prevUserSelect;
|
|
3050
|
+
if (rafId) {
|
|
3051
|
+
cancelAnimationFrame(rafId);
|
|
3052
|
+
rafId = 0;
|
|
3053
|
+
}
|
|
3054
|
+
};
|
|
3055
|
+
const onUp = () => {
|
|
3056
|
+
cleanup();
|
|
3057
|
+
if (hasMoved && draggedColumnId && targetIndex >= 0 && onColumnOrderChange.value) {
|
|
3058
|
+
const newOrder = reorderColumnArray(
|
|
3059
|
+
columnOrder.value,
|
|
3060
|
+
draggedColumnId,
|
|
3061
|
+
targetIndex
|
|
3062
|
+
);
|
|
3063
|
+
onColumnOrderChange.value(newOrder);
|
|
3064
|
+
}
|
|
3065
|
+
draggedColumnId = null;
|
|
3066
|
+
isDragging.value = false;
|
|
3067
|
+
dropIndicatorX.value = null;
|
|
3068
|
+
targetIndex = -1;
|
|
3069
|
+
};
|
|
3070
|
+
window.addEventListener("mousemove", onMove, true);
|
|
3071
|
+
window.addEventListener("mouseup", onUp, true);
|
|
3072
|
+
cleanupFn = cleanup;
|
|
3073
|
+
};
|
|
3074
|
+
return { isDragging, dropIndicatorX, handleHeaderMouseDown };
|
|
3075
|
+
}
|
|
3076
|
+
function useVirtualScroll(params) {
|
|
3077
|
+
const { totalRows, rowHeight, enabled, overscan = 5 } = params;
|
|
3078
|
+
const containerRef = ref(null);
|
|
3079
|
+
const scrollTop = ref(0);
|
|
3080
|
+
const containerHeight = ref(0);
|
|
3081
|
+
let rafId = 0;
|
|
3082
|
+
let resizeObserver;
|
|
3083
|
+
let prevObservedEl = null;
|
|
3084
|
+
const visibleRange = computed(() => {
|
|
3085
|
+
if (!enabled.value) {
|
|
3086
|
+
return { startIndex: 0, endIndex: totalRows.value - 1, offsetTop: 0, offsetBottom: 0 };
|
|
3087
|
+
}
|
|
3088
|
+
return computeVisibleRange(
|
|
3089
|
+
scrollTop.value,
|
|
3090
|
+
rowHeight,
|
|
3091
|
+
containerHeight.value,
|
|
3092
|
+
totalRows.value,
|
|
3093
|
+
overscan
|
|
3094
|
+
);
|
|
3095
|
+
});
|
|
3096
|
+
const totalHeight = computed(() => {
|
|
3097
|
+
if (!enabled.value) return 0;
|
|
3098
|
+
return computeTotalHeight(totalRows.value, rowHeight);
|
|
3099
|
+
});
|
|
3100
|
+
const onScroll = () => {
|
|
3101
|
+
if (!rafId) {
|
|
3102
|
+
rafId = requestAnimationFrame(() => {
|
|
3103
|
+
rafId = 0;
|
|
3104
|
+
const el = containerRef.value;
|
|
3105
|
+
if (el) {
|
|
3106
|
+
scrollTop.value = el.scrollTop;
|
|
3107
|
+
}
|
|
3108
|
+
});
|
|
3109
|
+
}
|
|
3110
|
+
};
|
|
3111
|
+
const measure = () => {
|
|
3112
|
+
const el = containerRef.value;
|
|
3113
|
+
if (!el) return;
|
|
3114
|
+
containerHeight.value = el.clientHeight;
|
|
3115
|
+
};
|
|
3116
|
+
watch(containerRef, (el) => {
|
|
3117
|
+
if (el === prevObservedEl) return;
|
|
3118
|
+
if (prevObservedEl) {
|
|
3119
|
+
prevObservedEl.removeEventListener("scroll", onScroll);
|
|
3120
|
+
}
|
|
3121
|
+
if (resizeObserver) {
|
|
3122
|
+
resizeObserver.disconnect();
|
|
3123
|
+
resizeObserver = void 0;
|
|
3124
|
+
}
|
|
3125
|
+
prevObservedEl = el;
|
|
3126
|
+
if (el) {
|
|
3127
|
+
el.addEventListener("scroll", onScroll, { passive: true });
|
|
3128
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
3129
|
+
resizeObserver = new ResizeObserver(measure);
|
|
3130
|
+
resizeObserver.observe(el);
|
|
3131
|
+
}
|
|
3132
|
+
measure();
|
|
3133
|
+
scrollTop.value = el.scrollTop;
|
|
3134
|
+
}
|
|
3135
|
+
});
|
|
3136
|
+
onUnmounted(() => {
|
|
3137
|
+
const el = containerRef.value;
|
|
3138
|
+
if (el) {
|
|
3139
|
+
el.removeEventListener("scroll", onScroll);
|
|
3140
|
+
}
|
|
3141
|
+
resizeObserver?.disconnect();
|
|
3142
|
+
if (rafId) {
|
|
3143
|
+
cancelAnimationFrame(rafId);
|
|
3144
|
+
rafId = 0;
|
|
3145
|
+
}
|
|
3146
|
+
});
|
|
3147
|
+
const scrollToRow = (index, align = "start") => {
|
|
3148
|
+
const el = containerRef.value;
|
|
3149
|
+
if (!el) return;
|
|
3150
|
+
el.scrollTop = getScrollTopForRow(index, rowHeight, containerHeight.value, align);
|
|
3151
|
+
};
|
|
3152
|
+
return { containerRef, visibleRange, totalHeight, scrollToRow };
|
|
3153
|
+
}
|
|
3154
|
+
function useDataGridTableSetup(params) {
|
|
3155
|
+
const { props: propsRef } = params;
|
|
3156
|
+
const wrapperRef = ref(null);
|
|
3157
|
+
const tableContainerRef = ref(null);
|
|
3158
|
+
const tableRef = ref(null);
|
|
3159
|
+
const lastMouseShift = ref(false);
|
|
3160
|
+
const state = useDataGridState({ props: propsRef, wrapperRef });
|
|
3161
|
+
const columnOrderRef = computed(() => {
|
|
3162
|
+
const p = propsRef.value;
|
|
3163
|
+
if (p.columnOrder) return p.columnOrder;
|
|
3164
|
+
return flattenColumns(p.columns).filter((c) => p.visibleColumns?.has(c.columnId) ?? true).map((c) => c.columnId);
|
|
3165
|
+
});
|
|
3166
|
+
const onColumnOrderChangeRef = computed(() => propsRef.value.onColumnOrderChange);
|
|
3167
|
+
const columnReorder = useColumnReorder({
|
|
3168
|
+
columnOrder: columnOrderRef,
|
|
3169
|
+
onColumnOrderChange: onColumnOrderChangeRef,
|
|
3170
|
+
tableRef
|
|
3171
|
+
});
|
|
3172
|
+
const virtualScrollEnabled = computed(() => propsRef.value.virtualScroll?.enabled ?? false);
|
|
3173
|
+
const totalRowsRef = computed(() => propsRef.value.items.length);
|
|
3174
|
+
const rowHeight = propsRef.value.virtualScroll?.rowHeight ?? 36;
|
|
3175
|
+
const overscan = propsRef.value.virtualScroll?.overscan ?? 5;
|
|
3176
|
+
const virtualScroll = useVirtualScroll({
|
|
3177
|
+
totalRows: totalRowsRef,
|
|
3178
|
+
rowHeight,
|
|
3179
|
+
enabled: virtualScrollEnabled,
|
|
3180
|
+
overscan
|
|
3181
|
+
});
|
|
3182
|
+
const columnSizingOverridesRef = computed(() => state.layout.value.columnSizingOverrides);
|
|
3183
|
+
const columnResize = useColumnResize({
|
|
3184
|
+
columnSizingOverrides: columnSizingOverridesRef,
|
|
3185
|
+
setColumnSizingOverrides: (v) => state.layout.value.setColumnSizingOverrides(v)
|
|
3186
|
+
});
|
|
3187
|
+
return {
|
|
3188
|
+
wrapperRef,
|
|
3189
|
+
tableContainerRef,
|
|
3190
|
+
tableRef,
|
|
3191
|
+
lastMouseShift,
|
|
3192
|
+
state,
|
|
3193
|
+
columnReorder,
|
|
3194
|
+
virtualScroll,
|
|
3195
|
+
virtualScrollEnabled,
|
|
3196
|
+
columnResize
|
|
3197
|
+
};
|
|
3198
|
+
}
|
|
3199
|
+
function getCellInteractionProps(descriptor, columnId, handlers) {
|
|
3200
|
+
const base = {
|
|
3201
|
+
"data-row-index": descriptor.rowIndex,
|
|
3202
|
+
"data-col-index": descriptor.globalColIndex,
|
|
3203
|
+
...descriptor.isInRange ? { "data-in-range": "true" } : {},
|
|
3204
|
+
tabindex: descriptor.isActive ? 0 : -1,
|
|
3205
|
+
onMousedown: (e) => handlers.handleCellMouseDown(e, descriptor.rowIndex, descriptor.globalColIndex),
|
|
3206
|
+
onClick: () => handlers.setActiveCell({ rowIndex: descriptor.rowIndex, columnIndex: descriptor.globalColIndex }),
|
|
3207
|
+
onContextmenu: (e) => handlers.handleCellContextMenu(e)
|
|
3208
|
+
};
|
|
3209
|
+
if (descriptor.canEditAny) {
|
|
3210
|
+
base.role = "button";
|
|
3211
|
+
base.onDblclick = () => handlers.setEditingCell({ rowId: descriptor.rowId, columnId });
|
|
3212
|
+
}
|
|
3213
|
+
return base;
|
|
3214
|
+
}
|
|
3215
|
+
var NOOP2 = () => {
|
|
3216
|
+
};
|
|
3217
|
+
function createDataGridTable(ui) {
|
|
3218
|
+
return defineComponent({
|
|
3219
|
+
name: "DataGridTable",
|
|
3220
|
+
props: {
|
|
3221
|
+
gridProps: { type: Object, required: true }
|
|
3222
|
+
},
|
|
3223
|
+
setup(props) {
|
|
3224
|
+
const propsRef = computed(() => props.gridProps);
|
|
3225
|
+
const {
|
|
3226
|
+
wrapperRef,
|
|
3227
|
+
tableContainerRef,
|
|
3228
|
+
tableRef,
|
|
3229
|
+
lastMouseShift,
|
|
3230
|
+
state,
|
|
3231
|
+
columnReorder: { isDragging: isReorderDragging, dropIndicatorX, handleHeaderMouseDown: handleReorderMouseDown },
|
|
3232
|
+
virtualScroll: { containerRef: vsContainerRef, visibleRange, totalHeight: _totalHeight, scrollToRow: _scrollToRow },
|
|
3233
|
+
virtualScrollEnabled,
|
|
3234
|
+
columnResize: { handleResizeStart, getColumnWidth }
|
|
3235
|
+
} = useDataGridTableSetup({ props: propsRef });
|
|
3236
|
+
const onWrapperMousedown = (e) => {
|
|
3237
|
+
lastMouseShift.value = e.shiftKey;
|
|
3238
|
+
};
|
|
3239
|
+
const onContextmenu = (e) => e.preventDefault();
|
|
3240
|
+
const stopPropagation = (e) => e.stopPropagation();
|
|
3241
|
+
const headerRowsComputed = computed(() => buildHeaderRows(propsRef.value.columns, propsRef.value.visibleColumns));
|
|
3242
|
+
const columnMetaCache = computed(() => {
|
|
3243
|
+
const layout = state.layout.value;
|
|
3244
|
+
const pinning = state.pinning.value;
|
|
3245
|
+
const { visibleCols, columnSizingOverrides, measuredColumnWidths } = layout;
|
|
3246
|
+
const { leftOffsets, rightOffsets } = pinning;
|
|
3247
|
+
const cellStyles = {};
|
|
3248
|
+
const cellClasses = {};
|
|
3249
|
+
const hdrStyles = {};
|
|
3250
|
+
const hdrClasses = {};
|
|
3251
|
+
for (let colIdx = 0; colIdx < visibleCols.length; colIdx++) {
|
|
3252
|
+
const col = visibleCols[colIdx];
|
|
3253
|
+
const isPinnedLeft = col.pinned === "left";
|
|
3254
|
+
const isPinnedRight = col.pinned === "right";
|
|
3255
|
+
const columnWidth = getColumnWidth(col);
|
|
3256
|
+
const hasResizeOverride = !!columnSizingOverrides[col.columnId];
|
|
3257
|
+
const measuredW = measuredColumnWidths[col.columnId];
|
|
3258
|
+
const baseMinWidth = col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
|
|
3259
|
+
const effectiveMinWidth = hasResizeOverride ? columnWidth : Math.max(baseMinWidth, measuredW ?? 0);
|
|
3260
|
+
const tdStyle = {
|
|
3261
|
+
minWidth: `${effectiveMinWidth}px`,
|
|
3262
|
+
width: `${columnWidth}px`,
|
|
3263
|
+
maxWidth: `${columnWidth}px`
|
|
3264
|
+
};
|
|
3265
|
+
const hdrStyle = {
|
|
3266
|
+
minWidth: `${effectiveMinWidth}px`,
|
|
3267
|
+
width: `${columnWidth}px`,
|
|
3268
|
+
maxWidth: `${columnWidth}px`
|
|
3269
|
+
};
|
|
3270
|
+
const tdClassParts = ["ogrid-data-cell"];
|
|
3271
|
+
const hdrClassParts = ["ogrid-header-cell"];
|
|
3272
|
+
if (isPinnedLeft) {
|
|
3273
|
+
tdClassParts.push("ogrid-data-cell--pinned-left");
|
|
3274
|
+
tdStyle.left = `${leftOffsets[col.columnId] ?? 0}px`;
|
|
3275
|
+
hdrClassParts.push("ogrid-header-cell--pinned-left");
|
|
3276
|
+
hdrStyle.left = `${leftOffsets[col.columnId] ?? 0}px`;
|
|
3277
|
+
} else if (isPinnedRight) {
|
|
3278
|
+
tdClassParts.push("ogrid-data-cell--pinned-right");
|
|
3279
|
+
tdStyle.right = `${rightOffsets[col.columnId] ?? 0}px`;
|
|
3280
|
+
hdrClassParts.push("ogrid-header-cell--pinned-right");
|
|
3281
|
+
hdrStyle.right = `${rightOffsets[col.columnId] ?? 0}px`;
|
|
3282
|
+
}
|
|
3283
|
+
cellStyles[col.columnId] = tdStyle;
|
|
3284
|
+
cellClasses[col.columnId] = tdClassParts.join(" ");
|
|
3285
|
+
hdrStyles[col.columnId] = hdrStyle;
|
|
3286
|
+
hdrClasses[col.columnId] = hdrClassParts.join(" ");
|
|
3287
|
+
}
|
|
3288
|
+
return { cellStyles, cellClasses, hdrStyles, hdrClasses };
|
|
3289
|
+
});
|
|
3290
|
+
return () => {
|
|
3291
|
+
const p = props.gridProps;
|
|
3292
|
+
const layout = state.layout.value;
|
|
3293
|
+
const rowSel = state.rowSelection.value;
|
|
3294
|
+
const editing = state.editing.value;
|
|
3295
|
+
const interaction = state.interaction.value;
|
|
3296
|
+
const ctxMenu = state.contextMenu.value;
|
|
3297
|
+
const viewModels = state.viewModels.value;
|
|
3298
|
+
const pinning = state.pinning.value;
|
|
3299
|
+
const { headerMenu } = pinning;
|
|
3300
|
+
const {
|
|
3301
|
+
visibleCols,
|
|
3302
|
+
hasCheckboxCol,
|
|
3303
|
+
hasRowNumbersCol,
|
|
3304
|
+
colOffset: _colOffset,
|
|
3305
|
+
containerWidth,
|
|
3306
|
+
minTableWidth,
|
|
3307
|
+
desiredTableWidth
|
|
3308
|
+
} = layout;
|
|
3309
|
+
const currentPage = p.currentPage ?? 1;
|
|
3310
|
+
const pageSize = p.pageSize ?? 25;
|
|
3311
|
+
const rowNumberOffset = hasRowNumbersCol ? (currentPage - 1) * pageSize : 0;
|
|
3312
|
+
const { selectedRowIds, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected } = rowSel;
|
|
3313
|
+
const { editingCell: _editingCell, setEditingCell, pendingEditorValue, setPendingEditorValue, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl } = editing;
|
|
3314
|
+
const {
|
|
3315
|
+
setActiveCell,
|
|
3316
|
+
setSelectionRange,
|
|
3317
|
+
handleCellMouseDown,
|
|
3318
|
+
handleSelectAllCells,
|
|
3319
|
+
selectionRange,
|
|
3320
|
+
hasCellSelection,
|
|
3321
|
+
handleGridKeyDown,
|
|
3322
|
+
handleFillHandleMouseDown,
|
|
3323
|
+
handleCopy,
|
|
3324
|
+
handleCut,
|
|
3325
|
+
handlePaste,
|
|
3326
|
+
cutRange: _cutRange,
|
|
3327
|
+
copyRange: _copyRange,
|
|
3328
|
+
canUndo,
|
|
3329
|
+
canRedo,
|
|
3330
|
+
onUndo,
|
|
3331
|
+
onRedo,
|
|
3332
|
+
isDragging: _isDragging
|
|
3333
|
+
} = interaction;
|
|
3334
|
+
const { menuPosition, handleCellContextMenu, closeContextMenu } = ctxMenu;
|
|
3335
|
+
const { headerFilterInput, cellDescriptorInput, statusBarConfig, showEmptyInGrid, onCellError: _onCellError } = viewModels;
|
|
3336
|
+
const items = p.items;
|
|
3337
|
+
const getRowId = p.getRowId;
|
|
3338
|
+
const layoutMode = p.layoutMode ?? "fill";
|
|
3339
|
+
const rowSelection = p.rowSelection ?? "none";
|
|
3340
|
+
const suppressHorizontalScroll = p.suppressHorizontalScroll;
|
|
3341
|
+
const stickyHeader = p.stickyHeader ?? true;
|
|
3342
|
+
const isLoading = p.isLoading ?? false;
|
|
3343
|
+
const loadingMessage = p.loadingMessage ?? "Loading\u2026";
|
|
3344
|
+
const ariaLabel = p["aria-label"];
|
|
3345
|
+
const ariaLabelledBy = p["aria-labelledby"];
|
|
3346
|
+
const fitToContent = layoutMode === "content";
|
|
3347
|
+
const allowOverflowX = !suppressHorizontalScroll && containerWidth > 0 && (minTableWidth > containerWidth || desiredTableWidth > containerWidth);
|
|
3348
|
+
const headerRows = headerRowsComputed.value;
|
|
3349
|
+
const editCallbacks = { commitCellEdit, setEditingCell, setPendingEditorValue, cancelPopoverEdit };
|
|
3350
|
+
const interactionHandlers = { handleCellMouseDown, setActiveCell, setEditingCell, handleCellContextMenu };
|
|
3351
|
+
const handleSingleRowClick = (e) => {
|
|
3352
|
+
if (rowSelection !== "single") return;
|
|
3353
|
+
const tr = e.currentTarget;
|
|
3354
|
+
const rowId = tr.dataset.rowId;
|
|
3355
|
+
if (!rowId) return;
|
|
3356
|
+
rowSel.updateSelection(selectedRowIds.has(rowId) ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([rowId]));
|
|
3357
|
+
};
|
|
3358
|
+
const renderCellContent = (item, col, rowIndex, colIdx) => {
|
|
3359
|
+
const descriptor = getCellRenderDescriptor(item, col, rowIndex, colIdx, cellDescriptorInput);
|
|
3360
|
+
if (descriptor.mode === "editing-inline") {
|
|
3361
|
+
const editorProps = buildInlineEditorProps(item, col, descriptor, editCallbacks);
|
|
3362
|
+
return h(
|
|
3363
|
+
"div",
|
|
3364
|
+
{ class: "ogrid-editing-cell" },
|
|
3365
|
+
h(ui.InlineCellEditor, {
|
|
3366
|
+
value: editorProps.value,
|
|
3367
|
+
item: editorProps.item,
|
|
3368
|
+
column: editorProps.column,
|
|
3369
|
+
rowIndex: editorProps.rowIndex,
|
|
3370
|
+
editorType: editorProps.editorType,
|
|
3371
|
+
onCommit: editorProps.onCommit,
|
|
3372
|
+
onCancel: editorProps.onCancel
|
|
3373
|
+
})
|
|
3374
|
+
);
|
|
3375
|
+
}
|
|
3376
|
+
if (descriptor.mode === "editing-popover" && typeof col.cellEditor === "function") {
|
|
3377
|
+
const editorProps = buildPopoverEditorProps(item, col, descriptor, pendingEditorValue, editCallbacks);
|
|
3378
|
+
const CustomEditor = col.cellEditor;
|
|
3379
|
+
return h("div", [
|
|
3380
|
+
h("div", {
|
|
3381
|
+
ref: (el) => {
|
|
3382
|
+
if (el) setPopoverAnchorEl(el);
|
|
3383
|
+
},
|
|
3384
|
+
class: "ogrid-popover-anchor",
|
|
3385
|
+
"aria-hidden": "true"
|
|
3386
|
+
}),
|
|
3387
|
+
popoverAnchorEl ? h(CustomEditor, editorProps) : null
|
|
3388
|
+
]);
|
|
3389
|
+
}
|
|
3390
|
+
const content = resolveCellDisplayContent(col, item, descriptor.displayValue);
|
|
3391
|
+
const cellStyle = resolveCellStyle(col, item);
|
|
3392
|
+
const interactionProps2 = getCellInteractionProps(descriptor, col.columnId, interactionHandlers);
|
|
3393
|
+
const cellClasses = ["ogrid-cell-content"];
|
|
3394
|
+
if (col.type === "numeric") cellClasses.push("ogrid-cell-content--numeric");
|
|
3395
|
+
else if (col.type === "boolean") cellClasses.push("ogrid-cell-content--boolean");
|
|
3396
|
+
if (descriptor.canEditAny) cellClasses.push("ogrid-cell-content--editable");
|
|
3397
|
+
if (descriptor.isActive && !descriptor.isInRange) cellClasses.push("ogrid-cell-content--active");
|
|
3398
|
+
if (descriptor.isInRange) cellClasses.push("ogrid-cell-in-range");
|
|
3399
|
+
if (descriptor.isInCutRange) cellClasses.push("ogrid-cell-cut");
|
|
3400
|
+
const styledContent = cellStyle ? h("span", { style: cellStyle }, content) : content;
|
|
3401
|
+
return h("div", {
|
|
3402
|
+
...interactionProps2,
|
|
3403
|
+
class: cellClasses.join(" ")
|
|
3404
|
+
}, [
|
|
3405
|
+
styledContent,
|
|
3406
|
+
...descriptor.canEditAny && descriptor.isSelectionEndCell ? [
|
|
3407
|
+
h("div", {
|
|
3408
|
+
onMousedown: handleFillHandleMouseDown,
|
|
3409
|
+
"aria-label": "Fill handle",
|
|
3410
|
+
class: "ogrid-fill-handle"
|
|
3411
|
+
})
|
|
3412
|
+
] : []
|
|
3413
|
+
]);
|
|
3414
|
+
};
|
|
3415
|
+
const { cellStyles: colCellStyles, cellClasses: colCellClasses, hdrStyles: colHdrStyles, hdrClasses: colHdrClasses } = columnMetaCache.value;
|
|
3416
|
+
const columnLayouts = visibleCols.map((col) => ({
|
|
3417
|
+
col,
|
|
3418
|
+
tdClasses: colCellClasses[col.columnId] || "ogrid-data-cell",
|
|
3419
|
+
tdDynamicStyle: colCellStyles[col.columnId] || {}
|
|
3420
|
+
}));
|
|
3421
|
+
const getHeaderClassAndStyle = (col) => {
|
|
3422
|
+
const base = colHdrStyles[col.columnId] || {};
|
|
3423
|
+
return {
|
|
3424
|
+
classes: colHdrClasses[col.columnId] || "ogrid-header-cell",
|
|
3425
|
+
style: { ...base, cursor: isReorderDragging.value ? "grabbing" : "grab" }
|
|
3426
|
+
};
|
|
3427
|
+
};
|
|
3428
|
+
const wrapperStyle = {
|
|
3429
|
+
position: "relative",
|
|
3430
|
+
flex: "1",
|
|
3431
|
+
minHeight: isLoading && items.length === 0 ? "200px" : "0",
|
|
3432
|
+
width: fitToContent ? "fit-content" : "100%",
|
|
3433
|
+
maxWidth: "100%",
|
|
3434
|
+
overflowX: suppressHorizontalScroll ? "hidden" : allowOverflowX ? "auto" : "hidden",
|
|
3435
|
+
overflowY: "auto",
|
|
3436
|
+
backgroundColor: "#fff",
|
|
3437
|
+
willChange: "scroll-position"
|
|
3438
|
+
};
|
|
3439
|
+
if (p.rowHeight) {
|
|
3440
|
+
wrapperStyle["--ogrid-row-height"] = `${p.rowHeight}px`;
|
|
3441
|
+
}
|
|
3442
|
+
return h("div", { class: "ogrid-outer-container" }, [
|
|
3443
|
+
// Scrollable wrapper
|
|
3444
|
+
h("div", {
|
|
3445
|
+
ref: (el) => {
|
|
3446
|
+
wrapperRef.value = el;
|
|
3447
|
+
vsContainerRef.value = el;
|
|
3448
|
+
},
|
|
3449
|
+
tabindex: 0,
|
|
3450
|
+
role: "region",
|
|
3451
|
+
"aria-label": ariaLabel ?? (ariaLabelledBy ? void 0 : "Data grid"),
|
|
3452
|
+
"aria-labelledby": ariaLabelledBy,
|
|
3453
|
+
onMousedown: onWrapperMousedown,
|
|
3454
|
+
onKeydown: handleGridKeyDown,
|
|
3455
|
+
onContextmenu,
|
|
3456
|
+
"data-overflow-x": allowOverflowX ? "true" : "false",
|
|
3457
|
+
style: wrapperStyle
|
|
3458
|
+
}, [
|
|
3459
|
+
h("div", { class: "ogrid-scroll-wrapper" }, [
|
|
3460
|
+
h("div", { style: { minWidth: allowOverflowX ? `${minTableWidth}px` : void 0 } }, [
|
|
3461
|
+
h("div", {
|
|
3462
|
+
ref: (el) => {
|
|
3463
|
+
tableContainerRef.value = el;
|
|
3464
|
+
},
|
|
3465
|
+
class: ["ogrid-table-container", isLoading && items.length > 0 ? "ogrid-table-container--loading" : ""]
|
|
3466
|
+
}, [
|
|
3467
|
+
// Drop indicator for column reorder
|
|
3468
|
+
...isReorderDragging.value && dropIndicatorX.value !== null ? [
|
|
3469
|
+
h("div", {
|
|
3470
|
+
class: "ogrid-drop-indicator",
|
|
3471
|
+
style: { left: `${dropIndicatorX.value}px` }
|
|
3472
|
+
})
|
|
3473
|
+
] : [],
|
|
3474
|
+
// Table
|
|
3475
|
+
h("table", {
|
|
3476
|
+
ref: (el) => {
|
|
3477
|
+
tableRef.value = el;
|
|
3478
|
+
},
|
|
3479
|
+
class: "ogrid-table",
|
|
3480
|
+
style: { minWidth: `${minTableWidth}px` }
|
|
3481
|
+
}, [
|
|
3482
|
+
// Header
|
|
3483
|
+
h(
|
|
3484
|
+
"thead",
|
|
3485
|
+
{ class: stickyHeader ? "ogrid-thead ogrid-sticky-header" : "ogrid-thead" },
|
|
3486
|
+
headerRows.map(
|
|
3487
|
+
(row, rowIdx) => h("tr", { key: rowIdx, class: "ogrid-header-row" }, [
|
|
3488
|
+
// Checkbox header cell
|
|
3489
|
+
...rowIdx === headerRows.length - 1 && hasCheckboxCol ? [
|
|
3490
|
+
h(
|
|
3491
|
+
"th",
|
|
3492
|
+
{
|
|
3493
|
+
class: "ogrid-checkbox-header",
|
|
3494
|
+
style: {
|
|
3495
|
+
width: `${CHECKBOX_COLUMN_WIDTH}px`,
|
|
3496
|
+
minWidth: `${CHECKBOX_COLUMN_WIDTH}px`,
|
|
3497
|
+
maxWidth: `${CHECKBOX_COLUMN_WIDTH}px`
|
|
3498
|
+
}
|
|
3499
|
+
},
|
|
3500
|
+
ui.renderCheckbox({
|
|
3501
|
+
modelValue: allSelected,
|
|
3502
|
+
// Indeterminate only when some (but not all) rows are selected
|
|
3503
|
+
indeterminate: someSelected && !allSelected,
|
|
3504
|
+
ariaLabel: "Select all rows",
|
|
3505
|
+
onChange: (c) => handleSelectAll(!!c)
|
|
3506
|
+
})
|
|
3507
|
+
)
|
|
3508
|
+
] : [],
|
|
3509
|
+
// Checkbox spacer in group header row
|
|
3510
|
+
...rowIdx === 0 && rowIdx < headerRows.length - 1 && hasCheckboxCol ? [
|
|
3511
|
+
h("th", {
|
|
3512
|
+
rowSpan: headerRows.length - 1,
|
|
3513
|
+
class: "ogrid-checkbox-spacer",
|
|
3514
|
+
style: { width: `${CHECKBOX_COLUMN_WIDTH}px`, minWidth: `${CHECKBOX_COLUMN_WIDTH}px` }
|
|
3515
|
+
})
|
|
3516
|
+
] : [],
|
|
3517
|
+
// Row numbers header
|
|
3518
|
+
...rowIdx === headerRows.length - 1 && hasRowNumbersCol ? [
|
|
3519
|
+
h("th", {
|
|
3520
|
+
class: "ogrid-row-number-header",
|
|
3521
|
+
style: {
|
|
3522
|
+
width: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
3523
|
+
minWidth: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
3524
|
+
maxWidth: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
3525
|
+
position: "sticky",
|
|
3526
|
+
left: hasCheckboxCol ? `${CHECKBOX_COLUMN_WIDTH}px` : "0",
|
|
3527
|
+
zIndex: 3
|
|
3528
|
+
}
|
|
3529
|
+
}, "#")
|
|
3530
|
+
] : [],
|
|
3531
|
+
// Row numbers spacer
|
|
3532
|
+
...rowIdx === 0 && rowIdx < headerRows.length - 1 && hasRowNumbersCol ? [
|
|
3533
|
+
h("th", {
|
|
3534
|
+
rowSpan: headerRows.length - 1,
|
|
3535
|
+
class: "ogrid-row-number-spacer",
|
|
3536
|
+
style: {
|
|
3537
|
+
width: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
3538
|
+
position: "sticky",
|
|
3539
|
+
left: hasCheckboxCol ? `${CHECKBOX_COLUMN_WIDTH}px` : "0",
|
|
3540
|
+
zIndex: 3
|
|
3541
|
+
}
|
|
3542
|
+
})
|
|
3543
|
+
] : [],
|
|
3544
|
+
// Header cells
|
|
3545
|
+
...row.map((cell, cellIdx) => {
|
|
3546
|
+
if (cell.isGroup) {
|
|
3547
|
+
return h("th", {
|
|
3548
|
+
key: cellIdx,
|
|
3549
|
+
colSpan: cell.colSpan,
|
|
3550
|
+
scope: "colgroup",
|
|
3551
|
+
class: "ogrid-column-group-header"
|
|
3552
|
+
}, cell.label);
|
|
3553
|
+
}
|
|
3554
|
+
if (!cell.columnDef) return null;
|
|
3555
|
+
const col = cell.columnDef;
|
|
3556
|
+
const { classes: headerClasses, style: headerStyle } = getHeaderClassAndStyle(col);
|
|
3557
|
+
return h("th", {
|
|
3558
|
+
key: col.columnId,
|
|
3559
|
+
scope: "col",
|
|
3560
|
+
"data-column-id": col.columnId,
|
|
3561
|
+
rowSpan: headerRows.length > 1 ? headerRows.length - rowIdx : void 0,
|
|
3562
|
+
class: headerClasses,
|
|
3563
|
+
style: headerStyle,
|
|
3564
|
+
onMousedown: (e) => handleReorderMouseDown(col.columnId, e)
|
|
3565
|
+
}, [
|
|
3566
|
+
h("div", { class: "ogrid-header-content" }, [
|
|
3567
|
+
h(ui.ColumnHeaderFilter, getHeaderFilterConfig(col, headerFilterInput)),
|
|
3568
|
+
h("button", {
|
|
3569
|
+
onClick: (e) => {
|
|
3570
|
+
e.stopPropagation();
|
|
3571
|
+
headerMenu.open(col.columnId, e.currentTarget);
|
|
3572
|
+
},
|
|
3573
|
+
"aria-label": "Column options",
|
|
3574
|
+
title: "Column options",
|
|
3575
|
+
class: "ogrid-column-menu-btn"
|
|
3576
|
+
}, "\u22EE")
|
|
3577
|
+
]),
|
|
3578
|
+
h("div", {
|
|
3579
|
+
onMousedown: (e) => {
|
|
3580
|
+
setActiveCell(null);
|
|
3581
|
+
setSelectionRange(null);
|
|
3582
|
+
wrapperRef.value?.focus({ preventScroll: true });
|
|
3583
|
+
e.stopPropagation();
|
|
3584
|
+
handleResizeStart(e, col);
|
|
3585
|
+
},
|
|
3586
|
+
class: "ogrid-resize-handle"
|
|
3587
|
+
})
|
|
3588
|
+
]);
|
|
3589
|
+
})
|
|
3590
|
+
])
|
|
3591
|
+
)
|
|
3592
|
+
),
|
|
3593
|
+
// Body
|
|
3594
|
+
...!showEmptyInGrid ? [
|
|
3595
|
+
h("tbody", {}, (() => {
|
|
3596
|
+
const vsEnabled = virtualScrollEnabled.value;
|
|
3597
|
+
const vr = visibleRange.value;
|
|
3598
|
+
const startIdx = vsEnabled ? vr.startIndex : 0;
|
|
3599
|
+
const endIdx = vsEnabled ? Math.min(vr.endIndex, items.length - 1) : items.length - 1;
|
|
3600
|
+
const rows = [];
|
|
3601
|
+
if (vsEnabled && vr.offsetTop > 0) {
|
|
3602
|
+
rows.push(h("tr", { key: "__vs-top", style: { height: `${vr.offsetTop}px` } }));
|
|
3603
|
+
}
|
|
3604
|
+
for (let rowIndex = startIdx; rowIndex <= endIdx; rowIndex++) {
|
|
3605
|
+
const item = items[rowIndex];
|
|
3606
|
+
if (!item) continue;
|
|
3607
|
+
const rowIdStr = getRowId(item);
|
|
3608
|
+
const isSelected = selectedRowIds.has(rowIdStr);
|
|
3609
|
+
rows.push(h("tr", {
|
|
3610
|
+
key: rowIdStr,
|
|
3611
|
+
"data-row-id": rowIdStr,
|
|
3612
|
+
onClick: handleSingleRowClick,
|
|
3613
|
+
style: { cursor: rowSelection === "single" ? "pointer" : void 0 }
|
|
3614
|
+
}, [
|
|
3615
|
+
// Checkbox cell
|
|
3616
|
+
...hasCheckboxCol ? [
|
|
3617
|
+
h(
|
|
3618
|
+
"td",
|
|
3619
|
+
{
|
|
3620
|
+
class: "ogrid-checkbox-cell",
|
|
3621
|
+
style: {
|
|
3622
|
+
width: `${CHECKBOX_COLUMN_WIDTH}px`,
|
|
3623
|
+
minWidth: `${CHECKBOX_COLUMN_WIDTH}px`,
|
|
3624
|
+
maxWidth: `${CHECKBOX_COLUMN_WIDTH}px`
|
|
3625
|
+
}
|
|
3626
|
+
},
|
|
3627
|
+
h(
|
|
3628
|
+
"div",
|
|
3629
|
+
{
|
|
3630
|
+
"data-row-index": rowIndex,
|
|
3631
|
+
"data-col-index": 0,
|
|
3632
|
+
onClick: stopPropagation,
|
|
3633
|
+
class: "ogrid-checkbox-wrapper"
|
|
3634
|
+
},
|
|
3635
|
+
ui.renderCheckbox({
|
|
3636
|
+
modelValue: isSelected,
|
|
3637
|
+
ariaLabel: `Select row ${rowIndex + 1}`,
|
|
3638
|
+
onChange: (checked) => handleRowCheckboxChange(rowIdStr, checked, rowIndex, lastMouseShift.value)
|
|
3639
|
+
})
|
|
3640
|
+
)
|
|
3641
|
+
)
|
|
3642
|
+
] : [],
|
|
3643
|
+
// Row numbers cell
|
|
3644
|
+
...hasRowNumbersCol ? [
|
|
3645
|
+
h("td", {
|
|
3646
|
+
class: "ogrid-row-number-cell",
|
|
3647
|
+
style: {
|
|
3648
|
+
width: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
3649
|
+
minWidth: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
3650
|
+
maxWidth: `${ROW_NUMBER_COLUMN_WIDTH}px`,
|
|
3651
|
+
padding: "6px",
|
|
3652
|
+
position: "sticky",
|
|
3653
|
+
left: hasCheckboxCol ? `${CHECKBOX_COLUMN_WIDTH}px` : "0",
|
|
3654
|
+
zIndex: 2
|
|
3655
|
+
}
|
|
3656
|
+
}, String(rowNumberOffset + rowIndex + 1))
|
|
3657
|
+
] : [],
|
|
3658
|
+
// Data cells
|
|
3659
|
+
...columnLayouts.map(
|
|
3660
|
+
(cl, colIdx) => h("td", {
|
|
3661
|
+
key: cl.col.columnId,
|
|
3662
|
+
"data-column-id": cl.col.columnId,
|
|
3663
|
+
class: cl.tdClasses,
|
|
3664
|
+
style: cl.tdDynamicStyle
|
|
3665
|
+
}, [renderCellContent(item, cl.col, rowIndex, colIdx)])
|
|
3666
|
+
)
|
|
3667
|
+
]));
|
|
3668
|
+
}
|
|
3669
|
+
if (vsEnabled && vr.offsetBottom > 0) {
|
|
3670
|
+
rows.push(h("tr", { key: "__vs-bottom", style: { height: `${vr.offsetBottom}px` } }));
|
|
3671
|
+
}
|
|
3672
|
+
return rows;
|
|
3673
|
+
})())
|
|
3674
|
+
] : []
|
|
3675
|
+
]),
|
|
3676
|
+
// Empty state
|
|
3677
|
+
...showEmptyInGrid && p.emptyState ? [
|
|
3678
|
+
ui.renderEmptyState(p.emptyState)
|
|
3679
|
+
] : []
|
|
3680
|
+
])
|
|
3681
|
+
])
|
|
3682
|
+
])
|
|
3683
|
+
]),
|
|
3684
|
+
// Context menu (teleported to body)
|
|
3685
|
+
...menuPosition ? [
|
|
3686
|
+
h(
|
|
3687
|
+
Teleport,
|
|
3688
|
+
{ to: "body" },
|
|
3689
|
+
h(ui.GridContextMenu, {
|
|
3690
|
+
x: menuPosition.x,
|
|
3691
|
+
y: menuPosition.y,
|
|
3692
|
+
hasSelection: hasCellSelection,
|
|
3693
|
+
canUndo,
|
|
3694
|
+
canRedo,
|
|
3695
|
+
onUndo: onUndo ?? NOOP2,
|
|
3696
|
+
onRedo: onRedo ?? NOOP2,
|
|
3697
|
+
onCopy: handleCopy,
|
|
3698
|
+
onCut: handleCut,
|
|
3699
|
+
onPaste: () => {
|
|
3700
|
+
void handlePaste();
|
|
3701
|
+
},
|
|
3702
|
+
onSelectAll: handleSelectAllCells,
|
|
3703
|
+
onClose: closeContextMenu
|
|
3704
|
+
})
|
|
3705
|
+
)
|
|
3706
|
+
] : [],
|
|
3707
|
+
// Marching ants overlay
|
|
3708
|
+
h(MarchingAntsOverlay, {
|
|
3709
|
+
containerRef: tableContainerRef,
|
|
3710
|
+
selectionRange,
|
|
3711
|
+
copyRange: _copyRange,
|
|
3712
|
+
cutRange: _cutRange,
|
|
3713
|
+
colOffset: _colOffset,
|
|
3714
|
+
items,
|
|
3715
|
+
visibleColumns: p.visibleColumns instanceof Set ? Array.from(p.visibleColumns) : p.visibleColumns,
|
|
3716
|
+
columnSizingOverrides: layout.columnSizingOverrides,
|
|
3717
|
+
columnOrder: p.columnOrder
|
|
3718
|
+
}),
|
|
3719
|
+
// Column header menu
|
|
3720
|
+
h(ui.ColumnHeaderMenu, {
|
|
3721
|
+
isOpen: headerMenu.isOpen,
|
|
3722
|
+
anchorElement: headerMenu.anchorElement,
|
|
3723
|
+
onClose: headerMenu.close,
|
|
3724
|
+
onPinLeft: headerMenu.handlePinLeft,
|
|
3725
|
+
onPinRight: headerMenu.handlePinRight,
|
|
3726
|
+
onUnpin: headerMenu.handleUnpin,
|
|
3727
|
+
onSortAsc: headerMenu.handleSortAsc,
|
|
3728
|
+
onSortDesc: headerMenu.handleSortDesc,
|
|
3729
|
+
onClearSort: headerMenu.handleClearSort,
|
|
3730
|
+
onAutosizeThis: headerMenu.handleAutosizeThis,
|
|
3731
|
+
onAutosizeAll: headerMenu.handleAutosizeAll,
|
|
3732
|
+
canPinLeft: headerMenu.canPinLeft,
|
|
3733
|
+
canPinRight: headerMenu.canPinRight,
|
|
3734
|
+
canUnpin: headerMenu.canUnpin,
|
|
3735
|
+
currentSort: headerMenu.currentSort,
|
|
3736
|
+
isSortable: headerMenu.isSortable,
|
|
3737
|
+
isResizable: headerMenu.isResizable
|
|
3738
|
+
}),
|
|
3739
|
+
// Status bar
|
|
3740
|
+
...statusBarConfig ? [
|
|
3741
|
+
h(StatusBar, {
|
|
3742
|
+
totalCount: statusBarConfig.totalCount,
|
|
3743
|
+
filteredCount: statusBarConfig.filteredCount,
|
|
3744
|
+
selectedCount: statusBarConfig.selectedCount ?? selectedRowIds.size,
|
|
3745
|
+
selectedCellCount: selectionRange ? (Math.abs(selectionRange.endRow - selectionRange.startRow) + 1) * (Math.abs(selectionRange.endCol - selectionRange.startCol) + 1) : void 0,
|
|
3746
|
+
aggregation: statusBarConfig.aggregation,
|
|
3747
|
+
suppressRowCount: statusBarConfig.suppressRowCount
|
|
3748
|
+
})
|
|
3749
|
+
] : [],
|
|
3750
|
+
// Loading overlay
|
|
3751
|
+
...isLoading ? [
|
|
3752
|
+
h(
|
|
3753
|
+
"div",
|
|
3754
|
+
{ class: "ogrid-loading-overlay" },
|
|
3755
|
+
ui.renderSpinner(loadingMessage)
|
|
3756
|
+
)
|
|
3757
|
+
] : []
|
|
3758
|
+
]);
|
|
3759
|
+
};
|
|
3760
|
+
}
|
|
3761
|
+
});
|
|
3762
|
+
}
|
|
3763
|
+
var editorWrapperStyle = {
|
|
3764
|
+
width: "100%",
|
|
3765
|
+
height: "100%",
|
|
3766
|
+
display: "flex",
|
|
3767
|
+
alignItems: "center",
|
|
3768
|
+
padding: "0 2px",
|
|
3769
|
+
boxSizing: "border-box"
|
|
3770
|
+
};
|
|
3771
|
+
function createInlineCellEditor(options) {
|
|
3772
|
+
const { renderCheckbox, renderDatePicker } = options;
|
|
3773
|
+
return defineComponent({
|
|
3774
|
+
name: "InlineCellEditor",
|
|
3775
|
+
props: {
|
|
3776
|
+
value: { default: void 0 },
|
|
3777
|
+
item: { type: Object, required: true },
|
|
3778
|
+
column: { type: Object, required: true },
|
|
3779
|
+
rowIndex: { type: Number, required: true },
|
|
3780
|
+
editorType: { type: String, required: true },
|
|
3781
|
+
onCommit: { type: Function, required: true },
|
|
3782
|
+
onCancel: { type: Function, required: true }
|
|
3783
|
+
},
|
|
3784
|
+
setup(props) {
|
|
3785
|
+
const inputRef = ref(null);
|
|
3786
|
+
const selectWrapperRef = ref(null);
|
|
3787
|
+
const selectDropdownRef = ref(null);
|
|
3788
|
+
const localValue = ref(props.value);
|
|
3789
|
+
const highlightedIndex = ref(0);
|
|
3790
|
+
const positionDropdown = () => {
|
|
3791
|
+
const wrapper = selectWrapperRef.value;
|
|
3792
|
+
const dropdown = selectDropdownRef.value;
|
|
3793
|
+
if (!wrapper || !dropdown) return;
|
|
3794
|
+
const rect = wrapper.getBoundingClientRect();
|
|
3795
|
+
const maxH = 200;
|
|
3796
|
+
const spaceBelow = window.innerHeight - rect.bottom;
|
|
3797
|
+
const flipUp = spaceBelow < maxH && rect.top > spaceBelow;
|
|
3798
|
+
dropdown.style.position = "fixed";
|
|
3799
|
+
dropdown.style.left = `${rect.left}px`;
|
|
3800
|
+
dropdown.style.width = `${rect.width}px`;
|
|
3801
|
+
dropdown.style.maxHeight = `${maxH}px`;
|
|
3802
|
+
dropdown.style.zIndex = "9999";
|
|
3803
|
+
dropdown.style.right = "auto";
|
|
3804
|
+
if (flipUp) {
|
|
3805
|
+
dropdown.style.top = "auto";
|
|
3806
|
+
dropdown.style.bottom = `${window.innerHeight - rect.top}px`;
|
|
3807
|
+
} else {
|
|
3808
|
+
dropdown.style.top = `${rect.bottom}px`;
|
|
3809
|
+
}
|
|
3810
|
+
};
|
|
3811
|
+
onMounted(() => {
|
|
3812
|
+
nextTick(() => {
|
|
3813
|
+
if (selectWrapperRef.value) {
|
|
3814
|
+
selectWrapperRef.value.focus();
|
|
3815
|
+
positionDropdown();
|
|
3816
|
+
return;
|
|
3817
|
+
}
|
|
3818
|
+
inputRef.value?.focus();
|
|
3819
|
+
inputRef.value?.select();
|
|
3820
|
+
});
|
|
3821
|
+
});
|
|
3822
|
+
watch(() => props.value, (v) => {
|
|
3823
|
+
localValue.value = v;
|
|
3824
|
+
});
|
|
3825
|
+
const initHighlightedIndex = () => {
|
|
3826
|
+
const values = props.column.cellEditorParams?.values ?? [];
|
|
3827
|
+
const idx = values.findIndex((v) => String(v) === String(props.value));
|
|
3828
|
+
highlightedIndex.value = Math.max(idx, 0);
|
|
3829
|
+
};
|
|
3830
|
+
initHighlightedIndex();
|
|
3831
|
+
const scrollHighlightedIntoView = () => {
|
|
3832
|
+
nextTick(() => {
|
|
3833
|
+
const dropdown = selectDropdownRef.value;
|
|
3834
|
+
if (!dropdown) return;
|
|
3835
|
+
const highlighted = dropdown.children[highlightedIndex.value];
|
|
3836
|
+
highlighted?.scrollIntoView({ block: "nearest" });
|
|
3837
|
+
});
|
|
3838
|
+
};
|
|
3839
|
+
const getDisplayText = (value) => {
|
|
3840
|
+
const formatValue = props.column.cellEditorParams?.formatValue;
|
|
3841
|
+
if (formatValue) return formatValue(value);
|
|
3842
|
+
return value != null ? String(value) : "";
|
|
3843
|
+
};
|
|
3844
|
+
const handleSelectKeyDown = (e) => {
|
|
3845
|
+
const values = props.column.cellEditorParams?.values ?? [];
|
|
3846
|
+
switch (e.key) {
|
|
3847
|
+
case "ArrowDown":
|
|
3848
|
+
e.preventDefault();
|
|
3849
|
+
highlightedIndex.value = Math.min(highlightedIndex.value + 1, values.length - 1);
|
|
3850
|
+
scrollHighlightedIntoView();
|
|
3851
|
+
break;
|
|
3852
|
+
case "ArrowUp":
|
|
3853
|
+
e.preventDefault();
|
|
3854
|
+
highlightedIndex.value = Math.max(highlightedIndex.value - 1, 0);
|
|
3855
|
+
scrollHighlightedIntoView();
|
|
3856
|
+
break;
|
|
3857
|
+
case "Enter":
|
|
3858
|
+
e.preventDefault();
|
|
3859
|
+
e.stopPropagation();
|
|
3860
|
+
if (values.length > 0 && highlightedIndex.value < values.length) {
|
|
3861
|
+
props.onCommit(values[highlightedIndex.value]);
|
|
3862
|
+
}
|
|
3863
|
+
break;
|
|
3864
|
+
case "Tab":
|
|
3865
|
+
e.preventDefault();
|
|
3866
|
+
if (values.length > 0 && highlightedIndex.value < values.length) {
|
|
3867
|
+
props.onCommit(values[highlightedIndex.value]);
|
|
3868
|
+
}
|
|
3869
|
+
break;
|
|
3870
|
+
case "Escape":
|
|
3871
|
+
e.preventDefault();
|
|
3872
|
+
e.stopPropagation();
|
|
3873
|
+
props.onCancel();
|
|
3874
|
+
break;
|
|
3875
|
+
}
|
|
3876
|
+
};
|
|
3877
|
+
return () => {
|
|
3878
|
+
if (props.editorType === "checkbox") {
|
|
3879
|
+
const checked = !!props.value;
|
|
3880
|
+
return h(
|
|
3881
|
+
"div",
|
|
3882
|
+
{ style: { ...editorWrapperStyle, justifyContent: "center" } },
|
|
3883
|
+
renderCheckbox({
|
|
3884
|
+
checked,
|
|
3885
|
+
onChange: (c) => props.onCommit(c),
|
|
3886
|
+
onCancel: props.onCancel
|
|
3887
|
+
})
|
|
3888
|
+
);
|
|
3889
|
+
}
|
|
3890
|
+
if (props.editorType === "select") {
|
|
3891
|
+
const values = props.column.cellEditorParams?.values ?? [];
|
|
3892
|
+
return h("div", {
|
|
3893
|
+
ref: (el) => {
|
|
3894
|
+
selectWrapperRef.value = el;
|
|
3895
|
+
},
|
|
3896
|
+
tabindex: 0,
|
|
3897
|
+
style: { ...editorWrapperStyle, position: "relative" },
|
|
3898
|
+
onKeydown: handleSelectKeyDown
|
|
3899
|
+
}, [
|
|
3900
|
+
h("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%", cursor: "pointer", fontSize: "13px", color: "inherit" } }, [
|
|
3901
|
+
h("span", getDisplayText(props.value)),
|
|
3902
|
+
h("span", { style: { marginLeft: "4px", fontSize: "10px", opacity: "0.5" } }, "\u25BE")
|
|
3903
|
+
]),
|
|
3904
|
+
h("div", {
|
|
3905
|
+
ref: (el) => {
|
|
3906
|
+
selectDropdownRef.value = el;
|
|
3907
|
+
},
|
|
3908
|
+
role: "listbox",
|
|
3909
|
+
style: { position: "absolute", top: "100%", left: "0", right: "0", maxHeight: "200px", overflowY: "auto", background: "var(--ogrid-bg, #fff)", border: "1px solid var(--ogrid-border, rgba(0,0,0,0.12))", zIndex: "10", boxShadow: "0 4px 16px rgba(0,0,0,0.2)" }
|
|
3910
|
+
}, values.map(
|
|
3911
|
+
(v, i) => h("div", {
|
|
3912
|
+
key: String(v),
|
|
3913
|
+
role: "option",
|
|
3914
|
+
"aria-selected": i === highlightedIndex.value,
|
|
3915
|
+
onClick: () => props.onCommit(v),
|
|
3916
|
+
style: { padding: "6px 8px", cursor: "pointer", color: "var(--ogrid-fg, #242424)", ...i === highlightedIndex.value ? { background: "var(--ogrid-bg-hover, #e8f0fe)" } : {} }
|
|
3917
|
+
}, getDisplayText(v))
|
|
3918
|
+
))
|
|
3919
|
+
]);
|
|
3920
|
+
}
|
|
3921
|
+
if (props.editorType === "date") {
|
|
3922
|
+
let dateStr = "";
|
|
3923
|
+
if (localValue.value) {
|
|
3924
|
+
const d = new Date(String(localValue.value));
|
|
3925
|
+
if (!Number.isNaN(d.getTime())) {
|
|
3926
|
+
dateStr = d.toISOString().slice(0, 10);
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
return h(
|
|
3930
|
+
"div",
|
|
3931
|
+
{ style: editorWrapperStyle },
|
|
3932
|
+
renderDatePicker({
|
|
3933
|
+
value: dateStr,
|
|
3934
|
+
onChange: (val) => props.onCommit(val),
|
|
3935
|
+
onCancel: props.onCancel
|
|
3936
|
+
})
|
|
3937
|
+
);
|
|
3938
|
+
}
|
|
3939
|
+
return h(
|
|
3940
|
+
"div",
|
|
3941
|
+
{ style: editorWrapperStyle },
|
|
3942
|
+
h("input", {
|
|
3943
|
+
ref: (el) => {
|
|
3944
|
+
inputRef.value = el;
|
|
3945
|
+
},
|
|
3946
|
+
type: "text",
|
|
3947
|
+
value: localValue.value != null ? String(localValue.value) : "",
|
|
3948
|
+
style: { width: "100%", height: "100%", border: "none", outline: "none", padding: "0 4px", fontSize: "inherit", boxSizing: "border-box" },
|
|
3949
|
+
onInput: (e) => {
|
|
3950
|
+
localValue.value = e.target.value;
|
|
3951
|
+
},
|
|
3952
|
+
onKeydown: (e) => {
|
|
3953
|
+
if (e.key === "Enter") {
|
|
3954
|
+
e.preventDefault();
|
|
3955
|
+
props.onCommit(localValue.value);
|
|
3956
|
+
}
|
|
3957
|
+
if (e.key === "Escape") {
|
|
3958
|
+
e.preventDefault();
|
|
3959
|
+
props.onCancel();
|
|
3960
|
+
}
|
|
3961
|
+
if (e.key === "Tab") {
|
|
3962
|
+
e.preventDefault();
|
|
3963
|
+
props.onCommit(localValue.value);
|
|
3964
|
+
}
|
|
3965
|
+
},
|
|
3966
|
+
onBlur: () => props.onCommit(localValue.value)
|
|
3967
|
+
})
|
|
3968
|
+
);
|
|
3969
|
+
};
|
|
3970
|
+
}
|
|
3971
|
+
});
|
|
3972
|
+
}
|
|
3973
|
+
var PANEL_WIDTH = 240;
|
|
3974
|
+
var TAB_WIDTH = 36;
|
|
3975
|
+
var PANEL_LABELS = {
|
|
3976
|
+
columns: "Columns",
|
|
3977
|
+
filters: "Filters"
|
|
3978
|
+
};
|
|
3979
|
+
var PANEL_ICONS = {
|
|
3980
|
+
columns: "\u2261",
|
|
3981
|
+
// hamburger icon
|
|
3982
|
+
filters: "\u2A65"
|
|
3983
|
+
// filter icon
|
|
3984
|
+
};
|
|
3985
|
+
function renderSideBar(sb) {
|
|
3986
|
+
const isOpen = sb.activePanel !== null;
|
|
3987
|
+
const position = sb.position ?? "right";
|
|
3988
|
+
const tabStripStyle = {
|
|
3989
|
+
display: "flex",
|
|
3990
|
+
flexDirection: "column",
|
|
3991
|
+
width: `${TAB_WIDTH}px`,
|
|
3992
|
+
background: "var(--ogrid-header-bg, #f5f5f5)",
|
|
3993
|
+
...position === "right" ? { borderLeft: "1px solid var(--ogrid-border, #e0e0e0)" } : { borderRight: "1px solid var(--ogrid-border, #e0e0e0)" }
|
|
3994
|
+
};
|
|
3995
|
+
const tabStrip = h(
|
|
3996
|
+
"div",
|
|
3997
|
+
{ style: tabStripStyle, role: "tablist", "aria-label": "Side bar tabs" },
|
|
3998
|
+
sb.panels.map(
|
|
3999
|
+
(panel) => h("button", {
|
|
4000
|
+
key: panel,
|
|
4001
|
+
role: "tab",
|
|
4002
|
+
"aria-selected": sb.activePanel === panel,
|
|
4003
|
+
"aria-label": PANEL_LABELS[panel],
|
|
4004
|
+
title: PANEL_LABELS[panel],
|
|
4005
|
+
onClick: () => sb.onPanelChange(sb.activePanel === panel ? null : panel),
|
|
4006
|
+
style: {
|
|
4007
|
+
width: `${TAB_WIDTH}px`,
|
|
4008
|
+
height: `${TAB_WIDTH}px`,
|
|
4009
|
+
border: "none",
|
|
4010
|
+
cursor: "pointer",
|
|
4011
|
+
color: "var(--ogrid-fg, #242424)",
|
|
4012
|
+
fontSize: "14px",
|
|
4013
|
+
display: "flex",
|
|
4014
|
+
alignItems: "center",
|
|
4015
|
+
justifyContent: "center",
|
|
4016
|
+
background: sb.activePanel === panel ? "var(--ogrid-bg, #fff)" : "transparent",
|
|
4017
|
+
fontWeight: sb.activePanel === panel ? "bold" : "normal"
|
|
4018
|
+
}
|
|
4019
|
+
}, PANEL_ICONS[panel])
|
|
4020
|
+
)
|
|
4021
|
+
);
|
|
4022
|
+
let panelContent = null;
|
|
4023
|
+
if (isOpen && sb.activePanel) {
|
|
4024
|
+
const panelContainerStyle = {
|
|
4025
|
+
width: `${PANEL_WIDTH}px`,
|
|
4026
|
+
display: "flex",
|
|
4027
|
+
flexDirection: "column",
|
|
4028
|
+
overflow: "hidden",
|
|
4029
|
+
background: "var(--ogrid-bg, #fff)",
|
|
4030
|
+
color: "var(--ogrid-fg, #242424)",
|
|
4031
|
+
...position === "right" ? { borderLeft: "1px solid var(--ogrid-border, #e0e0e0)" } : { borderRight: "1px solid var(--ogrid-border, #e0e0e0)" }
|
|
4032
|
+
};
|
|
4033
|
+
const panelBodyChildren = [];
|
|
4034
|
+
if (sb.activePanel === "columns") {
|
|
4035
|
+
const allVisible = sb.columns.every((c) => sb.visibleColumns.has(c.columnId));
|
|
4036
|
+
panelBodyChildren.push(
|
|
4037
|
+
h("div", { style: { display: "flex", gap: "8px", marginBottom: "8px" } }, [
|
|
4038
|
+
h("button", {
|
|
4039
|
+
disabled: allVisible,
|
|
4040
|
+
onClick: () => {
|
|
4041
|
+
const next = new Set(sb.visibleColumns);
|
|
4042
|
+
sb.columns.forEach((c) => next.add(c.columnId));
|
|
4043
|
+
sb.onSetVisibleColumns(next);
|
|
4044
|
+
},
|
|
4045
|
+
style: { flex: "1", cursor: "pointer", background: "var(--ogrid-bg-subtle, #f3f2f1)", color: "var(--ogrid-fg, #242424)", border: "1px solid var(--ogrid-border, #e0e0e0)", borderRadius: "4px", padding: "4px 8px" }
|
|
4046
|
+
}, "Select All"),
|
|
4047
|
+
h("button", {
|
|
4048
|
+
onClick: () => {
|
|
4049
|
+
const next = /* @__PURE__ */ new Set();
|
|
4050
|
+
sb.columns.forEach((c) => {
|
|
4051
|
+
if (c.required && sb.visibleColumns.has(c.columnId)) next.add(c.columnId);
|
|
4052
|
+
});
|
|
4053
|
+
sb.onSetVisibleColumns(next);
|
|
4054
|
+
},
|
|
4055
|
+
style: { flex: "1", cursor: "pointer", background: "var(--ogrid-bg-subtle, #f3f2f1)", color: "var(--ogrid-fg, #242424)", border: "1px solid var(--ogrid-border, #e0e0e0)", borderRadius: "4px", padding: "4px 8px" }
|
|
4056
|
+
}, "Clear All")
|
|
4057
|
+
])
|
|
4058
|
+
);
|
|
4059
|
+
sb.columns.forEach((col) => {
|
|
4060
|
+
panelBodyChildren.push(
|
|
4061
|
+
h("label", { key: col.columnId, style: { display: "flex", alignItems: "center", gap: "6px", padding: "2px 0", cursor: "pointer" } }, [
|
|
4062
|
+
h("input", {
|
|
4063
|
+
type: "checkbox",
|
|
4064
|
+
checked: sb.visibleColumns.has(col.columnId),
|
|
4065
|
+
disabled: col.required,
|
|
4066
|
+
onChange: (e) => sb.onVisibilityChange(col.columnId, e.target.checked)
|
|
4067
|
+
}),
|
|
4068
|
+
h("span", null, col.name)
|
|
4069
|
+
])
|
|
4070
|
+
);
|
|
4071
|
+
});
|
|
4072
|
+
}
|
|
4073
|
+
if (sb.activePanel === "filters") {
|
|
4074
|
+
if (sb.filterableColumns.length === 0) {
|
|
4075
|
+
panelBodyChildren.push(
|
|
4076
|
+
h("div", { style: { color: "var(--ogrid-muted, #999)", fontStyle: "italic" } }, "No filterable columns")
|
|
4077
|
+
);
|
|
4078
|
+
} else {
|
|
4079
|
+
sb.filterableColumns.forEach((col) => {
|
|
4080
|
+
const filterKey = col.filterField;
|
|
4081
|
+
const groupChildren = [
|
|
4082
|
+
h("div", { style: { fontWeight: "500", marginBottom: "4px", fontSize: "13px" } }, col.name)
|
|
4083
|
+
];
|
|
4084
|
+
if (col.filterType === "text") {
|
|
4085
|
+
const filterEntry = sb.filters[filterKey];
|
|
4086
|
+
const currentVal = filterEntry?.type === "text" ? filterEntry.value : "";
|
|
4087
|
+
groupChildren.push(
|
|
4088
|
+
h("input", {
|
|
4089
|
+
type: "text",
|
|
4090
|
+
value: currentVal,
|
|
4091
|
+
onInput: (e) => {
|
|
4092
|
+
const val = e.target.value;
|
|
4093
|
+
sb.onFilterChange(filterKey, val ? { type: "text", value: val } : void 0);
|
|
4094
|
+
},
|
|
4095
|
+
placeholder: `Filter ${col.name}...`,
|
|
4096
|
+
"aria-label": `Filter ${col.name}`,
|
|
4097
|
+
style: { width: "100%", boxSizing: "border-box", padding: "4px 6px", background: "var(--ogrid-bg, #fff)", color: "var(--ogrid-fg, #242424)", border: "1px solid var(--ogrid-border, #e0e0e0)", borderRadius: "4px" }
|
|
4098
|
+
})
|
|
4099
|
+
);
|
|
4100
|
+
}
|
|
4101
|
+
if (col.filterType === "multiSelect") {
|
|
4102
|
+
const options = sb.filterOptions[filterKey] ?? [];
|
|
4103
|
+
const msChildren = options.map((opt) => {
|
|
4104
|
+
const msFilter = sb.filters[filterKey];
|
|
4105
|
+
const selected = msFilter?.type === "multiSelect" ? msFilter.value.includes(opt) : false;
|
|
4106
|
+
return h("label", { key: opt, style: { display: "flex", alignItems: "center", gap: "4px", padding: "1px 0", cursor: "pointer", fontSize: "13px" } }, [
|
|
4107
|
+
h("input", {
|
|
4108
|
+
type: "checkbox",
|
|
4109
|
+
checked: selected,
|
|
4110
|
+
onChange: (e) => {
|
|
4111
|
+
const curFilter = sb.filters[filterKey];
|
|
4112
|
+
const current = curFilter?.type === "multiSelect" ? curFilter.value : [];
|
|
4113
|
+
const next = e.target.checked ? [...current, opt] : current.filter((v) => v !== opt);
|
|
4114
|
+
sb.onFilterChange(filterKey, next.length > 0 ? { type: "multiSelect", value: next } : void 0);
|
|
4115
|
+
}
|
|
4116
|
+
}),
|
|
4117
|
+
h("span", null, opt)
|
|
4118
|
+
]);
|
|
4119
|
+
});
|
|
4120
|
+
groupChildren.push(
|
|
4121
|
+
h("div", { style: { maxHeight: "120px", overflowY: "auto" }, role: "group", "aria-label": `${col.name} options` }, msChildren)
|
|
4122
|
+
);
|
|
4123
|
+
}
|
|
4124
|
+
if (col.filterType === "date") {
|
|
4125
|
+
const dateFilter = sb.filters[filterKey];
|
|
4126
|
+
const existingValue = dateFilter?.type === "date" ? dateFilter.value : { from: void 0, to: void 0 };
|
|
4127
|
+
groupChildren.push(
|
|
4128
|
+
h("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } }, [
|
|
4129
|
+
h("label", { style: { display: "flex", alignItems: "center", gap: "4px", fontSize: "12px" } }, [
|
|
4130
|
+
"From:",
|
|
4131
|
+
h("input", {
|
|
4132
|
+
type: "date",
|
|
4133
|
+
value: existingValue.from ?? "",
|
|
4134
|
+
onInput: (e) => {
|
|
4135
|
+
const from = e.target.value || void 0;
|
|
4136
|
+
const to = existingValue.to;
|
|
4137
|
+
sb.onFilterChange(filterKey, from || to ? { type: "date", value: { from, to } } : void 0);
|
|
4138
|
+
},
|
|
4139
|
+
"aria-label": `${col.name} from date`,
|
|
4140
|
+
style: { flex: "1", padding: "2px 4px", background: "var(--ogrid-bg, #fff)", color: "var(--ogrid-fg, #242424)", border: "1px solid var(--ogrid-border, #e0e0e0)", borderRadius: "4px" }
|
|
4141
|
+
})
|
|
4142
|
+
]),
|
|
4143
|
+
h("label", { style: { display: "flex", alignItems: "center", gap: "4px", fontSize: "12px" } }, [
|
|
4144
|
+
"To:",
|
|
4145
|
+
h("input", {
|
|
4146
|
+
type: "date",
|
|
4147
|
+
value: existingValue.to ?? "",
|
|
4148
|
+
onInput: (e) => {
|
|
4149
|
+
const to = e.target.value || void 0;
|
|
4150
|
+
const from = existingValue.from;
|
|
4151
|
+
sb.onFilterChange(filterKey, from || to ? { type: "date", value: { from, to } } : void 0);
|
|
4152
|
+
},
|
|
4153
|
+
"aria-label": `${col.name} to date`,
|
|
4154
|
+
style: { flex: "1", padding: "2px 4px", background: "var(--ogrid-bg, #fff)", color: "var(--ogrid-fg, #242424)", border: "1px solid var(--ogrid-border, #e0e0e0)", borderRadius: "4px" }
|
|
4155
|
+
})
|
|
4156
|
+
])
|
|
4157
|
+
])
|
|
4158
|
+
);
|
|
4159
|
+
}
|
|
4160
|
+
panelBodyChildren.push(
|
|
4161
|
+
h("div", { key: col.columnId, style: { marginBottom: "12px" } }, groupChildren)
|
|
4162
|
+
);
|
|
4163
|
+
});
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
4166
|
+
panelContent = h("div", { role: "tabpanel", "aria-label": PANEL_LABELS[sb.activePanel], style: panelContainerStyle }, [
|
|
4167
|
+
// Panel header
|
|
4168
|
+
h("div", {
|
|
4169
|
+
style: {
|
|
4170
|
+
display: "flex",
|
|
4171
|
+
justifyContent: "space-between",
|
|
4172
|
+
alignItems: "center",
|
|
4173
|
+
padding: "8px 12px",
|
|
4174
|
+
borderBottom: "1px solid var(--ogrid-border, #e0e0e0)",
|
|
4175
|
+
fontWeight: "600"
|
|
4176
|
+
}
|
|
4177
|
+
}, [
|
|
4178
|
+
h("span", null, PANEL_LABELS[sb.activePanel]),
|
|
4179
|
+
h("button", {
|
|
4180
|
+
onClick: () => sb.onPanelChange(null),
|
|
4181
|
+
style: { border: "none", background: "transparent", cursor: "pointer", fontSize: "16px", color: "var(--ogrid-fg, #242424)" },
|
|
4182
|
+
"aria-label": "Close panel"
|
|
4183
|
+
}, "\xD7")
|
|
4184
|
+
]),
|
|
4185
|
+
// Panel body
|
|
4186
|
+
h("div", { style: { flex: "1", overflowY: "auto", padding: "8px 12px" } }, panelBodyChildren)
|
|
4187
|
+
]);
|
|
4188
|
+
}
|
|
4189
|
+
const children = [];
|
|
4190
|
+
if (position === "left") {
|
|
4191
|
+
children.push(tabStrip);
|
|
4192
|
+
if (panelContent) children.push(panelContent);
|
|
4193
|
+
} else {
|
|
4194
|
+
if (panelContent) children.push(panelContent);
|
|
4195
|
+
children.push(tabStrip);
|
|
4196
|
+
}
|
|
4197
|
+
return h("div", {
|
|
4198
|
+
style: { display: "flex", flexDirection: "row", flexShrink: "0" },
|
|
4199
|
+
role: "complementary",
|
|
4200
|
+
"aria-label": "Side bar"
|
|
4201
|
+
}, children);
|
|
4202
|
+
}
|
|
4203
|
+
function createOGrid(ui) {
|
|
4204
|
+
return defineComponent({
|
|
4205
|
+
name: "OGrid",
|
|
4206
|
+
props: {
|
|
4207
|
+
gridProps: { type: Object, required: true }
|
|
4208
|
+
},
|
|
4209
|
+
setup(props, { expose }) {
|
|
4210
|
+
const propsRef = computed(() => props.gridProps);
|
|
4211
|
+
const { dataGridProps, pagination, columnChooser, layout, api } = useOGrid(propsRef);
|
|
4212
|
+
expose({ api });
|
|
4213
|
+
const isFullScreen = ref(false);
|
|
4214
|
+
const toggleFullScreen = () => {
|
|
4215
|
+
isFullScreen.value = !isFullScreen.value;
|
|
4216
|
+
};
|
|
4217
|
+
const handleEscKey = (e) => {
|
|
4218
|
+
if (e.key === "Escape" && isFullScreen.value) isFullScreen.value = false;
|
|
4219
|
+
};
|
|
4220
|
+
onMounted(() => {
|
|
4221
|
+
document.addEventListener("keydown", handleEscKey);
|
|
4222
|
+
});
|
|
4223
|
+
onUnmounted(() => {
|
|
4224
|
+
document.removeEventListener("keydown", handleEscKey);
|
|
4225
|
+
});
|
|
4226
|
+
return () => {
|
|
4227
|
+
const sideBar = layout.value.sideBarProps;
|
|
4228
|
+
const hasSideBar = sideBar != null;
|
|
4229
|
+
const sideBarPosition = sideBar?.position ?? "right";
|
|
4230
|
+
const toolbarChildren = [];
|
|
4231
|
+
if (layout.value.toolbar) {
|
|
4232
|
+
toolbarChildren.push(layout.value.toolbar);
|
|
4233
|
+
}
|
|
4234
|
+
const showFullScreen = layout.value.fullScreen === true;
|
|
4235
|
+
const fullscreenButton = showFullScreen ? h("button", {
|
|
4236
|
+
type: "button",
|
|
4237
|
+
title: isFullScreen.value ? "Exit fullscreen" : "Fullscreen",
|
|
4238
|
+
"aria-label": isFullScreen.value ? "Exit fullscreen" : "Fullscreen",
|
|
4239
|
+
onClick: toggleFullScreen,
|
|
4240
|
+
style: {
|
|
4241
|
+
background: "none",
|
|
4242
|
+
border: "1px solid var(--ogrid-border, rgba(0,0,0,0.12))",
|
|
4243
|
+
borderRadius: "4px",
|
|
4244
|
+
padding: "4px 6px",
|
|
4245
|
+
cursor: "pointer",
|
|
4246
|
+
display: "flex",
|
|
4247
|
+
alignItems: "center",
|
|
4248
|
+
justifyContent: "center",
|
|
4249
|
+
color: "var(--ogrid-fg, rgba(0,0,0,0.87))"
|
|
4250
|
+
}
|
|
4251
|
+
}, [
|
|
4252
|
+
isFullScreen.value ? h("svg", { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round", innerHTML: '<polyline points="4 10 0 10 0 14"/><polyline points="12 6 16 6 16 2"/><line x1="0" y1="10" x2="4" y2="6"/><line x1="16" y1="6" x2="12" y2="10"/>' }) : h("svg", { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round", innerHTML: '<polyline points="10 2 14 2 14 6"/><polyline points="6 14 2 14 2 10"/><line x1="14" y1="2" x2="10" y2="6"/><line x1="2" y1="14" x2="6" y2="10"/>' })
|
|
4253
|
+
]) : null;
|
|
4254
|
+
const toolbarEnd = columnChooser.value.placement === "toolbar" ? h(ui.ColumnChooser, {
|
|
4255
|
+
columns: columnChooser.value.columns,
|
|
4256
|
+
visibleColumns: columnChooser.value.visibleColumns,
|
|
4257
|
+
onVisibilityChange: columnChooser.value.onVisibilityChange
|
|
4258
|
+
}) : null;
|
|
4259
|
+
const paginationNode = h(ui.PaginationControls, {
|
|
4260
|
+
currentPage: pagination.value.page,
|
|
4261
|
+
pageSize: pagination.value.pageSize,
|
|
4262
|
+
totalCount: pagination.value.displayTotalCount,
|
|
4263
|
+
onPageChange: pagination.value.setPage,
|
|
4264
|
+
onPageSizeChange: (size) => {
|
|
4265
|
+
pagination.value.setPageSize(size);
|
|
4266
|
+
},
|
|
4267
|
+
pageSizeOptions: pagination.value.pageSizeOptions,
|
|
4268
|
+
entityLabelPlural: pagination.value.entityLabelPlural
|
|
4269
|
+
});
|
|
4270
|
+
const gridChild = h("div", {
|
|
4271
|
+
style: { flex: "1", minWidth: "0", minHeight: "0", display: "flex", flexDirection: "column" }
|
|
4272
|
+
}, [
|
|
4273
|
+
h(ui.DataGridTable, {
|
|
4274
|
+
gridProps: dataGridProps.value
|
|
4275
|
+
})
|
|
4276
|
+
]);
|
|
4277
|
+
const mainAreaChildren = [];
|
|
4278
|
+
if (hasSideBar && sideBarPosition === "left") {
|
|
4279
|
+
mainAreaChildren.push(renderSideBar(sideBar));
|
|
4280
|
+
}
|
|
4281
|
+
mainAreaChildren.push(gridChild);
|
|
4282
|
+
if (hasSideBar && sideBarPosition !== "left") {
|
|
4283
|
+
mainAreaChildren.push(renderSideBar(sideBar));
|
|
4284
|
+
}
|
|
4285
|
+
const hasToolbar = toolbarChildren.length > 0 || toolbarEnd != null || fullscreenButton != null;
|
|
4286
|
+
const rootStyle = isFullScreen.value ? { position: "fixed", inset: "0", zIndex: 9999, display: "flex", flexDirection: "column", background: "var(--ogrid-bg, #fff)" } : { display: "flex", flexDirection: "column", border: "1px solid var(--ogrid-border, rgba(0,0,0,0.12))", borderRadius: "4px", overflow: "hidden" };
|
|
4287
|
+
const containerStyle = isFullScreen.value ? { display: "flex", flexDirection: "column", flex: "1", minHeight: "0", overflow: "hidden", background: "var(--ogrid-bg, #fff)" } : void 0;
|
|
4288
|
+
return h("div", {
|
|
4289
|
+
class: layout.value.className,
|
|
4290
|
+
style: rootStyle
|
|
4291
|
+
}, [
|
|
4292
|
+
// Inner container (for fullscreen: no border/radius)
|
|
4293
|
+
h("div", { style: containerStyle ?? {} }, [
|
|
4294
|
+
// Toolbar strip
|
|
4295
|
+
...hasToolbar ? [
|
|
4296
|
+
h("div", {
|
|
4297
|
+
style: {
|
|
4298
|
+
display: "flex",
|
|
4299
|
+
alignItems: "center",
|
|
4300
|
+
justifyContent: "space-between",
|
|
4301
|
+
padding: "8px 12px",
|
|
4302
|
+
borderBottom: "1px solid var(--ogrid-border, rgba(0,0,0,0.12))",
|
|
4303
|
+
gap: "8px"
|
|
4304
|
+
}
|
|
4305
|
+
}, [
|
|
4306
|
+
h("div", { style: { display: "flex", alignItems: "center", gap: "8px", flex: "1" } }, toolbarChildren),
|
|
4307
|
+
h("div", { style: { display: "flex", alignItems: "center", gap: "8px" } }, [
|
|
4308
|
+
...toolbarEnd ? [toolbarEnd] : [],
|
|
4309
|
+
...fullscreenButton ? [fullscreenButton] : []
|
|
4310
|
+
])
|
|
4311
|
+
])
|
|
4312
|
+
] : [],
|
|
4313
|
+
// Below toolbar strip
|
|
4314
|
+
...layout.value.toolbarBelow ? [
|
|
4315
|
+
h("div", {
|
|
4316
|
+
style: { padding: "8px 12px", borderBottom: "1px solid var(--ogrid-border, rgba(0,0,0,0.12))" }
|
|
4317
|
+
}, [layout.value.toolbarBelow])
|
|
4318
|
+
] : [],
|
|
4319
|
+
// Main content area (sidebar + grid)
|
|
4320
|
+
h("div", { style: { display: "flex", flex: "1", minHeight: "0" } }, mainAreaChildren),
|
|
4321
|
+
// Footer strip (pagination)
|
|
4322
|
+
h("div", {
|
|
4323
|
+
style: {
|
|
4324
|
+
display: "flex",
|
|
4325
|
+
alignItems: "center",
|
|
4326
|
+
padding: "8px 0",
|
|
4327
|
+
borderTop: "1px solid var(--ogrid-border, rgba(0,0,0,0.12))"
|
|
4328
|
+
}
|
|
4329
|
+
}, [paginationNode])
|
|
4330
|
+
])
|
|
4331
|
+
]);
|
|
4332
|
+
};
|
|
4333
|
+
}
|
|
4334
|
+
});
|
|
4335
|
+
}
|
|
4336
|
+
|
|
4337
|
+
export { MarchingAntsOverlay, StatusBar, createDataGridTable, createInlineCellEditor, createOGrid, getCellInteractionProps, useActiveCell, useCellEditing, useCellSelection, useClipboard, useColumnChooserState, useColumnHeaderFilterState, useColumnHeaderMenuState, useColumnPinning, useColumnReorder, useColumnResize, useContextMenu, useDataGridState, useDataGridTableSetup, useDateFilterState, useDebounce, useDebouncedCallback, useFillHandle, useFilterOptions, useInlineCellEditorState, useKeyboardNavigation, useMultiSelectFilterState, useOGrid, usePeopleFilterState, useRichSelectState, useRowSelection, useSideBarState, useTableLayout, useTextFilterState, useUndoRedo, useVirtualScroll };
|