@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.
Files changed (44) hide show
  1. package/dist/esm/index.js +4336 -15
  2. package/package.json +4 -4
  3. package/dist/esm/components/MarchingAntsOverlay.js +0 -144
  4. package/dist/esm/components/SideBar.js +0 -1
  5. package/dist/esm/components/StatusBar.js +0 -49
  6. package/dist/esm/components/createDataGridTable.js +0 -514
  7. package/dist/esm/components/createInlineCellEditor.js +0 -194
  8. package/dist/esm/components/createOGrid.js +0 -383
  9. package/dist/esm/composables/index.js +0 -33
  10. package/dist/esm/composables/useActiveCell.js +0 -77
  11. package/dist/esm/composables/useCellEditing.js +0 -27
  12. package/dist/esm/composables/useCellSelection.js +0 -359
  13. package/dist/esm/composables/useClipboard.js +0 -87
  14. package/dist/esm/composables/useColumnChooserState.js +0 -74
  15. package/dist/esm/composables/useColumnHeaderFilterState.js +0 -189
  16. package/dist/esm/composables/useColumnHeaderMenuState.js +0 -113
  17. package/dist/esm/composables/useColumnPinning.js +0 -64
  18. package/dist/esm/composables/useColumnReorder.js +0 -110
  19. package/dist/esm/composables/useColumnResize.js +0 -73
  20. package/dist/esm/composables/useContextMenu.js +0 -23
  21. package/dist/esm/composables/useDataGridState.js +0 -425
  22. package/dist/esm/composables/useDataGridTableSetup.js +0 -66
  23. package/dist/esm/composables/useDateFilterState.js +0 -36
  24. package/dist/esm/composables/useDebounce.js +0 -60
  25. package/dist/esm/composables/useFillHandle.js +0 -205
  26. package/dist/esm/composables/useFilterOptions.js +0 -39
  27. package/dist/esm/composables/useInlineCellEditorState.js +0 -42
  28. package/dist/esm/composables/useKeyboardNavigation.js +0 -232
  29. package/dist/esm/composables/useLatestRef.js +0 -27
  30. package/dist/esm/composables/useMultiSelectFilterState.js +0 -59
  31. package/dist/esm/composables/useOGrid.js +0 -491
  32. package/dist/esm/composables/usePeopleFilterState.js +0 -66
  33. package/dist/esm/composables/useRichSelectState.js +0 -59
  34. package/dist/esm/composables/useRowSelection.js +0 -75
  35. package/dist/esm/composables/useSideBarState.js +0 -41
  36. package/dist/esm/composables/useTableLayout.js +0 -85
  37. package/dist/esm/composables/useTextFilterState.js +0 -26
  38. package/dist/esm/composables/useUndoRedo.js +0 -65
  39. package/dist/esm/composables/useVirtualScroll.js +0 -87
  40. package/dist/esm/types/columnTypes.js +0 -1
  41. package/dist/esm/types/dataGridTypes.js +0 -1
  42. package/dist/esm/types/index.js +0 -1
  43. package/dist/esm/utils/dataGridViewModel.js +0 -23
  44. package/dist/esm/utils/index.js +0 -1
package/dist/esm/index.js CHANGED
@@ -1,16 +1,4337 @@
1
- // Re-export core types + utils
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 { toUserLike, isInSelectionRange, normalizeSelectionRange } from './types';
4
- // Shared components
5
- export { MarchingAntsOverlay } from './components/MarchingAntsOverlay';
6
- export { StatusBar } from './components/StatusBar';
7
- // Composables
8
- export { useOGrid, useDataGridState, useActiveCell, useCellEditing, useCellSelection, useClipboard, useRowSelection, useKeyboardNavigation, useFillHandle, useUndoRedo, useContextMenu, useColumnResize, useColumnReorder, useVirtualScroll, useFilterOptions, useDebounce, useDebouncedCallback, useTableLayout, useColumnHeaderFilterState, useTextFilterState, useMultiSelectFilterState, usePeopleFilterState, useDateFilterState, useColumnChooserState, useInlineCellEditorState, useRichSelectState, useSideBarState, useColumnPinning, useColumnHeaderMenuState, useDataGridTableSetup, } from './composables';
9
- // View model utilities (for UI packages)
10
- export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, } from './utils';
11
- // DataGridTable factory (for UI packages)
12
- export { createDataGridTable } from './components/createDataGridTable';
13
- // InlineCellEditor factory (for UI packages)
14
- export { createInlineCellEditor } from './components/createInlineCellEditor';
15
- // OGrid factory (for UI packages)
16
- export { createOGrid } from './components/createOGrid';
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 };