@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,549 +1,387 @@
1
- <script lang="ts">
2
- /* eslint-disable @typescript-eslint/no-explicit-any */
3
- import { Grid, Willow, WillowDark } from '@svar-ui/svelte-grid';
4
- import type { IApi } from '@svar-ui/svelte-grid';
5
- import type { Snippet } from 'svelte';
6
- import BetterMenu from '../../BetterMenu/BetterMenu.svelte';
7
- import type { BetterMenuInstance } from '../../BetterMenu/types';
8
- import SvarkContextMenu from '../components/SvarkContextMenu.svelte';
9
- import SvarkInfiniteLoader from '../components/SvarkInfiniteLoader.svelte';
10
- import SvarkPager from '../components/SvarkPager.svelte';
11
- import SvarkSearchPopover from '../components/SvarkSearchPopover.svelte';
12
- import SvarkTopBar from '../components/SvarkTopBar.svelte';
13
- import type { GridColumn, GridCommonProps } from '../../Types/generic_grid';
14
- import type {
15
- IColumnConfig,
16
- SvarkContextMenuContext,
17
- SvarkContextMenuItem,
18
- SvarkContextMenuProps
19
- } from '../types.js';
20
-
21
- export interface Props {
22
- // sizing
23
- heightStyle: string;
24
- widthStyle: string;
25
- menuProviderID: string;
26
-
27
- // grid render
28
- hideHeader: boolean;
29
- columns: IColumnConfig[];
30
- data: Array<Record<string, unknown>>;
31
- loading: boolean;
32
- error: string | null;
33
- select: boolean;
34
- multiselect: boolean;
35
- gridDynamic?: any;
36
- gridConfig: Record<string, unknown>;
37
- onInit: (api: IApi) => void;
38
-
39
- // top bar
40
- topBarLeft?: Snippet;
41
- topBarRight?: Snippet;
42
- searchOpen: boolean;
43
- searchValue: string;
44
- onToggleSearch: () => void;
45
- onSearchChange: (value: string) => void;
46
-
47
- // infinite scroll
48
- infiniteScroll: boolean;
49
- infiniteLoading: boolean;
50
- total: number;
51
- onLoadNextPage: () => void;
52
-
53
- // pager
54
- showPager: boolean;
55
- page: number;
56
- totalPages: number;
57
- pageStart: number;
58
- pageEnd: number;
59
- onPageFirst: () => void;
60
- onPagePrev: () => void;
61
- onPageNext: () => void;
62
- onPageLast: () => void;
63
-
64
- // row + context menu
65
- gridApi: IApi | null;
66
- uniqueID: string;
67
- genericColumnsById: Record<string, GridColumn<Record<string, unknown>, unknown>>;
68
- selectedIds: Array<string | number>;
69
- onSelectedIdsChange: (ids: Array<string | number>) => void;
70
- onRowClick?: (ctx: { id: string | number; row?: any; event: MouseEvent }) => void;
71
- onRowDblClick?: (ctx: { id: string | number; row?: any; event: MouseEvent }) => void;
72
- onRowContextMenu?: (ctx: { id: string | number; row?: any; event: MouseEvent }) => void;
73
- contextMenuProps?: SvarkContextMenuProps;
74
- onGridEvent?: GridCommonProps<Record<string, unknown>, unknown>['onGridEvent'];
75
- onCellEvent?: GridCommonProps<Record<string, unknown>, unknown>['onCellEvent'];
76
- onMenuClick?: GridCommonProps<Record<string, unknown>, unknown>['onMenuClick'];
77
- }
78
-
79
- const {
80
- heightStyle,
81
- widthStyle,
82
- menuProviderID,
83
- hideHeader,
84
- columns,
85
- data,
86
- loading,
87
- error,
88
- select,
89
- multiselect,
90
- gridDynamic,
91
- gridConfig,
92
- onInit,
93
- // top bar
94
- topBarLeft,
95
- topBarRight,
96
- searchOpen,
97
- searchValue,
98
- onToggleSearch,
99
- onSearchChange,
100
- // infinite scroll
101
- infiniteScroll,
102
- infiniteLoading,
103
- total,
104
- onLoadNextPage,
105
- // pager
106
- showPager,
107
- page,
108
- totalPages,
109
- pageStart,
110
- pageEnd,
111
- onPageFirst,
112
- onPagePrev,
113
- onPageNext,
114
- onPageLast,
115
- // row + context menu
116
- gridApi,
117
- uniqueID,
118
- genericColumnsById,
119
- selectedIds,
120
- onSelectedIdsChange,
121
- onRowClick,
122
- onRowDblClick,
123
- onRowContextMenu,
124
- contextMenuProps,
125
- onGridEvent,
126
- onCellEvent,
127
- onMenuClick
128
- }: Props = $props();
129
-
130
- // Dark mode detection — priority: inline color-scheme style > .dark class > OS media query
131
- let isDark = $state(false);
132
-
133
- $effect(() => {
134
- const mq = window.matchMedia('(prefers-color-scheme: dark)');
135
- const html = document.documentElement;
136
-
137
- function update() {
138
- const explicit = html.style.colorScheme;
139
- if (explicit === 'dark' || explicit === 'light') {
140
- isDark = explicit === 'dark';
141
- } else {
142
- isDark = html.classList.contains('dark') || mq.matches;
143
- }
1
+ <script lang="ts">/* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { Grid, Willow, WillowDark } from "@svar-ui/svelte-grid";
3
+ import BetterMenu from "../../BetterMenu/BetterMenu.svelte";
4
+ import SvarkContextMenu from "../components/SvarkContextMenu.svelte";
5
+ import SvarkInfiniteLoader from "../components/SvarkInfiniteLoader.svelte";
6
+ import SvarkPager from "../components/SvarkPager.svelte";
7
+ import SvarkSearchPopover from "../components/SvarkSearchPopover.svelte";
8
+ import SvarkTopBar from "../components/SvarkTopBar.svelte";
9
+ const { heightStyle, widthStyle, menuProviderID, hideHeader, columns, data, loading, error, select, multiselect, gridDynamic, gridConfig, onInit, topBarLeft, topBarRight, searchOpen, searchValue, onToggleSearch, onSearchChange, infiniteScroll, infiniteLoading, total, onLoadNextPage, showPager, page, totalPages, pageStart, pageEnd, onPageFirst, onPagePrev, onPageNext, onPageLast, gridApi, uniqueID, genericColumnsById, selectedIds, onSelectedIdsChange, onRowClick, onRowDblClick, onRowContextMenu, contextMenuProps, onGridEvent, onCellEvent, onMenuClick } = $props();
10
+ // Dark mode detection — priority: inline color-scheme style > .dark class > OS media query
11
+ let isDark = $state(false);
12
+ $effect(() => {
13
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
14
+ const html = document.documentElement;
15
+ function update() {
16
+ const explicit = html.style.colorScheme;
17
+ if (explicit === "dark" || explicit === "light") {
18
+ isDark = explicit === "dark";
19
+ } else {
20
+ isDark = html.classList.contains("dark") || mq.matches;
144
21
  }
145
-
146
- update();
147
- mq.addEventListener('change', update);
148
-
149
- const observer = new MutationObserver(update);
150
- observer.observe(html, { attributeFilter: ['class', 'style'] });
151
-
152
- return () => {
153
- mq.removeEventListener('change', update);
154
- observer.disconnect();
155
- };
156
- });
157
-
158
- let containerRef = $state<HTMLElement | undefined>();
159
- let scrollContainerRef = $state<HTMLElement | null>(null);
160
- let gridWrapperRef = $state<HTMLElement | null>(null);
161
-
162
- type BetterMenuRef = {
163
- show: (id: string, options?: Partial<Omit<BetterMenuInstance, 'id'>>) => void;
164
- set: (id: string, options?: Partial<Omit<BetterMenuInstance, 'id'>>) => void;
165
- hide: (id: string) => void;
166
- };
167
- let betterMenuRef = $state<BetterMenuRef | undefined>();
168
-
169
- const searchMenuId = $derived(`${menuProviderID}:search`);
170
- let searchMenuWasOpen = $state(false);
171
-
172
- function openSearchMenu() {
173
- if (!betterMenuRef) return;
174
- const rect =
175
- gridWrapperRef?.getBoundingClientRect() ?? containerRef?.getBoundingClientRect();
176
- if (!rect) return;
177
- betterMenuRef.set(searchMenuId, {
178
- x: rect.left + 10,
179
- y: rect.top + 10,
180
- renderer: SearchMenu as any,
181
- onHide: () => {
182
- if (searchOpen) onToggleSearch();
183
- }
184
- });
185
22
  }
186
-
187
- $effect(() => {
188
- if (!betterMenuRef) return;
189
-
190
- if (searchOpen && !searchMenuWasOpen) {
191
- searchMenuWasOpen = true;
192
- queueMicrotask(() => openSearchMenu());
193
- return;
194
- }
195
-
196
- if (!searchOpen && searchMenuWasOpen) {
197
- searchMenuWasOpen = false;
198
- betterMenuRef.hide(searchMenuId);
23
+ update();
24
+ mq.addEventListener("change", update);
25
+ const observer = new MutationObserver(update);
26
+ observer.observe(html, { attributeFilter: ["class", "style"] });
27
+ return () => {
28
+ mq.removeEventListener("change", update);
29
+ observer.disconnect();
30
+ };
31
+ });
32
+ let containerRef = $state();
33
+ let scrollContainerRef = $state(null);
34
+ let gridWrapperRef = $state(null);
35
+ let betterMenuRef = $state();
36
+ const searchMenuId = $derived(`${menuProviderID}:search`);
37
+ let searchMenuWasOpen = $state(false);
38
+ function openSearchMenu() {
39
+ if (!betterMenuRef) return;
40
+ const rect = gridWrapperRef?.getBoundingClientRect() ?? containerRef?.getBoundingClientRect();
41
+ if (!rect) return;
42
+ betterMenuRef.set(searchMenuId, {
43
+ x: rect.left + 10,
44
+ y: rect.top + 10,
45
+ renderer: SearchMenu,
46
+ onHide: () => {
47
+ if (searchOpen) onToggleSearch();
199
48
  }
200
49
  });
201
-
202
- // While the search popover is open, keep it in sync with the grid search value.
203
- // (Context-menu "Clear Search" updates the grid state but would not rerender the popover otherwise.)
204
- $effect(() => {
205
- if (!betterMenuRef) return;
206
- if (!searchMenuWasOpen) return;
207
- void searchValue;
208
- betterMenuRef.set(searchMenuId, { renderer: SearchMenu as any });
209
- });
210
-
211
- // ── Row resolution (SVAR renders rows with data-id) ───────────────────────
212
-
213
- function resolveRowFromTarget(target: EventTarget | null) {
214
- if (!containerRef || !(target instanceof Element)) return undefined;
215
- const rowEl = target.closest('[data-id]') as HTMLElement | null;
216
- if (!rowEl || !containerRef.contains(rowEl)) return undefined;
217
-
218
- const attrId = rowEl.getAttribute('data-id');
219
- if (!attrId) return undefined;
220
-
221
- const row = data.find((item) => String(item?.[uniqueID]) === attrId);
222
- const idValue = row?.[uniqueID];
223
- const resolvedId =
224
- typeof idValue === 'string' || typeof idValue === 'number' ? idValue : attrId;
225
-
226
- return { id: resolvedId, row };
50
+ }
51
+ $effect(() => {
52
+ if (!betterMenuRef) return;
53
+ if (searchOpen && !searchMenuWasOpen) {
54
+ searchMenuWasOpen = true;
55
+ queueMicrotask(() => openSearchMenu());
56
+ return;
227
57
  }
228
-
229
- function resolveColumnIdFromTarget(target: EventTarget | null): string | undefined {
230
- if (!containerRef || !(target instanceof Element)) return undefined;
231
- const cellEl = target.closest('[data-col-id], [data-header-id]') as HTMLElement | null;
232
- if (!cellEl || !containerRef.contains(cellEl)) return undefined;
233
-
234
- const colId = cellEl.getAttribute('data-col-id') ?? cellEl.getAttribute('data-header-id');
235
- return colId ? String(colId) : undefined;
58
+ if (!searchOpen && searchMenuWasOpen) {
59
+ searchMenuWasOpen = false;
60
+ betterMenuRef.hide(searchMenuId);
236
61
  }
237
-
238
- function resolveGenericColumn(target: EventTarget | null) {
239
- const columnId = resolveColumnIdFromTarget(target);
240
- return columnId ? genericColumnsById[columnId] : undefined;
62
+ });
63
+ // While the search popover is open, keep it in sync with the grid search value.
64
+ // (Context-menu "Clear Search" updates the grid state but would not rerender the popover otherwise.)
65
+ $effect(() => {
66
+ if (!betterMenuRef) return;
67
+ if (!searchMenuWasOpen) return;
68
+ void searchValue;
69
+ betterMenuRef.set(searchMenuId, { renderer: SearchMenu });
70
+ });
71
+ // ── Row resolution (SVAR renders rows with data-id) ───────────────────────
72
+ function resolveRowFromTarget(target) {
73
+ if (!containerRef || !(target instanceof Element)) return undefined;
74
+ const rowEl = target.closest("[data-id]");
75
+ if (!rowEl || !containerRef.contains(rowEl)) return undefined;
76
+ const attrId = rowEl.getAttribute("data-id");
77
+ if (!attrId) return undefined;
78
+ const row = data.find((item) => String(item?.[uniqueID]) === attrId);
79
+ const idValue = row?.[uniqueID];
80
+ const resolvedId = typeof idValue === "string" || typeof idValue === "number" ? idValue : attrId;
81
+ return {
82
+ id: resolvedId,
83
+ row
84
+ };
85
+ }
86
+ function resolveColumnIdFromTarget(target) {
87
+ if (!containerRef || !(target instanceof Element)) return undefined;
88
+ const cellEl = target.closest("[data-col-id], [data-header-id]");
89
+ if (!cellEl || !containerRef.contains(cellEl)) return undefined;
90
+ const colId = cellEl.getAttribute("data-col-id") ?? cellEl.getAttribute("data-header-id");
91
+ return colId ? String(colId) : undefined;
92
+ }
93
+ function resolveGenericColumn(target) {
94
+ const columnId = resolveColumnIdFromTarget(target);
95
+ return columnId ? genericColumnsById[columnId] : undefined;
96
+ }
97
+ // ── Context menu ──────────────────────────────────────────────────────────
98
+ let contextMenuOpen = $state(false);
99
+ let contextMenuX = $state(0);
100
+ let contextMenuY = $state(0);
101
+ let contextMenuEvent = $state(null);
102
+ let contextMenuRowId = $state(null);
103
+ let contextMenuRow = $state(null);
104
+ function buildContextMenuCtx() {
105
+ if (!contextMenuEvent) return null;
106
+ return {
107
+ api: gridApi ?? undefined,
108
+ row: contextMenuRow,
109
+ rowId: contextMenuRowId,
110
+ selectedIds,
111
+ data,
112
+ event: contextMenuEvent
113
+ };
114
+ }
115
+ const contextMenuItems = $derived.by(() => {
116
+ if (!contextMenuOpen) return [];
117
+ if (!contextMenuProps) return [];
118
+ const ctx = buildContextMenuCtx();
119
+ if (!ctx) return [];
120
+ const resolved = typeof contextMenuProps.items === "function" ? contextMenuProps.items(ctx) : contextMenuProps.items ?? [];
121
+ return (resolved ?? []).map((it) => ({
122
+ ...it,
123
+ kind: it.kind ?? "item"
124
+ }));
125
+ });
126
+ function closeContextMenu() {
127
+ contextMenuOpen = false;
128
+ contextMenuEvent = null;
129
+ contextMenuRowId = null;
130
+ contextMenuRow = null;
131
+ }
132
+ function handleContextMenuItem(item) {
133
+ if (item.id === "__close__") {
134
+ closeContextMenu();
135
+ return;
241
136
  }
242
-
243
- // ── Context menu ──────────────────────────────────────────────────────────
244
-
245
- let contextMenuOpen = $state(false);
246
- let contextMenuX = $state(0);
247
- let contextMenuY = $state(0);
248
- let contextMenuEvent = $state<MouseEvent | null>(null);
249
- let contextMenuRowId = $state<string | number | null>(null);
250
- let contextMenuRow = $state<any | null>(null);
251
-
252
- function buildContextMenuCtx(): SvarkContextMenuContext | null {
253
- if (!contextMenuEvent) return null;
254
- return {
255
- api: gridApi ?? undefined,
256
- row: contextMenuRow,
257
- rowId: contextMenuRowId,
258
- selectedIds,
259
- data: data as any[],
260
- event: contextMenuEvent
137
+ const ctx = buildContextMenuCtx();
138
+ closeContextMenu();
139
+ if (!ctx) return;
140
+ const menuItem = item;
141
+ menuItem.onselect?.(ctx);
142
+ contextMenuProps?.onselectitem?.(menuItem, ctx);
143
+ }
144
+ // ── Events: click/dblclick/contextmenu + keydown shortcuts ────────────────
145
+ $effect(() => {
146
+ if (!containerRef) return;
147
+ const node = containerRef;
148
+ function handleClick(ev) {
149
+ const resolved = resolveRowFromTarget(ev.target);
150
+ const column = resolveGenericColumn(ev.target);
151
+ const coords = {
152
+ x: ev.clientX,
153
+ y: ev.clientY,
154
+ code: String(ev.button ?? 0)
261
155
  };
156
+ onGridEvent?.("click", resolved?.row, column, coords, {
157
+ rowId: resolved?.id,
158
+ event: ev
159
+ });
160
+ if (resolved?.row && column) {
161
+ onCellEvent?.("click", resolved.row, column, coords, {
162
+ rowId: resolved.id,
163
+ event: ev
164
+ });
165
+ }
166
+ if (!resolved) return;
167
+ onRowClick?.({
168
+ ...resolved,
169
+ event: ev
170
+ });
262
171
  }
263
-
264
- const contextMenuItems = $derived.by(() => {
265
- if (!contextMenuOpen) return [] as SvarkContextMenuItem[];
266
- if (!contextMenuProps) return [] as SvarkContextMenuItem[];
267
- const ctx = buildContextMenuCtx();
268
- if (!ctx) return [] as SvarkContextMenuItem[];
269
- const resolved =
270
- typeof contextMenuProps.items === 'function'
271
- ? contextMenuProps.items(ctx)
272
- : contextMenuProps.items ?? [];
273
- return (resolved ?? []).map((it) => ({ ...it, kind: (it as any).kind ?? 'item' }));
274
- });
275
-
276
- function closeContextMenu() {
277
- contextMenuOpen = false;
278
- contextMenuEvent = null;
279
- contextMenuRowId = null;
280
- contextMenuRow = null;
172
+ function handleDoubleClick(ev) {
173
+ const resolved = resolveRowFromTarget(ev.target);
174
+ const column = resolveGenericColumn(ev.target);
175
+ const coords = {
176
+ x: ev.clientX,
177
+ y: ev.clientY,
178
+ code: String(ev.button ?? 0)
179
+ };
180
+ onGridEvent?.("dblclick", resolved?.row, column, coords, {
181
+ rowId: resolved?.id,
182
+ event: ev
183
+ });
184
+ if (resolved?.row && column) {
185
+ onCellEvent?.("dblclick", resolved.row, column, coords, {
186
+ rowId: resolved.id,
187
+ event: ev
188
+ });
189
+ }
190
+ if (!resolved) return;
191
+ onRowDblClick?.({
192
+ ...resolved,
193
+ event: ev
194
+ });
281
195
  }
282
-
283
- function handleContextMenuItem(item: SvarkContextMenuItem | { id: '__close__' }) {
284
- if (item.id === '__close__') {
285
- closeContextMenu();
196
+ function handleContextMenu(ev) {
197
+ // Only handle right-clicks on the grid surface itself, not on the toolbar/pager.
198
+ if (!containerRef || !(ev.target instanceof Element) || !ev.target.closest(".wx-grid") || !containerRef.contains(ev.target.closest(".wx-grid"))) {
286
199
  return;
287
200
  }
288
- const ctx = buildContextMenuCtx();
289
- closeContextMenu();
290
- if (!ctx) return;
291
- const menuItem = item as SvarkContextMenuItem;
292
- menuItem.onselect?.(ctx);
293
- contextMenuProps?.onselectitem?.(menuItem, ctx);
294
- }
295
-
296
- // ── Events: click/dblclick/contextmenu + keydown shortcuts ────────────────
297
-
298
- $effect(() => {
299
- if (!containerRef) return;
300
- const node = containerRef;
301
-
302
- function handleClick(ev: MouseEvent) {
303
- const resolved = resolveRowFromTarget(ev.target);
304
- const column = resolveGenericColumn(ev.target);
305
- const coords = { x: ev.clientX, y: ev.clientY, code: String(ev.button ?? 0) };
306
- onGridEvent?.('click', resolved?.row, column, coords, {
307
- rowId: resolved?.id,
201
+ const resolved = resolveRowFromTarget(ev.target);
202
+ const column = resolveGenericColumn(ev.target);
203
+ const coords = {
204
+ x: ev.clientX,
205
+ y: ev.clientY,
206
+ code: String(ev.button ?? 2)
207
+ };
208
+ onGridEvent?.("contextmenu", resolved?.row, column, coords, {
209
+ rowId: resolved?.id,
210
+ event: ev
211
+ });
212
+ if (resolved?.row && column) {
213
+ onCellEvent?.("contextmenu", resolved.row, column, coords, {
214
+ rowId: resolved.id,
308
215
  event: ev
309
216
  });
310
- if (resolved?.row && column) {
311
- onCellEvent?.('click', resolved.row, column, coords, {
217
+ }
218
+ void onMenuClick?.(resolved?.row, column, coords);
219
+ if (resolved) onRowContextMenu?.({
220
+ ...resolved,
221
+ event: ev
222
+ });
223
+ ev.preventDefault();
224
+ ev.stopPropagation();
225
+ // Right-clicking a row should behave like "select + open menu".
226
+ if (resolved?.id != null && gridApi && select !== false) {
227
+ gridApi.exec("select-row", {
228
+ id: resolved.id,
229
+ toggle: false,
230
+ range: false
231
+ });
232
+ onSelectedIdsChange([resolved.id]);
233
+ }
234
+ contextMenuEvent = ev;
235
+ contextMenuX = ev.clientX;
236
+ contextMenuY = ev.clientY;
237
+ const ctxRowId = resolved?.id ?? selectedIds[0] ?? null;
238
+ const ctxRow = ctxRowId != null ? data.find((item) => String(item?.[uniqueID]) === String(ctxRowId)) ?? null : null;
239
+ contextMenuRowId = ctxRowId;
240
+ contextMenuRow = ctxRow;
241
+ contextMenuOpen = true;
242
+ }
243
+ function onKeyDown(ev) {
244
+ const resolved = resolveRowFromTarget(ev.target);
245
+ const column = resolveGenericColumn(ev.target);
246
+ const coords = {
247
+ x: 0,
248
+ y: 0,
249
+ code: ev.code || ev.key
250
+ };
251
+ onGridEvent?.("keydown", resolved?.row, column, coords, {
252
+ rowId: resolved?.id,
253
+ event: ev
254
+ });
255
+ if (resolved?.row && column) {
256
+ if (ev.key === "Enter") {
257
+ onCellEvent?.("enter_key", resolved.row, column, coords, {
312
258
  rowId: resolved.id,
313
259
  event: ev
314
260
  });
315
261
  }
316
- if (!resolved) return;
317
- onRowClick?.({ ...resolved, event: ev });
318
- }
319
-
320
- function handleDoubleClick(ev: MouseEvent) {
321
- const resolved = resolveRowFromTarget(ev.target);
322
- const column = resolveGenericColumn(ev.target);
323
- const coords = { x: ev.clientX, y: ev.clientY, code: String(ev.button ?? 0) };
324
- onGridEvent?.('dblclick', resolved?.row, column, coords, {
325
- rowId: resolved?.id,
326
- event: ev
327
- });
328
- if (resolved?.row && column) {
329
- onCellEvent?.('dblclick', resolved.row, column, coords, {
262
+ if (ev.key === "Delete" || ev.key === "Backspace") {
263
+ onCellEvent?.("delete_key", resolved.row, column, coords, {
330
264
  rowId: resolved.id,
331
265
  event: ev
332
266
  });
333
267
  }
334
- if (!resolved) return;
335
- onRowDblClick?.({ ...resolved, event: ev });
336
- }
337
-
338
- function handleContextMenu(ev: MouseEvent) {
339
- // Only handle right-clicks on the grid surface itself, not on the toolbar/pager.
340
- if (
341
- !containerRef ||
342
- !(ev.target instanceof Element) ||
343
- !ev.target.closest('.wx-grid') ||
344
- !containerRef.contains(ev.target.closest('.wx-grid'))
345
- ) {
346
- return;
347
- }
348
-
349
- const resolved = resolveRowFromTarget(ev.target);
350
- const column = resolveGenericColumn(ev.target);
351
- const coords = { x: ev.clientX, y: ev.clientY, code: String(ev.button ?? 2) };
352
- onGridEvent?.('contextmenu', resolved?.row, column, coords, {
353
- rowId: resolved?.id,
354
- event: ev
355
- });
356
- if (resolved?.row && column) {
357
- onCellEvent?.('contextmenu', resolved.row, column, coords, {
268
+ if (ev.key === "Tab") {
269
+ onCellEvent?.("tab_key", resolved.row, column, coords, {
358
270
  rowId: resolved.id,
359
271
  event: ev
360
272
  });
361
273
  }
362
- void onMenuClick?.(resolved?.row, column, coords);
363
- if (resolved) onRowContextMenu?.({ ...resolved, event: ev });
364
-
274
+ }
275
+ const key = ev.key?.toLowerCase?.() ?? "";
276
+ const isFind = (ev.ctrlKey || ev.metaKey) && !ev.altKey && !ev.shiftKey && key === "f";
277
+ if (isFind) {
365
278
  ev.preventDefault();
366
279
  ev.stopPropagation();
367
-
368
- // Right-clicking a row should behave like "select + open menu".
369
- if (resolved?.id != null && gridApi && select !== false) {
370
- gridApi.exec('select-row', { id: resolved.id, toggle: false, range: false });
371
- onSelectedIdsChange([resolved.id]);
372
- }
373
-
374
- contextMenuEvent = ev;
375
- contextMenuX = ev.clientX;
376
- contextMenuY = ev.clientY;
377
-
378
- const ctxRowId = (resolved?.id ?? selectedIds[0] ?? null) as string | number | null;
379
- const ctxRow: Record<string, unknown> | null =
380
- ctxRowId != null
381
- ? data.find((item) => String(item?.[uniqueID]) === String(ctxRowId)) ?? null
382
- : null;
383
-
384
- contextMenuRowId = ctxRowId;
385
- contextMenuRow = ctxRow;
386
- contextMenuOpen = true;
387
- }
388
-
389
- function onKeyDown(ev: KeyboardEvent) {
390
- const resolved = resolveRowFromTarget(ev.target);
391
- const column = resolveGenericColumn(ev.target);
392
- const coords = { x: 0, y: 0, code: ev.code || ev.key };
393
- onGridEvent?.('keydown', resolved?.row, column, coords, {
394
- rowId: resolved?.id,
395
- event: ev
396
- });
397
-
398
- if (resolved?.row && column) {
399
- if (ev.key === 'Enter') {
400
- onCellEvent?.('enter_key', resolved.row, column, coords, { rowId: resolved.id, event: ev });
401
- }
402
- if (ev.key === 'Delete' || ev.key === 'Backspace') {
403
- onCellEvent?.('delete_key', resolved.row, column, coords, { rowId: resolved.id, event: ev });
404
- }
405
- if (ev.key === 'Tab') {
406
- onCellEvent?.('tab_key', resolved.row, column, coords, { rowId: resolved.id, event: ev });
407
- }
408
- }
409
-
410
- const key = ev.key?.toLowerCase?.() ?? '';
411
- const isFind = (ev.ctrlKey || ev.metaKey) && !ev.altKey && !ev.shiftKey && key === 'f';
412
- if (isFind) {
413
- ev.preventDefault();
414
- ev.stopPropagation();
415
- onToggleSearch();
416
- return;
417
- }
418
-
419
- if (searchOpen && ev.key === 'Escape') {
420
- ev.preventDefault();
421
- ev.stopPropagation();
422
- onToggleSearch();
423
- return;
424
- }
425
-
426
- if (contextMenuOpen && ev.key === 'Escape') {
427
- ev.preventDefault();
428
- ev.stopPropagation();
429
- closeContextMenu();
430
- }
280
+ onToggleSearch();
281
+ return;
431
282
  }
432
-
433
- node.addEventListener('click', handleClick);
434
- node.addEventListener('dblclick', handleDoubleClick);
435
- node.addEventListener('contextmenu', handleContextMenu);
436
- node.addEventListener('keydown', onKeyDown, true);
437
-
438
- return () => {
439
- node.removeEventListener('click', handleClick);
440
- node.removeEventListener('dblclick', handleDoubleClick);
441
- node.removeEventListener('contextmenu', handleContextMenu);
442
- node.removeEventListener('keydown', onKeyDown, true);
443
- };
444
- });
445
-
446
- // ── Infinite scroll watchers (calls onLoadNextPage) ───────────────────────
447
-
448
- function maybeLoadNextPage() {
449
- if (!infiniteScroll || !containerRef || loading || infiniteLoading || data.length >= total) return;
450
-
451
- const scrollEl = scrollContainerRef;
452
- if (scrollEl && scrollEl.scrollHeight > scrollEl.clientHeight + 1) {
453
- const { scrollTop, scrollHeight, clientHeight } = scrollEl;
454
- if (scrollHeight - scrollTop - clientHeight < 150) {
455
- onLoadNextPage();
456
- }
283
+ if (searchOpen && ev.key === "Escape") {
284
+ ev.preventDefault();
285
+ ev.stopPropagation();
286
+ onToggleSearch();
457
287
  return;
458
288
  }
459
-
460
- const { bottom } = containerRef.getBoundingClientRect();
461
- if (bottom - window.innerHeight < 150) {
289
+ if (contextMenuOpen && ev.key === "Escape") {
290
+ ev.preventDefault();
291
+ ev.stopPropagation();
292
+ closeContextMenu();
293
+ }
294
+ }
295
+ node.addEventListener("click", handleClick);
296
+ node.addEventListener("dblclick", handleDoubleClick);
297
+ node.addEventListener("contextmenu", handleContextMenu);
298
+ node.addEventListener("keydown", onKeyDown, true);
299
+ return () => {
300
+ node.removeEventListener("click", handleClick);
301
+ node.removeEventListener("dblclick", handleDoubleClick);
302
+ node.removeEventListener("contextmenu", handleContextMenu);
303
+ node.removeEventListener("keydown", onKeyDown, true);
304
+ };
305
+ });
306
+ // ── Infinite scroll watchers (calls onLoadNextPage) ───────────────────────
307
+ function maybeLoadNextPage() {
308
+ if (!infiniteScroll || !containerRef || loading || infiniteLoading || data.length >= total) return;
309
+ const scrollEl = scrollContainerRef;
310
+ if (scrollEl && scrollEl.scrollHeight > scrollEl.clientHeight + 1) {
311
+ const { scrollTop, scrollHeight, clientHeight } = scrollEl;
312
+ if (scrollHeight - scrollTop - clientHeight < 150) {
462
313
  onLoadNextPage();
463
314
  }
315
+ return;
464
316
  }
465
-
466
- $effect(() => {
467
- if (!infiniteScroll || loading || infiniteLoading) return;
468
- void data.length;
469
- void total;
470
-
471
- queueMicrotask(() => {
472
- maybeLoadNextPage();
473
- });
317
+ const { bottom } = containerRef.getBoundingClientRect();
318
+ if (bottom - window.innerHeight < 150) {
319
+ onLoadNextPage();
320
+ }
321
+ }
322
+ $effect(() => {
323
+ if (!infiniteScroll || loading || infiniteLoading) return;
324
+ void data.length;
325
+ void total;
326
+ queueMicrotask(() => {
327
+ maybeLoadNextPage();
474
328
  });
475
-
476
- $effect(() => {
477
- if (!infiniteScroll || !containerRef) return;
478
-
479
- let scrollEl: Element | null = null;
480
-
481
- function attach(el: Element) {
482
- scrollEl = el;
483
- scrollContainerRef = el as HTMLElement;
484
- el.addEventListener('scroll', onScroll);
485
- queueMicrotask(onViewportChange);
486
- }
487
-
488
- function onScroll() {
489
- onGridEvent?.(
490
- 'scroll',
491
- undefined,
492
- undefined,
493
- {
494
- x: scrollContainerRef?.scrollLeft ?? 0,
495
- y: scrollContainerRef?.scrollTop ?? 0
496
- },
497
- undefined
498
- );
499
- maybeLoadNextPage();
500
- }
501
-
502
- function onViewportChange() {
503
- onGridEvent?.(
504
- 'resize',
505
- undefined,
506
- undefined,
507
- {
508
- x: containerRef?.clientWidth ?? 0,
509
- y: containerRef?.clientHeight ?? 0
510
- },
511
- undefined
512
- );
513
- maybeLoadNextPage();
514
- }
515
-
516
- window.addEventListener('scroll', onViewportChange, true);
517
- window.addEventListener('resize', onViewportChange);
518
-
519
- const candidate = containerRef.querySelector('.wx-scroll');
520
- if (candidate) {
521
- attach(candidate);
522
- } else {
523
- const obs = new MutationObserver(() => {
524
- const el = containerRef?.querySelector('.wx-scroll');
525
- if (el) {
526
- attach(el);
527
- obs.disconnect();
528
- }
529
- });
530
- obs.observe(containerRef, { childList: true, subtree: true });
531
- return () => {
329
+ });
330
+ $effect(() => {
331
+ if (!infiniteScroll || !containerRef) return;
332
+ let scrollEl = null;
333
+ function attach(el) {
334
+ scrollEl = el;
335
+ scrollContainerRef = el;
336
+ el.addEventListener("scroll", onScroll);
337
+ queueMicrotask(onViewportChange);
338
+ }
339
+ function onScroll() {
340
+ onGridEvent?.("scroll", undefined, undefined, {
341
+ x: scrollContainerRef?.scrollLeft ?? 0,
342
+ y: scrollContainerRef?.scrollTop ?? 0
343
+ }, undefined);
344
+ maybeLoadNextPage();
345
+ }
346
+ function onViewportChange() {
347
+ onGridEvent?.("resize", undefined, undefined, {
348
+ x: containerRef?.clientWidth ?? 0,
349
+ y: containerRef?.clientHeight ?? 0
350
+ }, undefined);
351
+ maybeLoadNextPage();
352
+ }
353
+ window.addEventListener("scroll", onViewportChange, true);
354
+ window.addEventListener("resize", onViewportChange);
355
+ const candidate = containerRef.querySelector(".wx-scroll");
356
+ if (candidate) {
357
+ attach(candidate);
358
+ } else {
359
+ const obs = new MutationObserver(() => {
360
+ const el = containerRef?.querySelector(".wx-scroll");
361
+ if (el) {
362
+ attach(el);
532
363
  obs.disconnect();
533
- scrollEl?.removeEventListener('scroll', onScroll);
534
- window.removeEventListener('scroll', onViewportChange, true);
535
- window.removeEventListener('resize', onViewportChange);
536
- scrollContainerRef = null;
537
- };
538
- }
539
-
364
+ }
365
+ });
366
+ obs.observe(containerRef, {
367
+ childList: true,
368
+ subtree: true
369
+ });
540
370
  return () => {
541
- scrollEl?.removeEventListener('scroll', onScroll);
542
- window.removeEventListener('scroll', onViewportChange, true);
543
- window.removeEventListener('resize', onViewportChange);
371
+ obs.disconnect();
372
+ scrollEl?.removeEventListener("scroll", onScroll);
373
+ window.removeEventListener("scroll", onViewportChange, true);
374
+ window.removeEventListener("resize", onViewportChange);
544
375
  scrollContainerRef = null;
545
376
  };
546
- });
377
+ }
378
+ return () => {
379
+ scrollEl?.removeEventListener("scroll", onScroll);
380
+ window.removeEventListener("scroll", onViewportChange, true);
381
+ window.removeEventListener("resize", onViewportChange);
382
+ scrollContainerRef = null;
383
+ };
384
+ });
547
385
  </script>
548
386
 
549
387
  {#snippet SearchMenu({ onClose }: { onClose: () => void })}