@warkypublic/svelix 0.1.0

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 (212) hide show
  1. package/LICENSE +73 -0
  2. package/README.md +3 -0
  3. package/dist/actions/index.d.ts +1 -0
  4. package/dist/actions/index.js +2 -0
  5. package/dist/components/BetterMenu/BetterMenu.stories.js +68 -0
  6. package/dist/components/BetterMenu/BetterMenu.svelte +38 -0
  7. package/dist/components/BetterMenu/BetterMenu.svelte.d.ts +14 -0
  8. package/dist/components/BetterMenu/BetterMenuAsyncButton.svelte +34 -0
  9. package/dist/components/BetterMenu/BetterMenuAsyncButton.svelte.d.ts +8 -0
  10. package/dist/components/BetterMenu/BetterMenuPreview.svelte +43 -0
  11. package/dist/components/BetterMenu/BetterMenuPreview.svelte.d.ts +7 -0
  12. package/dist/components/BetterMenu/MenuRenderer.svelte +75 -0
  13. package/dist/components/BetterMenu/MenuRenderer.svelte.d.ts +6 -0
  14. package/dist/components/BetterMenu/Plan.mdx +155 -0
  15. package/dist/components/BetterMenu/index.d.ts +4 -0
  16. package/dist/components/BetterMenu/index.js +4 -0
  17. package/dist/components/BetterMenu/store.d.ts +10 -0
  18. package/dist/components/BetterMenu/store.js +48 -0
  19. package/dist/components/BetterMenu/types.d.ts +24 -0
  20. package/dist/components/BetterMenu/types.js +1 -0
  21. package/dist/components/Boxer/Boxer.stories.d.ts +19 -0
  22. package/dist/components/Boxer/Boxer.stories.js +102 -0
  23. package/dist/components/Boxer/Boxer.svelte +411 -0
  24. package/dist/components/Boxer/Boxer.svelte.d.ts +11 -0
  25. package/dist/components/Boxer/BoxerTarget.svelte +88 -0
  26. package/dist/components/Boxer/BoxerTarget.svelte.d.ts +20 -0
  27. package/dist/components/Boxer/Plan.mdx +140 -0
  28. package/dist/components/Boxer/features.mdx +81 -0
  29. package/dist/components/Boxer/index.d.ts +4 -0
  30. package/dist/components/Boxer/index.js +4 -0
  31. package/dist/components/Boxer/store.d.ts +26 -0
  32. package/dist/components/Boxer/store.js +103 -0
  33. package/dist/components/Boxer/types.d.ts +46 -0
  34. package/dist/components/Boxer/types.js +1 -0
  35. package/dist/components/Button.stories.d.ts +11 -0
  36. package/dist/components/Button.stories.js +109 -0
  37. package/dist/components/Button.svelte +50 -0
  38. package/dist/components/Button.svelte.d.ts +12 -0
  39. package/dist/components/ButtonPreview.svelte +14 -0
  40. package/dist/components/ButtonPreview.svelte.d.ts +4 -0
  41. package/dist/components/ErrorBoundary/ErrorBoundary.stories.js +17 -0
  42. package/dist/components/ErrorBoundary/ErrorBoundary.svelte +127 -0
  43. package/dist/components/ErrorBoundary/ErrorBoundary.svelte.d.ts +13 -0
  44. package/dist/components/ErrorBoundary/ErrorBoundaryPreview.svelte +28 -0
  45. package/dist/components/ErrorBoundary/ErrorBoundaryPreview.svelte.d.ts +6 -0
  46. package/dist/components/ErrorBoundary/ErrorManager.d.ts +15 -0
  47. package/dist/components/ErrorBoundary/ErrorManager.js +158 -0
  48. package/dist/components/ErrorBoundary/Plan.mdx +182 -0
  49. package/dist/components/ErrorBoundary/index.d.ts +3 -0
  50. package/dist/components/ErrorBoundary/index.js +3 -0
  51. package/dist/components/ErrorBoundary/types.d.ts +43 -0
  52. package/dist/components/ErrorBoundary/types.js +1 -0
  53. package/dist/components/Former/Former.stories.js +228 -0
  54. package/dist/components/Former/Former.svelte +405 -0
  55. package/dist/components/Former/Former.svelte.d.ts +33 -0
  56. package/dist/components/Former/FormerButtonArea.svelte +93 -0
  57. package/dist/components/Former/FormerButtonArea.svelte.d.ts +15 -0
  58. package/dist/components/Former/FormerDrawer.svelte +115 -0
  59. package/dist/components/Former/FormerDrawer.svelte.d.ts +19 -0
  60. package/dist/components/Former/FormerDrawerPreview.svelte +226 -0
  61. package/dist/components/Former/FormerDrawerPreview.svelte.d.ts +7 -0
  62. package/dist/components/Former/FormerModal.svelte +108 -0
  63. package/dist/components/Former/FormerModal.svelte.d.ts +14 -0
  64. package/dist/components/Former/FormerModalPreview.svelte +226 -0
  65. package/dist/components/Former/FormerModalPreview.svelte.d.ts +7 -0
  66. package/dist/components/Former/FormerPreview.svelte +238 -0
  67. package/dist/components/Former/FormerPreview.svelte.d.ts +8 -0
  68. package/dist/components/Former/FormerResolveSpecAPI.d.ts +26 -0
  69. package/dist/components/Former/FormerResolveSpecAPI.js +44 -0
  70. package/dist/components/Former/FormerRestApiPreview.svelte +198 -0
  71. package/dist/components/Former/FormerRestApiPreview.svelte.d.ts +3 -0
  72. package/dist/components/Former/FormerRestHeadSpecAPI.d.ts +8 -0
  73. package/dist/components/Former/FormerRestHeadSpecAPI.js +38 -0
  74. package/dist/components/Former/Plan.mdx +115 -0
  75. package/dist/components/Former/formerState.svelte.d.ts +21 -0
  76. package/dist/components/Former/formerState.svelte.js +57 -0
  77. package/dist/components/Former/index.d.ts +8 -0
  78. package/dist/components/Former/index.js +8 -0
  79. package/dist/components/Former/types.d.ts +61 -0
  80. package/dist/components/Former/types.js +1 -0
  81. package/dist/components/FormerControllers/ButtonCtrl.stories.js +102 -0
  82. package/dist/components/FormerControllers/ButtonCtrl.svelte +65 -0
  83. package/dist/components/FormerControllers/ButtonCtrl.svelte.d.ts +14 -0
  84. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrl.stories.js +73 -0
  85. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrl.svelte +630 -0
  86. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrl.svelte.d.ts +54 -0
  87. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrl.utils.d.ts +40 -0
  88. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrl.utils.js +688 -0
  89. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlCalendar.svelte +193 -0
  90. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlCalendar.svelte.d.ts +13 -0
  91. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlPickerPanel.svelte +119 -0
  92. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlPickerPanel.svelte.d.ts +39 -0
  93. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlTimeFields.svelte +343 -0
  94. package/dist/components/FormerControllers/DateTimeCtrl/DateTimeCtrlTimeFields.svelte.d.ts +27 -0
  95. package/dist/components/FormerControllers/DateTimeCtrl/index.d.ts +2 -0
  96. package/dist/components/FormerControllers/DateTimeCtrl/index.js +1 -0
  97. package/dist/components/FormerControllers/FormerControllers.stories.js +76 -0
  98. package/dist/components/FormerControllers/IconButtonCtrl.stories.js +77 -0
  99. package/dist/components/FormerControllers/IconButtonCtrl.svelte +64 -0
  100. package/dist/components/FormerControllers/IconButtonCtrl.svelte.d.ts +13 -0
  101. package/dist/components/FormerControllers/InlineWrapper.stories.js +133 -0
  102. package/dist/components/FormerControllers/InlineWrapper.svelte +85 -0
  103. package/dist/components/FormerControllers/InlineWrapper.svelte.d.ts +16 -0
  104. package/dist/components/FormerControllers/InlineWrapperPreview.svelte +41 -0
  105. package/dist/components/FormerControllers/InlineWrapperPreview.svelte.d.ts +13 -0
  106. package/dist/components/FormerControllers/NativeSelectCtrl.stories.js +79 -0
  107. package/dist/components/FormerControllers/NativeSelectCtrl.svelte +61 -0
  108. package/dist/components/FormerControllers/NativeSelectCtrl.svelte.d.ts +13 -0
  109. package/dist/components/FormerControllers/NumberInputCtrl.stories.js +77 -0
  110. package/dist/components/FormerControllers/NumberInputCtrl.svelte +61 -0
  111. package/dist/components/FormerControllers/NumberInputCtrl.svelte.d.ts +11 -0
  112. package/dist/components/FormerControllers/PasswordInputCtrl.stories.js +79 -0
  113. package/dist/components/FormerControllers/PasswordInputCtrl.svelte +57 -0
  114. package/dist/components/FormerControllers/PasswordInputCtrl.svelte.d.ts +8 -0
  115. package/dist/components/FormerControllers/Plan.mdx +129 -0
  116. package/dist/components/FormerControllers/SwitchCtrl.stories.js +73 -0
  117. package/dist/components/FormerControllers/SwitchCtrl.svelte +38 -0
  118. package/dist/components/FormerControllers/SwitchCtrl.svelte.d.ts +8 -0
  119. package/dist/components/FormerControllers/TextAreaCtrl.stories.js +71 -0
  120. package/dist/components/FormerControllers/TextAreaCtrl.svelte +47 -0
  121. package/dist/components/FormerControllers/TextAreaCtrl.svelte.d.ts +9 -0
  122. package/dist/components/FormerControllers/TextInputCtrl.svelte +47 -0
  123. package/dist/components/FormerControllers/TextInputCtrl.svelte.d.ts +9 -0
  124. package/dist/components/FormerControllers/index.d.ts +12 -0
  125. package/dist/components/FormerControllers/index.js +11 -0
  126. package/dist/components/FormerControllers/types.d.ts +10 -0
  127. package/dist/components/FormerControllers/types.js +1 -0
  128. package/dist/components/GlobalStateStore/GlobalStateStore.d.ts +19 -0
  129. package/dist/components/GlobalStateStore/GlobalStateStore.js +349 -0
  130. package/dist/components/GlobalStateStore/GlobalStateStore.types.d.ts +127 -0
  131. package/dist/components/GlobalStateStore/GlobalStateStore.types.js +2 -0
  132. package/dist/components/GlobalStateStore/GlobalStateStore.utils.d.ts +4 -0
  133. package/dist/components/GlobalStateStore/GlobalStateStore.utils.js +92 -0
  134. package/dist/components/GlobalStateStore/GlobalStateStoreContext.d.ts +10 -0
  135. package/dist/components/GlobalStateStore/GlobalStateStoreContext.js +10 -0
  136. package/dist/components/GlobalStateStore/GlobalStateStoreProvider.svelte +113 -0
  137. package/dist/components/GlobalStateStore/GlobalStateStoreProvider.svelte.d.ts +16 -0
  138. package/dist/components/GlobalStateStore/index.d.ts +5 -0
  139. package/dist/components/GlobalStateStore/index.js +3 -0
  140. package/dist/components/Gridler/CellEditor.svelte +126 -0
  141. package/dist/components/Gridler/CellEditor.svelte.d.ts +15 -0
  142. package/dist/components/Gridler/Gridler.stories.d.ts +56 -0
  143. package/dist/components/Gridler/Gridler.stories.js +262 -0
  144. package/dist/components/Gridler/Gridler.svelte +778 -0
  145. package/dist/components/Gridler/Gridler.svelte.d.ts +11 -0
  146. package/dist/components/Gridler/GridlerHeader.svelte +179 -0
  147. package/dist/components/Gridler/GridlerHeader.svelte.d.ts +13 -0
  148. package/dist/components/Gridler/Plan.mdx +692 -0
  149. package/dist/components/Gridler/index.d.ts +6 -0
  150. package/dist/components/Gridler/index.js +6 -0
  151. package/dist/components/Gridler/types.d.ts +84 -0
  152. package/dist/components/Gridler/types.js +16 -0
  153. package/dist/components/Gridler/utils/rendering.d.ts +16 -0
  154. package/dist/components/Gridler/utils/rendering.js +202 -0
  155. package/dist/components/Gridler/utils/scrolling.d.ts +12 -0
  156. package/dist/components/Gridler/utils/scrolling.js +97 -0
  157. package/dist/components/Portal/Portal.mdx +125 -0
  158. package/dist/components/Portal/Portal.svelte +47 -0
  159. package/dist/components/Portal/Portal.svelte.d.ts +18 -0
  160. package/dist/components/Screenshot/Screenshot.stories.d.ts +16 -0
  161. package/dist/components/Screenshot/Screenshot.stories.js +15 -0
  162. package/dist/components/Screenshot/Screenshot.svelte +54 -0
  163. package/dist/components/Screenshot/Screenshot.svelte.d.ts +3 -0
  164. package/dist/components/Screenshot/Screenshot.util.d.ts +1 -0
  165. package/dist/components/Screenshot/Screenshot.util.js +49 -0
  166. package/dist/components/Screenshot/index.d.ts +2 -0
  167. package/dist/components/Screenshot/index.js +2 -0
  168. package/dist/components/Svark/Svark.stories.js +659 -0
  169. package/dist/components/Svark/Svark.svelte +691 -0
  170. package/dist/components/Svark/Svark.svelte.d.ts +26 -0
  171. package/dist/components/Svark/SvarkResolveSpecAdapter.d.ts +16 -0
  172. package/dist/components/Svark/SvarkResolveSpecAdapter.js +68 -0
  173. package/dist/components/Svark/SvarkSelectionDemo.svelte +59 -0
  174. package/dist/components/Svark/SvarkSelectionDemo.svelte.d.ts +4 -0
  175. package/dist/components/Svark/index.d.ts +3 -0
  176. package/dist/components/Svark/index.js +3 -0
  177. package/dist/components/Svark/types.d.ts +63 -0
  178. package/dist/components/Svark/types.js +1 -0
  179. package/dist/components/VTree/VTree.models.d.ts +12 -0
  180. package/dist/components/VTree/VTree.models.js +1 -0
  181. package/dist/components/VTree/VTree.stories.d.ts +40 -0
  182. package/dist/components/VTree/VTree.stories.js +112 -0
  183. package/dist/components/VTree/VTree.svelte +471 -0
  184. package/dist/components/VTree/VTree.svelte.d.ts +5 -0
  185. package/dist/components/VTree/VTreeContextMenu.svelte +40 -0
  186. package/dist/components/VTree/VTreeContextMenu.svelte.d.ts +11 -0
  187. package/dist/components/VTree/VTreeEventsDemo.svelte +88 -0
  188. package/dist/components/VTree/VTreeEventsDemo.svelte.d.ts +3 -0
  189. package/dist/components/VTree/VTreeResolveSpecAdapter.d.ts +14 -0
  190. package/dist/components/VTree/VTreeResolveSpecAdapter.js +103 -0
  191. package/dist/components/VTree/VTreeRow.svelte +136 -0
  192. package/dist/components/VTree/VTreeRow.svelte.d.ts +37 -0
  193. package/dist/components/VTree/VTreeSearch.svelte +25 -0
  194. package/dist/components/VTree/VTreeSearch.svelte.d.ts +8 -0
  195. package/dist/components/VTree/VTreeVirtualViewport.svelte +154 -0
  196. package/dist/components/VTree/VTreeVirtualViewport.svelte.d.ts +45 -0
  197. package/dist/components/VTree/index.d.ts +3 -0
  198. package/dist/components/VTree/index.js +3 -0
  199. package/dist/components/VTree/types.d.ts +83 -0
  200. package/dist/components/VTree/types.js +1 -0
  201. package/dist/components/index.d.ts +11 -0
  202. package/dist/components/index.js +11 -0
  203. package/dist/index.d.ts +16 -0
  204. package/dist/index.js +20 -0
  205. package/dist/stores/index.d.ts +1 -0
  206. package/dist/stores/index.js +1 -0
  207. package/dist/themes/svelix_orange.css +205 -0
  208. package/dist/utils/PropsWithChildren.d.ts +5 -0
  209. package/dist/utils/PropsWithChildren.js +1 -0
  210. package/dist/utils/index.d.ts +1 -0
  211. package/dist/utils/index.js +2 -0
  212. package/package.json +85 -0
