@alaarab/ogrid-vue 2.1.3 → 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
@@ -1,77 +0,0 @@
1
- import { shallowRef, watch, onUnmounted } from 'vue';
2
- /**
3
- * Tracks the active cell for keyboard navigation.
4
- * When wrapperRef and editingCell are provided, scrolls the active cell into view when it changes (and not editing).
5
- */
6
- export function useActiveCell(wrapperRef, editingCell) {
7
- const activeCell = shallowRef(null);
8
- let pendingRaf = 0;
9
- // Deduplicating setter — skips update when the cell coordinates haven't actually changed.
10
- const setActiveCell = (cell) => {
11
- const prev = activeCell.value;
12
- if (prev === cell)
13
- return;
14
- if (prev && cell && prev.rowIndex === cell.rowIndex && prev.columnIndex === cell.columnIndex)
15
- return;
16
- activeCell.value = cell;
17
- };
18
- // Scroll active cell into view when it changes (equivalent to useLayoutEffect).
19
- // Uses requestAnimationFrame to batch DOM reads (getBoundingClientRect) with the
20
- // browser's layout cycle, avoiding forced reflows when rapidly clicking cells.
21
- watch([activeCell, () => editingCell?.value], () => {
22
- // Cancel any pending scroll from a previous cell change
23
- if (pendingRaf) {
24
- cancelAnimationFrame(pendingRaf);
25
- pendingRaf = 0;
26
- }
27
- if (activeCell.value == null ||
28
- !wrapperRef?.value ||
29
- editingCell?.value != null)
30
- return;
31
- // Capture the target coordinates before the async boundary
32
- const { rowIndex, columnIndex } = activeCell.value;
33
- pendingRaf = requestAnimationFrame(() => {
34
- pendingRaf = 0;
35
- const wrapper = wrapperRef.value;
36
- if (!wrapper)
37
- return;
38
- // Verify the active cell hasn't changed since we scheduled
39
- const current = activeCell.value;
40
- if (!current || current.rowIndex !== rowIndex || current.columnIndex !== columnIndex)
41
- return;
42
- const selector = `[data-row-index="${rowIndex}"][data-col-index="${columnIndex}"]`;
43
- const cell = wrapper.querySelector(selector);
44
- if (cell) {
45
- const thead = wrapper.querySelector('thead');
46
- const headerHeight = thead ? thead.getBoundingClientRect().height : 0;
47
- const wrapperRect = wrapper.getBoundingClientRect();
48
- const cellRect = cell.getBoundingClientRect();
49
- // Vertical scroll (account for sticky thead)
50
- const visibleTop = wrapperRect.top + headerHeight;
51
- if (cellRect.top < visibleTop) {
52
- wrapper.scrollTop -= visibleTop - cellRect.top;
53
- }
54
- else if (cellRect.bottom > wrapperRect.bottom) {
55
- wrapper.scrollTop += cellRect.bottom - wrapperRect.bottom;
56
- }
57
- // Horizontal scroll
58
- if (cellRect.left < wrapperRect.left) {
59
- wrapper.scrollLeft -= wrapperRect.left - cellRect.left;
60
- }
61
- else if (cellRect.right > wrapperRect.right) {
62
- wrapper.scrollLeft += cellRect.right - wrapperRect.right;
63
- }
64
- if (document.activeElement !== cell && typeof cell.focus === 'function') {
65
- cell.focus({ preventScroll: true });
66
- }
67
- }
68
- });
69
- }, { flush: 'post' });
70
- onUnmounted(() => {
71
- if (pendingRaf) {
72
- cancelAnimationFrame(pendingRaf);
73
- pendingRaf = 0;
74
- }
75
- });
76
- return { activeCell, setActiveCell };
77
- }
@@ -1,27 +0,0 @@
1
- import { shallowRef, ref } from 'vue';
2
- /**
3
- * Manages cell editing state: which cell is being edited and its pending value.
4
- * Optionally scrolls to the cell's row before opening the editor when virtual scrolling is active.
5
- */
6
- export function useCellEditing(params) {
7
- const editingCell = shallowRef(null);
8
- const pendingEditorValue = ref(undefined);
9
- const setEditingCell = (cell) => {
10
- if (cell && params?.scrollToRow && params?.getRowIndex) {
11
- const rowIndex = params.getRowIndex(cell.rowId);
12
- if (rowIndex >= 0) {
13
- params.scrollToRow(rowIndex, 'center');
14
- }
15
- }
16
- editingCell.value = cell;
17
- };
18
- const setPendingEditorValue = (value) => {
19
- pendingEditorValue.value = value;
20
- };
21
- return {
22
- editingCell,
23
- setEditingCell,
24
- pendingEditorValue,
25
- setPendingEditorValue,
26
- };
27
- }
@@ -1,359 +0,0 @@
1
- import { shallowRef, ref, isRef, onMounted, onUnmounted } from 'vue';
2
- import { normalizeSelectionRange, rangesEqual, computeAutoScrollSpeed } from '@alaarab/ogrid-core';
3
- import { useLatestRef } from './useLatestRef';
4
- /** DOM attribute names used for drag-range highlighting (bypasses Vue). */
5
- const DRAG_ATTR = 'data-drag-range';
6
- const DRAG_ANCHOR_ATTR = 'data-drag-anchor';
7
- /** Auto-scroll config */
8
- const AUTO_SCROLL_EDGE = 40;
9
- const AUTO_SCROLL_INTERVAL = 16;
10
- /**
11
- * Manages cell selection range with drag-to-select and select-all support.
12
- */
13
- export function useCellSelection(params) {
14
- // Store latest params in a ref for stable handler references
15
- const paramsRef = useLatestRef(params);
16
- const { wrapperRef, setActiveCell } = params; // These are stable, safe to destructure
17
- const getColOffset = () => isRef(params.colOffset) ? params.colOffset.value : params.colOffset;
18
- const selectionRange = shallowRef(null);
19
- const isDragging = ref(false); // boolean primitive, ref is fine
20
- const isDraggingInternal = ref(false); // ref so event handlers always read current value
21
- const isUnmounted = ref(false); // ref for clean unmount tracking
22
- let dragMoved = false;
23
- let dragStart = null;
24
- let rafId = 0;
25
- let liveDragRange = null;
26
- let autoScrollInterval = null;
27
- let lastMousePos = null;
28
- const setSelectionRange = (next) => {
29
- if (rangesEqual(selectionRange.value, next))
30
- return;
31
- selectionRange.value = next;
32
- };
33
- const handleCellMouseDown = (e, rowIndex, globalColIndex) => {
34
- if (e.button !== 0)
35
- return;
36
- const colOffset = getColOffset();
37
- if (globalColIndex < colOffset)
38
- return;
39
- e.preventDefault();
40
- const dataColIndex = globalColIndex - colOffset;
41
- const currentRange = selectionRange.value;
42
- if (e.shiftKey && currentRange != null) {
43
- setSelectionRange(normalizeSelectionRange({
44
- startRow: currentRange.startRow,
45
- startCol: currentRange.startCol,
46
- endRow: rowIndex,
47
- endCol: dataColIndex,
48
- }));
49
- setActiveCell({ rowIndex, columnIndex: globalColIndex });
50
- }
51
- else {
52
- dragStart = { row: rowIndex, col: dataColIndex };
53
- dragMoved = false;
54
- const initial = {
55
- startRow: rowIndex,
56
- startCol: dataColIndex,
57
- endRow: rowIndex,
58
- endCol: dataColIndex,
59
- };
60
- setSelectionRange(initial);
61
- liveDragRange = initial;
62
- setActiveCell({ rowIndex, columnIndex: globalColIndex });
63
- isDraggingInternal.value = true;
64
- // Apply drag attrs immediately for the initial cell so the anchor styling shows
65
- // even before the first mousemove. This ensures instant visual feedback.
66
- setTimeout(() => applyDragAttrs(initial), 0);
67
- }
68
- };
69
- const handleSelectAllCells = () => {
70
- const { rowCount, visibleColCount } = paramsRef.value;
71
- if (rowCount.value === 0 || visibleColCount.value === 0)
72
- return;
73
- setSelectionRange({
74
- startRow: 0,
75
- startCol: 0,
76
- endRow: rowCount.value - 1,
77
- endCol: visibleColCount.value - 1,
78
- });
79
- setActiveCell({ rowIndex: 0, columnIndex: getColOffset() });
80
- };
81
- // --- Window mouse move/up for drag selection ---
82
- /** Set of currently drag-marked HTMLElements — avoids O(n) full DOM scan on each frame. */
83
- const markedCells = new Set();
84
- /** Cell lookup index built on drag start — O(1) lookups per frame instead of querySelectorAll. */
85
- let cellIndex = null;
86
- /** Build cell lookup index from a single querySelectorAll scan. */
87
- const buildCellIndex = () => {
88
- const wrapper = wrapperRef.value;
89
- if (!wrapper)
90
- return;
91
- cellIndex = new Map();
92
- const cells = wrapper.querySelectorAll('[data-row-index][data-col-index]');
93
- for (let i = 0; i < cells.length; i++) {
94
- const el = cells[i];
95
- const r = el.getAttribute('data-row-index') ?? '';
96
- const c = el.getAttribute('data-col-index') ?? '';
97
- cellIndex.set(`${r},${c}`, el);
98
- }
99
- };
100
- /** Apply styling to a single in-range cell (attrs + box-shadow). */
101
- const styleCellInRange = (el, r, c, minR, maxR, minC, maxC, anchor) => {
102
- if (!el.hasAttribute(DRAG_ATTR))
103
- el.setAttribute(DRAG_ATTR, '');
104
- const isAnchor = anchor && r === anchor.row && c === anchor.col;
105
- if (isAnchor) {
106
- if (!el.hasAttribute(DRAG_ANCHOR_ATTR))
107
- el.setAttribute(DRAG_ANCHOR_ATTR, '');
108
- }
109
- else {
110
- if (el.hasAttribute(DRAG_ANCHOR_ATTR))
111
- el.removeAttribute(DRAG_ANCHOR_ATTR);
112
- }
113
- const shadows = [];
114
- if (r === minR)
115
- shadows.push('inset 0 2px 0 0 var(--ogrid-selection, #217346)');
116
- if (r === maxR)
117
- shadows.push('inset 0 -2px 0 0 var(--ogrid-selection, #217346)');
118
- if (c === minC)
119
- shadows.push('inset 2px 0 0 0 var(--ogrid-selection, #217346)');
120
- if (c === maxC)
121
- shadows.push('inset -2px 0 0 0 var(--ogrid-selection, #217346)');
122
- el.style.boxShadow = shadows.length > 0 ? shadows.join(', ') : '';
123
- markedCells.add(el);
124
- };
125
- /** Remove drag styling from a single cell. */
126
- const unstyleCell = (el) => {
127
- el.removeAttribute(DRAG_ATTR);
128
- el.removeAttribute(DRAG_ANCHOR_ATTR);
129
- el.style.boxShadow = '';
130
- };
131
- /** Toggle DRAG_ATTR on cells to show the range highlight via CSS.
132
- * Uses a cell index Map for O(1) lookups per cell in the range instead of scanning all cells.
133
- * Also sets edge box-shadows for a green border around the selection range,
134
- * and marks the anchor cell with DRAG_ANCHOR_ATTR (white background). */
135
- const applyDragAttrs = (range) => {
136
- const wrapper = wrapperRef.value;
137
- if (!wrapper)
138
- return;
139
- const minR = Math.min(range.startRow, range.endRow);
140
- const maxR = Math.max(range.startRow, range.endRow);
141
- const minC = Math.min(range.startCol, range.endCol);
142
- const maxC = Math.max(range.startCol, range.endCol);
143
- const anchor = dragStart;
144
- const colOff = getColOffset();
145
- // 1. Un-mark cells that are no longer in the new range (iterate the small set, not all DOM)
146
- for (const el of markedCells) {
147
- const r = parseInt(el.getAttribute('data-row-index') ?? '', 10);
148
- const c = parseInt(el.getAttribute('data-col-index') ?? '', 10) - colOff;
149
- const stillInRange = r >= minR && r <= maxR && c >= minC && c <= maxC;
150
- if (!stillInRange) {
151
- unstyleCell(el);
152
- markedCells.delete(el);
153
- }
154
- }
155
- // Build index on first call if not yet initialized
156
- if (!cellIndex)
157
- buildCellIndex();
158
- // 2. Look up only the cells in the new range — O(range size) via Map lookup.
159
- for (let r = minR; r <= maxR; r++) {
160
- for (let c = minC; c <= maxC; c++) {
161
- const key = `${r},${c + colOff}`;
162
- let el = cellIndex?.get(key);
163
- // Handle virtual scroll recycling — if element is stale, rebuild index once
164
- if (el && !el.isConnected) {
165
- buildCellIndex();
166
- el = cellIndex?.get(key);
167
- }
168
- if (el) {
169
- styleCellInRange(el, r, c, minR, maxR, minC, maxC, anchor);
170
- }
171
- }
172
- }
173
- };
174
- /** Clear all drag styling using the tracked set — O(marked) not O(all cells). */
175
- const clearDragAttrs = () => {
176
- for (const el of markedCells) {
177
- unstyleCell(el);
178
- }
179
- markedCells.clear();
180
- cellIndex = null;
181
- };
182
- const resolveRange = (cx, cy) => {
183
- if (!dragStart)
184
- return null;
185
- const target = document.elementFromPoint(cx, cy);
186
- const cell = target?.closest?.('[data-row-index][data-col-index]');
187
- if (!cell)
188
- return null;
189
- const r = parseInt(cell.getAttribute('data-row-index') ?? '', 10);
190
- const c = parseInt(cell.getAttribute('data-col-index') ?? '', 10);
191
- const colOffset = getColOffset();
192
- if (Number.isNaN(r) || Number.isNaN(c) || c < colOffset)
193
- return null;
194
- const dataCol = c - colOffset;
195
- return normalizeSelectionRange({
196
- startRow: dragStart.row,
197
- startCol: dragStart.col,
198
- endRow: r,
199
- endCol: dataCol,
200
- });
201
- };
202
- const stopAutoScroll = () => {
203
- if (autoScrollInterval) {
204
- clearInterval(autoScrollInterval);
205
- autoScrollInterval = null;
206
- }
207
- };
208
- const updateAutoScroll = () => {
209
- const wrapper = wrapperRef.value;
210
- if (!wrapper || !lastMousePos || !isDraggingInternal.value) {
211
- stopAutoScroll();
212
- return;
213
- }
214
- const rect = wrapper.getBoundingClientRect();
215
- let dx = 0;
216
- let dy = 0;
217
- if (lastMousePos.cy < rect.top + AUTO_SCROLL_EDGE) {
218
- dy = -computeAutoScrollSpeed(rect.top + AUTO_SCROLL_EDGE - lastMousePos.cy);
219
- }
220
- else if (lastMousePos.cy > rect.bottom - AUTO_SCROLL_EDGE) {
221
- dy = computeAutoScrollSpeed(lastMousePos.cy - (rect.bottom - AUTO_SCROLL_EDGE));
222
- }
223
- if (lastMousePos.cx < rect.left + AUTO_SCROLL_EDGE) {
224
- dx = -computeAutoScrollSpeed(rect.left + AUTO_SCROLL_EDGE - lastMousePos.cx);
225
- }
226
- else if (lastMousePos.cx > rect.right - AUTO_SCROLL_EDGE) {
227
- dx = computeAutoScrollSpeed(lastMousePos.cx - (rect.right - AUTO_SCROLL_EDGE));
228
- }
229
- if (dx === 0 && dy === 0) {
230
- stopAutoScroll();
231
- return;
232
- }
233
- if (!autoScrollInterval) {
234
- autoScrollInterval = setInterval(() => {
235
- const w = wrapperRef.value;
236
- const p = lastMousePos;
237
- if (!w || !p || !isDraggingInternal.value) {
238
- stopAutoScroll();
239
- return;
240
- }
241
- const r = w.getBoundingClientRect();
242
- let sdx = 0;
243
- let sdy = 0;
244
- if (p.cy < r.top + AUTO_SCROLL_EDGE)
245
- sdy = -computeAutoScrollSpeed(r.top + AUTO_SCROLL_EDGE - p.cy);
246
- else if (p.cy > r.bottom - AUTO_SCROLL_EDGE)
247
- sdy = computeAutoScrollSpeed(p.cy - (r.bottom - AUTO_SCROLL_EDGE));
248
- if (p.cx < r.left + AUTO_SCROLL_EDGE)
249
- sdx = -computeAutoScrollSpeed(r.left + AUTO_SCROLL_EDGE - p.cx);
250
- else if (p.cx > r.right - AUTO_SCROLL_EDGE)
251
- sdx = computeAutoScrollSpeed(p.cx - (r.right - AUTO_SCROLL_EDGE));
252
- if (sdx === 0 && sdy === 0) {
253
- stopAutoScroll();
254
- return;
255
- }
256
- w.scrollTop += sdy;
257
- w.scrollLeft += sdx;
258
- const newRange = resolveRange(p.cx, p.cy);
259
- if (newRange) {
260
- liveDragRange = newRange;
261
- applyDragAttrs(newRange);
262
- }
263
- }, AUTO_SCROLL_INTERVAL);
264
- }
265
- };
266
- const onMove = (e) => {
267
- if (!isDraggingInternal.value || !dragStart)
268
- return;
269
- if (!dragMoved) {
270
- dragMoved = true;
271
- isDragging.value = true;
272
- // Build cell index once at drag start for O(1) lookups during drag
273
- buildCellIndex();
274
- }
275
- lastMousePos = { cx: e.clientX, cy: e.clientY };
276
- updateAutoScroll();
277
- if (rafId)
278
- cancelAnimationFrame(rafId);
279
- rafId = requestAnimationFrame(() => {
280
- rafId = 0;
281
- if (!lastMousePos)
282
- return;
283
- const newRange = resolveRange(lastMousePos.cx, lastMousePos.cy);
284
- if (!newRange)
285
- return;
286
- const prev = liveDragRange;
287
- if (prev &&
288
- prev.startRow === newRange.startRow &&
289
- prev.startCol === newRange.startCol &&
290
- prev.endRow === newRange.endRow &&
291
- prev.endCol === newRange.endCol) {
292
- return;
293
- }
294
- liveDragRange = newRange;
295
- applyDragAttrs(newRange);
296
- });
297
- };
298
- const onUp = () => {
299
- if (!isDraggingInternal.value)
300
- return;
301
- stopAutoScroll();
302
- if (rafId) {
303
- cancelAnimationFrame(rafId);
304
- rafId = 0;
305
- }
306
- isDraggingInternal.value = false;
307
- const wasDrag = dragMoved;
308
- if (wasDrag) {
309
- if (lastMousePos) {
310
- const flushed = resolveRange(lastMousePos.cx, lastMousePos.cy);
311
- if (flushed)
312
- liveDragRange = flushed;
313
- }
314
- const finalRange = liveDragRange;
315
- if (finalRange) {
316
- setSelectionRange(finalRange);
317
- setActiveCell({
318
- rowIndex: finalRange.endRow,
319
- columnIndex: finalRange.endCol + getColOffset(),
320
- });
321
- }
322
- }
323
- clearDragAttrs();
324
- liveDragRange = null;
325
- lastMousePos = null;
326
- dragStart = null;
327
- if (wasDrag)
328
- isDragging.value = false;
329
- };
330
- const onMoveSafe = (e) => {
331
- if (isUnmounted.value)
332
- return;
333
- onMove(e);
334
- };
335
- const onUpSafe = () => {
336
- if (isUnmounted.value)
337
- return;
338
- onUp();
339
- };
340
- onMounted(() => {
341
- window.addEventListener('mousemove', onMoveSafe, true);
342
- window.addEventListener('mouseup', onUpSafe, true);
343
- });
344
- onUnmounted(() => {
345
- isUnmounted.value = true;
346
- window.removeEventListener('mousemove', onMoveSafe, true);
347
- window.removeEventListener('mouseup', onUpSafe, true);
348
- if (rafId)
349
- cancelAnimationFrame(rafId);
350
- stopAutoScroll();
351
- });
352
- return {
353
- selectionRange,
354
- setSelectionRange,
355
- handleCellMouseDown,
356
- handleSelectAllCells,
357
- isDragging,
358
- };
359
- }
@@ -1,87 +0,0 @@
1
- import { ref, shallowRef, isRef } from 'vue';
2
- import { normalizeSelectionRange, formatSelectionAsTsv, parseTsvClipboard, applyPastedValues, applyCutClear } from '@alaarab/ogrid-core';
3
- /**
4
- * Manages copy, cut, and paste operations for cell ranges with TSV clipboard format.
5
- */
6
- export function useClipboard(params) {
7
- const { items, visibleCols, selectionRange, activeCell, editable, onCellValueChanged, beginBatch, endBatch, } = params;
8
- const getColOffset = () => isRef(params.colOffset) ? params.colOffset.value : params.colOffset;
9
- const cutRange = shallowRef(null);
10
- const copyRange = shallowRef(null);
11
- const internalClipboardRef = ref(null);
12
- const getEffectiveRange = () => {
13
- const sel = selectionRange.value;
14
- const ac = activeCell.value;
15
- const colOffset = getColOffset();
16
- return sel ?? (ac != null
17
- ? { startRow: ac.rowIndex, startCol: ac.columnIndex - colOffset, endRow: ac.rowIndex, endCol: ac.columnIndex - colOffset }
18
- : null);
19
- };
20
- const handleCopy = () => {
21
- const range = getEffectiveRange();
22
- if (range == null)
23
- return;
24
- const norm = normalizeSelectionRange(range);
25
- const tsv = formatSelectionAsTsv(items.value, visibleCols.value, norm);
26
- internalClipboardRef.value = tsv;
27
- copyRange.value = norm;
28
- void navigator.clipboard.writeText(tsv).catch((err) => {
29
- if (typeof console !== 'undefined')
30
- console.warn('[OGrid] Clipboard write failed:', err);
31
- });
32
- };
33
- const handleCut = () => {
34
- if (editable.value === false)
35
- return;
36
- const range = getEffectiveRange();
37
- if (range == null || onCellValueChanged.value == null)
38
- return;
39
- const norm = normalizeSelectionRange(range);
40
- cutRange.value = norm;
41
- copyRange.value = null;
42
- handleCopy();
43
- copyRange.value = null;
44
- };
45
- const handlePaste = async () => {
46
- if (editable.value === false)
47
- return;
48
- const callback = onCellValueChanged.value;
49
- if (callback == null)
50
- return;
51
- let text;
52
- try {
53
- text = await navigator.clipboard.readText();
54
- }
55
- catch {
56
- text = '';
57
- }
58
- if (!text.trim() && internalClipboardRef.value != null) {
59
- text = internalClipboardRef.value;
60
- }
61
- if (!text.trim())
62
- return;
63
- const norm = getEffectiveRange();
64
- const anchorRow = norm ? norm.startRow : 0;
65
- const anchorCol = norm ? norm.startCol : 0;
66
- const currentItems = items.value;
67
- const currentCols = visibleCols.value;
68
- const parsedRows = parseTsvClipboard(text);
69
- beginBatch?.();
70
- const pasteEvents = applyPastedValues(parsedRows, anchorRow, anchorCol, currentItems, currentCols);
71
- for (const evt of pasteEvents)
72
- callback(evt);
73
- if (cutRange.value) {
74
- const cutEvents = applyCutClear(cutRange.value, currentItems, currentCols);
75
- for (const evt of cutEvents)
76
- callback(evt);
77
- cutRange.value = null;
78
- }
79
- endBatch?.();
80
- copyRange.value = null;
81
- };
82
- const clearClipboardRanges = () => {
83
- copyRange.value = null;
84
- cutRange.value = null;
85
- };
86
- return { handleCopy, handleCut, handlePaste, cutRange, copyRange, clearClipboardRanges };
87
- }
@@ -1,74 +0,0 @@
1
- import { ref, computed, watch, onUnmounted } from 'vue';
2
- /**
3
- * Returns open/setOpen, handleToggle, handleClose, handleCheckboxChange, handleSelectAll, handleClearAll.
4
- */
5
- export function useColumnChooserState(params) {
6
- const { columns, visibleColumns, onVisibilityChange } = params;
7
- const open = ref(false);
8
- let keyDownHandler = null;
9
- const setupEscapeHandler = () => {
10
- cleanupEscapeHandler();
11
- keyDownHandler = (event) => {
12
- if (event.key === 'Escape') {
13
- event.preventDefault();
14
- open.value = false;
15
- }
16
- };
17
- document.addEventListener('keydown', keyDownHandler, true);
18
- };
19
- const cleanupEscapeHandler = () => {
20
- if (keyDownHandler) {
21
- document.removeEventListener('keydown', keyDownHandler, true);
22
- keyDownHandler = null;
23
- }
24
- };
25
- watch(open, (isOpen) => {
26
- if (isOpen)
27
- setupEscapeHandler();
28
- else
29
- cleanupEscapeHandler();
30
- });
31
- onUnmounted(() => cleanupEscapeHandler());
32
- const setOpen = (value) => {
33
- open.value = value;
34
- };
35
- const handleToggle = () => {
36
- open.value = !open.value;
37
- };
38
- const handleClose = () => {
39
- open.value = false;
40
- };
41
- const handleCheckboxChange = (columnKey) => {
42
- return (visible) => {
43
- onVisibilityChange(columnKey, visible);
44
- };
45
- };
46
- const handleSelectAll = () => {
47
- columns.value.forEach((col) => {
48
- if (!visibleColumns.value.has(col.columnId)) {
49
- onVisibilityChange(col.columnId, true);
50
- }
51
- });
52
- };
53
- const handleClearAll = () => {
54
- // Required columns are silently skipped — no feedback is provided to the user
55
- columns.value.forEach((col) => {
56
- if (!col.required && visibleColumns.value.has(col.columnId)) {
57
- onVisibilityChange(col.columnId, false);
58
- }
59
- });
60
- };
61
- const visibleCount = computed(() => visibleColumns.value.size);
62
- const totalCount = computed(() => columns.value.length);
63
- return {
64
- open,
65
- setOpen,
66
- handleToggle,
67
- handleClose,
68
- handleCheckboxChange,
69
- handleSelectAll,
70
- handleClearAll,
71
- visibleCount,
72
- totalCount,
73
- };
74
- }