@warkypublic/svelix 0.1.46 → 0.1.47

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 (97) 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 +488 -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 +443 -614
  13. package/dist/components/CardGrid/CardGridFilterPanel.svelte +111 -90
  14. package/dist/components/CardGrid/DefaultCard.svelte +80 -96
  15. package/dist/components/CardGrid/ImageCardStory.svelte +87 -36
  16. package/dist/components/ContentEditor/CollaboraInsertTextDemo.svelte +5 -10
  17. package/dist/components/ContentEditor/ContentEditor.svelte +24 -50
  18. package/dist/components/ContentEditor/subcomponents/AudioPlayer.svelte +17 -17
  19. package/dist/components/ContentEditor/subcomponents/CollaboraEditor.svelte +244 -309
  20. package/dist/components/ContentEditor/subcomponents/EmailViewer.svelte +2 -3
  21. package/dist/components/ContentEditor/subcomponents/ImageViewer.svelte +14 -15
  22. package/dist/components/ContentEditor/subcomponents/MarkdownViewer.svelte +74 -90
  23. package/dist/components/ContentEditor/subcomponents/MonacoEditor.svelte +75 -103
  24. package/dist/components/ContentEditor/subcomponents/Office365Editor.svelte +234 -297
  25. package/dist/components/ContentEditor/subcomponents/PdfViewer.svelte +14 -20
  26. package/dist/components/ContentEditor/subcomponents/TextEditor.svelte +122 -150
  27. package/dist/components/ContentEditor/subcomponents/UnknownFile.svelte +105 -126
  28. package/dist/components/ContentEditor/subcomponents/VideoPlayer.svelte +17 -17
  29. package/dist/components/ContentEditor/subcomponents/ZipViewer.svelte +2 -3
  30. package/dist/components/ErrorBoundary/ErrorBoundary.svelte +41 -58
  31. package/dist/components/ErrorBoundary/ErrorBoundaryPreview.svelte +8 -16
  32. package/dist/components/Former/Former.svelte +306 -424
  33. package/dist/components/Former/FormerButtonArea.svelte +17 -68
  34. package/dist/components/Former/FormerDrawer.svelte +26 -78
  35. package/dist/components/Former/FormerDrawerPreview.svelte +99 -89
  36. package/dist/components/Former/FormerModal.svelte +24 -70
  37. package/dist/components/Former/FormerModalPreview.svelte +99 -89
  38. package/dist/components/Former/FormerPreview.svelte +76 -78
  39. package/dist/components/Former/FormerRestApiPreview.svelte +55 -48
  40. package/dist/components/FormerControllers/ButtonCtrl.svelte +13 -44
  41. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrl.svelte +280 -445
  42. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlCalendar.svelte +95 -136
  43. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlPickerPanel.svelte +3 -62
  44. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlTimeFields.svelte +143 -217
  45. package/dist/components/FormerControllers/IconButtonCtrl.svelte +13 -42
  46. package/dist/components/FormerControllers/InlineWrapper.svelte +12 -41
  47. package/dist/components/FormerControllers/InlineWrapperPreview.svelte +4 -26
  48. package/dist/components/FormerControllers/NativeSelectCtrl.svelte +3 -29
  49. package/dist/components/FormerControllers/NumberInputCtrl.svelte +9 -35
  50. package/dist/components/FormerControllers/PasswordInputCtrl.svelte +4 -23
  51. package/dist/components/FormerControllers/SwitchCtrl.svelte +3 -20
  52. package/dist/components/FormerControllers/TextAreaCtrl.svelte +3 -24
  53. package/dist/components/FormerControllers/TextInputCtrl.svelte +3 -24
  54. package/dist/components/GlobalStateStore/GlobalStateStoreProvider.svelte +73 -120
  55. package/dist/components/Gridler/components/Gridler.svelte +338 -442
  56. package/dist/components/Gridler/components/GridlerCanvas.svelte +720 -1115
  57. package/dist/components/Gridler/components/GridlerEditor.svelte +2 -15
  58. package/dist/components/Gridler/components/GridlerFull.svelte +520 -841
  59. package/dist/components/Gridler/components/GridlerFull.svelte.d.ts +1 -1
  60. package/dist/components/Gridler/components/GridlerFullWithFormerPreview.svelte +194 -147
  61. package/dist/components/Gridler/components/GridlerSearch.svelte +7 -16
  62. package/dist/components/Gridler/components/GridlerSearchToggle.svelte +7 -19
  63. package/dist/components/Gridler/types.d.ts +1 -1
  64. package/dist/components/Icons/IconAdd.svelte +1 -3
  65. package/dist/components/Icons/IconAlertCircle.svelte +1 -3
  66. package/dist/components/Icons/IconAlertTriangle.svelte +1 -3
  67. package/dist/components/Icons/IconCamera.svelte +1 -3
  68. package/dist/components/Icons/IconClose.svelte +1 -3
  69. package/dist/components/Icons/IconEdit.svelte +1 -3
  70. package/dist/components/Icons/IconFilter.svelte +1 -3
  71. package/dist/components/Icons/IconSearch.svelte +1 -3
  72. package/dist/components/Icons/IconSort.svelte +1 -3
  73. package/dist/components/Icons/IconTrash.svelte +1 -3
  74. package/dist/components/Portal/Portal.svelte +14 -38
  75. package/dist/components/Screenshot/Screenshot.svelte +7 -13
  76. package/dist/components/Svark/Svark.svelte +680 -852
  77. package/dist/components/Svark/SvarkContextMenu.svelte +44 -63
  78. package/dist/components/Svark/SvarkInfiniteLoader.svelte +2 -6
  79. package/dist/components/Svark/SvarkPager.svelte +2 -28
  80. package/dist/components/Svark/SvarkSelectionDemo.svelte +4 -8
  81. package/dist/components/Svark/SvarkTopBar.svelte +9 -74
  82. package/dist/components/SvarkGrid/SvarkGrid.svelte +877 -1123
  83. package/dist/components/SvarkGrid/components/SvarkColumnFilterForm.svelte +45 -58
  84. package/dist/components/SvarkGrid/components/SvarkContextMenu.svelte +44 -63
  85. package/dist/components/SvarkGrid/components/SvarkHeaderFilterCell.svelte +41 -75
  86. package/dist/components/SvarkGrid/components/SvarkInfiniteLoader.svelte +2 -6
  87. package/dist/components/SvarkGrid/components/SvarkPager.svelte +2 -28
  88. package/dist/components/SvarkGrid/components/SvarkSearchPopover.svelte +8 -16
  89. package/dist/components/SvarkGrid/components/SvarkTopBar.svelte +6 -62
  90. package/dist/components/SvarkGrid/internal/SvarkGridView.svelte +345 -507
  91. package/dist/components/VTree/VTree.svelte +349 -431
  92. package/dist/components/VTree/VTreeContextMenu.svelte +21 -33
  93. package/dist/components/VTree/VTreeEventsDemo.svelte +48 -58
  94. package/dist/components/VTree/VTreeRow.svelte +13 -65
  95. package/dist/components/VTree/VTreeSearch.svelte +6 -13
  96. package/dist/components/VTree/VTreeVirtualViewport.svelte +37 -106
  97. package/package.json +30 -29
