@warkypublic/svelix 0.1.46 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/README.md +93 -33
  2. package/dist/components/BetterMenu/BetterMenu.svelte +21 -37
  3. package/dist/components/BetterMenu/BetterMenuAsyncButton.svelte +21 -31
  4. package/dist/components/BetterMenu/BetterMenuPreview.svelte +27 -29
  5. package/dist/components/BetterMenu/MenuRenderer.svelte +40 -51
  6. package/dist/components/Boxer/Boxer.svelte +492 -602
  7. package/dist/components/Boxer/BoxerTarget.svelte +6 -46
  8. package/dist/components/Boxer/BoxerTarget.svelte.d.ts +1 -0
  9. package/dist/components/Boxer/types.d.ts +0 -1
  10. package/dist/components/Button.svelte +19 -43
  11. package/dist/components/ButtonPreview.svelte +2 -11
  12. package/dist/components/CardGrid/CardGrid.svelte +489 -614
  13. package/dist/components/CardGrid/CardGrid.svelte.d.ts +4 -0
  14. package/dist/components/CardGrid/CardGridFilterPanel.svelte +111 -90
  15. package/dist/components/CardGrid/DefaultCard.svelte +86 -97
  16. package/dist/components/CardGrid/DefaultCard.svelte.d.ts +1 -0
  17. package/dist/components/CardGrid/ImageCardStory.svelte +87 -36
  18. package/dist/components/CardGrid/cardGrid.d.ts +24 -0
  19. package/dist/components/CardGrid/cardGrid.js +20 -0
  20. package/dist/components/ContentEditor/CollaboraInsertTextDemo.svelte +5 -10
  21. package/dist/components/ContentEditor/ContentEditor.svelte +24 -50
  22. package/dist/components/ContentEditor/subcomponents/AudioPlayer.svelte +17 -17
  23. package/dist/components/ContentEditor/subcomponents/CollaboraEditor.svelte +244 -309
  24. package/dist/components/ContentEditor/subcomponents/EmailViewer.svelte +2 -3
  25. package/dist/components/ContentEditor/subcomponents/ImageViewer.svelte +14 -15
  26. package/dist/components/ContentEditor/subcomponents/MarkdownViewer.svelte +74 -90
  27. package/dist/components/ContentEditor/subcomponents/MonacoEditor.svelte +75 -103
  28. package/dist/components/ContentEditor/subcomponents/Office365Editor.svelte +234 -297
  29. package/dist/components/ContentEditor/subcomponents/PdfViewer.svelte +14 -20
  30. package/dist/components/ContentEditor/subcomponents/TextEditor.svelte +122 -150
  31. package/dist/components/ContentEditor/subcomponents/UnknownFile.svelte +105 -126
  32. package/dist/components/ContentEditor/subcomponents/VideoPlayer.svelte +17 -17
  33. package/dist/components/ContentEditor/subcomponents/ZipViewer.svelte +2 -3
  34. package/dist/components/ErrorBoundary/ErrorBoundary.svelte +41 -58
  35. package/dist/components/ErrorBoundary/ErrorBoundaryPreview.svelte +8 -16
  36. package/dist/components/Former/Former.svelte +306 -424
  37. package/dist/components/Former/FormerButtonArea.svelte +17 -68
  38. package/dist/components/Former/FormerDrawer.svelte +16 -80
  39. package/dist/components/Former/FormerDrawerPreview.svelte +99 -89
  40. package/dist/components/Former/FormerModal.svelte +16 -74
  41. package/dist/components/Former/FormerModalPreview.svelte +99 -89
  42. package/dist/components/Former/FormerPreview.svelte +76 -78
  43. package/dist/components/Former/FormerRestApiPreview.svelte +55 -48
  44. package/dist/components/Former/formerOverlayHelpers.svelte.d.ts +11 -0
  45. package/dist/components/Former/formerOverlayHelpers.svelte.js +38 -0
  46. package/dist/components/Former/index.d.ts +1 -0
  47. package/dist/components/Former/index.js +1 -0
  48. package/dist/components/FormerControllers/ButtonCtrl.svelte +13 -49
  49. package/dist/components/FormerControllers/CtrlFieldShell.svelte +15 -0
  50. package/dist/components/FormerControllers/CtrlFieldShell.svelte.d.ts +12 -0
  51. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrl.svelte +285 -457
  52. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlCalendar.svelte +95 -136
  53. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlPickerPanel.svelte +3 -62
  54. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlTimeFields.svelte +143 -217
  55. package/dist/components/FormerControllers/IconButtonCtrl.svelte +13 -47
  56. package/dist/components/FormerControllers/InlineWrapper.svelte +12 -41
  57. package/dist/components/FormerControllers/InlineWrapperPreview.svelte +4 -26
  58. package/dist/components/FormerControllers/NativeSelectCtrl.svelte +7 -39
  59. package/dist/components/FormerControllers/NumberInputCtrl.svelte +21 -43
  60. package/dist/components/FormerControllers/PasswordInputCtrl.svelte +8 -33
  61. package/dist/components/FormerControllers/SwitchCtrl.svelte +4 -20
  62. package/dist/components/FormerControllers/TextAreaCtrl.svelte +7 -34
  63. package/dist/components/FormerControllers/TextInputCtrl.svelte +7 -34
  64. package/dist/components/FormerControllers/ctrlLoading.svelte.d.ts +8 -0
  65. package/dist/components/FormerControllers/ctrlLoading.svelte.js +43 -0
  66. package/dist/components/FormerControllers/index.d.ts +2 -0
  67. package/dist/components/FormerControllers/index.js +2 -0
  68. package/dist/components/FormerControllers/utils.d.ts +1 -0
  69. package/dist/components/FormerControllers/utils.js +3 -0
  70. package/dist/components/GlobalStateStore/GlobalStateStoreProvider.svelte +73 -120
  71. package/dist/components/Gridler/components/Gridler.svelte +344 -442
  72. package/dist/components/Gridler/components/GridlerCanvas.svelte +720 -1115
  73. package/dist/components/Gridler/components/GridlerEditor.svelte +2 -15
  74. package/dist/components/Gridler/components/GridlerFull.svelte +521 -841
  75. package/dist/components/Gridler/components/GridlerFull.svelte.d.ts +1 -1
  76. package/dist/components/Gridler/components/GridlerFullWithFormerPreview.svelte +194 -147
  77. package/dist/components/Gridler/components/GridlerSearch.svelte +7 -16
  78. package/dist/components/Gridler/components/GridlerSearchToggle.svelte +7 -19
  79. package/dist/components/Gridler/types.d.ts +1 -1
  80. package/dist/components/Gridler/utils/serverPaging.d.ts +1 -0
  81. package/dist/components/Gridler/utils/serverPaging.js +7 -0
  82. package/dist/components/Icons/IconAdd.svelte +1 -3
  83. package/dist/components/Icons/IconAlertCircle.svelte +1 -3
  84. package/dist/components/Icons/IconAlertTriangle.svelte +1 -3
  85. package/dist/components/Icons/IconCamera.svelte +1 -3
  86. package/dist/components/Icons/IconClose.svelte +1 -3
  87. package/dist/components/Icons/IconEdit.svelte +1 -3
  88. package/dist/components/Icons/IconFilter.svelte +1 -3
  89. package/dist/components/Icons/IconSearch.svelte +1 -3
  90. package/dist/components/Icons/IconSort.svelte +1 -3
  91. package/dist/components/Icons/IconTrash.svelte +1 -3
  92. package/dist/components/Portal/Portal.svelte +14 -38
  93. package/dist/components/Screenshot/Screenshot.svelte +7 -13
  94. package/dist/components/Svark/Svark.svelte +680 -852
  95. package/dist/components/Svark/SvarkContextMenu.svelte +44 -63
  96. package/dist/components/Svark/SvarkInfiniteLoader.svelte +2 -6
  97. package/dist/components/Svark/SvarkPager.svelte +2 -28
  98. package/dist/components/Svark/SvarkSelectionDemo.svelte +4 -8
  99. package/dist/components/Svark/SvarkTopBar.svelte +9 -74
  100. package/dist/components/SvarkGrid/SvarkGrid.svelte +877 -1123
  101. package/dist/components/SvarkGrid/components/SvarkColumnFilterForm.svelte +45 -58
  102. package/dist/components/SvarkGrid/components/SvarkContextMenu.svelte +44 -63
  103. package/dist/components/SvarkGrid/components/SvarkHeaderFilterCell.svelte +41 -75
  104. package/dist/components/SvarkGrid/components/SvarkInfiniteLoader.svelte +2 -6
  105. package/dist/components/SvarkGrid/components/SvarkPager.svelte +2 -28
  106. package/dist/components/SvarkGrid/components/SvarkSearchPopover.svelte +8 -16
  107. package/dist/components/SvarkGrid/components/SvarkTopBar.svelte +6 -62
  108. package/dist/components/SvarkGrid/internal/SvarkGridView.svelte +345 -507
  109. package/dist/components/VTree/VTree.svelte +454 -431
  110. package/dist/components/VTree/VTree.utils.d.ts +23 -0
  111. package/dist/components/VTree/VTree.utils.js +73 -0
  112. package/dist/components/VTree/VTreeContextMenu.svelte +21 -33
  113. package/dist/components/VTree/VTreeControlledSelectionDemo.svelte +20 -0
  114. package/dist/components/VTree/VTreeControlledSelectionDemo.svelte.d.ts +18 -0
  115. package/dist/components/VTree/VTreeCustomizationDemo.svelte +23 -0
  116. package/dist/components/VTree/VTreeCustomizationDemo.svelte.d.ts +18 -0
  117. package/dist/components/VTree/VTreeEventsDemo.svelte +48 -58
  118. package/dist/components/VTree/VTreeInteractionDemo.svelte +29 -0
  119. package/dist/components/VTree/VTreeInteractionDemo.svelte.d.ts +18 -0
  120. package/dist/components/VTree/VTreeMultiSelectDemo.svelte +27 -0
  121. package/dist/components/VTree/VTreeMultiSelectDemo.svelte.d.ts +18 -0
  122. package/dist/components/VTree/VTreeRow.svelte +77 -115
  123. package/dist/components/VTree/VTreeRow.svelte.d.ts +34 -18
  124. package/dist/components/VTree/VTreeSearch.svelte +6 -13
  125. package/dist/components/VTree/VTreeVirtualViewport.svelte +54 -143
  126. package/dist/components/VTree/VTreeVirtualViewport.svelte.d.ts +35 -18
  127. package/dist/components/VTree/types.d.ts +39 -1
  128. package/llm/COMPONENT_GUIDE.md +25 -0
  129. package/package.json +51 -50
