@gp-grid/vue 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1934 @@
1
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, h, normalizeClass, normalizeStyle, onMounted, onUnmounted, openBlock, reactive, ref, renderList, resolveDynamicComponent, shallowRef, toDisplayString, unref, vModelText, watch, withDirectives } from "vue";
2
+ import { GridCore, GridCore as GridCore$1, buildCellClasses, buildCellClasses as buildCellClasses$1, calculateColumnPositions, calculateScaledColumnPositions, createClientDataSource, createClientDataSource as createClientDataSource$1, createDataSourceFromArray, createDataSourceFromArray as createDataSourceFromArray$1, createMutableClientDataSource, createServerDataSource, findColumnAtX, getTotalWidth, getTotalWidth as getTotalWidth$1, gridStyles, injectStyles, injectStyles as injectStyles$1, isCellActive, isCellActive as isCellActive$1, isCellEditing, isCellEditing as isCellEditing$1, isCellInFillPreview, isCellInFillPreview as isCellInFillPreview$1, isCellSelected, isCellSelected as isCellSelected$1, isRowVisible } from "gp-grid-core";
3
+
4
+ //#region src/gridState/useGridState.ts
5
+ function createInitialState(args) {
6
+ return {
7
+ slots: /* @__PURE__ */ new Map(),
8
+ activeCell: null,
9
+ selectionRange: null,
10
+ editingCell: null,
11
+ contentWidth: 0,
12
+ contentHeight: args?.initialHeight ?? 0,
13
+ viewportWidth: args?.initialWidth ?? 0,
14
+ headers: /* @__PURE__ */ new Map(),
15
+ filterPopup: null,
16
+ isLoading: false,
17
+ error: null,
18
+ totalRows: 0,
19
+ visibleRowRange: null,
20
+ hoverPosition: null
21
+ };
22
+ }
23
+ /**
24
+ * Apply a single instruction to mutable state
25
+ */
26
+ function applyInstruction(instruction, state) {
27
+ switch (instruction.type) {
28
+ case "CREATE_SLOT":
29
+ state.slots.set(instruction.slotId, {
30
+ slotId: instruction.slotId,
31
+ rowIndex: -1,
32
+ rowData: {},
33
+ translateY: 0
34
+ });
35
+ break;
36
+ case "DESTROY_SLOT":
37
+ state.slots.delete(instruction.slotId);
38
+ break;
39
+ case "ASSIGN_SLOT": {
40
+ const existing = state.slots.get(instruction.slotId);
41
+ if (existing) state.slots.set(instruction.slotId, {
42
+ ...existing,
43
+ rowIndex: instruction.rowIndex,
44
+ rowData: instruction.rowData
45
+ });
46
+ break;
47
+ }
48
+ case "MOVE_SLOT": {
49
+ const existing = state.slots.get(instruction.slotId);
50
+ if (existing) state.slots.set(instruction.slotId, {
51
+ ...existing,
52
+ translateY: instruction.translateY
53
+ });
54
+ break;
55
+ }
56
+ case "SET_ACTIVE_CELL":
57
+ state.activeCell = instruction.position;
58
+ break;
59
+ case "SET_SELECTION_RANGE":
60
+ state.selectionRange = instruction.range;
61
+ break;
62
+ case "UPDATE_VISIBLE_RANGE":
63
+ state.visibleRowRange = {
64
+ start: instruction.start,
65
+ end: instruction.end
66
+ };
67
+ break;
68
+ case "SET_HOVER_POSITION":
69
+ state.hoverPosition = instruction.position;
70
+ break;
71
+ case "START_EDIT":
72
+ state.editingCell = {
73
+ row: instruction.row,
74
+ col: instruction.col,
75
+ initialValue: instruction.initialValue
76
+ };
77
+ break;
78
+ case "STOP_EDIT":
79
+ state.editingCell = null;
80
+ break;
81
+ case "SET_CONTENT_SIZE":
82
+ state.contentWidth = instruction.width;
83
+ state.contentHeight = instruction.height;
84
+ state.viewportWidth = instruction.viewportWidth;
85
+ break;
86
+ case "UPDATE_HEADER":
87
+ state.headers.set(instruction.colIndex, {
88
+ column: instruction.column,
89
+ sortDirection: instruction.sortDirection,
90
+ sortIndex: instruction.sortIndex,
91
+ sortable: instruction.sortable,
92
+ filterable: instruction.filterable,
93
+ hasFilter: instruction.hasFilter
94
+ });
95
+ break;
96
+ case "OPEN_FILTER_POPUP":
97
+ state.filterPopup = {
98
+ isOpen: true,
99
+ colIndex: instruction.colIndex,
100
+ column: instruction.column,
101
+ anchorRect: instruction.anchorRect,
102
+ distinctValues: instruction.distinctValues,
103
+ currentFilter: instruction.currentFilter
104
+ };
105
+ break;
106
+ case "CLOSE_FILTER_POPUP":
107
+ state.filterPopup = null;
108
+ break;
109
+ case "DATA_LOADING":
110
+ state.isLoading = true;
111
+ state.error = null;
112
+ break;
113
+ case "DATA_LOADED":
114
+ state.isLoading = false;
115
+ state.totalRows = instruction.totalRows;
116
+ break;
117
+ case "DATA_ERROR":
118
+ state.isLoading = false;
119
+ state.error = instruction.error;
120
+ break;
121
+ case "ROWS_ADDED":
122
+ case "ROWS_REMOVED":
123
+ state.totalRows = instruction.totalRows;
124
+ break;
125
+ case "ROWS_UPDATED":
126
+ case "TRANSACTION_PROCESSED": break;
127
+ }
128
+ }
129
+ /**
130
+ * Vue composable for managing grid state
131
+ */
132
+ function useGridState(args) {
133
+ const state = reactive(createInitialState(args));
134
+ /**
135
+ * Apply a batch of instructions to the state
136
+ */
137
+ function applyInstructions(instructions) {
138
+ for (const instruction of instructions) applyInstruction(instruction, state);
139
+ }
140
+ /**
141
+ * Reset state to initial values
142
+ */
143
+ function reset() {
144
+ const initial = createInitialState();
145
+ state.slots = initial.slots;
146
+ state.activeCell = initial.activeCell;
147
+ state.selectionRange = initial.selectionRange;
148
+ state.editingCell = initial.editingCell;
149
+ state.contentWidth = initial.contentWidth;
150
+ state.contentHeight = initial.contentHeight;
151
+ state.viewportWidth = initial.viewportWidth;
152
+ state.headers = initial.headers;
153
+ state.filterPopup = initial.filterPopup;
154
+ state.isLoading = initial.isLoading;
155
+ state.error = initial.error;
156
+ state.totalRows = initial.totalRows;
157
+ state.visibleRowRange = initial.visibleRowRange;
158
+ state.hoverPosition = initial.hoverPosition;
159
+ }
160
+ return {
161
+ state,
162
+ applyInstructions,
163
+ reset
164
+ };
165
+ }
166
+
167
+ //#endregion
168
+ //#region src/composables/useAutoScroll.ts
169
+ const AUTO_SCROLL_INTERVAL = 16;
170
+ /**
171
+ * Vue composable for auto-scrolling during drag operations
172
+ */
173
+ function useAutoScroll(containerRef) {
174
+ const autoScrollInterval = ref(null);
175
+ /**
176
+ * Start auto-scrolling in the given direction
177
+ */
178
+ function startAutoScroll(dx, dy) {
179
+ if (autoScrollInterval.value) clearInterval(autoScrollInterval.value);
180
+ autoScrollInterval.value = setInterval(() => {
181
+ const container = containerRef.value;
182
+ if (container) {
183
+ container.scrollTop += dy;
184
+ container.scrollLeft += dx;
185
+ }
186
+ }, AUTO_SCROLL_INTERVAL);
187
+ }
188
+ /**
189
+ * Stop auto-scrolling
190
+ */
191
+ function stopAutoScroll() {
192
+ if (autoScrollInterval.value) {
193
+ clearInterval(autoScrollInterval.value);
194
+ autoScrollInterval.value = null;
195
+ }
196
+ }
197
+ onUnmounted(() => {
198
+ stopAutoScroll();
199
+ });
200
+ return {
201
+ startAutoScroll,
202
+ stopAutoScroll
203
+ };
204
+ }
205
+
206
+ //#endregion
207
+ //#region src/composables/useInputHandler.ts
208
+ /**
209
+ * Find the slot for a given row index
210
+ */
211
+ function findSlotForRow(slots, rowIndex) {
212
+ for (const slot of slots.values()) if (slot.rowIndex === rowIndex) return slot;
213
+ return null;
214
+ }
215
+ /**
216
+ * Scroll a cell into view if needed
217
+ */
218
+ function scrollCellIntoView(core, container, row, rowHeight, headerHeight, slots) {
219
+ const slot = findSlotForRow(slots, row);
220
+ const cellViewportTop = (slot ? slot.translateY : headerHeight + row * rowHeight) - container.scrollTop;
221
+ const cellViewportBottom = cellViewportTop + rowHeight;
222
+ const visibleTop = headerHeight;
223
+ const visibleBottom = container.clientHeight;
224
+ if (cellViewportTop < visibleTop) container.scrollTop = core.getScrollTopForRow(row);
225
+ else if (cellViewportBottom > visibleBottom) {
226
+ const visibleDataHeight = container.clientHeight - headerHeight;
227
+ const rowsInView = Math.floor(visibleDataHeight / rowHeight);
228
+ const targetRow = Math.max(0, row - rowsInView + 1);
229
+ container.scrollTop = core.getScrollTopForRow(targetRow);
230
+ }
231
+ }
232
+ /**
233
+ * Vue composable for handling all input interactions
234
+ */
235
+ function useInputHandler(coreRef, containerRef, columns, options) {
236
+ const { activeCell, selectionRange, editingCell, filterPopupOpen, rowHeight, headerHeight, columnPositions, visibleColumnsWithIndices, slots } = options;
237
+ const { startAutoScroll, stopAutoScroll } = useAutoScroll(containerRef);
238
+ const dragState = ref({
239
+ isDragging: false,
240
+ dragType: null,
241
+ fillSourceRange: null,
242
+ fillTarget: null
243
+ });
244
+ watch([
245
+ () => headerHeight,
246
+ () => rowHeight,
247
+ columnPositions,
248
+ visibleColumnsWithIndices
249
+ ], () => {
250
+ const core = coreRef.value;
251
+ if (core?.input) {
252
+ const visible = visibleColumnsWithIndices.value;
253
+ core.input.updateDeps({
254
+ getHeaderHeight: () => headerHeight,
255
+ getRowHeight: () => rowHeight,
256
+ getColumnPositions: () => columnPositions.value,
257
+ getColumnCount: () => visible.length,
258
+ getOriginalColumnIndex: (visibleIndex) => {
259
+ const info = visible[visibleIndex];
260
+ return info ? info.originalIndex : visibleIndex;
261
+ }
262
+ });
263
+ }
264
+ }, { immediate: true });
265
+ function getContainerBounds() {
266
+ const container = containerRef.value;
267
+ if (!container) return null;
268
+ const rect = container.getBoundingClientRect();
269
+ return {
270
+ top: rect.top,
271
+ left: rect.left,
272
+ width: rect.width,
273
+ height: rect.height,
274
+ scrollTop: container.scrollTop,
275
+ scrollLeft: container.scrollLeft
276
+ };
277
+ }
278
+ function toPointerEventData(e) {
279
+ return {
280
+ clientX: e.clientX,
281
+ clientY: e.clientY,
282
+ button: e.button,
283
+ shiftKey: e.shiftKey,
284
+ ctrlKey: e.ctrlKey,
285
+ metaKey: e.metaKey
286
+ };
287
+ }
288
+ function startGlobalDragListeners() {
289
+ const handleMouseMove = (e) => {
290
+ const core = coreRef.value;
291
+ const bounds = getContainerBounds();
292
+ if (!core?.input || !bounds) return;
293
+ const result = core.input.handleDragMove(toPointerEventData(e), bounds);
294
+ if (result) {
295
+ if (result.autoScroll) startAutoScroll(result.autoScroll.dx, result.autoScroll.dy);
296
+ else stopAutoScroll();
297
+ dragState.value = core.input.getDragState();
298
+ }
299
+ };
300
+ const handleMouseUp = () => {
301
+ const core = coreRef.value;
302
+ if (core?.input) {
303
+ core.input.handleDragEnd();
304
+ dragState.value = core.input.getDragState();
305
+ }
306
+ stopAutoScroll();
307
+ document.removeEventListener("mousemove", handleMouseMove);
308
+ document.removeEventListener("mouseup", handleMouseUp);
309
+ };
310
+ document.addEventListener("mousemove", handleMouseMove);
311
+ document.addEventListener("mouseup", handleMouseUp);
312
+ }
313
+ function handleCellMouseDown(rowIndex, colIndex, e) {
314
+ const core = coreRef.value;
315
+ if (!core?.input) return;
316
+ const result = core.input.handleCellMouseDown(rowIndex, colIndex, toPointerEventData(e));
317
+ if (result.focusContainer) containerRef.value?.focus();
318
+ if (result.startDrag === "selection") {
319
+ core.input.startSelectionDrag();
320
+ dragState.value = core.input.getDragState();
321
+ startGlobalDragListeners();
322
+ }
323
+ }
324
+ function handleCellDoubleClick(rowIndex, colIndex) {
325
+ const core = coreRef.value;
326
+ if (!core?.input) return;
327
+ core.input.handleCellDoubleClick(rowIndex, colIndex);
328
+ }
329
+ function handleFillHandleMouseDown(e) {
330
+ const core = coreRef.value;
331
+ if (!core?.input) return;
332
+ const result = core.input.handleFillHandleMouseDown(activeCell.value, selectionRange.value, toPointerEventData(e));
333
+ if (result.preventDefault) e.preventDefault();
334
+ if (result.stopPropagation) e.stopPropagation();
335
+ if (result.startDrag === "fill") {
336
+ dragState.value = core.input.getDragState();
337
+ startGlobalDragListeners();
338
+ }
339
+ }
340
+ function handleHeaderClick(colIndex, e) {
341
+ const core = coreRef.value;
342
+ if (!core?.input) return;
343
+ const column = columns.value[colIndex];
344
+ if (!column) return;
345
+ const colId = column.colId ?? column.field;
346
+ core.input.handleHeaderClick(colId, e.shiftKey);
347
+ }
348
+ function handleKeyDown(e) {
349
+ const core = coreRef.value;
350
+ const container = containerRef.value;
351
+ if (!core?.input) return;
352
+ const result = core.input.handleKeyDown({
353
+ key: e.key,
354
+ shiftKey: e.shiftKey,
355
+ ctrlKey: e.ctrlKey,
356
+ metaKey: e.metaKey
357
+ }, activeCell.value, editingCell.value, filterPopupOpen.value);
358
+ if (result.preventDefault) e.preventDefault();
359
+ if (result.scrollToCell && container) scrollCellIntoView(core, container, result.scrollToCell.row, rowHeight, headerHeight, slots.value);
360
+ }
361
+ function handleWheel(e, wheelDampening) {
362
+ const core = coreRef.value;
363
+ const container = containerRef.value;
364
+ if (!core?.input || !container) return;
365
+ const dampened = core.input.handleWheel(e.deltaY, e.deltaX, wheelDampening);
366
+ if (dampened) {
367
+ e.preventDefault();
368
+ container.scrollTop += dampened.dy;
369
+ container.scrollLeft += dampened.dx;
370
+ }
371
+ }
372
+ onUnmounted(() => {
373
+ stopAutoScroll();
374
+ });
375
+ return {
376
+ handleCellMouseDown,
377
+ handleCellDoubleClick,
378
+ handleFillHandleMouseDown,
379
+ handleHeaderClick,
380
+ handleKeyDown,
381
+ handleWheel,
382
+ dragState
383
+ };
384
+ }
385
+
386
+ //#endregion
387
+ //#region src/composables/useFillHandle.ts
388
+ /**
389
+ * Composable for calculating the fill handle position.
390
+ * The fill handle appears at the bottom-right corner of the selection
391
+ * when all selected columns are editable.
392
+ */
393
+ function useFillHandle(options) {
394
+ const { activeCell, selectionRange, slots, columns, visibleColumnsWithIndices, columnPositions, columnWidths, rowHeight } = options;
395
+ return { fillHandlePosition: computed(() => {
396
+ const active = activeCell.value;
397
+ const selection = selectionRange.value;
398
+ const slotsMap = slots.value;
399
+ if (!active && !selection) return null;
400
+ let row, col;
401
+ let minCol, maxCol;
402
+ if (selection) {
403
+ row = Math.max(selection.startRow, selection.endRow);
404
+ col = Math.max(selection.startCol, selection.endCol);
405
+ minCol = Math.min(selection.startCol, selection.endCol);
406
+ maxCol = Math.max(selection.startCol, selection.endCol);
407
+ } else if (active) {
408
+ row = active.row;
409
+ col = active.col;
410
+ minCol = col;
411
+ maxCol = col;
412
+ } else return null;
413
+ const cols = columns.value;
414
+ for (let c = minCol; c <= maxCol; c++) {
415
+ const column = cols[c];
416
+ if (!column || column.hidden) continue;
417
+ if (column.editable !== true) return null;
418
+ }
419
+ const visibleIndex = visibleColumnsWithIndices.value.findIndex((v) => v.originalIndex === col);
420
+ if (visibleIndex === -1) return null;
421
+ let cellTop = null;
422
+ for (const slot of slotsMap.values()) if (slot.rowIndex === row) {
423
+ cellTop = slot.translateY;
424
+ break;
425
+ }
426
+ if (cellTop === null) return null;
427
+ const cellLeft = columnPositions.value[visibleIndex] ?? 0;
428
+ const cellWidth = columnWidths.value[visibleIndex] ?? 0;
429
+ return {
430
+ top: cellTop + rowHeight - 5,
431
+ left: cellLeft + cellWidth - 20
432
+ };
433
+ }) };
434
+ }
435
+
436
+ //#endregion
437
+ //#region src/renderers/cellRenderer.ts
438
+ /**
439
+ * Ensure we always return a VNode, never a plain string
440
+ */
441
+ function toVNode$2(value) {
442
+ if (value == null || value === "") return createTextVNode("");
443
+ if (typeof value === "string") return createTextVNode(value);
444
+ return value;
445
+ }
446
+ /**
447
+ * Get cell value from row data, supporting dot-notation for nested fields
448
+ */
449
+ function getCellValue(rowData, field) {
450
+ const parts = field.split(".");
451
+ let value = rowData;
452
+ for (const part of parts) {
453
+ if (value == null || typeof value !== "object") return null;
454
+ value = value[part];
455
+ }
456
+ return value ?? null;
457
+ }
458
+ /**
459
+ * Render cell content based on column configuration and renderer registries
460
+ */
461
+ function renderCell(options) {
462
+ const { column, rowData, rowIndex, colIndex, isActive, isSelected, isEditing, cellRenderers, globalCellRenderer } = options;
463
+ const value = getCellValue(rowData, column.field);
464
+ const params = {
465
+ value,
466
+ rowData,
467
+ column,
468
+ rowIndex,
469
+ colIndex,
470
+ isActive,
471
+ isSelected,
472
+ isEditing
473
+ };
474
+ if (column.cellRenderer && typeof column.cellRenderer === "string") {
475
+ const renderer = cellRenderers[column.cellRenderer];
476
+ if (renderer) return toVNode$2(renderer(params));
477
+ }
478
+ if (globalCellRenderer) return toVNode$2(globalCellRenderer(params));
479
+ return createTextVNode(value == null ? "" : String(value));
480
+ }
481
+
482
+ //#endregion
483
+ //#region src/renderers/editRenderer.ts
484
+ /**
485
+ * Ensure we always return a VNode, never a plain string
486
+ */
487
+ function toVNode$1(value) {
488
+ if (value == null || value === "") return createTextVNode("");
489
+ if (typeof value === "string") return createTextVNode(value);
490
+ return value;
491
+ }
492
+ /**
493
+ * Render edit cell content based on column configuration and renderer registries
494
+ */
495
+ function renderEditCell(options) {
496
+ const { column, rowData, rowIndex, colIndex, initialValue, core, editRenderers, globalEditRenderer } = options;
497
+ if (!core) return createTextVNode("");
498
+ const params = {
499
+ value: getCellValue(rowData, column.field),
500
+ rowData,
501
+ column,
502
+ rowIndex,
503
+ colIndex,
504
+ isActive: true,
505
+ isSelected: true,
506
+ isEditing: true,
507
+ initialValue,
508
+ onValueChange: (newValue) => core.updateEditValue(newValue),
509
+ onCommit: () => core.commitEdit(),
510
+ onCancel: () => core.cancelEdit()
511
+ };
512
+ if (column.editRenderer && typeof column.editRenderer === "string") {
513
+ const renderer = editRenderers[column.editRenderer];
514
+ if (renderer) return toVNode$1(renderer(params));
515
+ }
516
+ if (globalEditRenderer) return toVNode$1(globalEditRenderer(params));
517
+ return h("input", {
518
+ class: "gp-grid-edit-input",
519
+ type: "text",
520
+ value: initialValue == null ? "" : String(initialValue),
521
+ autofocus: true,
522
+ onFocus: (e) => e.target.select(),
523
+ onInput: (e) => core.updateEditValue(e.target.value),
524
+ onKeydown: (e) => {
525
+ e.stopPropagation();
526
+ if (e.key === "Enter") core.commitEdit();
527
+ else if (e.key === "Escape") core.cancelEdit();
528
+ else if (e.key === "Tab") {
529
+ e.preventDefault();
530
+ core.commitEdit();
531
+ core.selection.moveFocus(e.shiftKey ? "left" : "right", false);
532
+ }
533
+ },
534
+ onBlur: () => core.commitEdit()
535
+ });
536
+ }
537
+
538
+ //#endregion
539
+ //#region src/renderers/headerRenderer.ts
540
+ /**
541
+ * Ensure we always return a VNode, never a plain string
542
+ */
543
+ function toVNode(value) {
544
+ if (value == null || value === "") return createTextVNode("");
545
+ if (typeof value === "string") return createTextVNode(value);
546
+ return value;
547
+ }
548
+ /**
549
+ * Render header content based on column configuration and renderer registries
550
+ */
551
+ function renderHeader(options) {
552
+ const { column, colIndex, sortDirection, sortIndex, sortable, filterable, hasFilter, core, container, headerRenderers, globalHeaderRenderer } = options;
553
+ const params = {
554
+ column,
555
+ colIndex,
556
+ sortDirection,
557
+ sortIndex,
558
+ sortable,
559
+ filterable,
560
+ hasFilter,
561
+ onSort: (direction, addToExisting) => {
562
+ if (core && sortable) core.setSort(column.colId ?? column.field, direction, addToExisting);
563
+ },
564
+ onFilterClick: () => {
565
+ if (core && filterable) {
566
+ const headerCell = container?.querySelector(`[data-col-index="${colIndex}"]`);
567
+ if (headerCell) {
568
+ const rect = headerCell.getBoundingClientRect();
569
+ core.openFilterPopup(colIndex, {
570
+ top: rect.top,
571
+ left: rect.left,
572
+ width: rect.width,
573
+ height: rect.height
574
+ });
575
+ }
576
+ }
577
+ }
578
+ };
579
+ if (column.headerRenderer && typeof column.headerRenderer === "string") {
580
+ const renderer = headerRenderers[column.headerRenderer];
581
+ if (renderer) return toVNode(renderer(params));
582
+ }
583
+ if (globalHeaderRenderer) return toVNode(globalHeaderRenderer(params));
584
+ const children = [h("span", { class: "gp-grid-header-text" }, column.headerName ?? column.field)];
585
+ const iconsChildren = [];
586
+ if (sortable) {
587
+ const arrowsChildren = [h("span", { class: "gp-grid-sort-arrows-stack" }, [h("svg", {
588
+ class: `gp-grid-sort-arrow-up${sortDirection === "asc" ? " active" : ""}`,
589
+ width: "8",
590
+ height: "6",
591
+ viewBox: "0 0 8 6"
592
+ }, [h("path", {
593
+ d: "M4 0L8 6H0L4 0Z",
594
+ fill: "currentColor"
595
+ })]), h("svg", {
596
+ class: `gp-grid-sort-arrow-down${sortDirection === "desc" ? " active" : ""}`,
597
+ width: "8",
598
+ height: "6",
599
+ viewBox: "0 0 8 6"
600
+ }, [h("path", {
601
+ d: "M4 6L0 0H8L4 6Z",
602
+ fill: "currentColor"
603
+ })])])];
604
+ if (sortIndex !== void 0 && sortIndex > 0) arrowsChildren.push(h("span", { class: "gp-grid-sort-index" }, String(sortIndex)));
605
+ iconsChildren.push(h("span", { class: "gp-grid-sort-arrows" }, arrowsChildren));
606
+ }
607
+ if (filterable) iconsChildren.push(h("span", {
608
+ class: `gp-grid-filter-icon${hasFilter ? " active" : ""}`,
609
+ onMousedown: (e) => {
610
+ e.stopPropagation();
611
+ e.preventDefault();
612
+ params.onFilterClick();
613
+ },
614
+ onClick: (e) => {
615
+ e.stopPropagation();
616
+ }
617
+ }, [h("svg", {
618
+ width: "16",
619
+ height: "16",
620
+ viewBox: "0 0 24 24",
621
+ fill: "currentColor"
622
+ }, [h("path", { d: "M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z" })])]));
623
+ if (iconsChildren.length > 0) children.push(h("span", { class: "gp-grid-header-icons" }, iconsChildren));
624
+ return h(Fragment, children);
625
+ }
626
+
627
+ //#endregion
628
+ //#region src/composables/useFilterPopup.ts
629
+ /**
630
+ * Composable for filter popup behavior.
631
+ * Handles click-outside detection and escape key to close the popup.
632
+ */
633
+ function useFilterPopup(popupRef, options) {
634
+ const { onClose, ignoreSelector = ".gp-grid-filter-icon" } = options;
635
+ let handleClickOutside = null;
636
+ let handleKeyDown = null;
637
+ onMounted(() => {
638
+ handleClickOutside = (e) => {
639
+ const target = e.target;
640
+ if (ignoreSelector && target.closest(ignoreSelector)) return;
641
+ if (popupRef.value && !popupRef.value.contains(target)) onClose();
642
+ };
643
+ handleKeyDown = (e) => {
644
+ if (e.key === "Escape") onClose();
645
+ };
646
+ requestAnimationFrame(() => {
647
+ if (handleClickOutside) document.addEventListener("mousedown", handleClickOutside);
648
+ if (handleKeyDown) document.addEventListener("keydown", handleKeyDown);
649
+ });
650
+ });
651
+ onUnmounted(() => {
652
+ if (handleClickOutside) document.removeEventListener("mousedown", handleClickOutside);
653
+ if (handleKeyDown) document.removeEventListener("keydown", handleKeyDown);
654
+ });
655
+ }
656
+
657
+ //#endregion
658
+ //#region src/composables/useFilterConditions.ts
659
+ /**
660
+ * Composable for managing filter conditions.
661
+ * Used by NumberFilterContent, DateFilterContent, and TextFilterContent (condition mode).
662
+ */
663
+ function useFilterConditions(initialConditions, initialCombination = "and") {
664
+ const conditions = ref([...initialConditions]);
665
+ const combination = ref(initialCombination);
666
+ const updateCondition = (index, updates) => {
667
+ const next = [...conditions.value];
668
+ next[index] = {
669
+ ...next[index],
670
+ ...updates
671
+ };
672
+ conditions.value = next;
673
+ };
674
+ const addCondition = (defaultOperator) => {
675
+ conditions.value = [...conditions.value, {
676
+ operator: defaultOperator,
677
+ value: "",
678
+ valueTo: "",
679
+ nextOperator: "and"
680
+ }];
681
+ };
682
+ const removeCondition = (index) => {
683
+ conditions.value = conditions.value.filter((_, i) => i !== index);
684
+ };
685
+ return {
686
+ conditions,
687
+ combination,
688
+ updateCondition,
689
+ addCondition,
690
+ removeCondition
691
+ };
692
+ }
693
+
694
+ //#endregion
695
+ //#region src/components/TextFilterContent.vue
696
+ const _hoisted_1$4 = { class: "gp-grid-filter-content gp-grid-filter-text" };
697
+ const _hoisted_2$3 = {
698
+ key: 0,
699
+ class: "gp-grid-filter-mode-toggle"
700
+ };
701
+ const _hoisted_3$3 = {
702
+ key: 1,
703
+ class: "gp-grid-filter-info"
704
+ };
705
+ const _hoisted_4$3 = { class: "gp-grid-filter-actions" };
706
+ const _hoisted_5$3 = ["disabled"];
707
+ const _hoisted_6$2 = { class: "gp-grid-filter-list" };
708
+ const _hoisted_7$2 = {
709
+ key: 0,
710
+ class: "gp-grid-filter-option"
711
+ };
712
+ const _hoisted_8$2 = ["checked"];
713
+ const _hoisted_9$2 = ["checked", "onChange"];
714
+ const _hoisted_10$2 = {
715
+ key: 0,
716
+ class: "gp-grid-filter-combination"
717
+ };
718
+ const _hoisted_11 = ["onClick"];
719
+ const _hoisted_12 = ["onClick"];
720
+ const _hoisted_13 = { class: "gp-grid-filter-row" };
721
+ const _hoisted_14 = [
722
+ "value",
723
+ "autofocus",
724
+ "onChange"
725
+ ];
726
+ const _hoisted_15 = ["value"];
727
+ const _hoisted_16 = ["value", "onInput"];
728
+ const _hoisted_17 = ["onClick"];
729
+ const MAX_VALUES_FOR_LIST = 100;
730
+ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
731
+ __name: "TextFilterContent",
732
+ props: {
733
+ distinctValues: {},
734
+ currentFilter: {}
735
+ },
736
+ emits: ["apply", "close"],
737
+ setup(__props, { emit: __emit }) {
738
+ const OPERATORS = [
739
+ {
740
+ value: "contains",
741
+ label: "Contains"
742
+ },
743
+ {
744
+ value: "notContains",
745
+ label: "Does not contain"
746
+ },
747
+ {
748
+ value: "equals",
749
+ label: "Equals"
750
+ },
751
+ {
752
+ value: "notEquals",
753
+ label: "Does not equal"
754
+ },
755
+ {
756
+ value: "startsWith",
757
+ label: "Starts with"
758
+ },
759
+ {
760
+ value: "endsWith",
761
+ label: "Ends with"
762
+ },
763
+ {
764
+ value: "blank",
765
+ label: "Is blank"
766
+ },
767
+ {
768
+ value: "notBlank",
769
+ label: "Is not blank"
770
+ }
771
+ ];
772
+ const props = __props;
773
+ const emit = __emit;
774
+ function valueToString(v) {
775
+ if (Array.isArray(v)) return v.join(", ");
776
+ return String(v ?? "");
777
+ }
778
+ const uniqueValues = computed(() => {
779
+ const values = props.distinctValues.filter((v) => v != null && v !== "" && !(Array.isArray(v) && v.length === 0)).map((v) => valueToString(v));
780
+ return Array.from(new Set(values)).sort((a, b) => {
781
+ const numA = parseFloat(a);
782
+ const numB = parseFloat(b);
783
+ if (!isNaN(numA) && !isNaN(numB)) return numA - numB;
784
+ return a.localeCompare(b, void 0, {
785
+ numeric: true,
786
+ sensitivity: "base"
787
+ });
788
+ });
789
+ });
790
+ const hasTooManyValues = computed(() => uniqueValues.value.length > MAX_VALUES_FOR_LIST);
791
+ const mode = ref(computed(() => {
792
+ if (!props.currentFilter?.conditions[0]) return hasTooManyValues.value ? "condition" : "values";
793
+ const cond = props.currentFilter.conditions[0];
794
+ if (cond.selectedValues && cond.selectedValues.size > 0) return "values";
795
+ return "condition";
796
+ }).value);
797
+ const initialSelected = computed(() => {
798
+ if (!props.currentFilter?.conditions[0]) return /* @__PURE__ */ new Set();
799
+ return props.currentFilter.conditions[0].selectedValues ?? /* @__PURE__ */ new Set();
800
+ });
801
+ const initialIncludeBlanks = computed(() => {
802
+ if (!props.currentFilter?.conditions[0]) return true;
803
+ return props.currentFilter.conditions[0].includeBlank ?? true;
804
+ });
805
+ const searchText = ref("");
806
+ const selectedValues = ref(new Set(initialSelected.value));
807
+ const includeBlanks = ref(initialIncludeBlanks.value);
808
+ const { conditions, combination, updateCondition, addCondition, removeCondition } = useFilterConditions(computed(() => {
809
+ if (!props.currentFilter?.conditions.length) return [{
810
+ operator: "contains",
811
+ value: "",
812
+ valueTo: "",
813
+ nextOperator: "and"
814
+ }];
815
+ const cond = props.currentFilter.conditions[0];
816
+ if (cond.selectedValues && cond.selectedValues.size > 0) return [{
817
+ operator: "contains",
818
+ value: "",
819
+ valueTo: "",
820
+ nextOperator: "and"
821
+ }];
822
+ const defaultCombination = props.currentFilter.combination ?? "and";
823
+ return props.currentFilter.conditions.map((c) => {
824
+ const tc = c;
825
+ return {
826
+ operator: tc.operator,
827
+ value: tc.value ?? "",
828
+ valueTo: "",
829
+ nextOperator: tc.nextOperator ?? defaultCombination
830
+ };
831
+ });
832
+ }).value, props.currentFilter?.combination ?? "and");
833
+ const displayValues = computed(() => {
834
+ if (!searchText.value) return uniqueValues.value;
835
+ const lower = searchText.value.toLowerCase();
836
+ return uniqueValues.value.filter((v) => v.toLowerCase().includes(lower));
837
+ });
838
+ const hasBlanks = computed(() => {
839
+ return props.distinctValues.some((v) => v == null || v === "");
840
+ });
841
+ const allSelected = computed(() => {
842
+ return displayValues.value.every((v) => selectedValues.value.has(v)) && (!hasBlanks.value || includeBlanks.value);
843
+ });
844
+ function handleSelectAll() {
845
+ selectedValues.value = new Set(displayValues.value);
846
+ if (hasBlanks.value) includeBlanks.value = true;
847
+ }
848
+ function handleDeselectAll() {
849
+ selectedValues.value = /* @__PURE__ */ new Set();
850
+ includeBlanks.value = false;
851
+ }
852
+ function handleValueToggle(value) {
853
+ const next = new Set(selectedValues.value);
854
+ if (next.has(value)) next.delete(value);
855
+ else next.add(value);
856
+ selectedValues.value = next;
857
+ }
858
+ function handleApply() {
859
+ if (mode.value === "values") {
860
+ if (uniqueValues.value.every((v) => selectedValues.value.has(v)) && (!hasBlanks.value || includeBlanks.value)) {
861
+ emit("apply", null);
862
+ return;
863
+ }
864
+ emit("apply", {
865
+ conditions: [{
866
+ type: "text",
867
+ operator: "equals",
868
+ selectedValues: selectedValues.value,
869
+ includeBlank: includeBlanks.value
870
+ }],
871
+ combination: "and"
872
+ });
873
+ } else {
874
+ const validConditions = conditions.value.filter((c) => {
875
+ if (c.operator === "blank" || c.operator === "notBlank") return true;
876
+ return c.value.trim() !== "";
877
+ });
878
+ if (validConditions.length === 0) {
879
+ emit("apply", null);
880
+ return;
881
+ }
882
+ emit("apply", {
883
+ conditions: validConditions.map((c) => ({
884
+ type: "text",
885
+ operator: c.operator,
886
+ value: c.value,
887
+ nextOperator: c.nextOperator
888
+ })),
889
+ combination: "and"
890
+ });
891
+ }
892
+ }
893
+ function handleClear() {
894
+ emit("apply", null);
895
+ }
896
+ return (_ctx, _cache) => {
897
+ return openBlock(), createElementBlock("div", _hoisted_1$4, [
898
+ createCommentVNode(" Mode toggle - only show if not too many values "),
899
+ !hasTooManyValues.value ? (openBlock(), createElementBlock("div", _hoisted_2$3, [createElementVNode("button", {
900
+ type: "button",
901
+ class: normalizeClass({ active: mode.value === "values" }),
902
+ onClick: _cache[0] || (_cache[0] = ($event) => mode.value = "values")
903
+ }, " Values ", 2), createElementVNode("button", {
904
+ type: "button",
905
+ class: normalizeClass({ active: mode.value === "condition" }),
906
+ onClick: _cache[1] || (_cache[1] = ($event) => mode.value = "condition")
907
+ }, " Condition ", 2)])) : createCommentVNode("v-if", true),
908
+ createCommentVNode(" Too many values message "),
909
+ hasTooManyValues.value && mode.value === "condition" ? (openBlock(), createElementBlock("div", _hoisted_3$3, " Too many unique values (" + toDisplayString(uniqueValues.value.length) + "). Use conditions to filter. ", 1)) : createCommentVNode("v-if", true),
910
+ createCommentVNode(" VALUES MODE "),
911
+ mode.value === "values" ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [
912
+ createCommentVNode(" Search input "),
913
+ withDirectives(createElementVNode("input", {
914
+ "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => searchText.value = $event),
915
+ class: "gp-grid-filter-search",
916
+ type: "text",
917
+ placeholder: "Search...",
918
+ autofocus: ""
919
+ }, null, 512), [[vModelText, searchText.value]]),
920
+ createCommentVNode(" Select all / Deselect all "),
921
+ createElementVNode("div", _hoisted_4$3, [createElementVNode("button", {
922
+ type: "button",
923
+ disabled: allSelected.value,
924
+ onClick: handleSelectAll
925
+ }, " Select All ", 8, _hoisted_5$3), createElementVNode("button", {
926
+ type: "button",
927
+ onClick: handleDeselectAll
928
+ }, " Deselect All ")]),
929
+ createCommentVNode(" Checkbox list "),
930
+ createElementVNode("div", _hoisted_6$2, [
931
+ createCommentVNode(" Blanks option "),
932
+ hasBlanks.value ? (openBlock(), createElementBlock("label", _hoisted_7$2, [createElementVNode("input", {
933
+ type: "checkbox",
934
+ checked: includeBlanks.value,
935
+ onChange: _cache[3] || (_cache[3] = ($event) => includeBlanks.value = !includeBlanks.value)
936
+ }, null, 40, _hoisted_8$2), _cache[5] || (_cache[5] = createElementVNode("span", { class: "gp-grid-filter-blank" }, "(Blanks)", -1))])) : createCommentVNode("v-if", true),
937
+ createCommentVNode(" Values "),
938
+ (openBlock(true), createElementBlock(Fragment, null, renderList(displayValues.value, (value) => {
939
+ return openBlock(), createElementBlock("label", {
940
+ key: value,
941
+ class: "gp-grid-filter-option"
942
+ }, [createElementVNode("input", {
943
+ type: "checkbox",
944
+ checked: selectedValues.value.has(value),
945
+ onChange: ($event) => handleValueToggle(value)
946
+ }, null, 40, _hoisted_9$2), createElementVNode("span", null, toDisplayString(value), 1)]);
947
+ }), 128))
948
+ ])
949
+ ], 64)) : createCommentVNode("v-if", true),
950
+ createCommentVNode(" CONDITION MODE "),
951
+ mode.value === "condition" ? (openBlock(), createElementBlock(Fragment, { key: 3 }, [
952
+ (openBlock(true), createElementBlock(Fragment, null, renderList(unref(conditions), (cond, index) => {
953
+ return openBlock(), createElementBlock("div", {
954
+ key: index,
955
+ class: "gp-grid-filter-condition"
956
+ }, [
957
+ createCommentVNode(" Combination toggle (AND/OR) for conditions after the first "),
958
+ index > 0 ? (openBlock(), createElementBlock("div", _hoisted_10$2, [createElementVNode("button", {
959
+ type: "button",
960
+ class: normalizeClass({ active: unref(conditions)[index - 1]?.nextOperator === "and" }),
961
+ onClick: ($event) => unref(updateCondition)(index - 1, { nextOperator: "and" })
962
+ }, " AND ", 10, _hoisted_11), createElementVNode("button", {
963
+ type: "button",
964
+ class: normalizeClass({ active: unref(conditions)[index - 1]?.nextOperator === "or" }),
965
+ onClick: ($event) => unref(updateCondition)(index - 1, { nextOperator: "or" })
966
+ }, " OR ", 10, _hoisted_12)])) : createCommentVNode("v-if", true),
967
+ createElementVNode("div", _hoisted_13, [
968
+ createCommentVNode(" Operator select "),
969
+ createElementVNode("select", {
970
+ value: cond.operator,
971
+ autofocus: index === 0,
972
+ onChange: ($event) => unref(updateCondition)(index, { operator: $event.target.value })
973
+ }, [(openBlock(), createElementBlock(Fragment, null, renderList(OPERATORS, (op) => {
974
+ return createElementVNode("option", {
975
+ key: op.value,
976
+ value: op.value
977
+ }, toDisplayString(op.label), 9, _hoisted_15);
978
+ }), 64))], 40, _hoisted_14),
979
+ createCommentVNode(" Text input (hidden for blank/notBlank) "),
980
+ cond.operator !== "blank" && cond.operator !== "notBlank" ? (openBlock(), createElementBlock("input", {
981
+ key: 0,
982
+ type: "text",
983
+ value: cond.value,
984
+ placeholder: "Value",
985
+ class: "gp-grid-filter-text-input",
986
+ onInput: ($event) => unref(updateCondition)(index, { value: $event.target.value })
987
+ }, null, 40, _hoisted_16)) : createCommentVNode("v-if", true),
988
+ createCommentVNode(" Remove button (only if more than one condition) "),
989
+ unref(conditions).length > 1 ? (openBlock(), createElementBlock("button", {
990
+ key: 1,
991
+ type: "button",
992
+ class: "gp-grid-filter-remove",
993
+ onClick: ($event) => unref(removeCondition)(index)
994
+ }, " × ", 8, _hoisted_17)) : createCommentVNode("v-if", true)
995
+ ])
996
+ ]);
997
+ }), 128)),
998
+ createCommentVNode(" Add condition button "),
999
+ createElementVNode("button", {
1000
+ type: "button",
1001
+ class: "gp-grid-filter-add",
1002
+ onClick: _cache[4] || (_cache[4] = ($event) => unref(addCondition)("contains"))
1003
+ }, " + Add condition ")
1004
+ ], 64)) : createCommentVNode("v-if", true),
1005
+ createCommentVNode(" Apply/Clear buttons "),
1006
+ createElementVNode("div", { class: "gp-grid-filter-buttons" }, [createElementVNode("button", {
1007
+ type: "button",
1008
+ class: "gp-grid-filter-btn-clear",
1009
+ onClick: handleClear
1010
+ }, " Clear "), createElementVNode("button", {
1011
+ type: "button",
1012
+ class: "gp-grid-filter-btn-apply",
1013
+ onClick: handleApply
1014
+ }, " Apply ")])
1015
+ ]);
1016
+ };
1017
+ }
1018
+ });
1019
+ var TextFilterContent_default = _sfc_main$4;
1020
+
1021
+ //#endregion
1022
+ //#region src/components/NumberFilterContent.vue
1023
+ const _hoisted_1$3 = { class: "gp-grid-filter-content gp-grid-filter-number" };
1024
+ const _hoisted_2$2 = {
1025
+ key: 0,
1026
+ class: "gp-grid-filter-combination"
1027
+ };
1028
+ const _hoisted_3$2 = ["onClick"];
1029
+ const _hoisted_4$2 = ["onClick"];
1030
+ const _hoisted_5$2 = { class: "gp-grid-filter-row" };
1031
+ const _hoisted_6$1 = ["value", "onChange"];
1032
+ const _hoisted_7$1 = ["value"];
1033
+ const _hoisted_8$1 = ["value", "onInput"];
1034
+ const _hoisted_9$1 = ["value", "onInput"];
1035
+ const _hoisted_10$1 = ["onClick"];
1036
+ const _sfc_main$3 = /* @__PURE__ */ defineComponent({
1037
+ __name: "NumberFilterContent",
1038
+ props: { currentFilter: {} },
1039
+ emits: ["apply", "close"],
1040
+ setup(__props, { emit: __emit }) {
1041
+ const OPERATORS = [
1042
+ {
1043
+ value: "=",
1044
+ label: "="
1045
+ },
1046
+ {
1047
+ value: "!=",
1048
+ label: "≠"
1049
+ },
1050
+ {
1051
+ value: ">",
1052
+ label: ">"
1053
+ },
1054
+ {
1055
+ value: "<",
1056
+ label: "<"
1057
+ },
1058
+ {
1059
+ value: ">=",
1060
+ label: "≥"
1061
+ },
1062
+ {
1063
+ value: "<=",
1064
+ label: "≤"
1065
+ },
1066
+ {
1067
+ value: "between",
1068
+ label: "↔"
1069
+ },
1070
+ {
1071
+ value: "blank",
1072
+ label: "Is blank"
1073
+ },
1074
+ {
1075
+ value: "notBlank",
1076
+ label: "Not blank"
1077
+ }
1078
+ ];
1079
+ const props = __props;
1080
+ const emit = __emit;
1081
+ const { conditions, combination, updateCondition, addCondition, removeCondition } = useFilterConditions(computed(() => {
1082
+ if (!props.currentFilter?.conditions.length) return [{
1083
+ operator: "=",
1084
+ value: "",
1085
+ valueTo: "",
1086
+ nextOperator: "and"
1087
+ }];
1088
+ const defaultCombination = props.currentFilter.combination ?? "and";
1089
+ return props.currentFilter.conditions.map((c) => {
1090
+ const cond = c;
1091
+ return {
1092
+ operator: cond.operator,
1093
+ value: cond.value != null ? String(cond.value) : "",
1094
+ valueTo: cond.valueTo != null ? String(cond.valueTo) : "",
1095
+ nextOperator: cond.nextOperator ?? defaultCombination
1096
+ };
1097
+ });
1098
+ }).value, props.currentFilter?.combination ?? "and");
1099
+ function handleApply() {
1100
+ const validConditions = conditions.value.filter((c) => {
1101
+ if (c.operator === "blank" || c.operator === "notBlank") return true;
1102
+ if (c.operator === "between") return c.value !== "" && c.valueTo !== "";
1103
+ return c.value !== "";
1104
+ });
1105
+ if (validConditions.length === 0) {
1106
+ emit("apply", null);
1107
+ return;
1108
+ }
1109
+ emit("apply", {
1110
+ conditions: validConditions.map((c) => ({
1111
+ type: "number",
1112
+ operator: c.operator,
1113
+ value: c.value ? parseFloat(c.value) : void 0,
1114
+ valueTo: c.valueTo ? parseFloat(c.valueTo) : void 0,
1115
+ nextOperator: c.nextOperator
1116
+ })),
1117
+ combination: "and"
1118
+ });
1119
+ }
1120
+ function handleClear() {
1121
+ emit("apply", null);
1122
+ }
1123
+ return (_ctx, _cache) => {
1124
+ return openBlock(), createElementBlock("div", _hoisted_1$3, [
1125
+ (openBlock(true), createElementBlock(Fragment, null, renderList(unref(conditions), (cond, index) => {
1126
+ return openBlock(), createElementBlock("div", {
1127
+ key: index,
1128
+ class: "gp-grid-filter-condition"
1129
+ }, [
1130
+ createCommentVNode(" Combination toggle (AND/OR) for conditions after the first "),
1131
+ index > 0 ? (openBlock(), createElementBlock("div", _hoisted_2$2, [createElementVNode("button", {
1132
+ type: "button",
1133
+ class: normalizeClass({ active: unref(conditions)[index - 1]?.nextOperator === "and" }),
1134
+ onClick: ($event) => unref(updateCondition)(index - 1, { nextOperator: "and" })
1135
+ }, " AND ", 10, _hoisted_3$2), createElementVNode("button", {
1136
+ type: "button",
1137
+ class: normalizeClass({ active: unref(conditions)[index - 1]?.nextOperator === "or" }),
1138
+ onClick: ($event) => unref(updateCondition)(index - 1, { nextOperator: "or" })
1139
+ }, " OR ", 10, _hoisted_4$2)])) : createCommentVNode("v-if", true),
1140
+ createElementVNode("div", _hoisted_5$2, [
1141
+ createCommentVNode(" Operator select "),
1142
+ createElementVNode("select", {
1143
+ value: cond.operator,
1144
+ onChange: ($event) => unref(updateCondition)(index, { operator: $event.target.value })
1145
+ }, [(openBlock(), createElementBlock(Fragment, null, renderList(OPERATORS, (op) => {
1146
+ return createElementVNode("option", {
1147
+ key: op.value,
1148
+ value: op.value
1149
+ }, toDisplayString(op.label), 9, _hoisted_7$1);
1150
+ }), 64))], 40, _hoisted_6$1),
1151
+ createCommentVNode(" Number input (hidden for blank/notBlank) "),
1152
+ cond.operator !== "blank" && cond.operator !== "notBlank" ? (openBlock(), createElementBlock("input", {
1153
+ key: 0,
1154
+ type: "number",
1155
+ value: cond.value,
1156
+ placeholder: "Value",
1157
+ onInput: ($event) => unref(updateCondition)(index, { value: $event.target.value })
1158
+ }, null, 40, _hoisted_8$1)) : createCommentVNode("v-if", true),
1159
+ createCommentVNode(" Second number input for \"between\" "),
1160
+ cond.operator === "between" ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [_cache[1] || (_cache[1] = createElementVNode("span", { class: "gp-grid-filter-to" }, "to", -1)), createElementVNode("input", {
1161
+ type: "number",
1162
+ value: cond.valueTo,
1163
+ placeholder: "Value",
1164
+ onInput: ($event) => unref(updateCondition)(index, { valueTo: $event.target.value })
1165
+ }, null, 40, _hoisted_9$1)], 64)) : createCommentVNode("v-if", true),
1166
+ createCommentVNode(" Remove button (only if more than one condition) "),
1167
+ unref(conditions).length > 1 ? (openBlock(), createElementBlock("button", {
1168
+ key: 2,
1169
+ type: "button",
1170
+ class: "gp-grid-filter-remove",
1171
+ onClick: ($event) => unref(removeCondition)(index)
1172
+ }, " × ", 8, _hoisted_10$1)) : createCommentVNode("v-if", true)
1173
+ ])
1174
+ ]);
1175
+ }), 128)),
1176
+ createCommentVNode(" Add condition button "),
1177
+ createElementVNode("button", {
1178
+ type: "button",
1179
+ class: "gp-grid-filter-add",
1180
+ onClick: _cache[0] || (_cache[0] = ($event) => unref(addCondition)("="))
1181
+ }, " + Add condition "),
1182
+ createCommentVNode(" Apply/Clear buttons "),
1183
+ createElementVNode("div", { class: "gp-grid-filter-buttons" }, [createElementVNode("button", {
1184
+ type: "button",
1185
+ class: "gp-grid-filter-btn-clear",
1186
+ onClick: handleClear
1187
+ }, " Clear "), createElementVNode("button", {
1188
+ type: "button",
1189
+ class: "gp-grid-filter-btn-apply",
1190
+ onClick: handleApply
1191
+ }, " Apply ")])
1192
+ ]);
1193
+ };
1194
+ }
1195
+ });
1196
+ var NumberFilterContent_default = _sfc_main$3;
1197
+
1198
+ //#endregion
1199
+ //#region src/components/DateFilterContent.vue
1200
+ const _hoisted_1$2 = { class: "gp-grid-filter-content gp-grid-filter-date" };
1201
+ const _hoisted_2$1 = {
1202
+ key: 0,
1203
+ class: "gp-grid-filter-combination"
1204
+ };
1205
+ const _hoisted_3$1 = ["onClick"];
1206
+ const _hoisted_4$1 = ["onClick"];
1207
+ const _hoisted_5$1 = { class: "gp-grid-filter-row" };
1208
+ const _hoisted_6 = ["value", "onChange"];
1209
+ const _hoisted_7 = ["value"];
1210
+ const _hoisted_8 = ["value", "onInput"];
1211
+ const _hoisted_9 = ["value", "onInput"];
1212
+ const _hoisted_10 = ["onClick"];
1213
+ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
1214
+ __name: "DateFilterContent",
1215
+ props: { currentFilter: {} },
1216
+ emits: ["apply", "close"],
1217
+ setup(__props, { emit: __emit }) {
1218
+ const OPERATORS = [
1219
+ {
1220
+ value: "=",
1221
+ label: "="
1222
+ },
1223
+ {
1224
+ value: "!=",
1225
+ label: "≠"
1226
+ },
1227
+ {
1228
+ value: ">",
1229
+ label: ">"
1230
+ },
1231
+ {
1232
+ value: "<",
1233
+ label: "<"
1234
+ },
1235
+ {
1236
+ value: "between",
1237
+ label: "↔"
1238
+ },
1239
+ {
1240
+ value: "blank",
1241
+ label: "Is blank"
1242
+ },
1243
+ {
1244
+ value: "notBlank",
1245
+ label: "Not blank"
1246
+ }
1247
+ ];
1248
+ const props = __props;
1249
+ const emit = __emit;
1250
+ function formatDateForInput(date) {
1251
+ if (!date) return "";
1252
+ const d = typeof date === "string" ? new Date(date) : date;
1253
+ if (isNaN(d.getTime())) return "";
1254
+ return d.toISOString().split("T")[0];
1255
+ }
1256
+ const { conditions, combination, updateCondition, addCondition, removeCondition } = useFilterConditions(computed(() => {
1257
+ if (!props.currentFilter?.conditions.length) return [{
1258
+ operator: "=",
1259
+ value: "",
1260
+ valueTo: "",
1261
+ nextOperator: "and"
1262
+ }];
1263
+ const defaultCombination = props.currentFilter.combination ?? "and";
1264
+ return props.currentFilter.conditions.map((c) => {
1265
+ const cond = c;
1266
+ return {
1267
+ operator: cond.operator,
1268
+ value: formatDateForInput(cond.value),
1269
+ valueTo: formatDateForInput(cond.valueTo),
1270
+ nextOperator: cond.nextOperator ?? defaultCombination
1271
+ };
1272
+ });
1273
+ }).value, props.currentFilter?.combination ?? "and");
1274
+ function handleApply() {
1275
+ const validConditions = conditions.value.filter((c) => {
1276
+ if (c.operator === "blank" || c.operator === "notBlank") return true;
1277
+ if (c.operator === "between") return c.value !== "" && c.valueTo !== "";
1278
+ return c.value !== "";
1279
+ });
1280
+ if (validConditions.length === 0) {
1281
+ emit("apply", null);
1282
+ return;
1283
+ }
1284
+ emit("apply", {
1285
+ conditions: validConditions.map((c) => ({
1286
+ type: "date",
1287
+ operator: c.operator,
1288
+ value: c.value || void 0,
1289
+ valueTo: c.valueTo || void 0,
1290
+ nextOperator: c.nextOperator
1291
+ })),
1292
+ combination: "and"
1293
+ });
1294
+ }
1295
+ function handleClear() {
1296
+ emit("apply", null);
1297
+ }
1298
+ return (_ctx, _cache) => {
1299
+ return openBlock(), createElementBlock("div", _hoisted_1$2, [
1300
+ (openBlock(true), createElementBlock(Fragment, null, renderList(unref(conditions), (cond, index) => {
1301
+ return openBlock(), createElementBlock("div", {
1302
+ key: index,
1303
+ class: "gp-grid-filter-condition"
1304
+ }, [
1305
+ createCommentVNode(" Combination toggle (AND/OR) for conditions after the first "),
1306
+ index > 0 ? (openBlock(), createElementBlock("div", _hoisted_2$1, [createElementVNode("button", {
1307
+ type: "button",
1308
+ class: normalizeClass({ active: unref(conditions)[index - 1]?.nextOperator === "and" }),
1309
+ onClick: ($event) => unref(updateCondition)(index - 1, { nextOperator: "and" })
1310
+ }, " AND ", 10, _hoisted_3$1), createElementVNode("button", {
1311
+ type: "button",
1312
+ class: normalizeClass({ active: unref(conditions)[index - 1]?.nextOperator === "or" }),
1313
+ onClick: ($event) => unref(updateCondition)(index - 1, { nextOperator: "or" })
1314
+ }, " OR ", 10, _hoisted_4$1)])) : createCommentVNode("v-if", true),
1315
+ createElementVNode("div", _hoisted_5$1, [
1316
+ createCommentVNode(" Operator select "),
1317
+ createElementVNode("select", {
1318
+ value: cond.operator,
1319
+ onChange: ($event) => unref(updateCondition)(index, { operator: $event.target.value })
1320
+ }, [(openBlock(), createElementBlock(Fragment, null, renderList(OPERATORS, (op) => {
1321
+ return createElementVNode("option", {
1322
+ key: op.value,
1323
+ value: op.value
1324
+ }, toDisplayString(op.label), 9, _hoisted_7);
1325
+ }), 64))], 40, _hoisted_6),
1326
+ createCommentVNode(" Date input (hidden for blank/notBlank) "),
1327
+ cond.operator !== "blank" && cond.operator !== "notBlank" ? (openBlock(), createElementBlock("input", {
1328
+ key: 0,
1329
+ type: "date",
1330
+ value: cond.value,
1331
+ onInput: ($event) => unref(updateCondition)(index, { value: $event.target.value })
1332
+ }, null, 40, _hoisted_8)) : createCommentVNode("v-if", true),
1333
+ createCommentVNode(" Second date input for \"between\" "),
1334
+ cond.operator === "between" ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [_cache[1] || (_cache[1] = createElementVNode("span", { class: "gp-grid-filter-to" }, "to", -1)), createElementVNode("input", {
1335
+ type: "date",
1336
+ value: cond.valueTo,
1337
+ onInput: ($event) => unref(updateCondition)(index, { valueTo: $event.target.value })
1338
+ }, null, 40, _hoisted_9)], 64)) : createCommentVNode("v-if", true),
1339
+ createCommentVNode(" Remove button (only if more than one condition) "),
1340
+ unref(conditions).length > 1 ? (openBlock(), createElementBlock("button", {
1341
+ key: 2,
1342
+ type: "button",
1343
+ class: "gp-grid-filter-remove",
1344
+ onClick: ($event) => unref(removeCondition)(index)
1345
+ }, " × ", 8, _hoisted_10)) : createCommentVNode("v-if", true)
1346
+ ])
1347
+ ]);
1348
+ }), 128)),
1349
+ createCommentVNode(" Add condition button "),
1350
+ createElementVNode("button", {
1351
+ type: "button",
1352
+ class: "gp-grid-filter-add",
1353
+ onClick: _cache[0] || (_cache[0] = ($event) => unref(addCondition)("="))
1354
+ }, " + Add condition "),
1355
+ createCommentVNode(" Apply/Clear buttons "),
1356
+ createElementVNode("div", { class: "gp-grid-filter-buttons" }, [createElementVNode("button", {
1357
+ type: "button",
1358
+ class: "gp-grid-filter-btn-clear",
1359
+ onClick: handleClear
1360
+ }, " Clear "), createElementVNode("button", {
1361
+ type: "button",
1362
+ class: "gp-grid-filter-btn-apply",
1363
+ onClick: handleApply
1364
+ }, " Apply ")])
1365
+ ]);
1366
+ };
1367
+ }
1368
+ });
1369
+ var DateFilterContent_default = _sfc_main$2;
1370
+
1371
+ //#endregion
1372
+ //#region src/components/FilterPopup.vue
1373
+ const _hoisted_1$1 = { class: "gp-grid-filter-header" };
1374
+ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
1375
+ __name: "FilterPopup",
1376
+ props: {
1377
+ column: {},
1378
+ colIndex: {},
1379
+ anchorRect: {},
1380
+ distinctValues: {},
1381
+ currentFilter: {}
1382
+ },
1383
+ emits: ["apply", "close"],
1384
+ setup(__props, { emit: __emit }) {
1385
+ const props = __props;
1386
+ const emit = __emit;
1387
+ const popupRef = ref(null);
1388
+ useFilterPopup(popupRef, {
1389
+ onClose: () => emit("close"),
1390
+ ignoreSelector: ".gp-grid-filter-icon"
1391
+ });
1392
+ const colId = computed(() => props.column.colId ?? props.column.field);
1393
+ function handleApply(filter) {
1394
+ emit("apply", colId.value, filter);
1395
+ emit("close");
1396
+ }
1397
+ function handleClose() {
1398
+ emit("close");
1399
+ }
1400
+ const dataType = computed(() => props.column.cellDataType);
1401
+ computed(() => dataType.value === "text" || dataType.value === "object");
1402
+ const isNumberType = computed(() => dataType.value === "number");
1403
+ const isDateType = computed(() => dataType.value === "date" || dataType.value === "dateString" || dataType.value === "dateTime" || dataType.value === "dateTimeString");
1404
+ const popupStyle = computed(() => ({
1405
+ position: "fixed",
1406
+ top: `${props.anchorRect.top + props.anchorRect.height + 4}px`,
1407
+ left: `${props.anchorRect.left}px`,
1408
+ minWidth: `${Math.max(200, props.anchorRect.width)}px`,
1409
+ zIndex: 1e4
1410
+ }));
1411
+ return (_ctx, _cache) => {
1412
+ return openBlock(), createElementBlock("div", {
1413
+ ref_key: "popupRef",
1414
+ ref: popupRef,
1415
+ class: "gp-grid-filter-popup",
1416
+ style: normalizeStyle(popupStyle.value)
1417
+ }, [
1418
+ createElementVNode("div", _hoisted_1$1, " Filter: " + toDisplayString(__props.column.headerName ?? __props.column.field), 1),
1419
+ createCommentVNode(" Number filter "),
1420
+ isNumberType.value ? (openBlock(), createBlock(NumberFilterContent_default, {
1421
+ key: 0,
1422
+ "current-filter": __props.currentFilter,
1423
+ onApply: handleApply,
1424
+ onClose: handleClose
1425
+ }, null, 8, ["current-filter"])) : isDateType.value ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [createCommentVNode(" Date filter "), createVNode(DateFilterContent_default, {
1426
+ "current-filter": __props.currentFilter,
1427
+ onApply: handleApply,
1428
+ onClose: handleClose
1429
+ }, null, 8, ["current-filter"])], 2112)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [createCommentVNode(" Text filter (default) "), createVNode(TextFilterContent_default, {
1430
+ "distinct-values": __props.distinctValues,
1431
+ "current-filter": __props.currentFilter,
1432
+ onApply: handleApply,
1433
+ onClose: handleClose
1434
+ }, null, 8, ["distinct-values", "current-filter"])], 2112))
1435
+ ], 4);
1436
+ };
1437
+ }
1438
+ });
1439
+ var FilterPopup_default = _sfc_main$1;
1440
+
1441
+ //#endregion
1442
+ //#region src/GpGrid.vue
1443
+ const _hoisted_1 = ["data-col-index", "onClick"];
1444
+ const _hoisted_2 = [
1445
+ "onMousedown",
1446
+ "onDblclick",
1447
+ "onMouseenter"
1448
+ ];
1449
+ const _hoisted_3 = {
1450
+ key: 1,
1451
+ class: "gp-grid-loading"
1452
+ };
1453
+ const _hoisted_4 = {
1454
+ key: 2,
1455
+ class: "gp-grid-error"
1456
+ };
1457
+ const _hoisted_5 = {
1458
+ key: 3,
1459
+ class: "gp-grid-empty"
1460
+ };
1461
+ const _sfc_main = /* @__PURE__ */ defineComponent({
1462
+ __name: "GpGrid",
1463
+ props: {
1464
+ columns: {},
1465
+ dataSource: {},
1466
+ rowData: {},
1467
+ rowHeight: {},
1468
+ headerHeight: {},
1469
+ overscan: { default: 3 },
1470
+ sortingEnabled: {
1471
+ type: Boolean,
1472
+ default: true
1473
+ },
1474
+ darkMode: {
1475
+ type: Boolean,
1476
+ default: false
1477
+ },
1478
+ wheelDampening: { default: .1 },
1479
+ cellRenderers: { default: () => ({}) },
1480
+ editRenderers: { default: () => ({}) },
1481
+ headerRenderers: { default: () => ({}) },
1482
+ cellRenderer: {},
1483
+ editRenderer: {},
1484
+ headerRenderer: {},
1485
+ initialWidth: {},
1486
+ initialHeight: {},
1487
+ highlighting: {}
1488
+ },
1489
+ setup(__props, { expose: __expose }) {
1490
+ injectStyles$1();
1491
+ const props = __props;
1492
+ const containerRef = ref(null);
1493
+ const coreRef = shallowRef(null);
1494
+ const currentDataSourceRef = shallowRef(null);
1495
+ const coreUnsubscribeRef = shallowRef(null);
1496
+ const { state, applyInstructions, reset: resetState } = useGridState({
1497
+ initialWidth: props.initialWidth,
1498
+ initialHeight: props.initialHeight
1499
+ });
1500
+ const totalHeaderHeight = computed(() => props.headerHeight ?? props.rowHeight);
1501
+ const visibleColumnsWithIndices = computed(() => props.columns.map((col, index) => ({
1502
+ column: col,
1503
+ originalIndex: index
1504
+ })).filter(({ column }) => !column.hidden));
1505
+ const scaledColumns = computed(() => calculateScaledColumnPositions(visibleColumnsWithIndices.value.map((v) => v.column), state.viewportWidth));
1506
+ const columnPositions = computed(() => scaledColumns.value.positions);
1507
+ const columnWidths = computed(() => scaledColumns.value.widths);
1508
+ const totalWidth = computed(() => getTotalWidth$1(columnPositions.value));
1509
+ const slotsArray = computed(() => Array.from(state.slots.values()));
1510
+ const { handleCellMouseDown, handleCellDoubleClick, handleFillHandleMouseDown, handleHeaderClick, handleKeyDown, handleWheel, dragState } = useInputHandler(coreRef, containerRef, computed(() => props.columns), {
1511
+ activeCell: computed(() => state.activeCell),
1512
+ selectionRange: computed(() => state.selectionRange),
1513
+ editingCell: computed(() => state.editingCell),
1514
+ filterPopupOpen: computed(() => state.filterPopup?.isOpen ?? false),
1515
+ rowHeight: props.rowHeight,
1516
+ headerHeight: totalHeaderHeight.value,
1517
+ columnPositions,
1518
+ visibleColumnsWithIndices,
1519
+ slots: computed(() => state.slots)
1520
+ });
1521
+ const { fillHandlePosition } = useFillHandle({
1522
+ activeCell: computed(() => state.activeCell),
1523
+ selectionRange: computed(() => state.selectionRange),
1524
+ slots: computed(() => state.slots),
1525
+ columns: computed(() => props.columns),
1526
+ visibleColumnsWithIndices,
1527
+ columnPositions,
1528
+ columnWidths,
1529
+ rowHeight: props.rowHeight
1530
+ });
1531
+ function handleScroll() {
1532
+ const container = containerRef.value;
1533
+ const core = coreRef.value;
1534
+ if (!container || !core) return;
1535
+ core.setViewport(container.scrollTop, container.scrollLeft, container.clientWidth, container.clientHeight);
1536
+ }
1537
+ function handleFilterApply(colId, filter) {
1538
+ const core = coreRef.value;
1539
+ if (core) core.setFilter(colId, filter);
1540
+ }
1541
+ function handleFilterPopupClose() {
1542
+ const core = coreRef.value;
1543
+ if (core) core.closeFilterPopup();
1544
+ }
1545
+ function handleCellMouseEnter(rowIndex, colIndex) {
1546
+ coreRef.value?.input.handleCellMouseEnter(rowIndex, colIndex);
1547
+ }
1548
+ function handleCellMouseLeave() {
1549
+ coreRef.value?.input.handleCellMouseLeave();
1550
+ }
1551
+ function getRowClasses(slot) {
1552
+ return ["gp-grid-row", ...coreRef.value?.highlight?.computeRowClasses(slot.rowIndex, slot.rowData) ?? []].filter(Boolean).join(" ");
1553
+ }
1554
+ function getCellClasses(rowIndex, colIndex, column, rowData, _hoverPosition) {
1555
+ const isEditing = isCellEditing$1(rowIndex, colIndex, state.editingCell);
1556
+ return [buildCellClasses$1(isCellActive$1(rowIndex, colIndex, state.activeCell), isCellSelected$1(rowIndex, colIndex, state.selectionRange), isEditing, isCellInFillPreview$1(rowIndex, colIndex, dragState.value.dragType === "fill", dragState.value.fillSourceRange, dragState.value.fillTarget)), ...coreRef.value?.highlight?.computeCombinedCellClasses(rowIndex, colIndex, column, rowData) ?? []].filter(Boolean).join(" ");
1557
+ }
1558
+ function getOrCreateDataSource() {
1559
+ return props.dataSource ?? (props.rowData ? createDataSourceFromArray$1(props.rowData) : createClientDataSource$1([]));
1560
+ }
1561
+ /**
1562
+ * Initialize or reinitialize the GridCore with a data source.
1563
+ * Handles cleanup of old core and subscription before creating new ones.
1564
+ */
1565
+ function initializeCore(dataSource) {
1566
+ if (coreUnsubscribeRef.value) {
1567
+ coreUnsubscribeRef.value();
1568
+ coreUnsubscribeRef.value = null;
1569
+ }
1570
+ if (coreRef.value) coreRef.value.destroy();
1571
+ const core = new GridCore$1({
1572
+ columns: props.columns,
1573
+ dataSource,
1574
+ rowHeight: props.rowHeight,
1575
+ headerHeight: totalHeaderHeight.value,
1576
+ overscan: props.overscan,
1577
+ sortingEnabled: props.sortingEnabled,
1578
+ highlighting: props.highlighting
1579
+ });
1580
+ coreRef.value = core;
1581
+ coreUnsubscribeRef.value = core.onBatchInstruction((instructions) => {
1582
+ applyInstructions(instructions);
1583
+ });
1584
+ core.initialize();
1585
+ const container = containerRef.value;
1586
+ if (container) core.setViewport(container.scrollTop, container.scrollLeft, container.clientWidth, container.clientHeight);
1587
+ }
1588
+ onMounted(() => {
1589
+ const dataSource = getOrCreateDataSource();
1590
+ currentDataSourceRef.value = dataSource;
1591
+ initializeCore(dataSource);
1592
+ const container = containerRef.value;
1593
+ if (container && typeof ResizeObserver !== "undefined") {
1594
+ const resizeObserver = new ResizeObserver(() => {
1595
+ coreRef.value?.setViewport(container.scrollTop, container.scrollLeft, container.clientWidth, container.clientHeight);
1596
+ });
1597
+ resizeObserver.observe(container);
1598
+ onUnmounted(() => {
1599
+ resizeObserver.disconnect();
1600
+ });
1601
+ }
1602
+ onUnmounted(() => {
1603
+ if (coreUnsubscribeRef.value) {
1604
+ coreUnsubscribeRef.value();
1605
+ coreUnsubscribeRef.value = null;
1606
+ }
1607
+ if (coreRef.value) {
1608
+ coreRef.value.destroy();
1609
+ coreRef.value = null;
1610
+ }
1611
+ if (currentDataSourceRef.value) {
1612
+ currentDataSourceRef.value.destroy?.();
1613
+ currentDataSourceRef.value = null;
1614
+ }
1615
+ });
1616
+ });
1617
+ watch([() => props.dataSource, () => props.rowData], () => {
1618
+ const newDataSource = getOrCreateDataSource();
1619
+ const oldDataSource = currentDataSourceRef.value;
1620
+ if (oldDataSource && oldDataSource !== newDataSource) {
1621
+ oldDataSource.destroy?.();
1622
+ resetState();
1623
+ currentDataSourceRef.value = newDataSource;
1624
+ initializeCore(newDataSource);
1625
+ } else if (!oldDataSource) currentDataSourceRef.value = newDataSource;
1626
+ });
1627
+ watch(() => props.dataSource, (dataSource) => {
1628
+ if (dataSource) {
1629
+ const mutableDataSource = dataSource;
1630
+ if (mutableDataSource.subscribe) {
1631
+ const unsubscribe = mutableDataSource.subscribe(() => {
1632
+ coreRef.value?.refresh();
1633
+ });
1634
+ onUnmounted(() => unsubscribe());
1635
+ }
1636
+ }
1637
+ }, { immediate: true });
1638
+ watch(() => props.highlighting, (highlighting) => {
1639
+ if (coreRef.value?.highlight && highlighting) coreRef.value.highlight.updateOptions(highlighting);
1640
+ });
1641
+ __expose({ core: coreRef });
1642
+ return (_ctx, _cache) => {
1643
+ return openBlock(), createElementBlock("div", {
1644
+ ref_key: "containerRef",
1645
+ ref: containerRef,
1646
+ class: normalizeClass(["gp-grid-container", { "gp-grid-container--dark": __props.darkMode }]),
1647
+ style: {
1648
+ "width": "100%",
1649
+ "height": "100%",
1650
+ "overflow": "auto",
1651
+ "position": "relative"
1652
+ },
1653
+ tabindex: "0",
1654
+ onScroll: handleScroll,
1655
+ onWheel: _cache[1] || (_cache[1] = (e) => unref(handleWheel)(e, __props.wheelDampening)),
1656
+ onKeydown: _cache[2] || (_cache[2] = (...args) => unref(handleKeyDown) && unref(handleKeyDown)(...args))
1657
+ }, [
1658
+ createCommentVNode(" Content sizer "),
1659
+ createElementVNode("div", { style: normalizeStyle({
1660
+ width: `${Math.max(unref(state).contentWidth, totalWidth.value)}px`,
1661
+ height: `${Math.max(unref(state).contentHeight, totalHeaderHeight.value)}px`,
1662
+ position: "relative",
1663
+ minWidth: "100%"
1664
+ }) }, [
1665
+ createCommentVNode(" Headers "),
1666
+ createElementVNode("div", {
1667
+ class: "gp-grid-header",
1668
+ style: normalizeStyle({
1669
+ position: "sticky",
1670
+ top: 0,
1671
+ left: 0,
1672
+ height: `${totalHeaderHeight.value}px`,
1673
+ width: `${Math.max(unref(state).contentWidth, totalWidth.value)}px`,
1674
+ minWidth: "100%"
1675
+ })
1676
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList(visibleColumnsWithIndices.value, ({ column, originalIndex }, visibleIndex) => {
1677
+ return openBlock(), createElementBlock("div", {
1678
+ key: column.colId ?? column.field,
1679
+ class: "gp-grid-header-cell",
1680
+ "data-col-index": originalIndex,
1681
+ style: normalizeStyle({
1682
+ position: "absolute",
1683
+ left: `${columnPositions.value[visibleIndex]}px`,
1684
+ top: 0,
1685
+ width: `${columnWidths.value[visibleIndex]}px`,
1686
+ height: `${totalHeaderHeight.value}px`,
1687
+ background: "transparent"
1688
+ }),
1689
+ onClick: (e) => unref(handleHeaderClick)(originalIndex, e)
1690
+ }, [(openBlock(), createBlock(resolveDynamicComponent(unref(renderHeader)({
1691
+ column,
1692
+ colIndex: originalIndex,
1693
+ sortDirection: unref(state).headers.get(originalIndex)?.sortDirection,
1694
+ sortIndex: unref(state).headers.get(originalIndex)?.sortIndex,
1695
+ sortable: unref(state).headers.get(originalIndex)?.sortable ?? true,
1696
+ filterable: unref(state).headers.get(originalIndex)?.filterable ?? true,
1697
+ hasFilter: unref(state).headers.get(originalIndex)?.hasFilter ?? false,
1698
+ core: coreRef.value,
1699
+ container: containerRef.value,
1700
+ headerRenderers: __props.headerRenderers ?? {},
1701
+ globalHeaderRenderer: __props.headerRenderer
1702
+ }))))], 12, _hoisted_1);
1703
+ }), 128))], 4),
1704
+ createCommentVNode(" Row slots "),
1705
+ (openBlock(true), createElementBlock(Fragment, null, renderList(slotsArray.value.filter((s) => s.rowIndex >= 0), (slot) => {
1706
+ return openBlock(), createElementBlock("div", {
1707
+ key: slot.slotId,
1708
+ class: normalizeClass(getRowClasses(slot)),
1709
+ style: normalizeStyle({
1710
+ position: "absolute",
1711
+ top: 0,
1712
+ left: 0,
1713
+ transform: `translateY(${slot.translateY}px)`,
1714
+ width: `${Math.max(unref(state).contentWidth, totalWidth.value)}px`,
1715
+ height: `${__props.rowHeight}px`
1716
+ })
1717
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList(visibleColumnsWithIndices.value, ({ column, originalIndex }, visibleIndex) => {
1718
+ return openBlock(), createElementBlock("div", {
1719
+ key: `${slot.slotId}-${originalIndex}`,
1720
+ class: normalizeClass(getCellClasses(slot.rowIndex, originalIndex, column, slot.rowData, unref(state).hoverPosition)),
1721
+ style: normalizeStyle({
1722
+ position: "absolute",
1723
+ left: `${columnPositions.value[visibleIndex]}px`,
1724
+ top: 0,
1725
+ width: `${columnWidths.value[visibleIndex]}px`,
1726
+ height: `${__props.rowHeight}px`
1727
+ }),
1728
+ onMousedown: (e) => unref(handleCellMouseDown)(slot.rowIndex, originalIndex, e),
1729
+ onDblclick: () => unref(handleCellDoubleClick)(slot.rowIndex, originalIndex),
1730
+ onMouseenter: () => handleCellMouseEnter(slot.rowIndex, originalIndex),
1731
+ onMouseleave: handleCellMouseLeave
1732
+ }, [createCommentVNode(" Edit mode "), unref(isCellEditing$1)(slot.rowIndex, originalIndex, unref(state).editingCell) && unref(state).editingCell ? (openBlock(), createBlock(resolveDynamicComponent(unref(renderEditCell)({
1733
+ column,
1734
+ rowData: slot.rowData,
1735
+ rowIndex: slot.rowIndex,
1736
+ colIndex: originalIndex,
1737
+ initialValue: unref(state).editingCell.initialValue,
1738
+ core: coreRef.value,
1739
+ editRenderers: __props.editRenderers ?? {},
1740
+ globalEditRenderer: __props.editRenderer
1741
+ })), { key: 0 })) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createCommentVNode(" View mode "), (openBlock(), createBlock(resolveDynamicComponent(unref(renderCell)({
1742
+ column,
1743
+ rowData: slot.rowData,
1744
+ rowIndex: slot.rowIndex,
1745
+ colIndex: originalIndex,
1746
+ isActive: unref(isCellActive$1)(slot.rowIndex, originalIndex, unref(state).activeCell),
1747
+ isSelected: unref(isCellSelected$1)(slot.rowIndex, originalIndex, unref(state).selectionRange),
1748
+ isEditing: false,
1749
+ cellRenderers: __props.cellRenderers ?? {},
1750
+ globalCellRenderer: __props.cellRenderer
1751
+ }))))], 64))], 46, _hoisted_2);
1752
+ }), 128))], 6);
1753
+ }), 128)),
1754
+ createCommentVNode(" Fill handle "),
1755
+ unref(fillHandlePosition) && !unref(state).editingCell ? (openBlock(), createElementBlock("div", {
1756
+ key: 0,
1757
+ class: "gp-grid-fill-handle",
1758
+ style: normalizeStyle({
1759
+ position: "absolute",
1760
+ top: `${unref(fillHandlePosition).top}px`,
1761
+ left: `${unref(fillHandlePosition).left}px`,
1762
+ zIndex: 200
1763
+ }),
1764
+ onMousedown: _cache[0] || (_cache[0] = (...args) => unref(handleFillHandleMouseDown) && unref(handleFillHandleMouseDown)(...args))
1765
+ }, null, 36)) : createCommentVNode("v-if", true),
1766
+ createCommentVNode(" Loading indicator "),
1767
+ unref(state).isLoading ? (openBlock(), createElementBlock("div", _hoisted_3, [..._cache[3] || (_cache[3] = [createElementVNode("div", { class: "gp-grid-loading-spinner" }, null, -1), createTextVNode(" Loading... ", -1)])])) : createCommentVNode("v-if", true),
1768
+ createCommentVNode(" Error message "),
1769
+ unref(state).error ? (openBlock(), createElementBlock("div", _hoisted_4, " Error: " + toDisplayString(unref(state).error), 1)) : createCommentVNode("v-if", true),
1770
+ createCommentVNode(" Empty state "),
1771
+ !unref(state).isLoading && !unref(state).error && unref(state).totalRows === 0 ? (openBlock(), createElementBlock("div", _hoisted_5, " No data to display ")) : createCommentVNode("v-if", true)
1772
+ ], 4),
1773
+ createCommentVNode(" Filter Popup "),
1774
+ unref(state).filterPopup?.isOpen && unref(state).filterPopup.column && unref(state).filterPopup.anchorRect ? (openBlock(), createBlock(FilterPopup_default, {
1775
+ key: 0,
1776
+ column: unref(state).filterPopup.column,
1777
+ "col-index": unref(state).filterPopup.colIndex,
1778
+ "anchor-rect": unref(state).filterPopup.anchorRect,
1779
+ "distinct-values": unref(state).filterPopup.distinctValues,
1780
+ "current-filter": unref(state).filterPopup.currentFilter,
1781
+ onApply: handleFilterApply,
1782
+ onClose: handleFilterPopupClose
1783
+ }, null, 8, [
1784
+ "column",
1785
+ "col-index",
1786
+ "anchor-rect",
1787
+ "distinct-values",
1788
+ "current-filter"
1789
+ ])) : createCommentVNode("v-if", true)
1790
+ ], 34);
1791
+ };
1792
+ }
1793
+ });
1794
+ var GpGrid_default = _sfc_main;
1795
+
1796
+ //#endregion
1797
+ //#region src/composables/useGpGrid.ts
1798
+ /**
1799
+ * Nuxt-friendly composable for using gp-grid.
1800
+ * Returns all the pieces needed to build a custom grid component.
1801
+ */
1802
+ function useGpGrid(options) {
1803
+ injectStyles$1();
1804
+ const containerRef = ref(null);
1805
+ const coreRef = ref(null);
1806
+ const { state, applyInstructions } = useGridState();
1807
+ const totalHeaderHeight = computed(() => options.headerHeight ?? options.rowHeight);
1808
+ const visibleColumnsWithIndices = computed(() => options.columns.map((col, index) => ({
1809
+ column: col,
1810
+ originalIndex: index
1811
+ })).filter(({ column }) => !column.hidden));
1812
+ const scaledColumns = computed(() => calculateScaledColumnPositions(visibleColumnsWithIndices.value.map((v) => v.column), state.viewportWidth));
1813
+ const columnPositions = computed(() => scaledColumns.value.positions);
1814
+ const columnWidths = computed(() => scaledColumns.value.widths);
1815
+ const totalWidth = computed(() => getTotalWidth$1(columnPositions.value));
1816
+ const slotsArray = computed(() => Array.from(state.slots.values()));
1817
+ const { handleCellMouseDown, handleCellDoubleClick, handleFillHandleMouseDown, handleHeaderClick, handleKeyDown, handleWheel, dragState } = useInputHandler(coreRef, containerRef, computed(() => options.columns), {
1818
+ activeCell: computed(() => state.activeCell),
1819
+ selectionRange: computed(() => state.selectionRange),
1820
+ editingCell: computed(() => state.editingCell),
1821
+ filterPopupOpen: computed(() => state.filterPopup?.isOpen ?? false),
1822
+ rowHeight: options.rowHeight,
1823
+ headerHeight: totalHeaderHeight.value,
1824
+ columnPositions,
1825
+ visibleColumnsWithIndices,
1826
+ slots: computed(() => state.slots)
1827
+ });
1828
+ const handleScroll = () => {
1829
+ const container = containerRef.value;
1830
+ const core = coreRef.value;
1831
+ if (!container || !core) return;
1832
+ core.setViewport(container.scrollTop, container.scrollLeft, container.clientWidth, container.clientHeight);
1833
+ };
1834
+ const handleFilterApply = (colId, filter) => {
1835
+ const core = coreRef.value;
1836
+ if (core) core.setFilter(colId, filter);
1837
+ };
1838
+ const handleFilterPopupClose = () => {
1839
+ const core = coreRef.value;
1840
+ if (core) core.closeFilterPopup();
1841
+ };
1842
+ const handleCellMouseEnter = (rowIndex, colIndex) => {
1843
+ coreRef.value?.input.handleCellMouseEnter(rowIndex, colIndex);
1844
+ };
1845
+ const handleCellMouseLeave = () => {
1846
+ coreRef.value?.input.handleCellMouseLeave();
1847
+ };
1848
+ onMounted(() => {
1849
+ const dataSource = options.dataSource ?? (options.rowData ? createDataSourceFromArray$1(options.rowData) : createClientDataSource$1([]));
1850
+ const core = new GridCore$1({
1851
+ columns: options.columns,
1852
+ dataSource,
1853
+ rowHeight: options.rowHeight,
1854
+ headerHeight: totalHeaderHeight.value,
1855
+ overscan: options.overscan ?? 3,
1856
+ sortingEnabled: options.sortingEnabled ?? true,
1857
+ highlighting: options.highlighting
1858
+ });
1859
+ coreRef.value = core;
1860
+ const unsubscribe = core.onBatchInstruction((instructions) => {
1861
+ applyInstructions(instructions);
1862
+ });
1863
+ core.initialize();
1864
+ const container = containerRef.value;
1865
+ if (container) {
1866
+ core.setViewport(container.scrollTop, container.scrollLeft, container.clientWidth, container.clientHeight);
1867
+ const resizeObserver = new ResizeObserver(() => {
1868
+ core.setViewport(container.scrollTop, container.scrollLeft, container.clientWidth, container.clientHeight);
1869
+ });
1870
+ resizeObserver.observe(container);
1871
+ onUnmounted(() => {
1872
+ resizeObserver.disconnect();
1873
+ unsubscribe();
1874
+ coreRef.value = null;
1875
+ });
1876
+ }
1877
+ });
1878
+ watch(() => options.dataSource, (dataSource) => {
1879
+ if (dataSource) {
1880
+ const mutableDataSource = dataSource;
1881
+ if (mutableDataSource.subscribe) {
1882
+ const unsubscribe = mutableDataSource.subscribe(() => {
1883
+ coreRef.value?.refresh();
1884
+ });
1885
+ onUnmounted(() => unsubscribe());
1886
+ }
1887
+ }
1888
+ }, { immediate: true });
1889
+ watch(() => options.highlighting, (highlighting) => {
1890
+ if (coreRef.value?.highlight && highlighting) coreRef.value.highlight.updateOptions(highlighting);
1891
+ });
1892
+ const { fillHandlePosition } = useFillHandle({
1893
+ activeCell: computed(() => state.activeCell),
1894
+ selectionRange: computed(() => state.selectionRange),
1895
+ slots: computed(() => state.slots),
1896
+ columns: computed(() => options.columns),
1897
+ visibleColumnsWithIndices,
1898
+ columnPositions,
1899
+ columnWidths,
1900
+ rowHeight: options.rowHeight
1901
+ });
1902
+ return {
1903
+ containerRef,
1904
+ coreRef,
1905
+ state,
1906
+ slotsArray,
1907
+ totalHeaderHeight,
1908
+ columnPositions,
1909
+ columnWidths,
1910
+ totalWidth,
1911
+ fillHandlePosition,
1912
+ handleScroll,
1913
+ handleCellMouseDown,
1914
+ handleCellDoubleClick,
1915
+ handleFillHandleMouseDown,
1916
+ handleHeaderClick,
1917
+ handleKeyDown,
1918
+ handleWheel,
1919
+ handleFilterApply,
1920
+ handleFilterPopupClose,
1921
+ handleCellMouseEnter,
1922
+ handleCellMouseLeave,
1923
+ dragState,
1924
+ isCellSelected: isCellSelected$1,
1925
+ isCellActive: isCellActive$1,
1926
+ isCellEditing: isCellEditing$1,
1927
+ isCellInFillPreview: isCellInFillPreview$1,
1928
+ buildCellClasses: buildCellClasses$1
1929
+ };
1930
+ }
1931
+
1932
+ //#endregion
1933
+ export { GpGrid_default as GpGrid, GpGrid_default as default, GridCore, buildCellClasses, calculateColumnPositions, createClientDataSource, createDataSourceFromArray, createInitialState, createMutableClientDataSource, createServerDataSource, findColumnAtX, getCellValue, getTotalWidth, gridStyles, injectStyles, isCellActive, isCellEditing, isCellInFillPreview, isCellSelected, isRowVisible, renderCell, renderEditCell, renderHeader, useAutoScroll, useFillHandle, useFilterConditions, useFilterPopup, useGpGrid, useGridState, useInputHandler };
1934
+ //# sourceMappingURL=index.js.map