@@ -1,445 +1,341 @@
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
+ $effect(() => {
14
+ isDarkMode = detectDarkMode();
15
+ resolvedSkeletonTheme = resolveSkeletonGridTheme(isDarkMode) ?? (isDarkMode ? DEFAULT_THEME_DARK : DEFAULT_THEME);
16
+ const mo = new MutationObserver(() => {
17
+ isDarkMode = detectDarkMode();
18
+ resolvedSkeletonTheme = resolveSkeletonGridTheme(isDarkMode) ?? (isDarkMode ? DEFAULT_THEME_DARK : DEFAULT_THEME);
19
+ });
20
+ const opts = {
21
+ attributes: true,
22
+ attributeFilter: ["class", "data-theme"]
23
+ };
24
+ mo.observe(document.documentElement, opts);
25
+ mo.observe(document.body, opts);
26
+ return () => mo.disconnect();
27
+ });
28
+ const baseTheme = $derived(resolvedSkeletonTheme);
29
+ const mergedTheme = $derived({
30
+ ...baseTheme,
31
+ ...theme
32
+ });
33
+ // ── Selection / editing state ────────────────────────────────────────────────
34
+ let currentSelection = $state({ type: "none" });
35
+ let focusedCell = $state(null);
36
+ $effect(() => {
37
+ if (!selection || selection.type === "none") return;
38
+ currentSelection = selection;
39
+ });
40
+ let isEditing = $state(false);
41
+ let editingCell = $state(null);
42
+ let canvasComponent = $state();
43
+ // ── Editing helpers ───────────────────────────────────────────────────────────
44
+ function beginEdit(cell) {
45
+ const cellContent = getCellContent(cell);
46
+ if (cellContent.readonly) return;
47
+ editingCell = cell;
48
+ isEditing = true;
49
+ }
50
+ function cancelEdit() {
51
+ isEditing = false;
52
+ editingCell = null;
53
+ }
54
+ function handleEditComplete(value) {
55
+ if (editingCell) {
56
+ const cell = getCellContent(editingCell);
57
+ const newCell = {
58
+ ...cell,
59
+ displayData: value,
60
+ data: value
61
+ };
62
+ onCellEdited?.(editingCell, newCell);
63
+ }
64
+ cancelEdit();
65
+ }
66
+ function getSelectionRowData(sel) {
67
+ if (!getRowData) return undefined;
68
+ if (sel.type === "cell") {
69
+ const d = getRowData(sel.item[1]);
70
+ return d ? [d] : undefined;
71
+ }
72
+ if (sel.type === "row") {
73
+ const items = sel.rows.map(getRowData).filter((r) => r !== undefined);
74
+ return items.length > 0 ? items : undefined;
75
+ }
76
+ if (sel.type === "range") {
77
+ const minRow = Math.min(sel.start[1], sel.end[1]);
78
+ const maxRow = Math.max(sel.start[1], sel.end[1]);
79
+ const items = [];
80
+ for (let i = minRow; i <= maxRow; i++) {
81
+ const d = getRowData(i);
82
+ if (d) items.push(d);
83
+ }
84
+ return items.length > 0 ? items : undefined;
85
+ }
86
+ return undefined;
87
+ }
88
+ // ── Row toggle (checkbox / number markers) ────────────────────────────────────
89
+ function handleRowToggle(row) {
90
+ const sel = canvasComponent?.getCurrentSelection() ?? currentSelection;
91
+ let newSel;
92
+ if (sel.type === "row" && sel.rows.includes(row)) {
93
+ const newRows = sel.rows.filter((r) => r !== row);
94
+ newSel = newRows.length > 0 ? {
95
+ type: "row",
96
+ rows: newRows
97
+ } : { type: "none" };
98
+ } else if (sel.type === "row") {
99
+ newSel = {
100
+ type: "row",
101
+ rows: [...sel.rows, row]
102
+ };
103
+ } else {
104
+ newSel = {
105
+ type: "row",
106
+ rows: [row]
107
+ };
108
+ }
109
+ currentSelection = newSel;
110
+ canvasComponent?.setCurrentSelection(newSel);
111
+ onSelectionChange?.(newSel, getSelectionRowData(newSel));
112
+ }
113
+ // ── Clipboard ────────────────────────────────────────────────────────────────
114
+ async function copySelection() {
115
+ const lines = [];
116
+ const sel = canvasComponent?.getCurrentSelection() ?? currentSelection;
117
+ if (sel.type === "cell") {
118
+ lines.push(getCellContent(sel.item).displayData);
119
+ } else if (sel.type === "range") {
120
+ const [startCol, startRow] = sel.start;
121
+ const [endCol, endRow] = sel.end;
122
+ const minCol = Math.min(startCol, endCol);
123
+ const maxCol = Math.max(startCol, endCol);
124
+ const minRow = Math.min(startRow, endRow);
125
+ const maxRow = Math.max(startRow, endRow);
126
+ for (let row = minRow; row <= maxRow; row++) {
127
+ const rowData = [];
128
+ for (let col = minCol; col <= maxCol; col++) {
129
+ rowData.push(getCellContent([col, row]).displayData);
130
+ }
131
+ lines.push(rowData.join(" "));
132
+ }
133
+ } else if (sel.type === "row") {
134
+ for (const row of sel.rows) {
135
+ const rowData = columns.map((_, col) => getCellContent([col, row]).displayData);
136
+ lines.push(rowData.join(" "));
137
+ }
138
+ } else if (sel.type === "column") {
139
+ for (let row = 0; row < rows; row++) {
140
+ const rowData = sel.columns.map((col) => getCellContent([col, row]).displayData);
141
+ lines.push(rowData.join(" "));
142
+ }
143
+ }
144
+ if (lines.length > 0) {
145
+ try {
146
+ await navigator.clipboard.writeText(lines.join("\n"));
147
+ canvasComponent?.setAnnouncement(`Copied ${lines.length} row${lines.length > 1 ? "s" : ""} to clipboard`);
148
+ } catch {
149
+ canvasComponent?.setAnnouncement("Clipboard access denied");
150
+ }
151
+ }
152
+ }
153
+ async function pasteFromClipboard() {
154
+ if (resolvedReadonly) return;
155
+ const fc = canvasComponent?.getFocusedCell() ?? focusedCell;
156
+ if (!fc) return;
157
+ let text;
158
+ try {
159
+ text = await navigator.clipboard.readText();
160
+ } catch {
161
+ canvasComponent?.setAnnouncement("Clipboard access denied");
162
+ return;
163
+ }
164
+ const pastedRows = text.split("\n").map((line) => line.split(" "));
165
+ const [startCol, startRow] = fc;
166
+ for (let r = 0; r < pastedRows.length; r++) {
167
+ for (let c = 0; c < pastedRows[r].length; c++) {
168
+ const col = startCol + c;
169
+ const row = startRow + r;
170
+ if (col >= columns.length || row >= rows) continue;
171
+ const item = [col, row];
172
+ const cell = getCellContent(item);
173
+ if (!cell.readonly && cell.allowEditing !== false) {
174
+ const newCell = {
175
+ ...cell,
176
+ displayData: pastedRows[r][c],
177
+ data: pastedRows[r][c]
178
+ };
179
+ onCellEdited?.(item, newCell);
180
+ }
181
+ }
182
+ }
183
+ canvasComponent?.setAnnouncement(`Pasted ${pastedRows.length} row${pastedRows.length > 1 ? "s" : ""}`);
184
+ }
185
+ // ── Keyboard handler ──────────────────────────────────────────────────────────
186
+ function handleKeyDown(e) {
187
+ if (!canvasComponent?.getHasFocus()) return;
188
+ onGridEvent?.("keydown", undefined, undefined, {
189
+ x: 0,
190
+ y: 0,
191
+ code: e.key
192
+ });
193
+ // Search toggle
194
+ if ((e.ctrlKey || e.metaKey) && e.key === "f") {
195
+ onSearchToggle?.();
196
+ e.preventDefault();
197
+ return;
198
+ }
199
+ // Copy/paste shortcuts
200
+ if ((e.ctrlKey || e.metaKey) && e.key === "c") {
201
+ copySelection();
202
+ e.preventDefault();
203
+ return;
204
+ }
205
+ if ((e.ctrlKey || e.metaKey) && e.key === "v") {
206
+ pasteFromClipboard();
207
+ e.preventDefault();
208
+ return;
209
+ }
210
+ if ((e.ctrlKey || e.metaKey) && e.key === "a") {
211
+ if (rows > 0 && columns.length > 0) {
212
+ const newSel = {
213
+ type: "range",
214
+ start: [0, 0],
215
+ end: [columns.length - 1, rows - 1]
216
+ };
217
+ currentSelection = newSel;
218
+ canvasComponent?.setCurrentSelection(newSel);
219
+ onSelectionChange?.(newSel, getSelectionRowData(newSel));
220
+ canvasComponent?.setAnnouncement("All cells selected");
221
+ }
222
+ e.preventDefault();
223
+ return;
224
+ }
225
+ focusedCell = canvasComponent?.getFocusedCell() ?? focusedCell;
226
+ if (!focusedCell) return;
227
+ const [col, row] = focusedCell;
228
+ let newCol = col;
229
+ let newRow = row;
230
+ switch (e.key) {
231
+ case "ArrowUp":
232
+ newRow = Math.max(0, row - 1);
233
+ e.preventDefault();
234
+ break;
235
+ case "ArrowDown":
236
+ newRow = Math.min(rows - 1, row + 1);
237
+ e.preventDefault();
238
+ break;
239
+ case "ArrowLeft":
240
+ newCol = Math.max(0, col - 1);
241
+ e.preventDefault();
242
+ break;
243
+ case "ArrowRight":
244
+ newCol = Math.min(columns.length - 1, col + 1);
245
+ e.preventDefault();
246
+ break;
247
+ case "Enter":
248
+ if (!resolvedReadonly) {
249
+ beginEdit(focusedCell);
250
+ }
251
+ onCellEvent?.("enter_key", getRowData?.(row) ?? {}, columns[col] ?? {
252
+ id: "",
253
+ title: ""
254
+ });
255
+ e.preventDefault();
256
+ return;
257
+ case "Escape": {
258
+ cancelEdit();
259
+ const sel = canvasComponent?.getCurrentSelection() ?? currentSelection;
260
+ if (sel.type !== "none") {
261
+ onSelectionCleared?.();
262
+ }
263
+ return;
264
+ }
265
+ case "Tab":
266
+ if (e.shiftKey) {
267
+ newCol = col - 1;
268
+ if (newCol < 0) {
269
+ newCol = columns.length - 1;
270
+ newRow = Math.max(0, row - 1);
271
+ }
272
+ } else {
273
+ newCol = col + 1;
274
+ if (newCol >= columns.length) {
275
+ newCol = 0;
276
+ newRow = Math.min(rows - 1, row + 1);
277
+ onRowAppended?.();
278
+ }
279
+ }
280
+ onCellEvent?.("tab_key", getRowData?.(row) ?? {}, columns[col] ?? {
281
+ id: "",
282
+ title: ""
283
+ });
284
+ e.preventDefault();
285
+ break;
286
+ case "Delete":
287
+ case "Backspace": {
288
+ if (!resolvedReadonly) {
289
+ const delSel = canvasComponent?.getCurrentSelection() ?? currentSelection;
290
+ if (delSel.type !== "none") {
291
+ onDelete?.(delSel);
292
+ }
293
+ }
294
+ onCellEvent?.("delete_key", getRowData?.(row) ?? {}, columns[col] ?? {
295
+ id: "",
296
+ title: ""
297
+ });
298
+ return;
299
+ }
300
+ default:
301
+ if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !resolvedReadonly) {
302
+ beginEdit(focusedCell);
303
+ }
304
+ return;
305
+ }
306
+ if (newCol !== col || newRow !== row) {
307
+ const newFocusedCell = [newCol, newRow];
308
+ focusedCell = newFocusedCell;
309
+ canvasComponent?.setFocusedCell(newFocusedCell);
310
+ const newSel = {
311
+ type: "cell",
312
+ item: newFocusedCell
313
+ };
314
+ currentSelection = newSel;
315
+ canvasComponent?.setCurrentSelection(newSel);
316
+ onSelectionChange?.(newSel, getSelectionRowData(newSel));
317
+ canvasComponent?.setAnnouncement(`Row ${newRow + 1}, column ${columns[newCol]?.title ?? newCol + 1}`);
318
+ canvasComponent?.scrollCellIntoView(newFocusedCell);
319
+ }
320
+ }
321
+ // ── Visible range / grid events ───────────────────────────────────────────────
322
+ function handleVisibleRangeChange(range) {
323
+ onVisibleRangeChange?.(range);
324
+ onGridEvent?.("scroll", undefined, undefined, undefined, {
325
+ row: range.firstRow,
326
+ column: range.firstCol
327
+ });
328
+ }
329
+ async function handleMenuOpen(d) {
330
+ if (onMenuClick) {
331
+ await onMenuClick(d.item, d.column, {
332
+ x: d.x,
333
+ y: d.y
334
+ });
335
+ return;
336
+ }
337
+ onGridMenuOpen?.(d);
338
+ }
443
339
  </script>
444
340
 
445
341
  <GridlerCanvas