@@ -1,445 +1,347 @@
1
- <script lang="ts">
2
- // @ts-nocheck
3
- import type { Snippet } from "svelte";
4
- import type {
5
- GridlerProps,
6
- GridlerColumn,
7
- GridlerCell,
8
- GridlerTheme,
9
- Item,
10
- Selection,
11
- VisibleRange,
12
- } from "../types";
13
- import { DEFAULT_THEME, DEFAULT_THEME_DARK } from "../types";
14
- import GridlerCanvas from "./GridlerCanvas.svelte";
15
- import { detectDarkMode, resolveSkeletonGridTheme } from "../utils/helpers";
16
-
17
- export interface Props extends Partial<GridlerProps> {
18
- columns: GridlerColumn[];
19
- rows: number;
20
- getCellContent: (item: Item) => GridlerCell;
21
- onSearchToggle?: () => void;
22
- onSearchClose?: () => void;
23
- onHeaderContextMenu?: (colIndex: number, x: number, y: number) => void;
24
- onGridMenuOpen?: (coords: {
25
- x: number;
26
- y: number;
27
- item?: Record<string, unknown>;
28
- column?: GridlerColumn;
29
- }) => void;
30
- onRowClick?: (row: number) => void;
31
- onRowContextMenu?: (row: number, x: number, y: number) => void;
32
- /** Resolve raw row data by row index for populating onCellEvent. */
33
- getRowData?: (row: number) => Record<string, unknown> | undefined;
34
- children?: Snippet;
35
- }
36
-
37
- const {
38
- columns,
39
- rows,
40
- getCellContent,
41
- onCellEdited,
42
- onCellDblClick,
43
- onRowAppended,
44
- onColumnMoved,
45
- onDelete,
46
- onSelectionCleared,
47
- width = "100%",
48
- height = "400px",
49
- rowHeight = 34,
50
- headerHeight = 36,
51
- fixedColumns = 0,
52
- resizableColumns,
53
- rowMarkers,
54
- showSearch,
55
- readonly,
56
- searchValue = "",
57
- onSearchValueChange,
58
- onSearchToggle,
59
- onSearchClose,
60
- onHeaderContextMenu,
61
- onMenuClick,
62
- onGridMenuOpen,
63
- onRowClick,
64
- onRowContextMenu,
65
- theme = {},
66
- selection = { type: "none" } as Selection,
67
- onSelectionChange,
68
- onVisibleRangeChange,
69
- // GridCommonProps additions
70
- onCellEvent,
71
- onGridEvent,
72
- onSortOrderChange,
73
- sortOrder,
74
- onFilterChange: _onFilterChange,
75
- filters: _filters,
76
- selectedItems: _selectedItems,
77
- onSelectedItemsChange: _onSelectedItemsChange,
78
- getRowData,
79
- settings,
80
- children,
81
- }: Props = $props();
82
-
83
- // ── Settings resolution (flat props win over settings object) ────────────
84
-
85
- const resolvedReadonly = $derived(readonly ?? settings?.readonly ?? false);
86
- const resolvedResizableColumns = $derived(resizableColumns ?? settings?.resizableColumns ?? true);
87
- const resolvedShowSearch = $derived(showSearch ?? settings?.showSearch ?? false);
88
- const resolvedRowMarkers = $derived<"checkbox" | "number" | "none">(
89
- rowMarkers ??
90
- (settings?.showRowMarkers
91
- ? (settings?.rowMarkerType ?? "checkbox")
92
- : (settings?.rowMarkerType ?? "none")),
93
- );
94
-
95
- // ── Theme ─────────────────────────────────────────────────────────────────────
96
-
97
- let isDarkMode = $state(false);
98
- let resolvedSkeletonTheme = $state<GridlerTheme>(DEFAULT_THEME);
99
-
100
- $effect(() => {
101
- isDarkMode = detectDarkMode();
102
- resolvedSkeletonTheme =
103
- resolveSkeletonGridTheme(isDarkMode) ??
104
- (isDarkMode ? DEFAULT_THEME_DARK : DEFAULT_THEME);
105
- const mo = new MutationObserver(() => {
106
- isDarkMode = detectDarkMode();
107
- resolvedSkeletonTheme =
108
- resolveSkeletonGridTheme(isDarkMode) ??
109
- (isDarkMode ? DEFAULT_THEME_DARK : DEFAULT_THEME);
110
- });
111
- const opts: MutationObserverInit = {
112
- attributes: true,
113
- attributeFilter: ["class", "data-theme"],
114
- };
115
- mo.observe(document.documentElement, opts);
116
- mo.observe(document.body, opts);
117
- return () => mo.disconnect();
118
- });
119
-
120
- const baseTheme: GridlerTheme = $derived(resolvedSkeletonTheme);
121
- const mergedTheme: GridlerTheme = $derived({ ...baseTheme, ...theme });
122
-
123
- // ── Selection / editing state ────────────────────────────────────────────────
124
-
125
- let currentSelection = $state<Selection>({ type: "none" });
126
- let focusedCell = $state<Item | null>(null);
127
-
128
- $effect(() => {
129
- if (!selection || selection.type === "none") return;
130
- currentSelection = selection;
131
- });
132
- let isEditing = $state(false);
133
- let editingCell = $state<Item | null>(null);
134
-
135
- let canvasComponent: ReturnType<typeof GridlerCanvas> | undefined = $state();
136
-
137
- // ── Editing helpers ───────────────────────────────────────────────────────────
138
-
139
- function beginEdit(cell: Item) {
140
- const cellContent = getCellContent(cell);
141
- if (cellContent.readonly) return;
142
- editingCell = cell;
143
- isEditing = true;
144
- }
145
-
146
- function cancelEdit() {
147
- isEditing = false;
148
- editingCell = null;
149
- }
150
-
151
- function handleEditComplete(value: string) {
152
- if (editingCell) {
153
- const cell = getCellContent(editingCell);
154
- const newCell: GridlerCell = { ...cell, displayData: value, data: value };
155
- onCellEdited?.(editingCell, newCell);
156
- }
157
- cancelEdit();
158
- }
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
-
183
- // ── Row toggle (checkbox / number markers) ────────────────────────────────────
184
-
185
- function handleRowToggle(row: number) {
186
- const sel = canvasComponent?.getCurrentSelection() ?? currentSelection;
187
- let newSel: Selection;
188
- if (sel.type === "row" && sel.rows.includes(row)) {
189
- const newRows = sel.rows.filter((r) => r !== row);
190
- newSel =
191
- newRows.length > 0 ? { type: "row", rows: newRows } : { type: "none" };
192
- } else if (sel.type === "row") {
193
- newSel = { type: "row", rows: [...sel.rows, row] };
194
- } else {
195
- newSel = { type: "row", rows: [row] };
196
- }
197
- currentSelection = newSel;
198
- canvasComponent?.setCurrentSelection(newSel);
199
- onSelectionChange?.(newSel, getSelectionRowData(newSel));
200
- }
201
-
202
- // ── Clipboard ────────────────────────────────────────────────────────────────
203
-
204
- async function copySelection(): Promise<void> {
205
- const lines: string[] = [];
206
- const sel = canvasComponent?.getCurrentSelection() ?? currentSelection;
207
-
208
- if (sel.type === "cell") {
209
- lines.push(getCellContent(sel.item).displayData);
210
- } else if (sel.type === "range") {
211
- const [startCol, startRow] = sel.start;
212
- const [endCol, endRow] = sel.end;
213
- const minCol = Math.min(startCol, endCol);
214
- const maxCol = Math.max(startCol, endCol);
215
- const minRow = Math.min(startRow, endRow);
216
- const maxRow = Math.max(startRow, endRow);
217
- for (let row = minRow; row <= maxRow; row++) {
218
- const rowData: string[] = [];
219
- for (let col = minCol; col <= maxCol; col++) {
220
- rowData.push(getCellContent([col, row]).displayData);
221
- }
222
- lines.push(rowData.join("\t"));
223
- }
224
- } else if (sel.type === "row") {
225
- for (const row of sel.rows) {
226
- const rowData = columns.map(
227
- (_, col) => getCellContent([col, row]).displayData,
228
- );
229
- lines.push(rowData.join("\t"));
230
- }
231
- } else if (sel.type === "column") {
232
- for (let row = 0; row < rows; row++) {
233
- const rowData = sel.columns.map(
234
- (col) => getCellContent([col, row]).displayData,
235
- );
236
- lines.push(rowData.join("\t"));
237
- }
238
- }
239
-
240
- if (lines.length > 0) {
241
- try {
242
- await navigator.clipboard.writeText(lines.join("\n"));
243
- canvasComponent?.setAnnouncement(
244
- `Copied ${lines.length} row${lines.length > 1 ? "s" : ""} to clipboard`,
245
- );
246
- } catch {
247
- canvasComponent?.setAnnouncement("Clipboard access denied");
248
- }
249
- }
250
- }
251
-
252
- async function pasteFromClipboard(): Promise<void> {
253
- if (resolvedReadonly) return;
254
- const fc = canvasComponent?.getFocusedCell() ?? focusedCell;
255
- if (!fc) return;
256
- let text: string;
257
- try {
258
- text = await navigator.clipboard.readText();
259
- } catch {
260
- canvasComponent?.setAnnouncement("Clipboard access denied");
261
- return;
262
- }
263
- const pastedRows = text.split("\n").map((line) => line.split("\t"));
264
- const [startCol, startRow] = fc;
265
-
266
- for (let r = 0; r < pastedRows.length; r++) {
267
- for (let c = 0; c < pastedRows[r].length; c++) {
268
- const col = startCol + c;
269
- const row = startRow + r;
270
- if (col >= columns.length || row >= rows) continue;
271
- const item: Item = [col, row];
272
- const cell = getCellContent(item);
273
- if (!cell.readonly && cell.allowEditing !== false) {
274
- const newCell: GridlerCell = {
275
- ...cell,
276
- displayData: pastedRows[r][c],
277
- data: pastedRows[r][c],
278
- };
279
- onCellEdited?.(item, newCell);
280
- }
281
- }
282
- }
283
- canvasComponent?.setAnnouncement(
284
- `Pasted ${pastedRows.length} row${pastedRows.length > 1 ? "s" : ""}`,
285
- );
286
- }
287
-
288
- // ── Keyboard handler ──────────────────────────────────────────────────────────
289
-
290
- function handleKeyDown(e: KeyboardEvent) {
291
- if (!canvasComponent?.getHasFocus()) return;
292
- onGridEvent?.("keydown", undefined, undefined, { x: 0, y: 0, code: e.key });
293
-
294
- // Search toggle
295
- if ((e.ctrlKey || e.metaKey) && e.key === "f") {
296
- onSearchToggle?.();
297
- e.preventDefault();
298
- return;
299
- }
300
-
301
- // Copy/paste shortcuts
302
- if ((e.ctrlKey || e.metaKey) && e.key === "c") {
303
- copySelection();
304
- e.preventDefault();
305
- return;
306
- }
307
- if ((e.ctrlKey || e.metaKey) && e.key === "v") {
308
- pasteFromClipboard();
309
- e.preventDefault();
310
- return;
311
- }
312
- if ((e.ctrlKey || e.metaKey) && e.key === "a") {
313
- if (rows > 0 && columns.length > 0) {
314
- const newSel: Selection = {
315
- type: "range",
316
- start: [0, 0],
317
- end: [columns.length - 1, rows - 1],
318
- };
319
- currentSelection = newSel;
320
- canvasComponent?.setCurrentSelection(newSel);
321
- onSelectionChange?.(newSel, getSelectionRowData(newSel));
322
- canvasComponent?.setAnnouncement("All cells selected");
323
- }
324
- e.preventDefault();
325
- return;
326
- }
327
-
328
- focusedCell = canvasComponent?.getFocusedCell() ?? focusedCell;
329
- if (!focusedCell) return;
330
-
331
- const [col, row] = focusedCell;
332
- let newCol = col;
333
- let newRow = row;
334
-
335
- switch (e.key) {
336
- case "ArrowUp":
337
- newRow = Math.max(0, row - 1);
338
- e.preventDefault();
339
- break;
340
- case "ArrowDown":
341
- newRow = Math.min(rows - 1, row + 1);
342
- e.preventDefault();
343
- break;
344
- case "ArrowLeft":
345
- newCol = Math.max(0, col - 1);
346
- e.preventDefault();
347
- break;
348
- case "ArrowRight":
349
- newCol = Math.min(columns.length - 1, col + 1);
350
- e.preventDefault();
351
- break;
352
- case "Enter":
353
- if (!resolvedReadonly) {
354
- beginEdit(focusedCell);
355
- }
356
- onCellEvent?.("enter_key", getRowData?.(row) ?? {}, columns[col] ?? { id: "", title: "" });
357
- e.preventDefault();
358
- return;
359
- case "Escape": {
360
- cancelEdit();
361
- const sel = canvasComponent?.getCurrentSelection() ?? currentSelection;
362
- if (sel.type !== "none") {
363
- onSelectionCleared?.();
364
- }
365
- return;
366
- }
367
- case "Tab":
368
- if (e.shiftKey) {
369
- newCol = col - 1;
370
- if (newCol < 0) {
371
- newCol = columns.length - 1;
372
- newRow = Math.max(0, row - 1);
373
- }
374
- } else {
375
- newCol = col + 1;
376
- if (newCol >= columns.length) {
377
- newCol = 0;
378
- newRow = Math.min(rows - 1, row + 1);
379
- onRowAppended?.();
380
- }
381
- }
382
- onCellEvent?.("tab_key", getRowData?.(row) ?? {}, columns[col] ?? { id: "", title: "" });
383
- e.preventDefault();
384
- break;
385
- case "Delete":
386
- case "Backspace": {
387
- if (!resolvedReadonly) {
388
- const delSel =
389
- canvasComponent?.getCurrentSelection() ?? currentSelection;
390
- if (delSel.type !== "none") {
391
- onDelete?.(delSel);
392
- }
393
- }
394
- onCellEvent?.("delete_key", getRowData?.(row) ?? {}, columns[col] ?? { id: "", title: "" });
395
- return;
396
- }
397
- default:
398
- if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !resolvedReadonly) {
399
- beginEdit(focusedCell);
400
- }
401
- return;
402
- }
403
-
404
- if (newCol !== col || newRow !== row) {
405
- const newFocusedCell: Item = [newCol, newRow];
406
- focusedCell = newFocusedCell;
407
- canvasComponent?.setFocusedCell(newFocusedCell);
408
-
409
- const newSel: Selection = { type: "cell", item: newFocusedCell };
410
- currentSelection = newSel;
411
- canvasComponent?.setCurrentSelection(newSel);
412
- onSelectionChange?.(newSel, getSelectionRowData(newSel));
413
- canvasComponent?.setAnnouncement(
414
- `Row ${newRow + 1}, column ${columns[newCol]?.title ?? newCol + 1}`,
415
- );
416
- canvasComponent?.scrollCellIntoView(newFocusedCell);
417
- }
418
- }
419
-
420
- // ── Visible range / grid events ───────────────────────────────────────────────
421
-
422
- function handleVisibleRangeChange(range: VisibleRange) {
423
- onVisibleRangeChange?.(range);
424
- onGridEvent?.("scroll", undefined, undefined, undefined, { row: range.firstRow, column: range.firstCol });
425
- }
426
-
427
- async function handleMenuOpen(d: {
428
- x: number;
429
- y: number;
430
- item?: Record<string, unknown>;
431
- column?: GridlerColumn;
432
- }) {
433
- if (onMenuClick) {
434
- await onMenuClick(
435
- d.item,
436
- d.column,
437
- { x: d.x, y: d.y },
438
- );
439
- return;
440
- }
441
- onGridMenuOpen?.(d);
442
- }
1
+ <script lang="ts">import { DEFAULT_THEME, DEFAULT_THEME_DARK } from "../types";
2
+ import GridlerCanvas from "./GridlerCanvas.svelte";
3
+ import { detectDarkMode, resolveSkeletonGridTheme } from "../utils/helpers";
4
+ const { columns, rows, getCellContent, onCellEdited, onCellDblClick, onRowAppended, onColumnMoved, onDelete, onSelectionCleared, width = "100%", height = "400px", rowHeight = 34, headerHeight = 36, fixedColumns = 0, resizableColumns, rowMarkers, showSearch, readonly, searchValue = "", onSearchValueChange, onSearchToggle, onSearchClose, onHeaderContextMenu, onMenuClick, onGridMenuOpen, onRowClick, onRowContextMenu, theme = {}, selection = { type: "none" }, onSelectionChange, onVisibleRangeChange, onCellEvent, onGridEvent, onSortOrderChange, sortOrder, onFilterChange: _onFilterChange, filters: _filters, selectedItems: _selectedItems, onSelectedItemsChange: _onSelectedItemsChange, getRowData, settings, children } = $props();
5
+ // ── Settings resolution (flat props win over settings object) ────────────
6
+ const resolvedReadonly = $derived(readonly ?? settings?.readonly ?? false);
7
+ const resolvedResizableColumns = $derived(resizableColumns ?? settings?.resizableColumns ?? true);
8
+ const resolvedShowSearch = $derived(showSearch ?? settings?.showSearch ?? false);
9
+ const resolvedRowMarkers = $derived(rowMarkers ?? (settings?.showRowMarkers ? settings?.rowMarkerType ?? "checkbox" : settings?.rowMarkerType ?? "none"));
10
+ // ── Theme ─────────────────────────────────────────────────────────────────────
11
+ let isDarkMode = $state(false);
12
+ let resolvedSkeletonTheme = $state(DEFAULT_THEME);
13
+ // Both isDarkMode and resolvedSkeletonTheme are derived from a local `dark`
14
+ // value rather than read back from isDarkMode itself, so this effect never
15
+ // tracks its own write target — avoids tearing down/recreating the
16
+ // MutationObserver on an incidental extra run.
17
+ $effect(() => {
18
+ const dark = detectDarkMode();
19
+ isDarkMode = dark;
20
+ resolvedSkeletonTheme = resolveSkeletonGridTheme(dark) ?? (dark ? DEFAULT_THEME_DARK : DEFAULT_THEME);
21
+ const mo = new MutationObserver(() => {
22
+ const dark = detectDarkMode();
23
+ isDarkMode = dark;
24
+ resolvedSkeletonTheme = resolveSkeletonGridTheme(dark) ?? (dark ? DEFAULT_THEME_DARK : DEFAULT_THEME);
25
+ });
26
+ const opts = {
27
+ attributes: true,
28
+ attributeFilter: ["class", "data-theme"]
29
+ };
30
+ mo.observe(document.documentElement, opts);
31
+ mo.observe(document.body, opts);
32
+ return () => mo.disconnect();
33
+ });
34
+ const baseTheme = $derived(resolvedSkeletonTheme);
35
+ const mergedTheme = $derived({
36
+ ...baseTheme,
37
+ ...theme
38
+ });
39
+ // ── Selection / editing state ────────────────────────────────────────────────
40
+ let currentSelection = $state({ type: "none" });
41
+ let focusedCell = $state(null);
42
+ $effect(() => {
43
+ if (!selection || selection.type === "none") return;
44
+ currentSelection = selection;
45
+ });
46
+ let isEditing = $state(false);
47
+ let editingCell = $state(null);
48
+ let canvasComponent = $state();
49
+ // ── Editing helpers ───────────────────────────────────────────────────────────
50
+ function beginEdit(cell) {
51
+ const cellContent = getCellContent(cell);
52
+ if (cellContent.readonly) return;
53
+ editingCell = cell;
54
+ isEditing = true;
55
+ }
56
+ function cancelEdit() {
57
+ isEditing = false;
58
+ editingCell = null;
59
+ }
60
+ function handleEditComplete(value) {
61
+ if (editingCell) {
62
+ const cell = getCellContent(editingCell);
63
+ const newCell = {
64
+ ...cell,
65
+ displayData: value,
66
+ data: value
67
+ };
68
+ onCellEdited?.(editingCell, newCell);
69
+ }
70
+ cancelEdit();
71
+ }
72
+ function getSelectionRowData(sel) {
73
+ if (!getRowData) return undefined;
74
+ if (sel.type === "cell") {
75
+ const d = getRowData(sel.item[1]);
76
+ return d ? [d] : undefined;
77
+ }
78
+ if (sel.type === "row") {
79
+ const items = sel.rows.map(getRowData).filter((r) => r !== undefined);
80
+ return items.length > 0 ? items : undefined;
81
+ }
82
+ if (sel.type === "range") {
83
+ const minRow = Math.min(sel.start[1], sel.end[1]);
84
+ const maxRow = Math.max(sel.start[1], sel.end[1]);
85
+ const items = [];
86
+ for (let i = minRow; i <= maxRow; i++) {
87
+ const d = getRowData(i);
88
+ if (d) items.push(d);
89
+ }
90
+ return items.length > 0 ? items : undefined;
91
+ }
92
+ return undefined;
93
+ }
94
+ // ── Row toggle (checkbox / number markers) ────────────────────────────────────
95
+ function handleRowToggle(row) {
96
+ const sel = canvasComponent?.getCurrentSelection() ?? currentSelection;
97
+ let newSel;
98
+ if (sel.type === "row" && sel.rows.includes(row)) {
99
+ const newRows = sel.rows.filter((r) => r !== row);
100
+ newSel = newRows.length > 0 ? {
101
+ type: "row",
102
+ rows: newRows
103
+ } : { type: "none" };
104
+ } else if (sel.type === "row") {
105
+ newSel = {
106
+ type: "row",
107
+ rows: [...sel.rows, row]
108
+ };
109
+ } else {
110
+ newSel = {
111
+ type: "row",
112
+ rows: [row]
113
+ };
114
+ }
115
+ currentSelection = newSel;
116
+ canvasComponent?.setCurrentSelection(newSel);
117
+ onSelectionChange?.(newSel, getSelectionRowData(newSel));
118
+ }
119
+ // ── Clipboard ────────────────────────────────────────────────────────────────
120
+ async function copySelection() {
121
+ const lines = [];
122
+ const sel = canvasComponent?.getCurrentSelection() ?? currentSelection;
123
+ if (sel.type === "cell") {
124
+ lines.push(getCellContent(sel.item).displayData);
125
+ } else if (sel.type === "range") {
126
+ const [startCol, startRow] = sel.start;
127
+ const [endCol, endRow] = sel.end;
128
+ const minCol = Math.min(startCol, endCol);
129
+ const maxCol = Math.max(startCol, endCol);
130
+ const minRow = Math.min(startRow, endRow);
131
+ const maxRow = Math.max(startRow, endRow);
132
+ for (let row = minRow; row <= maxRow; row++) {
133
+ const rowData = [];
134
+ for (let col = minCol; col <= maxCol; col++) {
135
+ rowData.push(getCellContent([col, row]).displayData);
136
+ }
137
+ lines.push(rowData.join(" "));
138
+ }
139
+ } else if (sel.type === "row") {
140
+ for (const row of sel.rows) {
141
+ const rowData = columns.map((_, col) => getCellContent([col, row]).displayData);
142
+ lines.push(rowData.join(" "));
143
+ }
144
+ } else if (sel.type === "column") {
145
+ for (let row = 0; row < rows; row++) {
146
+ const rowData = sel.columns.map((col) => getCellContent([col, row]).displayData);
147
+ lines.push(rowData.join(" "));
148
+ }
149
+ }
150
+ if (lines.length > 0) {
151
+ try {
152
+ await navigator.clipboard.writeText(lines.join("\n"));
153
+ canvasComponent?.setAnnouncement(`Copied ${lines.length} row${lines.length > 1 ? "s" : ""} to clipboard`);
154
+ } catch {
155
+ canvasComponent?.setAnnouncement("Clipboard access denied");
156
+ }
157
+ }
158
+ }
159
+ async function pasteFromClipboard() {
160
+ if (resolvedReadonly) return;
161
+ const fc = canvasComponent?.getFocusedCell() ?? focusedCell;
162
+ if (!fc) return;
163
+ let text;
164
+ try {
165
+ text = await navigator.clipboard.readText();
166
+ } catch {
167
+ canvasComponent?.setAnnouncement("Clipboard access denied");
168
+ return;
169
+ }
170
+ const pastedRows = text.split("\n").map((line) => line.split(" "));
171
+ const [startCol, startRow] = fc;
172
+ for (let r = 0; r < pastedRows.length; r++) {
173
+ for (let c = 0; c < pastedRows[r].length; c++) {
174
+ const col = startCol + c;
175
+ const row = startRow + r;
176
+ if (col >= columns.length || row >= rows) continue;
177
+ const item = [col, row];
178
+ const cell = getCellContent(item);
179
+ if (!cell.readonly && cell.allowEditing !== false) {
180
+ const newCell = {
181
+ ...cell,
182
+ displayData: pastedRows[r][c],
183
+ data: pastedRows[r][c]
184
+ };
185
+ onCellEdited?.(item, newCell);
186
+ }
187
+ }
188
+ }
189
+ canvasComponent?.setAnnouncement(`Pasted ${pastedRows.length} row${pastedRows.length > 1 ? "s" : ""}`);
190
+ }
191
+ // ── Keyboard handler ──────────────────────────────────────────────────────────
192
+ function handleKeyDown(e) {
193
+ if (!canvasComponent?.getHasFocus()) return;
194
+ onGridEvent?.("keydown", undefined, undefined, {
195
+ x: 0,
196
+ y: 0,
197
+ code: e.key
198
+ });
199
+ // Search toggle
200
+ if ((e.ctrlKey || e.metaKey) && e.key === "f") {
201
+ onSearchToggle?.();
202
+ e.preventDefault();
203
+ return;
204
+ }
205
+ // Copy/paste shortcuts
206
+ if ((e.ctrlKey || e.metaKey) && e.key === "c") {
207
+ copySelection();
208
+ e.preventDefault();
209
+ return;
210
+ }
211
+ if ((e.ctrlKey || e.metaKey) && e.key === "v") {
212
+ pasteFromClipboard();
213
+ e.preventDefault();
214
+ return;
215
+ }
216
+ if ((e.ctrlKey || e.metaKey) && e.key === "a") {
217
+ if (rows > 0 && columns.length > 0) {
218
+ const newSel = {
219
+ type: "range",
220
+ start: [0, 0],
221
+ end: [columns.length - 1, rows - 1]
222
+ };
223
+ currentSelection = newSel;
224
+ canvasComponent?.setCurrentSelection(newSel);
225
+ onSelectionChange?.(newSel, getSelectionRowData(newSel));
226
+ canvasComponent?.setAnnouncement("All cells selected");
227
+ }
228
+ e.preventDefault();
229
+ return;
230
+ }
231
+ focusedCell = canvasComponent?.getFocusedCell() ?? focusedCell;
232
+ if (!focusedCell) return;
233
+ const [col, row] = focusedCell;
234
+ let newCol = col;
235
+ let newRow = row;
236
+ switch (e.key) {
237
+ case "ArrowUp":
238
+ newRow = Math.max(0, row - 1);
239
+ e.preventDefault();
240
+ break;
241
+ case "ArrowDown":
242
+ newRow = Math.min(rows - 1, row + 1);
243
+ e.preventDefault();
244
+ break;
245
+ case "ArrowLeft":
246
+ newCol = Math.max(0, col - 1);
247
+ e.preventDefault();
248
+ break;
249
+ case "ArrowRight":
250
+ newCol = Math.min(columns.length - 1, col + 1);
251
+ e.preventDefault();
252
+ break;
253
+ case "Enter":
254
+ if (!resolvedReadonly) {
255
+ beginEdit(focusedCell);
256
+ }
257
+ onCellEvent?.("enter_key", getRowData?.(row) ?? {}, columns[col] ?? {
258
+ id: "",
259
+ title: ""
260
+ });
261
+ e.preventDefault();
262
+ return;
263
+ case "Escape": {
264
+ cancelEdit();
265
+ const sel = canvasComponent?.getCurrentSelection() ?? currentSelection;
266
+ if (sel.type !== "none") {
267
+ onSelectionCleared?.();
268
+ }
269
+ return;
270
+ }
271
+ case "Tab":
272
+ if (e.shiftKey) {
273
+ newCol = col - 1;
274
+ if (newCol < 0) {
275
+ newCol = columns.length - 1;
276
+ newRow = Math.max(0, row - 1);
277
+ }
278
+ } else {
279
+ newCol = col + 1;
280
+ if (newCol >= columns.length) {
281
+ newCol = 0;
282
+ newRow = Math.min(rows - 1, row + 1);
283
+ onRowAppended?.();
284
+ }
285
+ }
286
+ onCellEvent?.("tab_key", getRowData?.(row) ?? {}, columns[col] ?? {
287
+ id: "",
288
+ title: ""
289
+ });
290
+ e.preventDefault();
291
+ break;
292
+ case "Delete":
293
+ case "Backspace": {
294
+ if (!resolvedReadonly) {
295
+ const delSel = canvasComponent?.getCurrentSelection() ?? currentSelection;
296
+ if (delSel.type !== "none") {
297
+ onDelete?.(delSel);
298
+ }
299
+ }
300
+ onCellEvent?.("delete_key", getRowData?.(row) ?? {}, columns[col] ?? {
301
+ id: "",
302
+ title: ""
303
+ });
304
+ return;
305
+ }
306
+ default:
307
+ if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !resolvedReadonly) {
308
+ beginEdit(focusedCell);
309
+ }
310
+ return;
311
+ }
312
+ if (newCol !== col || newRow !== row) {
313
+ const newFocusedCell = [newCol, newRow];
314
+ focusedCell = newFocusedCell;
315
+ canvasComponent?.setFocusedCell(newFocusedCell);
316
+ const newSel = {
317
+ type: "cell",
318
+ item: newFocusedCell
319
+ };
320
+ currentSelection = newSel;
321
+ canvasComponent?.setCurrentSelection(newSel);
322
+ onSelectionChange?.(newSel, getSelectionRowData(newSel));
323
+ canvasComponent?.setAnnouncement(`Row ${newRow + 1}, column ${columns[newCol]?.title ?? newCol + 1}`);
324
+ canvasComponent?.scrollCellIntoView(newFocusedCell);
325
+ }
326
+ }
327
+ // ── Visible range / grid events ───────────────────────────────────────────────
328
+ function handleVisibleRangeChange(range) {
329
+ onVisibleRangeChange?.(range);
330
+ onGridEvent?.("scroll", undefined, undefined, undefined, {
331
+ row: range.firstRow,
332
+ column: range.firstCol
333
+ });
334
+ }
335
+ async function handleMenuOpen(d) {
336
+ if (onMenuClick) {
337
+ await onMenuClick(d.item, d.column, {
338
+ x: d.x,
339
+ y: d.y
340
+ });
341
+ return;
342
+ }
343
+ onGridMenuOpen?.(d);
344
+ }
443
345
  </script>
444
346
 
445
347
  <GridlerCanvas