@warkypublic/svelix 0.1.29 → 0.1.31
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/components/Boxer/Boxer.svelte +16 -1
- package/dist/components/Gridler/components/Gridler.svelte +41 -7
- package/dist/components/Gridler/components/Gridler.svelte.d.ts +2 -0
- package/dist/components/Gridler/components/GridlerCanvas.svelte +81 -6
- package/dist/components/Gridler/components/GridlerCanvas.svelte.d.ts +7 -0
- package/dist/components/Gridler/components/GridlerFull.svelte +36 -3
- package/llm/README.md +1 -0
- package/llm/gridler-events.md +139 -0
- package/package.json +29 -29
|
@@ -97,6 +97,7 @@
|
|
|
97
97
|
let dropdownZ = $state(1100);
|
|
98
98
|
let pointerInteractingWithDropdown = $state(false);
|
|
99
99
|
let insideDialog = $state(false);
|
|
100
|
+
let suppressOpenOnFocus = $state(false);
|
|
100
101
|
const effectiveDisablePortal = $derived(disablePortal || insideDialog);
|
|
101
102
|
// Plain variable — NOT $state to avoid deep proxy on the complex virtualizer object.
|
|
102
103
|
let rawVirtualizer: SvelteVirtualizer<
|
|
@@ -303,6 +304,12 @@
|
|
|
303
304
|
activeOptionIndex = null;
|
|
304
305
|
}
|
|
305
306
|
break;
|
|
307
|
+
case "Tab":
|
|
308
|
+
if (multiSelect && $store.opened && $store.boxerData.length > 0) {
|
|
309
|
+
e.preventDefault();
|
|
310
|
+
onOptionSubmit(0);
|
|
311
|
+
}
|
|
312
|
+
break;
|
|
306
313
|
}
|
|
307
314
|
}
|
|
308
315
|
|
|
@@ -331,6 +338,7 @@
|
|
|
331
338
|
e.preventDefault();
|
|
332
339
|
store.setOpened(false);
|
|
333
340
|
activeOptionIndex = null;
|
|
341
|
+
suppressOpenOnFocus = true;
|
|
334
342
|
targetRef?.focus();
|
|
335
343
|
break;
|
|
336
344
|
}
|
|
@@ -387,6 +395,7 @@
|
|
|
387
395
|
activeOptionIndex = null;
|
|
388
396
|
}
|
|
389
397
|
export function focus() {
|
|
398
|
+
suppressOpenOnFocus = false;
|
|
390
399
|
targetRef?.focus();
|
|
391
400
|
}
|
|
392
401
|
export function getValue() {
|
|
@@ -504,7 +513,13 @@
|
|
|
504
513
|
isFetching={$store.isFetching}
|
|
505
514
|
search={$store.input}
|
|
506
515
|
onclear={onClear}
|
|
507
|
-
onfocus={() =>
|
|
516
|
+
onfocus={() => {
|
|
517
|
+
if (suppressOpenOnFocus) {
|
|
518
|
+
suppressOpenOnFocus = false;
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
store.setOpened(true);
|
|
522
|
+
}}
|
|
508
523
|
onkeydown={onInputKeydown}
|
|
509
524
|
onsearch={(v) => {
|
|
510
525
|
store.setSearch(v);
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
item?: Record<string, unknown>;
|
|
29
29
|
column?: GridlerColumn;
|
|
30
30
|
}) => void;
|
|
31
|
+
/** Resolve raw row data by row index for populating onCellEvent. */
|
|
32
|
+
getRowData?: (row: number) => Record<string, unknown> | undefined;
|
|
31
33
|
children?: Snippet;
|
|
32
34
|
}
|
|
33
35
|
|
|
@@ -68,8 +70,9 @@
|
|
|
68
70
|
sortOrder,
|
|
69
71
|
onFilterChange: _onFilterChange,
|
|
70
72
|
filters: _filters,
|
|
71
|
-
selectedItems
|
|
73
|
+
selectedItems,
|
|
72
74
|
onSelectedItemsChange: _onSelectedItemsChange,
|
|
75
|
+
getRowData,
|
|
73
76
|
settings,
|
|
74
77
|
children,
|
|
75
78
|
}: Props = $props();
|
|
@@ -245,6 +248,7 @@
|
|
|
245
248
|
|
|
246
249
|
function handleKeyDown(e: KeyboardEvent) {
|
|
247
250
|
if (!canvasComponent?.getHasFocus()) return;
|
|
251
|
+
onGridEvent?.("keydown", undefined, undefined, { x: 0, y: 0, code: e.key });
|
|
248
252
|
|
|
249
253
|
// Search toggle
|
|
250
254
|
if ((e.ctrlKey || e.metaKey) && e.key === "f") {
|
|
@@ -308,7 +312,7 @@
|
|
|
308
312
|
if (!resolvedReadonly) {
|
|
309
313
|
beginEdit(focusedCell);
|
|
310
314
|
}
|
|
311
|
-
onCellEvent?.("enter_key", {}, columns[col] ?? { id: "", title: "" });
|
|
315
|
+
onCellEvent?.("enter_key", getRowData?.(row) ?? {}, columns[col] ?? { id: "", title: "" });
|
|
312
316
|
e.preventDefault();
|
|
313
317
|
return;
|
|
314
318
|
case "Escape": {
|
|
@@ -334,7 +338,7 @@
|
|
|
334
338
|
onRowAppended?.();
|
|
335
339
|
}
|
|
336
340
|
}
|
|
337
|
-
onCellEvent?.("tab_key", {}, columns[col] ?? { id: "", title: "" });
|
|
341
|
+
onCellEvent?.("tab_key", getRowData?.(row) ?? {}, columns[col] ?? { id: "", title: "" });
|
|
338
342
|
e.preventDefault();
|
|
339
343
|
break;
|
|
340
344
|
case "Delete":
|
|
@@ -346,7 +350,7 @@
|
|
|
346
350
|
onDelete?.(delSel);
|
|
347
351
|
}
|
|
348
352
|
}
|
|
349
|
-
onCellEvent?.("
|
|
353
|
+
onCellEvent?.("delete_key", getRowData?.(row) ?? {}, columns[col] ?? { id: "", title: "" });
|
|
350
354
|
return;
|
|
351
355
|
}
|
|
352
356
|
default:
|
|
@@ -376,7 +380,7 @@
|
|
|
376
380
|
|
|
377
381
|
function handleVisibleRangeChange(range: VisibleRange) {
|
|
378
382
|
onVisibleRangeChange?.(range);
|
|
379
|
-
onGridEvent?.("scroll", { row: range.firstRow, column: range.firstCol });
|
|
383
|
+
onGridEvent?.("scroll", undefined, undefined, undefined, { row: range.firstRow, column: range.firstCol });
|
|
380
384
|
}
|
|
381
385
|
|
|
382
386
|
async function handleMenuOpen(d: {
|
|
@@ -423,15 +427,45 @@
|
|
|
423
427
|
onSelectionChange={(sel) => {
|
|
424
428
|
currentSelection = sel;
|
|
425
429
|
onSelectionChange?.(sel);
|
|
430
|
+
onGridEvent?.("selection_changed");
|
|
426
431
|
}}
|
|
427
432
|
onCellDblClick={(item, cell) => {
|
|
428
433
|
onCellDblClick?.(item, cell);
|
|
429
|
-
onCellEvent?.("dblclick", {}, columns[item[0]] ?? { id: "", title: "" });
|
|
434
|
+
onCellEvent?.("dblclick", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
|
|
435
|
+
onGridEvent?.("dblclick", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
|
|
436
|
+
}}
|
|
437
|
+
onCellClick={(item) => {
|
|
438
|
+
onCellEvent?.("click", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
|
|
439
|
+
onGridEvent?.("click", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
|
|
440
|
+
}}
|
|
441
|
+
onCellHover={(item) => {
|
|
442
|
+
onCellEvent?.("hover", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
|
|
443
|
+
onGridEvent?.("hover", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
|
|
444
|
+
}}
|
|
445
|
+
onCellLeave={(item) => {
|
|
446
|
+
onCellEvent?.("leave", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
|
|
447
|
+
onGridEvent?.("leave", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" });
|
|
448
|
+
}}
|
|
449
|
+
onCellContextMenu={(item, x, y) => {
|
|
450
|
+
onCellEvent?.("contextmenu", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" }, { x, y });
|
|
451
|
+
onGridEvent?.("contextmenu", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" }, { x, y });
|
|
452
|
+
}}
|
|
453
|
+
onColumnResized={(col, width) => {
|
|
454
|
+
onGridEvent?.("column_resized", undefined, columns[col], undefined, { width });
|
|
455
|
+
}}
|
|
456
|
+
onGridResize={(width, height) => {
|
|
457
|
+
onGridEvent?.("resize", undefined, undefined, undefined, { width, height });
|
|
458
|
+
}}
|
|
459
|
+
onGridEnter={() => {
|
|
460
|
+
onGridEvent?.("enter");
|
|
430
461
|
}}
|
|
431
462
|
{sortOrder}
|
|
432
463
|
{onSortOrderChange}
|
|
433
464
|
{onHeaderContextMenu}
|
|
434
|
-
{onColumnMoved
|
|
465
|
+
onColumnMoved={onColumnMoved || onGridEvent ? (from, to) => {
|
|
466
|
+
onColumnMoved?.(from, to);
|
|
467
|
+
onGridEvent?.("column_moved", undefined, undefined, undefined, { from, to });
|
|
468
|
+
} : undefined}
|
|
435
469
|
{onCellEdited}
|
|
436
470
|
{onDelete}
|
|
437
471
|
{onRowAppended}
|
|
@@ -13,6 +13,8 @@ export interface Props extends Partial<GridlerProps> {
|
|
|
13
13
|
item?: Record<string, unknown>;
|
|
14
14
|
column?: GridlerColumn;
|
|
15
15
|
}) => void;
|
|
16
|
+
/** Resolve raw row data by row index for populating onCellEvent. */
|
|
17
|
+
getRowData?: (row: number) => Record<string, unknown> | undefined;
|
|
16
18
|
children?: Snippet;
|
|
17
19
|
}
|
|
18
20
|
declare const Gridler: import("svelte").Component<Props, {}, "">;
|
|
@@ -72,6 +72,13 @@
|
|
|
72
72
|
item?: Record<string, unknown>;
|
|
73
73
|
column?: GridColumn<Record<string, unknown>>;
|
|
74
74
|
}) => void;
|
|
75
|
+
onCellClick?: (item: Item) => void;
|
|
76
|
+
onCellHover?: (item: Item) => void;
|
|
77
|
+
onCellLeave?: (item: Item) => void;
|
|
78
|
+
onCellContextMenu?: (item: Item, x: number, y: number) => void;
|
|
79
|
+
onColumnResized?: (col: number, width: number) => void;
|
|
80
|
+
onGridResize?: (width: number, height: number) => void;
|
|
81
|
+
onGridEnter?: () => void;
|
|
75
82
|
onkeydown?: (e: KeyboardEvent) => void;
|
|
76
83
|
children?: Snippet;
|
|
77
84
|
}
|
|
@@ -111,6 +118,13 @@
|
|
|
111
118
|
onSearchValueChange,
|
|
112
119
|
onSearchClose,
|
|
113
120
|
onGridMenuOpen,
|
|
121
|
+
onCellClick,
|
|
122
|
+
onCellHover,
|
|
123
|
+
onCellLeave,
|
|
124
|
+
onCellContextMenu,
|
|
125
|
+
onColumnResized,
|
|
126
|
+
onGridResize,
|
|
127
|
+
onGridEnter,
|
|
114
128
|
onkeydown,
|
|
115
129
|
children,
|
|
116
130
|
}: Props = $props();
|
|
@@ -133,6 +147,7 @@
|
|
|
133
147
|
let hasFocus = $state(false);
|
|
134
148
|
const gridId = `gridler-${Math.random().toString(36).slice(2, 10)}`;
|
|
135
149
|
|
|
150
|
+
let hoveredCell = $state<Item | null>(null);
|
|
136
151
|
let isDraggingVScrollbar = $state(false);
|
|
137
152
|
let isDraggingHScrollbar = $state(false);
|
|
138
153
|
let vScrollDragStartY = $state(0);
|
|
@@ -286,6 +301,7 @@
|
|
|
286
301
|
if (entry) {
|
|
287
302
|
containerWidth = entry.contentRect.width;
|
|
288
303
|
containerHeight = entry.contentRect.height;
|
|
304
|
+
onGridResize?.(entry.contentRect.width, entry.contentRect.height);
|
|
289
305
|
}
|
|
290
306
|
});
|
|
291
307
|
observer.observe(containerRef);
|
|
@@ -468,8 +484,7 @@
|
|
|
468
484
|
// ── Header context menu ───────────────────────────────────────────────────────
|
|
469
485
|
|
|
470
486
|
function handleContextMenu(e: MouseEvent) {
|
|
471
|
-
if (
|
|
472
|
-
if (e.offsetY >= headerHeight || e.offsetX < rowMarkerWidth) return;
|
|
487
|
+
if (e.offsetX < rowMarkerWidth) return;
|
|
473
488
|
const fixedBoundaryX =
|
|
474
489
|
fixedColumns > 0
|
|
475
490
|
? getColumnX(effectiveColumns, Math.min(fixedColumns, columns.length))
|
|
@@ -477,10 +492,31 @@
|
|
|
477
492
|
const localX = e.offsetX - rowMarkerWidth;
|
|
478
493
|
const effectiveScrollX =
|
|
479
494
|
fixedColumns > 0 && localX >= 0 && localX < fixedBoundaryX ? 0 : scrollX;
|
|
480
|
-
|
|
481
|
-
if (
|
|
482
|
-
|
|
483
|
-
|
|
495
|
+
|
|
496
|
+
if (e.offsetY < headerHeight) {
|
|
497
|
+
if (!onHeaderContextMenu) return;
|
|
498
|
+
const col = getColumnFromX(localX + effectiveScrollX, effectiveColumns);
|
|
499
|
+
if (col >= 0 && col < columns.length) {
|
|
500
|
+
e.preventDefault();
|
|
501
|
+
onHeaderContextMenu(col, e.clientX, e.clientY);
|
|
502
|
+
}
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (onCellContextMenu) {
|
|
507
|
+
const cell = getCellFromPoint(
|
|
508
|
+
localX,
|
|
509
|
+
e.offsetY,
|
|
510
|
+
effectiveScrollX,
|
|
511
|
+
scrollY,
|
|
512
|
+
effectiveColumns,
|
|
513
|
+
rowHeight,
|
|
514
|
+
headerHeight,
|
|
515
|
+
);
|
|
516
|
+
if (cell) {
|
|
517
|
+
e.preventDefault();
|
|
518
|
+
onCellContextMenu(cell, e.clientX, e.clientY);
|
|
519
|
+
}
|
|
484
520
|
}
|
|
485
521
|
}
|
|
486
522
|
|
|
@@ -619,6 +655,7 @@
|
|
|
619
655
|
}
|
|
620
656
|
|
|
621
657
|
if (cell) {
|
|
658
|
+
onCellClick?.(cell);
|
|
622
659
|
if (e.shiftKey && focusedCell) {
|
|
623
660
|
const newSelection: Selection = {
|
|
624
661
|
type: "range",
|
|
@@ -802,6 +839,36 @@
|
|
|
802
839
|
return;
|
|
803
840
|
}
|
|
804
841
|
|
|
842
|
+
// Hover cell tracking
|
|
843
|
+
if (e.offsetY >= headerHeight && e.offsetX >= rowMarkerWidth) {
|
|
844
|
+
const hoverFixedBoundaryX =
|
|
845
|
+
fixedColumns > 0
|
|
846
|
+
? getColumnX(effectiveColumns, Math.min(fixedColumns, columns.length))
|
|
847
|
+
: 0;
|
|
848
|
+
const hoverLocalX = e.offsetX - rowMarkerWidth;
|
|
849
|
+
const hoverScrollX =
|
|
850
|
+
fixedColumns > 0 && hoverLocalX >= 0 && hoverLocalX < hoverFixedBoundaryX
|
|
851
|
+
? 0
|
|
852
|
+
: scrollX;
|
|
853
|
+
const hoverCell = getCellFromPoint(
|
|
854
|
+
hoverLocalX,
|
|
855
|
+
e.offsetY,
|
|
856
|
+
hoverScrollX,
|
|
857
|
+
scrollY,
|
|
858
|
+
effectiveColumns,
|
|
859
|
+
rowHeight,
|
|
860
|
+
headerHeight,
|
|
861
|
+
);
|
|
862
|
+
if (hoverCell?.[0] !== hoveredCell?.[0] || hoverCell?.[1] !== hoveredCell?.[1]) {
|
|
863
|
+
if (hoveredCell) onCellLeave?.(hoveredCell);
|
|
864
|
+
hoveredCell = hoverCell;
|
|
865
|
+
if (hoverCell) onCellHover?.(hoverCell);
|
|
866
|
+
}
|
|
867
|
+
} else if (hoveredCell !== null) {
|
|
868
|
+
onCellLeave?.(hoveredCell);
|
|
869
|
+
hoveredCell = null;
|
|
870
|
+
}
|
|
871
|
+
|
|
805
872
|
if (!isDragging || !dragStart) return;
|
|
806
873
|
|
|
807
874
|
const fixedBoundaryX =
|
|
@@ -865,6 +932,9 @@
|
|
|
865
932
|
scheduleDraw();
|
|
866
933
|
}
|
|
867
934
|
|
|
935
|
+
if (resizingColumn !== null) {
|
|
936
|
+
onColumnResized?.(resizingColumn, columns[resizingColumn].width);
|
|
937
|
+
}
|
|
868
938
|
isDragging = false;
|
|
869
939
|
dragStart = null;
|
|
870
940
|
resizingColumn = null;
|
|
@@ -876,6 +946,10 @@
|
|
|
876
946
|
hoverResizeCol = null;
|
|
877
947
|
hoverMenuButton = false;
|
|
878
948
|
hoverSortCol = null;
|
|
949
|
+
if (hoveredCell) {
|
|
950
|
+
onCellLeave?.(hoveredCell);
|
|
951
|
+
hoveredCell = null;
|
|
952
|
+
}
|
|
879
953
|
if (!isDraggingVScrollbar && !isDraggingHScrollbar) {
|
|
880
954
|
handleMouseUp(e);
|
|
881
955
|
} else {
|
|
@@ -1073,6 +1147,7 @@
|
|
|
1073
1147
|
style:--gridler-header-height={`${headerHeight}px`}
|
|
1074
1148
|
onfocusin={handleFocusIn}
|
|
1075
1149
|
onfocusout={handleFocusOut}
|
|
1150
|
+
onmouseenter={onGridEnter}
|
|
1076
1151
|
{onkeydown}
|
|
1077
1152
|
>
|
|
1078
1153
|
<canvas
|
|
@@ -41,6 +41,13 @@ interface Props {
|
|
|
41
41
|
item?: Record<string, unknown>;
|
|
42
42
|
column?: GridColumn<Record<string, unknown>>;
|
|
43
43
|
}) => void;
|
|
44
|
+
onCellClick?: (item: Item) => void;
|
|
45
|
+
onCellHover?: (item: Item) => void;
|
|
46
|
+
onCellLeave?: (item: Item) => void;
|
|
47
|
+
onCellContextMenu?: (item: Item, x: number, y: number) => void;
|
|
48
|
+
onColumnResized?: (col: number, width: number) => void;
|
|
49
|
+
onGridResize?: (width: number, height: number) => void;
|
|
50
|
+
onGridEnter?: () => void;
|
|
44
51
|
onkeydown?: (e: KeyboardEvent) => void;
|
|
45
52
|
children?: Snippet;
|
|
46
53
|
}
|
|
@@ -199,9 +199,11 @@
|
|
|
199
199
|
if (typeof data === "function") {
|
|
200
200
|
data().then((result) => {
|
|
201
201
|
dataState = result;
|
|
202
|
+
onGridEvent?.("load");
|
|
202
203
|
});
|
|
203
204
|
} else {
|
|
204
205
|
dataState = data.slice();
|
|
206
|
+
onGridEvent?.("load");
|
|
205
207
|
}
|
|
206
208
|
});
|
|
207
209
|
|
|
@@ -258,6 +260,7 @@
|
|
|
258
260
|
internalSearchValue = value;
|
|
259
261
|
}
|
|
260
262
|
onSearchValueChange?.(value);
|
|
263
|
+
onGridEvent?.("search_changed", undefined, undefined, undefined, { searchValue: value });
|
|
261
264
|
}
|
|
262
265
|
|
|
263
266
|
// ── Context menu ───────────────────────────────────────────────────────────
|
|
@@ -477,6 +480,7 @@
|
|
|
477
480
|
function handleFilterChange(next: GridColumnFilters) {
|
|
478
481
|
if (filters === undefined) internalFilters = next;
|
|
479
482
|
onFilterChange?.(next);
|
|
483
|
+
onGridEvent?.("filter_changed", undefined, undefined, undefined, { filters: next });
|
|
480
484
|
}
|
|
481
485
|
|
|
482
486
|
|
|
@@ -530,6 +534,7 @@
|
|
|
530
534
|
function handleSortOrderChange(order: GridColumnSortOrder) {
|
|
531
535
|
if (sortOrder === undefined) internalSortOrder = order;
|
|
532
536
|
onSortOrderChange?.(order);
|
|
537
|
+
onGridEvent?.("sort_changed", undefined, undefined, undefined, { sortOrder: order });
|
|
533
538
|
}
|
|
534
539
|
|
|
535
540
|
const resolvedSort = $derived(
|
|
@@ -619,6 +624,7 @@
|
|
|
619
624
|
serverData = [...serverData, ...result.data];
|
|
620
625
|
serverCursor = result.nextCursor;
|
|
621
626
|
serverAllLoaded = result.data.length < pageSize || !result.nextCursor;
|
|
627
|
+
onGridEvent?.("page_loaded", undefined, undefined, undefined, { data: result.data, total: serverData.length });
|
|
622
628
|
} catch {
|
|
623
629
|
// Silent — don't clobber existing rows with an error on append.
|
|
624
630
|
} finally {
|
|
@@ -652,16 +658,39 @@
|
|
|
652
658
|
|
|
653
659
|
// ── Selection → selectedItems bridge ──────────────────────────────────────
|
|
654
660
|
|
|
661
|
+
let internalSelectedItems = $state<Record<string, unknown>[]>([]);
|
|
662
|
+
|
|
655
663
|
function handleSelectionChange(sel: Selection) {
|
|
656
|
-
|
|
657
|
-
|
|
664
|
+
let items: Record<string, unknown>[] = [];
|
|
665
|
+
if (sel.type === "row") {
|
|
666
|
+
items = sel.rows
|
|
658
667
|
.map((i) => resolveRowData(i))
|
|
659
668
|
.filter((r): r is Record<string, unknown> => r !== undefined);
|
|
660
|
-
|
|
669
|
+
} else if (sel.type === "cell") {
|
|
670
|
+
const row = resolveRowData(sel.item[1]);
|
|
671
|
+
if (row) items = [row];
|
|
672
|
+
} else if (sel.type === "range") {
|
|
673
|
+
const minRow = Math.min(sel.start[1], sel.end[1]);
|
|
674
|
+
const maxRow = Math.max(sel.start[1], sel.end[1]);
|
|
675
|
+
for (let i = minRow; i <= maxRow; i++) {
|
|
676
|
+
const row = resolveRowData(i);
|
|
677
|
+
if (row) items.push(row);
|
|
678
|
+
}
|
|
661
679
|
}
|
|
680
|
+
internalSelectedItems = items;
|
|
681
|
+
if (items.length > 0) onSelectedItemsChange?.(items);
|
|
662
682
|
onSelectionChange?.(sel);
|
|
663
683
|
}
|
|
664
684
|
|
|
685
|
+
// ── Settings change event ─────────────────────────────────────────────────
|
|
686
|
+
|
|
687
|
+
let lastSettings = $state(settings);
|
|
688
|
+
$effect(() => {
|
|
689
|
+
if (settings === untrack(() => lastSettings)) return;
|
|
690
|
+
lastSettings = settings;
|
|
691
|
+
onGridEvent?.("settings_changed", undefined, undefined, undefined, { settings });
|
|
692
|
+
});
|
|
693
|
+
|
|
665
694
|
// ── Cell content ───────────────────────────────────────────────────────────
|
|
666
695
|
|
|
667
696
|
const serverGetCellContent = $derived(makeGetCellContent(serverData, columnsState));
|
|
@@ -700,6 +729,7 @@
|
|
|
700
729
|
};
|
|
701
730
|
dataState = next;
|
|
702
731
|
await onDataChange?.(next);
|
|
732
|
+
onGridEvent?.("data_changed", next[originalIndex], columnsState[col], undefined, { data: next });
|
|
703
733
|
}
|
|
704
734
|
|
|
705
735
|
async function handleGridMenuClick(d?: {
|
|
@@ -747,7 +777,10 @@
|
|
|
747
777
|
onCellEdited?.(item, value);
|
|
748
778
|
}}
|
|
749
779
|
onCellDblClick={handleCellDblClick}
|
|
780
|
+
selectedItems={internalSelectedItems}
|
|
781
|
+
getRowData={resolveRowData}
|
|
750
782
|
onSelectionChange={handleSelectionChange}
|
|
783
|
+
{onMenuClick}
|
|
751
784
|
onGridMenuOpen={handleGridMenuClick}
|
|
752
785
|
sortOrder={resolvedSortOrder}
|
|
753
786
|
onSortOrderChange={handleSortOrderChange}
|
package/llm/README.md
CHANGED
|
@@ -16,6 +16,7 @@ This folder contains AI-readable documentation that ships with the package.
|
|
|
16
16
|
- [tools/SVAR.md](./tools/SVAR.md)
|
|
17
17
|
- [tools/resolvespec-js.md](./tools/resolvespec-js.md)
|
|
18
18
|
- [plans/canvasgrid.md](./plans/canvasgrid.md)
|
|
19
|
+
- [gridler-events.md](./gridler-events.md)
|
|
19
20
|
|
|
20
21
|
## Installed package paths
|
|
21
22
|
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Gridler Events
|
|
2
|
+
|
|
3
|
+
`GridlerFull` (and the lower-level `Gridler`) emit two event callbacks defined in `GridCommonProps` (`src/lib/components/Types/generic_grid.ts`):
|
|
4
|
+
|
|
5
|
+
| Callback | Purpose |
|
|
6
|
+
|---|---|
|
|
7
|
+
| `onGridEvent` | Grid-level events (interaction, state, lifecycle) |
|
|
8
|
+
| `onCellEvent` | Cell-level events (mouse + keyboard on a specific cell) |
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## `onGridEvent`
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
onGridEvent?: (
|
|
16
|
+
type: GridEventType,
|
|
17
|
+
item?: Record<string, unknown>, // resolved raw row data
|
|
18
|
+
column?: GridColumn,
|
|
19
|
+
coords?: GridEventCoords, // { x, y, code? }
|
|
20
|
+
detail?: GridEventDetail, // event-specific payload
|
|
21
|
+
) => void
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### All `GridEventType` values
|
|
25
|
+
|
|
26
|
+
| Type | Fired from | `item` | `column` | `coords` | `detail` |
|
|
27
|
+
|---|---|---|---|---|---|
|
|
28
|
+
| `click` | Mouse click on a cell | row data | clicked column | — | — |
|
|
29
|
+
| `dblclick` | Double-click on a cell | row data | clicked column | — | — |
|
|
30
|
+
| `contextmenu` | Right-click on a cell | row data | clicked column | `{ x, y }` client coords | — |
|
|
31
|
+
| `keydown` | Any key pressed while grid focused | — | — | `{ x:0, y:0, code }` | — |
|
|
32
|
+
| `hover` | Mouse moves over a new cell | row data | hovered column | — | — |
|
|
33
|
+
| `leave` | Mouse leaves a cell | row data | vacated column | — | — |
|
|
34
|
+
| `enter` | Mouse enters the grid container | — | — | — | — |
|
|
35
|
+
| `load` | Local `data` prop set/changed, or first server page loaded | — | — | — | — |
|
|
36
|
+
| `scroll` | Visible range changes (scroll) | — | — | — | `{ row, column }` first visible |
|
|
37
|
+
| `resize` | Grid container resized (ResizeObserver) | — | — | — | `{ width, height }` |
|
|
38
|
+
| `page_loaded` | Next cursor page loaded from server | — | — | — | `{ data, total }` |
|
|
39
|
+
| `sort_changed` | Sort order applied or cleared | — | — | — | `{ sortOrder: GridColumnSortOrder }` |
|
|
40
|
+
| `filter_changed` | Filter applied or cleared | — | — | — | `{ filters: GridColumnFilters }` |
|
|
41
|
+
| `search_changed` | Search input value changed | — | — | — | `{ searchValue: string }` |
|
|
42
|
+
| `selection_changed` | Cell, row, range, or column selection changes | — | — | — | — |
|
|
43
|
+
| `column_moved` | Column dragged to new position | — | — | — | `{ from: number, to: number }` |
|
|
44
|
+
| `column_resized` | Column edge dragged to new width | — | moved column | — | `{ width: number }` |
|
|
45
|
+
| `data_changed` | Cell edited and committed (local mode only) | updated row | edited column | — | `{ data: Row[] }` full new dataset |
|
|
46
|
+
| `settings_changed` | `settings` prop reference changed | — | — | — | `{ settings }` |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## `onCellEvent`
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
onCellEvent?: (
|
|
54
|
+
type: GridCellEventType,
|
|
55
|
+
item: Record<string, unknown>, // resolved raw row data
|
|
56
|
+
column: GridColumn,
|
|
57
|
+
coords?: GridEventCoords,
|
|
58
|
+
detail?: GridEventDetail,
|
|
59
|
+
) => void
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### All `GridCellEventType` values
|
|
63
|
+
|
|
64
|
+
| Type | Fired when |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `click` | Mouse click on the cell |
|
|
67
|
+
| `dblclick` | Double-click on the cell |
|
|
68
|
+
| `contextmenu` | Right-click on the cell |
|
|
69
|
+
| `hover` | Mouse moves over this cell |
|
|
70
|
+
| `leave` | Mouse leaves this cell |
|
|
71
|
+
| `enter_key` | Enter pressed while this cell is focused |
|
|
72
|
+
| `tab_key` | Tab pressed while this cell is focused |
|
|
73
|
+
| `delete_key` | Delete or Backspace pressed while this cell is focused |
|
|
74
|
+
|
|
75
|
+
For all `onCellEvent` calls, `item` carries the **resolved raw row data** (from `serverData` or local `filteredDataState`). When the row cannot be resolved by index (e.g. from a top-level menu button), `item` falls back to the value in `selectedItems[0]`.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Row data resolution
|
|
80
|
+
|
|
81
|
+
`GridlerFull` passes `getRowData` to `Gridler`, which calls `resolveRowData(rowIndex)`:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
function resolveRowData(row: number): Record<string, unknown> | undefined {
|
|
85
|
+
return isServerMode ? serverData[row] : filteredDataState[row];
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The current selection's row data is also tracked in `internalSelectedItems` state and passed as `selectedItems` to `Gridler`. This is used as a fallback when no row index is available.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Field selection sent to adapters (hotfields)
|
|
94
|
+
|
|
95
|
+
When `dataSource="resolvespec"` or `"headerspec"`, `GridlerFull` derives a `resolvedFields` array and passes it to every `readPage` call as `options.columns` (maps to `X-Select-Fields` in headerspec):
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const resolvedFields = $derived.by(() => {
|
|
99
|
+
const columnFields = columnsState.map((c) => c.dataKey ?? c.id);
|
|
100
|
+
const hotfields = dataSourceOptions?.hotfields ?? [];
|
|
101
|
+
const seen = new Set(columnFields);
|
|
102
|
+
const extra = hotfields.filter((f) => !seen.has(f));
|
|
103
|
+
return [...columnFields, ...extra];
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- **Column fields** come from `column.dataKey ?? column.id` for every column.
|
|
108
|
+
- **Hotfields** (`dataSourceOptions.hotfields`) are extra fields needed server-side (for filtering, sorting, FK lookups) that are not grid columns. They are appended without duplicating existing column fields.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Usage example
|
|
113
|
+
|
|
114
|
+
```svelte
|
|
115
|
+
<GridlerFull
|
|
116
|
+
{columns}
|
|
117
|
+
{data}
|
|
118
|
+
onGridEvent={(type, item, column, coords, detail) => {
|
|
119
|
+
if (type === 'sort_changed') console.log('Sort:', detail?.sortOrder);
|
|
120
|
+
if (type === 'filter_changed') console.log('Filter:', detail?.filters);
|
|
121
|
+
if (type === 'search_changed') console.log('Search:', detail?.searchValue);
|
|
122
|
+
if (type === 'data_changed') console.log('Edited row:', item, 'All data:', detail?.data);
|
|
123
|
+
if (type === 'column_moved') console.log('Moved col', detail?.from, '→', detail?.to);
|
|
124
|
+
if (type === 'page_loaded') console.log('New page:', detail?.data, 'Total so far:', detail?.total);
|
|
125
|
+
}}
|
|
126
|
+
onCellEvent={(type, item, column) => {
|
|
127
|
+
if (type === 'click') console.log('Clicked:', item, column.id);
|
|
128
|
+
if (type === 'enter_key') console.log('Enter on row:', item);
|
|
129
|
+
}}
|
|
130
|
+
dataSource="resolvespec"
|
|
131
|
+
dataSourceOptions={{
|
|
132
|
+
url: 'https://api.example.com',
|
|
133
|
+
schema: 'public',
|
|
134
|
+
entity: 'users',
|
|
135
|
+
uniqueID: 'id',
|
|
136
|
+
hotfields: ['tenant_id', 'deleted_at'], // fetched but not shown as columns
|
|
137
|
+
}}
|
|
138
|
+
/>
|
|
139
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warkypublic/svelix",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"description": "Svelte 5 component library with Skeleton UI and Tailwind CSS",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"exports": {
|
|
@@ -26,43 +26,43 @@
|
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@changesets/cli": "^2.30.0",
|
|
29
|
-
"@chromatic-com/storybook": "^5.
|
|
30
|
-
"@eslint/compat": "^2.0.
|
|
29
|
+
"@chromatic-com/storybook": "^5.1.1",
|
|
30
|
+
"@eslint/compat": "^2.0.4",
|
|
31
31
|
"@eslint/js": "^10.0.1",
|
|
32
|
-
"@playwright/test": "^1.
|
|
33
|
-
"@sentry/svelte": "^10.
|
|
34
|
-
"@skeletonlabs/skeleton": "^4.
|
|
35
|
-
"@skeletonlabs/skeleton-svelte": "^4.
|
|
36
|
-
"@storybook/addon-a11y": "^10.3.
|
|
37
|
-
"@storybook/addon-docs": "^10.3.
|
|
38
|
-
"@storybook/addon-svelte-csf": "^5.
|
|
39
|
-
"@storybook/addon-vitest": "^10.3.
|
|
40
|
-
"@storybook/sveltekit": "^10.3.
|
|
32
|
+
"@playwright/test": "^1.59.1",
|
|
33
|
+
"@sentry/svelte": "^10.47.0",
|
|
34
|
+
"@skeletonlabs/skeleton": "^4.15.1",
|
|
35
|
+
"@skeletonlabs/skeleton-svelte": "^4.15.1",
|
|
36
|
+
"@storybook/addon-a11y": "^10.3.4",
|
|
37
|
+
"@storybook/addon-docs": "^10.3.4",
|
|
38
|
+
"@storybook/addon-svelte-csf": "^5.1.2",
|
|
39
|
+
"@storybook/addon-vitest": "^10.3.4",
|
|
40
|
+
"@storybook/sveltekit": "^10.3.4",
|
|
41
41
|
"@storybook/test": "^8.6.15",
|
|
42
42
|
"@sveltejs/adapter-auto": "^7.0.1",
|
|
43
|
-
"@sveltejs/kit": "^2.
|
|
43
|
+
"@sveltejs/kit": "^2.56.1",
|
|
44
44
|
"@sveltejs/package": "^2.5.7",
|
|
45
45
|
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
46
46
|
"@tailwindcss/vite": "^4.2.2",
|
|
47
47
|
"@tanstack/svelte-virtual": "^3.13.23",
|
|
48
|
-
"@types/node": "^25.5.
|
|
49
|
-
"@vitest/browser-playwright": "^4.1.
|
|
50
|
-
"@vitest/coverage-v8": "^4.1.
|
|
51
|
-
"eslint": "^10.0
|
|
52
|
-
"eslint-plugin-svelte": "^3.
|
|
48
|
+
"@types/node": "^25.5.2",
|
|
49
|
+
"@vitest/browser-playwright": "^4.1.3",
|
|
50
|
+
"@vitest/coverage-v8": "^4.1.3",
|
|
51
|
+
"eslint": "^10.2.0",
|
|
52
|
+
"eslint-plugin-svelte": "^3.17.0",
|
|
53
53
|
"globals": "^17.4.0",
|
|
54
|
-
"playwright": "^1.
|
|
54
|
+
"playwright": "^1.59.1",
|
|
55
55
|
"publint": "^0.3.18",
|
|
56
|
-
"storybook": "^10.3.
|
|
57
|
-
"svelte": "^5.
|
|
58
|
-
"svelte-check": "^4.4.
|
|
56
|
+
"storybook": "^10.3.4",
|
|
57
|
+
"svelte": "^5.55.1",
|
|
58
|
+
"svelte-check": "^4.4.6",
|
|
59
59
|
"tailwindcss": "^4.2.2",
|
|
60
60
|
"tslib": "^2.8.1",
|
|
61
|
-
"typescript": "^
|
|
62
|
-
"typescript-eslint": "^8.
|
|
63
|
-
"vite": "^8.0.
|
|
61
|
+
"typescript": "^6.0.2",
|
|
62
|
+
"typescript-eslint": "^8.58.0",
|
|
63
|
+
"vite": "^8.0.7",
|
|
64
64
|
"vite-plugin-monaco-editor": "^1.1.0",
|
|
65
|
-
"vitest": "^4.1.
|
|
65
|
+
"vitest": "^4.1.3"
|
|
66
66
|
},
|
|
67
67
|
"svelte": "./dist/index.js",
|
|
68
68
|
"types": "./dist/index.d.ts",
|
|
@@ -76,13 +76,13 @@
|
|
|
76
76
|
"@cartamd/plugin-math": "^4.3.1",
|
|
77
77
|
"@friendofsvelte/tipex": "^0.1.1",
|
|
78
78
|
"@js-temporal/polyfill": "^0.5.1",
|
|
79
|
-
"@svar-ui/svelte-grid": "^2.6.
|
|
79
|
+
"@svar-ui/svelte-grid": "^2.6.1",
|
|
80
80
|
"@warkypublic/resolvespec-js": "^1.0.1",
|
|
81
81
|
"@warkypublic/artemis-kit": "^1.0.10",
|
|
82
82
|
"carta-md": "^4.11.1",
|
|
83
83
|
"github-markdown-css": "^5.9.0",
|
|
84
|
-
"isomorphic-dompurify": "^3.
|
|
85
|
-
"katex": "^0.16.
|
|
84
|
+
"isomorphic-dompurify": "^3.7.1",
|
|
85
|
+
"katex": "^0.16.45",
|
|
86
86
|
"monaco-editor": "^0.55.1"
|
|
87
87
|
},
|
|
88
88
|
"scripts": {
|