@warkypublic/svelix 0.1.41 → 0.1.43
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/ContentEditor/subcomponents/TextEditor.svelte +14 -29
- package/dist/components/Gridler/components/Gridler.svelte +30 -7
- package/dist/components/Gridler/components/Gridler.svelte.d.ts +1 -1
- package/dist/components/Gridler/components/GridlerCanvas.svelte +2 -2
- package/dist/components/Gridler/components/GridlerCanvas.svelte.d.ts +1 -1
- package/dist/components/Gridler/components/GridlerFull.svelte +40 -7
- package/dist/components/Gridler/components/GridlerFull.svelte.d.ts +18 -4
- package/dist/components/Gridler/types.d.ts +3 -3
- package/dist/components/SvarkGrid/internal/SvarkGridView.svelte +3 -5
- package/llm/COMPONENT_GUIDE.md +72 -0
- package/llm/IMPLEMENTATION_PLAN.md +1 -0
- package/llm/gridler-events.md +50 -1
- package/package.json +1 -1
|
@@ -147,33 +147,18 @@
|
|
|
147
147
|
|
|
148
148
|
const contentPaddingClass = $derived(hideHeader ? 'p-0' : 'p-4');
|
|
149
149
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
chain: () => {
|
|
153
|
-
focus: () => {
|
|
154
|
-
toggleHeading: (opts: { level: number }) => { run: () => void };
|
|
155
|
-
setParagraph: () => { run: () => void };
|
|
156
|
-
toggleBold: () => { run: () => void };
|
|
157
|
-
toggleItalic: () => { run: () => void };
|
|
158
|
-
toggleUnderline: () => { run: () => void };
|
|
159
|
-
toggleBulletList: () => { run: () => void };
|
|
160
|
-
toggleOrderedList: () => { run: () => void };
|
|
161
|
-
toggleTaskList: () => { run: () => void };
|
|
162
|
-
toggleBlockquote: () => { run: () => void };
|
|
163
|
-
toggleCodeBlock: () => { run: () => void };
|
|
164
|
-
};
|
|
165
|
-
};
|
|
166
|
-
};
|
|
150
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
151
|
+
function echain(editor: TipexEditor): any { return echain(editor) ?? {}; }
|
|
167
152
|
</script>
|
|
168
153
|
|
|
169
|
-
{#snippet controlComponent(editor:
|
|
154
|
+
{#snippet controlComponent(editor: TipexEditor)}
|
|
170
155
|
<div class="tipex-controller">
|
|
171
156
|
<div class="tipex-basic-controller-wrapper">
|
|
172
157
|
<button
|
|
173
158
|
type="button"
|
|
174
159
|
class="tipex-edit-button tipex-button-extra tipex-button-rigid"
|
|
175
160
|
class:active={editor?.isActive('heading', { level: 1 })}
|
|
176
|
-
onclick={() => editor
|
|
161
|
+
onclick={() => echain(editor).toggleHeading({ level: 1 }).run()}
|
|
177
162
|
aria-label="Heading 1"
|
|
178
163
|
title="Heading 1"
|
|
179
164
|
>
|
|
@@ -183,7 +168,7 @@ type TipexControlEditor = {
|
|
|
183
168
|
type="button"
|
|
184
169
|
class="tipex-edit-button tipex-button-extra tipex-button-rigid"
|
|
185
170
|
class:active={editor?.isActive('heading', { level: 2 })}
|
|
186
|
-
onclick={() => editor
|
|
171
|
+
onclick={() => echain(editor).toggleHeading({ level: 2 }).run()}
|
|
187
172
|
aria-label="Heading 2"
|
|
188
173
|
title="Heading 2"
|
|
189
174
|
>
|
|
@@ -193,7 +178,7 @@ type TipexControlEditor = {
|
|
|
193
178
|
type="button"
|
|
194
179
|
class="tipex-edit-button tipex-button-extra tipex-button-rigid"
|
|
195
180
|
class:active={editor?.isActive('paragraph')}
|
|
196
|
-
onclick={() => editor
|
|
181
|
+
onclick={() => echain(editor).setParagraph().run()}
|
|
197
182
|
aria-label="Paragraph"
|
|
198
183
|
title="Paragraph"
|
|
199
184
|
>
|
|
@@ -206,7 +191,7 @@ type TipexControlEditor = {
|
|
|
206
191
|
type="button"
|
|
207
192
|
class="tipex-edit-button tipex-button-extra tipex-button-rigid"
|
|
208
193
|
class:active={editor?.isActive('bold')}
|
|
209
|
-
onclick={() => editor
|
|
194
|
+
onclick={() => echain(editor).toggleBold().run()}
|
|
210
195
|
aria-label="Bold"
|
|
211
196
|
title="Bold"
|
|
212
197
|
>
|
|
@@ -216,7 +201,7 @@ type TipexControlEditor = {
|
|
|
216
201
|
type="button"
|
|
217
202
|
class="tipex-edit-button tipex-button-extra tipex-button-rigid"
|
|
218
203
|
class:active={editor?.isActive('italic')}
|
|
219
|
-
onclick={() => editor
|
|
204
|
+
onclick={() => echain(editor).toggleItalic().run()}
|
|
220
205
|
aria-label="Italic"
|
|
221
206
|
title="Italic"
|
|
222
207
|
>
|
|
@@ -226,7 +211,7 @@ type TipexControlEditor = {
|
|
|
226
211
|
type="button"
|
|
227
212
|
class="tipex-edit-button tipex-button-extra tipex-button-rigid"
|
|
228
213
|
class:active={editor?.isActive('underline')}
|
|
229
|
-
onclick={() => editor
|
|
214
|
+
onclick={() => echain(editor).toggleUnderline().run()}
|
|
230
215
|
aria-label="Underline"
|
|
231
216
|
title="Underline"
|
|
232
217
|
>
|
|
@@ -239,7 +224,7 @@ type TipexControlEditor = {
|
|
|
239
224
|
type="button"
|
|
240
225
|
class="tipex-edit-button tipex-button-extra tipex-button-rigid"
|
|
241
226
|
class:active={editor?.isActive('bulletList')}
|
|
242
|
-
onclick={() => editor
|
|
227
|
+
onclick={() => echain(editor).toggleBulletList().run()}
|
|
243
228
|
aria-label="Bullet list"
|
|
244
229
|
title="Bullet list"
|
|
245
230
|
>
|
|
@@ -251,7 +236,7 @@ type TipexControlEditor = {
|
|
|
251
236
|
type="button"
|
|
252
237
|
class="tipex-edit-button tipex-button-extra tipex-button-rigid"
|
|
253
238
|
class:active={editor?.isActive('orderedList')}
|
|
254
|
-
onclick={() => editor
|
|
239
|
+
onclick={() => echain(editor).toggleOrderedList().run()}
|
|
255
240
|
aria-label="Ordered list"
|
|
256
241
|
title="Ordered list"
|
|
257
242
|
>
|
|
@@ -263,7 +248,7 @@ type TipexControlEditor = {
|
|
|
263
248
|
type="button"
|
|
264
249
|
class="tipex-edit-button tipex-button-extra tipex-button-rigid"
|
|
265
250
|
class:active={editor?.isActive('taskList')}
|
|
266
|
-
onclick={() => editor
|
|
251
|
+
onclick={() => echain(editor).toggleTaskList().run()}
|
|
267
252
|
aria-label="Task list"
|
|
268
253
|
title="Task list"
|
|
269
254
|
>
|
|
@@ -279,7 +264,7 @@ type TipexControlEditor = {
|
|
|
279
264
|
type="button"
|
|
280
265
|
class="tipex-edit-button tipex-button-extra tipex-button-rigid"
|
|
281
266
|
class:active={editor?.isActive('blockquote')}
|
|
282
|
-
onclick={() => editor
|
|
267
|
+
onclick={() => echain(editor).toggleBlockquote().run()}
|
|
283
268
|
aria-label="Blockquote"
|
|
284
269
|
title="Blockquote"
|
|
285
270
|
>
|
|
@@ -291,7 +276,7 @@ type TipexControlEditor = {
|
|
|
291
276
|
type="button"
|
|
292
277
|
class="tipex-edit-button tipex-button-extra tipex-button-rigid"
|
|
293
278
|
class:active={editor?.isActive('codeBlock')}
|
|
294
|
-
onclick={() => editor
|
|
279
|
+
onclick={() => echain(editor).toggleCodeBlock().run()}
|
|
295
280
|
aria-label="Code block"
|
|
296
281
|
title="Code block"
|
|
297
282
|
>
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
column?: GridlerColumn;
|
|
29
29
|
}) => void;
|
|
30
30
|
onRowClick?: (row: number) => void;
|
|
31
|
-
onRowContextMenu?: (row: number) => void;
|
|
31
|
+
onRowContextMenu?: (row: number, x: number, y: number) => void;
|
|
32
32
|
/** Resolve raw row data by row index for populating onCellEvent. */
|
|
33
33
|
getRowData?: (row: number) => Record<string, unknown> | undefined;
|
|
34
34
|
children?: Snippet;
|
|
@@ -157,6 +157,29 @@
|
|
|
157
157
|
cancelEdit();
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
function getSelectionRowData(sel: Selection): Record<string, unknown>[] | undefined {
|
|
161
|
+
if (!getRowData) return undefined;
|
|
162
|
+
if (sel.type === "cell") {
|
|
163
|
+
const d = getRowData(sel.item[1]);
|
|
164
|
+
return d ? [d] : undefined;
|
|
165
|
+
}
|
|
166
|
+
if (sel.type === "row") {
|
|
167
|
+
const items = sel.rows.map(getRowData).filter((r): r is Record<string, unknown> => r !== undefined);
|
|
168
|
+
return items.length > 0 ? items : undefined;
|
|
169
|
+
}
|
|
170
|
+
if (sel.type === "range") {
|
|
171
|
+
const minRow = Math.min(sel.start[1], sel.end[1]);
|
|
172
|
+
const maxRow = Math.max(sel.start[1], sel.end[1]);
|
|
173
|
+
const items: Record<string, unknown>[] = [];
|
|
174
|
+
for (let i = minRow; i <= maxRow; i++) {
|
|
175
|
+
const d = getRowData(i);
|
|
176
|
+
if (d) items.push(d);
|
|
177
|
+
}
|
|
178
|
+
return items.length > 0 ? items : undefined;
|
|
179
|
+
}
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
|
|
160
183
|
// ── Row toggle (checkbox / number markers) ────────────────────────────────────
|
|
161
184
|
|
|
162
185
|
function handleRowToggle(row: number) {
|
|
@@ -173,7 +196,7 @@
|
|
|
173
196
|
}
|
|
174
197
|
currentSelection = newSel;
|
|
175
198
|
canvasComponent?.setCurrentSelection(newSel);
|
|
176
|
-
onSelectionChange?.(newSel);
|
|
199
|
+
onSelectionChange?.(newSel, getSelectionRowData(newSel));
|
|
177
200
|
}
|
|
178
201
|
|
|
179
202
|
// ── Clipboard ────────────────────────────────────────────────────────────────
|
|
@@ -295,7 +318,7 @@
|
|
|
295
318
|
};
|
|
296
319
|
currentSelection = newSel;
|
|
297
320
|
canvasComponent?.setCurrentSelection(newSel);
|
|
298
|
-
onSelectionChange?.(newSel);
|
|
321
|
+
onSelectionChange?.(newSel, getSelectionRowData(newSel));
|
|
299
322
|
canvasComponent?.setAnnouncement("All cells selected");
|
|
300
323
|
}
|
|
301
324
|
e.preventDefault();
|
|
@@ -386,7 +409,7 @@
|
|
|
386
409
|
const newSel: Selection = { type: "cell", item: newFocusedCell };
|
|
387
410
|
currentSelection = newSel;
|
|
388
411
|
canvasComponent?.setCurrentSelection(newSel);
|
|
389
|
-
onSelectionChange?.(newSel);
|
|
412
|
+
onSelectionChange?.(newSel, getSelectionRowData(newSel));
|
|
390
413
|
canvasComponent?.setAnnouncement(
|
|
391
414
|
`Row ${newRow + 1}, column ${columns[newCol]?.title ?? newCol + 1}`,
|
|
392
415
|
);
|
|
@@ -445,7 +468,7 @@
|
|
|
445
468
|
onVisibleRangeChange={handleVisibleRangeChange}
|
|
446
469
|
onSelectionChange={(sel) => {
|
|
447
470
|
currentSelection = sel;
|
|
448
|
-
onSelectionChange?.(sel);
|
|
471
|
+
onSelectionChange?.(sel, getSelectionRowData(sel));
|
|
449
472
|
onGridEvent?.("selection_changed");
|
|
450
473
|
}}
|
|
451
474
|
onCellDblClick={(item, cell) => {
|
|
@@ -472,8 +495,8 @@
|
|
|
472
495
|
onCellEvent?.("contextmenu", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" }, { x, y });
|
|
473
496
|
onGridEvent?.("contextmenu", getRowData?.(item[1]) ?? {}, columns[item[0]] ?? { id: "", title: "" }, { x, y });
|
|
474
497
|
}}
|
|
475
|
-
onRowContextMenu={(row) => {
|
|
476
|
-
onRowContextMenu?.(row);
|
|
498
|
+
onRowContextMenu={(row, x, y) => {
|
|
499
|
+
onRowContextMenu?.(row, x, y);
|
|
477
500
|
}}
|
|
478
501
|
onColumnResized={(col, newWidth) => {
|
|
479
502
|
columns[col].width = newWidth;
|
|
@@ -14,7 +14,7 @@ export interface Props extends Partial<GridlerProps> {
|
|
|
14
14
|
column?: GridlerColumn;
|
|
15
15
|
}) => void;
|
|
16
16
|
onRowClick?: (row: number) => void;
|
|
17
|
-
onRowContextMenu?: (row: number) => void;
|
|
17
|
+
onRowContextMenu?: (row: number, x: number, y: number) => void;
|
|
18
18
|
/** Resolve raw row data by row index for populating onCellEvent. */
|
|
19
19
|
getRowData?: (row: number) => Record<string, unknown> | undefined;
|
|
20
20
|
children?: Snippet;
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
onSelectionChange?: (sel: Selection) => void;
|
|
59
59
|
onCellDblClick?: (item: Item, cell: GridlerCell) => void;
|
|
60
60
|
onRowClick?: (row: number) => void;
|
|
61
|
-
onRowContextMenu?: (row: number) => void;
|
|
61
|
+
onRowContextMenu?: (row: number, x: number, y: number) => void;
|
|
62
62
|
onColumnMoved?: (from: number, to: number) => void;
|
|
63
63
|
onCellEdited?: (item: Item, cell: GridlerCell) => void;
|
|
64
64
|
onDelete?: (sel: Selection) => void;
|
|
@@ -480,7 +480,7 @@
|
|
|
480
480
|
const row = getRowFromOffsetY(e.offsetY);
|
|
481
481
|
if (row !== null) {
|
|
482
482
|
e.preventDefault();
|
|
483
|
-
onRowContextMenu?.(row);
|
|
483
|
+
onRowContextMenu?.(row, e.clientX, e.clientY);
|
|
484
484
|
}
|
|
485
485
|
|
|
486
486
|
if (e.offsetX < rowMarkerWidth) return;
|
|
@@ -27,7 +27,7 @@ interface Props {
|
|
|
27
27
|
onSelectionChange?: (sel: Selection) => void;
|
|
28
28
|
onCellDblClick?: (item: Item, cell: GridlerCell) => void;
|
|
29
29
|
onRowClick?: (row: number) => void;
|
|
30
|
-
onRowContextMenu?: (row: number) => void;
|
|
30
|
+
onRowContextMenu?: (row: number, x: number, y: number) => void;
|
|
31
31
|
onColumnMoved?: (from: number, to: number) => void;
|
|
32
32
|
onCellEdited?: (item: Item, cell: GridlerCell) => void;
|
|
33
33
|
onDelete?: (sel: Selection) => void;
|
|
@@ -88,10 +88,12 @@
|
|
|
88
88
|
row: number,
|
|
89
89
|
rowData: Record<string, unknown> | undefined,
|
|
90
90
|
) => void;
|
|
91
|
-
/** Called on row context-menu with the row index
|
|
91
|
+
/** Called on row context-menu with the row index, resolved row data, and click coordinates. */
|
|
92
92
|
onRowContextMenu?: (
|
|
93
93
|
row: number,
|
|
94
94
|
rowData: Record<string, unknown> | undefined,
|
|
95
|
+
x: number,
|
|
96
|
+
y: number,
|
|
95
97
|
) => void;
|
|
96
98
|
|
|
97
99
|
// ── Server-side fetching ──────────────────────────────────────────────────
|
|
@@ -120,13 +122,25 @@
|
|
|
120
122
|
/** Called when a server fetch fails. */
|
|
121
123
|
onLoadError?: (error: string) => void;
|
|
122
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Bindable. Reflects the server-reported total row count.
|
|
127
|
+
* Updated after every successful page fetch; 0 until the first response.
|
|
128
|
+
* Read-only from the parent — do not write this value.
|
|
129
|
+
*/
|
|
130
|
+
total?: number;
|
|
131
|
+
/**
|
|
132
|
+
* Bindable. `true` while any server fetch is in flight (initial load or next-page).
|
|
133
|
+
* Read-only from the parent — do not write this value.
|
|
134
|
+
*/
|
|
135
|
+
loading?: boolean;
|
|
136
|
+
|
|
123
137
|
/**
|
|
124
138
|
* Extra items appended to the built-in context menu (Refresh, Clear Search, Clear Filters, Clear Sort).
|
|
125
139
|
* Pass a separator item `{ id: 'sep', kind: 'separator', label: '' }` to group your items.
|
|
126
140
|
*/
|
|
127
141
|
menuItems?: GridlerContextMenuItem[];
|
|
128
142
|
/** Called when any context menu item is selected (built-in or custom). */
|
|
129
|
-
onMenuItemSelect?: (item: GridlerContextMenuItem) => void;
|
|
143
|
+
onMenuItemSelect?: (item: GridlerContextMenuItem, rowData?: Record<string, unknown>) => void;
|
|
130
144
|
|
|
131
145
|
children?: Snippet;
|
|
132
146
|
}
|
|
@@ -150,6 +164,8 @@
|
|
|
150
164
|
serverSideSearch = false,
|
|
151
165
|
searchColumns,
|
|
152
166
|
onLoadError,
|
|
167
|
+
total = $bindable(0),
|
|
168
|
+
loading = $bindable(false),
|
|
153
169
|
// GridCommonProps filters/sort (generic types)
|
|
154
170
|
filters,
|
|
155
171
|
onFilterChange,
|
|
@@ -285,6 +301,7 @@
|
|
|
285
301
|
// ── Context menu ───────────────────────────────────────────────────────────
|
|
286
302
|
|
|
287
303
|
let refreshTrigger = $state(0);
|
|
304
|
+
let contextMenuRowData = $state<Record<string, unknown> | undefined>(undefined);
|
|
288
305
|
let menu = $state<{
|
|
289
306
|
show: (
|
|
290
307
|
id: string,
|
|
@@ -347,8 +364,8 @@
|
|
|
347
364
|
label: item.label,
|
|
348
365
|
icon: item.icon,
|
|
349
366
|
onClick: () => {
|
|
350
|
-
item.onselect?.();
|
|
351
|
-
onMenuItemSelect?.(item);
|
|
367
|
+
item.onselect?.(contextMenuRowData);
|
|
368
|
+
onMenuItemSelect?.(item, contextMenuRowData);
|
|
352
369
|
},
|
|
353
370
|
});
|
|
354
371
|
}
|
|
@@ -523,6 +540,9 @@
|
|
|
523
540
|
let serverFetchVersion = 0;
|
|
524
541
|
let serverLoading = $state(false);
|
|
525
542
|
let serverAllLoaded = $state(false);
|
|
543
|
+
|
|
544
|
+
$effect(() => { total = serverTotal; });
|
|
545
|
+
$effect(() => { loading = serverLoading; });
|
|
526
546
|
let serverError = $state<string | null>(null);
|
|
527
547
|
|
|
528
548
|
type Adapter = GridlerResolveSpecAdapter | GridlerRestHeaderSpecAdapter;
|
|
@@ -704,8 +724,10 @@
|
|
|
704
724
|
onRowClick?.(row, resolveRowData(row));
|
|
705
725
|
}
|
|
706
726
|
|
|
707
|
-
function handleRowContextMenu(row: number) {
|
|
708
|
-
|
|
727
|
+
function handleRowContextMenu(row: number, x: number, y: number) {
|
|
728
|
+
contextMenuRowData = resolveRowData(row);
|
|
729
|
+
menu?.show("gridler-menu", { x, y, items: buildMenuItems() });
|
|
730
|
+
onRowContextMenu?.(row, contextMenuRowData, x, y);
|
|
709
731
|
}
|
|
710
732
|
|
|
711
733
|
// ── Selection → selectedItems bridge ──────────────────────────────────────
|
|
@@ -736,7 +758,7 @@
|
|
|
736
758
|
}
|
|
737
759
|
internalSelectedItems = items;
|
|
738
760
|
if (items.length > 0) onSelectedItemsChange?.(items);
|
|
739
|
-
onSelectionChange?.(sel);
|
|
761
|
+
onSelectionChange?.(sel, items.length > 0 ? items : undefined);
|
|
740
762
|
}
|
|
741
763
|
|
|
742
764
|
// ── Settings change event ─────────────────────────────────────────────────
|
|
@@ -795,6 +817,7 @@
|
|
|
795
817
|
item?: Record<string, unknown>;
|
|
796
818
|
column?: GridlerColumn;
|
|
797
819
|
}) {
|
|
820
|
+
contextMenuRowData = undefined;
|
|
798
821
|
if (onMenuClick) {
|
|
799
822
|
await onMenuClick(
|
|
800
823
|
d?.item,
|
|
@@ -806,6 +829,16 @@
|
|
|
806
829
|
if (!d) return;
|
|
807
830
|
menu?.show("gridler-menu", { x: d.x, y: d.y, items: buildMenuItems() });
|
|
808
831
|
}
|
|
832
|
+
|
|
833
|
+
// ── Public API ────────────────────────────────────────────────────────────
|
|
834
|
+
|
|
835
|
+
export function getSelectedRows(): Record<string, unknown>[] {
|
|
836
|
+
return internalSelectedItems;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
export function getLoadedData(): Record<string, unknown>[] {
|
|
840
|
+
return isServerMode ? serverData : filteredDataState;
|
|
841
|
+
}
|
|
809
842
|
</script>
|
|
810
843
|
|
|
811
844
|
<BetterMenu bind:this={menu}>
|
|
@@ -30,8 +30,8 @@ export interface Props extends Omit<Partial<GridlerProps>, "columns" | "rows" |
|
|
|
30
30
|
onRowDblClick?: (row: number, rowData: Record<string, unknown> | undefined) => void;
|
|
31
31
|
/** Called on row click with the row index and resolved row data. */
|
|
32
32
|
onRowClick?: (row: number, rowData: Record<string, unknown> | undefined) => void;
|
|
33
|
-
/** Called on row context-menu with the row index
|
|
34
|
-
onRowContextMenu?: (row: number, rowData: Record<string, unknown> | undefined) => void;
|
|
33
|
+
/** Called on row context-menu with the row index, resolved row data, and click coordinates. */
|
|
34
|
+
onRowContextMenu?: (row: number, rowData: Record<string, unknown> | undefined, x: number, y: number) => void;
|
|
35
35
|
/** Rows per cursor-forward page. Defaults to 200. */
|
|
36
36
|
pageSize?: number;
|
|
37
37
|
/**
|
|
@@ -52,15 +52,29 @@ export interface Props extends Omit<Partial<GridlerProps>, "columns" | "rows" |
|
|
|
52
52
|
searchColumns?: string[];
|
|
53
53
|
/** Called when a server fetch fails. */
|
|
54
54
|
onLoadError?: (error: string) => void;
|
|
55
|
+
/**
|
|
56
|
+
* Bindable. Reflects the server-reported total row count.
|
|
57
|
+
* Updated after every successful page fetch; 0 until the first response.
|
|
58
|
+
* Read-only from the parent — do not write this value.
|
|
59
|
+
*/
|
|
60
|
+
total?: number;
|
|
61
|
+
/**
|
|
62
|
+
* Bindable. `true` while any server fetch is in flight (initial load or next-page).
|
|
63
|
+
* Read-only from the parent — do not write this value.
|
|
64
|
+
*/
|
|
65
|
+
loading?: boolean;
|
|
55
66
|
/**
|
|
56
67
|
* Extra items appended to the built-in context menu (Refresh, Clear Search, Clear Filters, Clear Sort).
|
|
57
68
|
* Pass a separator item `{ id: 'sep', kind: 'separator', label: '' }` to group your items.
|
|
58
69
|
*/
|
|
59
70
|
menuItems?: GridlerContextMenuItem[];
|
|
60
71
|
/** Called when any context menu item is selected (built-in or custom). */
|
|
61
|
-
onMenuItemSelect?: (item: GridlerContextMenuItem) => void;
|
|
72
|
+
onMenuItemSelect?: (item: GridlerContextMenuItem, rowData?: Record<string, unknown>) => void;
|
|
62
73
|
children?: Snippet;
|
|
63
74
|
}
|
|
64
|
-
declare const GridlerFull: import("svelte").Component<Props, {
|
|
75
|
+
declare const GridlerFull: import("svelte").Component<Props, {
|
|
76
|
+
getSelectedRows: () => Record<string, unknown>[];
|
|
77
|
+
getLoadedData: () => Record<string, unknown>[];
|
|
78
|
+
}, "loading" | "total">;
|
|
65
79
|
type GridlerFull = ReturnType<typeof GridlerFull>;
|
|
66
80
|
export default GridlerFull;
|
|
@@ -155,8 +155,8 @@ export interface GridlerProps extends GridCommonProps<Record<string, unknown>> {
|
|
|
155
155
|
theme?: Partial<GridlerTheme>;
|
|
156
156
|
/** Controlled selection state. */
|
|
157
157
|
selection?: Selection;
|
|
158
|
-
/** Called when the internal selection changes. */
|
|
159
|
-
onSelectionChange?: (selection: Selection) => void;
|
|
158
|
+
/** Called when the internal selection changes. Row data for all selected rows is provided where available. */
|
|
159
|
+
onSelectionChange?: (selection: Selection, rowData?: Record<string, unknown>[]) => void;
|
|
160
160
|
/** Called when the visible row/column range changes (e.g. on scroll). */
|
|
161
161
|
onVisibleRangeChange?: (range: VisibleRange) => void;
|
|
162
162
|
/** When true, all cells are read-only. Also settable via settings.readonly. */
|
|
@@ -173,7 +173,7 @@ export interface GridlerContextMenuItem {
|
|
|
173
173
|
icon?: string;
|
|
174
174
|
/** Use 'separator' to render a divider line. */
|
|
175
175
|
kind?: 'item' | 'separator';
|
|
176
|
-
onselect?: () => void;
|
|
176
|
+
onselect?: (rowData?: Record<string, unknown>) => void;
|
|
177
177
|
}
|
|
178
178
|
export declare const DEFAULT_THEME: GridlerTheme;
|
|
179
179
|
export declare const DEFAULT_THEME_DARK: GridlerTheme;
|
|
@@ -376,12 +376,10 @@
|
|
|
376
376
|
contextMenuY = ev.clientY;
|
|
377
377
|
|
|
378
378
|
const ctxRowId = (resolved?.id ?? selectedIds[0] ?? null) as string | number | null;
|
|
379
|
-
const ctxRow =
|
|
380
|
-
|
|
381
|
-
(ctxRowId != null
|
|
379
|
+
const ctxRow: Record<string, unknown> | null =
|
|
380
|
+
ctxRowId != null
|
|
382
381
|
? data.find((item) => String(item?.[uniqueID]) === String(ctxRowId)) ?? null
|
|
383
|
-
: null
|
|
384
|
-
null;
|
|
382
|
+
: null;
|
|
385
383
|
|
|
386
384
|
contextMenuRowId = ctxRowId;
|
|
387
385
|
contextMenuRow = ctxRow;
|
package/llm/COMPONENT_GUIDE.md
CHANGED
|
@@ -570,6 +570,78 @@ These are `Partial<Options>` from `@warkypublic/resolvespec-js`. Grid-controlled
|
|
|
570
570
|
| `where` | Raw SQL WHERE fragment for the relation. |
|
|
571
571
|
| `updatable` | Marks the preloaded relation as updatable. |
|
|
572
572
|
|
|
573
|
+
#### Bindable state props
|
|
574
|
+
|
|
575
|
+
| Prop | Type | Description |
|
|
576
|
+
|-----------|-----------|-----------------------------------------------------------------------------|
|
|
577
|
+
| `total` | `number` | Server-reported total row count. Updated after every successful page fetch. |
|
|
578
|
+
| `loading` | `boolean` | `true` while any fetch is in flight (initial load or next-page append). |
|
|
579
|
+
|
|
580
|
+
Both are read-only from the parent's perspective — bind them to a variable, do not pass a value in:
|
|
581
|
+
|
|
582
|
+
```svelte
|
|
583
|
+
<script lang="ts">
|
|
584
|
+
let total = $state(0);
|
|
585
|
+
let loading = $state(false);
|
|
586
|
+
</script>
|
|
587
|
+
|
|
588
|
+
<GridlerFull bind:total bind:loading {columns} dataSource="resolvespec" {dataSourceOptions} />
|
|
589
|
+
|
|
590
|
+
{#if loading}<span>Loading…</span>{/if}
|
|
591
|
+
<span>{total} rows</span>
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
#### Row/cell callbacks (GridlerFull enriches with row data)
|
|
595
|
+
|
|
596
|
+
| Callback | Signature | Notes |
|
|
597
|
+
|---|---|---|
|
|
598
|
+
| `onCellDblClick` | `(item: Item, cell: GridlerCell, rowData?)` | Double-click on a cell. |
|
|
599
|
+
| `onRowDblClick` | `(row: number, rowData?)` | Double-click on a row (fires alongside `onCellDblClick`). |
|
|
600
|
+
| `onRowClick` | `(row: number, rowData?)` | Single click on a body row. |
|
|
601
|
+
| `onRowContextMenu` | `(row: number, rowData?, x: number, y: number)` | Right-click on a body row. `x`/`y` are viewport pixel coordinates. |
|
|
602
|
+
| `onDataChange` | `(data: Record<string, unknown>[]) => Promise<void>` | Fires after an inline cell edit mutates local `dataState`. Server mode: not called. |
|
|
603
|
+
| `onColumnsChange` | `(columns: GridlerColumn[]) => void` | Fires when `manageColumns` reorders columns. |
|
|
604
|
+
| `onSearchValueChange` | `(value: string) => void` | Fires on every search input change. |
|
|
605
|
+
| `onLoadError` | `(error: string) => void` | Fires on any adapter fetch failure. |
|
|
606
|
+
| `onMenuItemSelect` | `(item: GridlerContextMenuItem, rowData?) => void` | Fires for both built-in and custom context menu items. |
|
|
607
|
+
|
|
608
|
+
`rowData` is `Record<string, unknown> | undefined` — it maps to the loaded row object for the clicked row index, or `undefined` if the row has not been loaded yet.
|
|
609
|
+
|
|
610
|
+
#### Context menu customisation
|
|
611
|
+
|
|
612
|
+
```svelte
|
|
613
|
+
<GridlerFull
|
|
614
|
+
{columns}
|
|
615
|
+
menuItems={[
|
|
616
|
+
{ id: 'edit', label: 'Edit', icon: 'pencil' },
|
|
617
|
+
{ id: 'sep', kind: 'separator', label: '' },
|
|
618
|
+
{ id: 'delete', label: 'Delete', icon: 'trash', onselect: (row) => handleDelete(row) },
|
|
619
|
+
]}
|
|
620
|
+
onMenuItemSelect={(item, rowData) => console.log(item.id, rowData)}
|
|
621
|
+
/>
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
Built-in items (Refresh, Clear Search, Clear Filters, Clear Sort) appear first; custom `menuItems` are appended after a divider.
|
|
625
|
+
|
|
626
|
+
#### Public methods
|
|
627
|
+
|
|
628
|
+
| Method | Returns | Description |
|
|
629
|
+
|---|---|---|
|
|
630
|
+
| `getSelectedRows()` | `Record<string, unknown>[]` | Currently selected row objects. |
|
|
631
|
+
| `getLoadedData()` | `Record<string, unknown>[]` | All rows in the current data set (filtered local data or full server page buffer). |
|
|
632
|
+
|
|
633
|
+
```svelte
|
|
634
|
+
<script lang="ts">
|
|
635
|
+
import { GridlerFull } from '@warkypublic/svelix';
|
|
636
|
+
|
|
637
|
+
let grid: ReturnType<typeof GridlerFull>;
|
|
638
|
+
</script>
|
|
639
|
+
|
|
640
|
+
<GridlerFull bind:this={grid} {columns} {dataSourceOptions} />
|
|
641
|
+
|
|
642
|
+
<button onclick={() => console.log(grid.getSelectedRows())}>Log selection</button>
|
|
643
|
+
```
|
|
644
|
+
|
|
573
645
|
Key behaviors:
|
|
574
646
|
- `extraOptions` is stored on the adapter at construction time; changing `dataSourceOptions` recreates the adapter and triggers a full refetch.
|
|
575
647
|
- Call-time `extraOptions` passed directly to `readPage` override the config-level defaults; specific params (sort, filters, limit, cursor, columns) override both.
|
|
@@ -41,6 +41,7 @@ This file now tracks the implemented state of the project rather than the origin
|
|
|
41
41
|
### Data and explorer components
|
|
42
42
|
- `Gridler`
|
|
43
43
|
- `GridlerFull`
|
|
44
|
+
- `onSelectionChange(selection, rowData?)` — second arg carries resolved row data for all selected rows (cell, row, range types)
|
|
44
45
|
- `SvarkGrid`
|
|
45
46
|
- `VTree`
|
|
46
47
|
- shared generic grid types and supporting utilities
|
package/llm/gridler-events.md
CHANGED
|
@@ -39,7 +39,7 @@ onGridEvent?: (
|
|
|
39
39
|
| `sort_changed` | Sort order applied or cleared | — | — | — | `{ sortOrder: GridColumnSortOrder }` |
|
|
40
40
|
| `filter_changed` | Filter applied or cleared | — | — | — | `{ filters: GridColumnFilters }` |
|
|
41
41
|
| `search_changed` | Search input value changed | — | — | — | `{ searchValue: string }` |
|
|
42
|
-
| `selection_changed` | Cell, row, range, or column selection changes | — | — | — | — |
|
|
42
|
+
| `selection_changed` | Cell, row, range, or column selection changes | — | — | — | — | (use `onSelectionChange` for the selection object + row data) |
|
|
43
43
|
| `column_moved` | Column dragged to new position | — | — | — | `{ from: number, to: number }` |
|
|
44
44
|
| `column_resized` | Column edge dragged to new width | — | moved column | — | `{ width: number }` |
|
|
45
45
|
| `data_changed` | Cell edited and committed (local mode only) | updated row | edited column | — | `{ data: Row[] }` full new dataset |
|
|
@@ -47,6 +47,55 @@ onGridEvent?: (
|
|
|
47
47
|
|
|
48
48
|
---
|
|
49
49
|
|
|
50
|
+
## `onSelectionChange`
|
|
51
|
+
|
|
52
|
+
Fired on every selection change — mouse clicks, arrow-key navigation, Ctrl+A, row-marker toggles.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
onSelectionChange?: (
|
|
56
|
+
selection: Selection,
|
|
57
|
+
rowData?: Record<string, unknown>[], // resolved row data for all selected rows
|
|
58
|
+
) => void
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### `Selection` type
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
type Selection =
|
|
65
|
+
| { type: "none" }
|
|
66
|
+
| { type: "cell"; item: [col, row] }
|
|
67
|
+
| { type: "range"; start: [col, row]; end: [col, row] }
|
|
68
|
+
| { type: "row"; rows: number[] }
|
|
69
|
+
| { type: "column"; columns: number[] }
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### `rowData` population
|
|
73
|
+
|
|
74
|
+
| Selection type | `rowData` |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `cell` | single-element array with the row's data |
|
|
77
|
+
| `row` | array of data for each selected row |
|
|
78
|
+
| `range` | array of data for every row from `start[1]` to `end[1]` |
|
|
79
|
+
| `column` / `none` | `undefined` |
|
|
80
|
+
|
|
81
|
+
`rowData` is `undefined` when no row data can be resolved (column/none selections, or when `getRowData`/`data` is not provided).
|
|
82
|
+
|
|
83
|
+
### Usage
|
|
84
|
+
|
|
85
|
+
```svelte
|
|
86
|
+
<GridlerFull
|
|
87
|
+
{columns}
|
|
88
|
+
{data}
|
|
89
|
+
onSelectionChange={(sel, rowData) => {
|
|
90
|
+
if (sel.type === 'cell') {
|
|
91
|
+
console.log('Selected row:', rowData?.[0]);
|
|
92
|
+
}
|
|
93
|
+
}}
|
|
94
|
+
/>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
50
99
|
## `onCellEvent`
|
|
51
100
|
|
|
52
101
|
```ts
|