@@ -0,0 +1,471 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import VTreeSearch from './VTreeSearch.svelte';
4
+ import VTreeVirtualViewport from './VTreeVirtualViewport.svelte';
5
+ import VTreeContextMenu from './VTreeContextMenu.svelte';
6
+ import type { VTreeFlatRow } from './VTree.models.js';
7
+ import type { VTreeContextMenuItem, VTreeProps, VTreeRecord } from './types.js';
8
+
9
+ export type Props<T extends VTreeRecord = VTreeRecord> = VTreeProps<T>;
10
+
11
+ const {
12
+ data = [],
13
+ adapter,
14
+ idField = 'id',
15
+ labelField = 'label',
16
+ descriptionField = 'description',
17
+ childrenField = 'children',
18
+ hasChildrenField = 'hasChildren',
19
+ height = 420,
20
+ rowHeight = 36,
21
+ indent = 18,
22
+ overscan = 8,
23
+ searchable = true,
24
+ searchPlaceholder = 'Search tree...',
25
+ searchValue = '',
26
+ serverSideSearch = false,
27
+ autoExpandSearchResults = true,
28
+ loadOnMount = true,
29
+ emptyMessage = 'Nothing here...',
30
+ loadingMessage = 'Loading...',
31
+ leftSection,
32
+ rightSection,
33
+ contextMenuItems,
34
+ onselect,
35
+ onclick,
36
+ ondoubleclick,
37
+ oncontext,
38
+ ontoggle,
39
+ onsearch,
40
+ onloaderror
41
+ }: Props = $props();
42
+
43
+ let viewportRef = $state<{ scrollToIndex: (index: number) => void } | undefined>(undefined);
44
+ let focusedIndex = $state(0);
45
+ let selectedId = $state<string | null>(null);
46
+ let searchQuery = $state('');
47
+ let loadingRoot = $state(false);
48
+
49
+ let rootNodes = $state<VTreeRecord[]>([]);
50
+ let searchNodes = $state<VTreeRecord[]>([]);
51
+ let childrenByParent = $state<Record<string, VTreeRecord[]>>({});
52
+ let expandedById = $state<Record<string, boolean>>({});
53
+ let loadingById = $state<Record<string, boolean>>({});
54
+ let loadedById = $state<Record<string, boolean>>({});
55
+ let errorsById = $state<Record<string, string>>({});
56
+
57
+ let lastSearchHandle: ReturnType<typeof setTimeout> | undefined;
58
+ let searchRunId = 0;
59
+ let contextOpen = $state(false);
60
+ let contextX = $state(0);
61
+ let contextY = $state(0);
62
+ let contextRow = $state<VTreeRecord | null>(null);
63
+ let contextItems = $state<VTreeContextMenuItem[]>([]);
64
+
65
+ function getNodeId(node: VTreeRecord): string {
66
+ const value = node?.[idField];
67
+ return String(value ?? '');
68
+ }
69
+
70
+ function getNodeLabel(node: VTreeRecord): string {
71
+ return String(node?.[labelField] ?? '');
72
+ }
73
+
74
+ function getNodeDescription(node: VTreeRecord): string {
75
+ const value = node?.[descriptionField];
76
+ return value === undefined || value === null ? '' : String(value);
77
+ }
78
+
79
+ function getNodeChildren(node: VTreeRecord): VTreeRecord[] {
80
+ const nodeId = getNodeId(node);
81
+ const remoteChildren = childrenByParent[nodeId];
82
+ if (remoteChildren) return remoteChildren;
83
+
84
+ const localChildren = node?.[childrenField];
85
+ return Array.isArray(localChildren) ? (localChildren as VTreeRecord[]) : [];
86
+ }
87
+
88
+ function nodeHasChildren(node: VTreeRecord): boolean {
89
+ const children = getNodeChildren(node);
90
+ if (children.length > 0) return true;
91
+ return typeof node?.[hasChildrenField] === 'boolean'
92
+ ? Boolean(node[hasChildrenField])
93
+ : false;
94
+ }
95
+
96
+ function normalizeRows(rows: VTreeRecord[]): VTreeRecord[] {
97
+ return Array.isArray(rows) ? rows : [];
98
+ }
99
+
100
+ async function loadRoot() {
101
+ if (!adapter) {
102
+ rootNodes = normalizeRows(data ?? []);
103
+ return;
104
+ }
105
+
106
+ loadingRoot = true;
107
+ try {
108
+ const rows = await adapter.readRoot();
109
+ rootNodes = normalizeRows(rows);
110
+ searchNodes = [];
111
+ errorsById = {};
112
+ } catch (error) {
113
+ onloaderror?.(error);
114
+ } finally {
115
+ loadingRoot = false;
116
+ }
117
+ }
118
+
119
+ async function loadChildren(node: VTreeRecord) {
120
+ if (!adapter) return;
121
+
122
+ const nodeId = getNodeId(node);
123
+ if (!nodeId || loadingById[nodeId] || loadedById[nodeId]) return;
124
+
125
+ loadingById = { ...loadingById, [nodeId]: true };
126
+ errorsById = { ...errorsById, [nodeId]: '' };
127
+
128
+ try {
129
+ const rows = await adapter.readChildren(node);
130
+ childrenByParent = { ...childrenByParent, [nodeId]: normalizeRows(rows) };
131
+ loadedById = { ...loadedById, [nodeId]: true };
132
+ } catch (error) {
133
+ const message = error instanceof Error ? error.message : 'Unable to load node';
134
+ errorsById = { ...errorsById, [nodeId]: message };
135
+ onloaderror?.(error, node);
136
+ } finally {
137
+ loadingById = { ...loadingById, [nodeId]: false };
138
+ }
139
+ }
140
+
141
+ function matchesLocalSearch(node: VTreeRecord, query: string): boolean {
142
+ const q = query.toLowerCase();
143
+ return (
144
+ getNodeLabel(node).toLowerCase().includes(q) ||
145
+ getNodeDescription(node).toLowerCase().includes(q)
146
+ );
147
+ }
148
+
149
+ function toggleNode(node: VTreeRecord) {
150
+ const nodeId = getNodeId(node);
151
+ if (!nodeId) return;
152
+
153
+ const currentlyExpanded = Boolean(expandedById[nodeId]);
154
+ expandedById = { ...expandedById, [nodeId]: !currentlyExpanded };
155
+ ontoggle?.(node, !currentlyExpanded);
156
+
157
+ if (!currentlyExpanded && adapter && nodeHasChildren(node)) {
158
+ void loadChildren(node);
159
+ }
160
+ }
161
+
162
+ const displayRoots = $derived.by(() => {
163
+ if (adapter && serverSideSearch && searchQuery.trim().length > 0) return searchNodes;
164
+ return rootNodes;
165
+ });
166
+
167
+ const flattenedRows = $derived.by(() => {
168
+ const rows: VTreeFlatRow[] = [];
169
+ const activeSearch = searchQuery.trim().toLowerCase();
170
+ const useLocalSearch = activeSearch.length > 0 && !(adapter && serverSideSearch);
171
+
172
+ const walk = (
173
+ nodes: VTreeRecord[],
174
+ level: number,
175
+ parentId: string | null
176
+ ): { rows: VTreeFlatRow[]; matches: boolean } => {
177
+ const walkRows: VTreeFlatRow[] = [];
178
+ let hasAnyMatch = false;
179
+
180
+ for (const node of nodes) {
181
+ const id = getNodeId(node);
182
+ const children = getNodeChildren(node);
183
+ const childResult = walk(children, level + 1, id);
184
+ const selfMatch = useLocalSearch ? matchesLocalSearch(node, activeSearch) : false;
185
+ const childHasMatch = childResult.matches;
186
+ const includeNode = !useLocalSearch || selfMatch || childHasMatch;
187
+
188
+ if (!includeNode) continue;
189
+
190
+ hasAnyMatch = true;
191
+ const expanded = Boolean(expandedById[id]);
192
+ const forceExpanded = useLocalSearch && autoExpandSearchResults && childHasMatch;
193
+ walkRows.push({
194
+ id,
195
+ node,
196
+ level,
197
+ parentId,
198
+ expanded,
199
+ hasChildren: nodeHasChildren(node),
200
+ loading: Boolean(loadingById[id]),
201
+ error: errorsById[id] || undefined,
202
+ matched: selfMatch
203
+ });
204
+
205
+ if ((expanded || forceExpanded) && childResult.rows.length > 0) {
206
+ walkRows.push(...childResult.rows);
207
+ }
208
+ }
209
+
210
+ return { rows: walkRows, matches: hasAnyMatch };
211
+ };
212
+
213
+ for (const root of displayRoots) {
214
+ rows.push(...walk([root], 1, null).rows);
215
+ }
216
+ return rows;
217
+ });
218
+
219
+ const treeDataSnapshot = $derived.by(() => {
220
+ const buildTree = (nodes: VTreeRecord[]): VTreeRecord[] =>
221
+ nodes.map((node) => {
222
+ const children = getNodeChildren(node);
223
+ if (children.length === 0) {
224
+ return node;
225
+ }
226
+ return {
227
+ ...node,
228
+ [childrenField]: buildTree(children)
229
+ };
230
+ });
231
+
232
+ return buildTree(displayRoots);
233
+ });
234
+
235
+ function selectRow(index: number) {
236
+ const row = flattenedRows[index];
237
+ if (!row) return;
238
+
239
+ focusedIndex = index;
240
+ selectedId = row.id;
241
+ onselect?.(row.node);
242
+ viewportRef?.scrollToIndex(index);
243
+ }
244
+
245
+ function moveFocus(delta: number) {
246
+ if (flattenedRows.length === 0) return;
247
+ const next = Math.min(flattenedRows.length - 1, Math.max(0, focusedIndex + delta));
248
+ selectRow(next);
249
+ }
250
+
251
+ function handleTreeKeydown(event: KeyboardEvent) {
252
+ if (flattenedRows.length === 0) return;
253
+
254
+ const current = flattenedRows[focusedIndex];
255
+ if (!current) {
256
+ selectRow(0);
257
+ return;
258
+ }
259
+
260
+ if (event.key === 'ArrowDown') {
261
+ event.preventDefault();
262
+ moveFocus(1);
263
+ return;
264
+ }
265
+ if (event.key === 'ArrowUp') {
266
+ event.preventDefault();
267
+ moveFocus(-1);
268
+ return;
269
+ }
270
+ if (event.key === 'PageDown') {
271
+ event.preventDefault();
272
+ moveFocus(10);
273
+ return;
274
+ }
275
+ if (event.key === 'PageUp') {
276
+ event.preventDefault();
277
+ moveFocus(-10);
278
+ return;
279
+ }
280
+ if (event.key === 'Home') {
281
+ event.preventDefault();
282
+ selectRow(0);
283
+ return;
284
+ }
285
+ if (event.key === 'End') {
286
+ event.preventDefault();
287
+ selectRow(flattenedRows.length - 1);
288
+ return;
289
+ }
290
+ if (event.key === 'ArrowRight') {
291
+ event.preventDefault();
292
+ if (current.hasChildren && !current.expanded) {
293
+ toggleNode(current.node);
294
+ } else {
295
+ moveFocus(1);
296
+ }
297
+ return;
298
+ }
299
+ if (event.key === 'ArrowLeft') {
300
+ event.preventDefault();
301
+ if (current.expanded) {
302
+ toggleNode(current.node);
303
+ } else if (current.parentId) {
304
+ const parentIndex = flattenedRows.findIndex((row) => row.id === current.parentId);
305
+ if (parentIndex >= 0) selectRow(parentIndex);
306
+ }
307
+ return;
308
+ }
309
+ if (event.key === ' ' || event.key === 'Enter') {
310
+ event.preventDefault();
311
+ selectRow(focusedIndex);
312
+ if (current.hasChildren) {
313
+ toggleNode(current.node);
314
+ }
315
+ }
316
+ }
317
+
318
+ async function runServerSearch(query: string) {
319
+ if (!adapter || !serverSideSearch) return;
320
+ const runId = ++searchRunId;
321
+
322
+ if (!query.trim()) {
323
+ searchNodes = [];
324
+ return;
325
+ }
326
+
327
+ try {
328
+ const rows = await adapter.search(query.trim());
329
+ if (runId !== searchRunId) return;
330
+ searchNodes = normalizeRows(rows);
331
+ focusedIndex = 0;
332
+ } catch (error) {
333
+ if (runId !== searchRunId) return;
334
+ onloaderror?.(error);
335
+ }
336
+ }
337
+
338
+ function handleSearchInput(value: string) {
339
+ searchQuery = value;
340
+ onsearch?.(value);
341
+ }
342
+
343
+ function handleRowClick(index: number, event: MouseEvent) {
344
+ const row = flattenedRows[index];
345
+ if (!row) return;
346
+ selectRow(index);
347
+ onclick?.(row.node, treeDataSnapshot, event);
348
+ }
349
+
350
+ function handleRowDoubleClick(index: number, event: MouseEvent) {
351
+ const row = flattenedRows[index];
352
+ if (!row) return;
353
+ selectRow(index);
354
+ ondoubleclick?.(row.node, treeDataSnapshot, event);
355
+ }
356
+
357
+ function handleRowContext(index: number, event: MouseEvent) {
358
+ const row = flattenedRows[index];
359
+ if (!row) return;
360
+ event.preventDefault();
361
+ selectRow(index);
362
+ oncontext?.(row.node, treeDataSnapshot, event);
363
+
364
+ if (!contextMenuItems) return;
365
+ const items =
366
+ typeof contextMenuItems === 'function'
367
+ ? contextMenuItems(row.node, treeDataSnapshot)
368
+ : contextMenuItems;
369
+ contextItems = items ?? [];
370
+ contextRow = row.node;
371
+ contextX = event.clientX;
372
+ contextY = event.clientY;
373
+ contextOpen = contextItems.length > 0;
374
+ }
375
+
376
+ function handleViewportContext(event: MouseEvent) {
377
+ event.preventDefault();
378
+ oncontext?.(null, treeDataSnapshot, event);
379
+
380
+ if (!contextMenuItems) return;
381
+ const items =
382
+ typeof contextMenuItems === 'function'
383
+ ? contextMenuItems(null, treeDataSnapshot)
384
+ : contextMenuItems;
385
+ contextItems = items ?? [];
386
+ contextRow = null;
387
+ contextX = event.clientX;
388
+ contextY = event.clientY;
389
+ contextOpen = contextItems.length > 0;
390
+ }
391
+
392
+ function handleContextMenuItem(item: VTreeContextMenuItem) {
393
+ if (item.id !== '__close__' && contextRow) {
394
+ item.onselect?.(contextRow, treeDataSnapshot);
395
+ }
396
+ contextOpen = false;
397
+ }
398
+
399
+ $effect(() => {
400
+ if (!adapter) rootNodes = normalizeRows(data ?? []);
401
+ });
402
+
403
+ $effect(() => {
404
+ if (!adapter || !serverSideSearch) return;
405
+ clearTimeout(lastSearchHandle);
406
+ lastSearchHandle = setTimeout(() => {
407
+ void runServerSearch(searchQuery);
408
+ }, 250);
409
+ return () => clearTimeout(lastSearchHandle);
410
+ });
411
+
412
+ $effect(() => {
413
+ if (focusedIndex > flattenedRows.length - 1) {
414
+ focusedIndex = Math.max(flattenedRows.length - 1, 0);
415
+ }
416
+ if (!selectedId && flattenedRows.length > 0) {
417
+ selectedId = flattenedRows[0].id;
418
+ }
419
+ });
420
+
421
+ onMount(() => {
422
+ searchQuery = searchValue ?? '';
423
+
424
+ if (adapter && loadOnMount) {
425
+ void loadRoot();
426
+ } else {
427
+ rootNodes = normalizeRows(data ?? []);
428
+ }
429
+
430
+ return () => clearTimeout(lastSearchHandle);
431
+ });
432
+ </script>
433
+
434
+ <div class="card space-y-2 border border-surface-300-700 bg-surface-100-900 p-2">
435
+ {#if searchable}
436
+ <VTreeSearch value={searchQuery} placeholder={searchPlaceholder} oninputvalue={handleSearchInput} />
437
+ {/if}
438
+
439
+ <VTreeVirtualViewport
440
+ bind:this={viewportRef}
441
+ rows={flattenedRows}
442
+ {rowHeight}
443
+ {height}
444
+ {indent}
445
+ {overscan}
446
+ {focusedIndex}
447
+ {selectedId}
448
+ {loadingRoot}
449
+ {loadingMessage}
450
+ {emptyMessage}
451
+ {getNodeLabel}
452
+ {getNodeDescription}
453
+ {treeDataSnapshot}
454
+ {leftSection}
455
+ {rightSection}
456
+ onselectrow={handleRowClick}
457
+ onrowdoubleclick={handleRowDoubleClick}
458
+ onrowcontext={handleRowContext}
459
+ onviewportcontext={handleViewportContext}
460
+ ontogglerow={toggleNode}
461
+ ontreekeydown={handleTreeKeydown}
462
+ />
463
+
464
+ <VTreeContextMenu
465
+ open={contextOpen}
466
+ x={contextX}
467
+ y={contextY}
468
+ items={contextItems}
469
+ onselectitem={handleContextMenuItem}
470
+ />
471
+ </div>
@@ -0,0 +1,5 @@
1
+ import type { VTreeProps, VTreeRecord } from './types.js';
2
+ export type Props<T extends VTreeRecord = VTreeRecord> = VTreeProps<T>;
3
+ declare const VTree: import("svelte").Component<Props<VTreeRecord>, {}, "">;
4
+ type VTree = ReturnType<typeof VTree>;
5
+ export default VTree;
@@ -0,0 +1,40 @@
1
+ <script lang="ts">
2
+ import type { VTreeContextMenuItem, VTreeRecord } from './types.js';
3
+
4
+ export interface Props<T extends VTreeRecord = VTreeRecord> {
5
+ open: boolean;
6
+ x: number;
7
+ y: number;
8
+ items: VTreeContextMenuItem<T>[];
9
+ onselectitem?: (item: VTreeContextMenuItem<T>) => void;
10
+ }
11
+
12
+ const { open, x, y, items, onselectitem }: Props = $props();
13
+ </script>
14
+
15
+ {#if open}
16
+ <button
17
+ class="fixed inset-0 z-50"
18
+ type="button"
19
+ aria-label="Close context menu"
20
+ onclick={() => onselectitem?.({ id: '__close__', label: '' })}
21
+ ></button>
22
+ <div
23
+ class="fixed z-[60] min-w-44 overflow-hidden rounded border border-surface-300-700 bg-surface-100-900 shadow-xl"
24
+ style={`left:${x}px; top:${y}px;`}
25
+ role="menu"
26
+ aria-label="Row context menu"
27
+ >
28
+ {#each items as item (item.id)}
29
+ <button
30
+ class="block w-full px-3 py-2 text-left text-sm hover:bg-surface-200-800 disabled:cursor-not-allowed disabled:opacity-50"
31
+ disabled={item.disabled}
32
+ type="button"
33
+ role="menuitem"
34
+ onclick={() => onselectitem?.(item)}
35
+ >
36
+ {item.label}
37
+ </button>
38
+ {/each}
39
+ </div>
40
+ {/if}
@@ -0,0 +1,11 @@
1
+ import type { VTreeContextMenuItem, VTreeRecord } from './types.js';
2
+ export interface Props<T extends VTreeRecord = VTreeRecord> {
3
+ open: boolean;
4
+ x: number;
5
+ y: number;
6
+ items: VTreeContextMenuItem<T>[];
7
+ onselectitem?: (item: VTreeContextMenuItem<T>) => void;
8
+ }
9
+ declare const VTreeContextMenu: import("svelte").Component<Props<VTreeRecord>, {}, "">;
10
+ type VTreeContextMenu = ReturnType<typeof VTreeContextMenu>;
11
+ export default VTreeContextMenu;
@@ -0,0 +1,88 @@
1
+ <script lang="ts">
2
+ // @ts-nocheck
3
+ import VTree from './VTree.svelte';
4
+ import type { VTreeContextMenuItem, VTreeRecord } from './types.js';
5
+
6
+ interface DemoNode extends VTreeRecord {
7
+ id: string;
8
+ label: string;
9
+ description?: string;
10
+ children?: DemoNode[];
11
+ hasChildren?: boolean;
12
+ }
13
+
14
+ const data: DemoNode[] = [
15
+ {
16
+ id: 'alpha',
17
+ label: 'Alpha Team',
18
+ description: 'Top-level group',
19
+ hasChildren: true,
20
+ children: [
21
+ { id: 'alpha-1', label: 'Alpha User 1', description: 'Owner' },
22
+ { id: 'alpha-2', label: 'Alpha User 2', description: 'Contributor' }
23
+ ]
24
+ },
25
+ {
26
+ id: 'beta',
27
+ label: 'Beta Team',
28
+ description: 'Top-level group',
29
+ hasChildren: true,
30
+ children: [
31
+ { id: 'beta-1', label: 'Beta User 1', description: 'Owner' },
32
+ { id: 'beta-2', label: 'Beta User 2', description: 'Contributor' }
33
+ ]
34
+ }
35
+ ];
36
+
37
+ let eventLog = $state('No row event yet.');
38
+
39
+ function printEvent(kind: string, row: DemoNode | null, treeData: DemoNode[]) {
40
+ eventLog = `${kind}: ${row?.label ?? 'null'} | tree roots: ${treeData.length}`;
41
+ }
42
+
43
+ const menuItems: VTreeContextMenuItem<DemoNode>[] = [
44
+ {
45
+ id: 'open',
46
+ label: 'Open',
47
+ onselect: (row) => {
48
+ eventLog = `menu: open -> ${row?.label ?? 'null'}`;
49
+ }
50
+ },
51
+ {
52
+ id: 'inspect',
53
+ label: 'Inspect Node',
54
+ onselect: (row, treeData) => {
55
+ eventLog = `menu: inspect -> ${row.id} (roots: ${treeData.length})`;
56
+ }
57
+ }
58
+ ];
59
+ </script>
60
+
61
+ {#snippet leftContent(args)}
62
+ <span class="inline-flex h-4 w-4 items-center justify-center rounded bg-surface-200-700 text-[10px]">
63
+ {args.level}
64
+ </span>
65
+ {/snippet}
66
+
67
+ {#snippet rightContent(args)}
68
+ <span class="text-[10px] text-surface-500-400-token">#{args.rowIndex + 1}</span>
69
+ {/snippet}
70
+
71
+ <div class="space-y-2">
72
+ <VTree
73
+ data={data}
74
+ height={300}
75
+ rowHeight={34}
76
+ searchable
77
+ leftSection={leftContent}
78
+ rightSection={rightContent}
79
+ contextMenuItems={menuItems}
80
+ onclick={(row, treeData) => printEvent('click', row, treeData)}
81
+ ondoubleclick={(row, treeData) => printEvent('double-click', row, treeData)}
82
+ oncontext={(row, treeData) => printEvent('context', row, treeData)}
83
+ />
84
+
85
+ <div class="card border border-surface-300-700 bg-surface-50-950 p-2 text-xs text-surface-700-200-token">
86
+ {eventLog}
87
+ </div>
88
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const VTreeEventsDemo: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type VTreeEventsDemo = ReturnType<typeof VTreeEventsDemo>;
3
+ export default VTreeEventsDemo;
@@ -0,0 +1,14 @@
1
+ import type { Options } from '@warkypublic/resolvespec-js';
2
+ import type { VTreeRecord, VTreeResolveSpecAdapterConfig, VTreeServerAdapter } from './types.js';
3
+ export declare class VTreeResolveSpecAdapter<T extends VTreeRecord = VTreeRecord> implements VTreeServerAdapter<T> {
4
+ private readonly client;
5
+ private readonly isHeader;
6
+ private readonly config;
7
+ constructor(config: VTreeResolveSpecAdapterConfig<T>);
8
+ readRoot(options?: Partial<Options>): Promise<T[]>;
9
+ readChildren(node: T, options?: Partial<Options>): Promise<T[]>;
10
+ search(query: string, options?: Partial<Options>): Promise<T[]>;
11
+ private readWithOptions;
12
+ private mapRows;
13
+ private resolveFilters;
14
+ }