@anymux/ui-kit 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 (244) hide show
  1. package/dist/ExplorerLayout-CSIJd7N4.js +105 -0
  2. package/dist/ExplorerLayout-CSIJd7N4.js.map +1 -0
  3. package/dist/FileBrowserContext-B6jixa2j.js +11 -0
  4. package/dist/FileBrowserContext-B6jixa2j.js.map +1 -0
  5. package/dist/calendar-DSlrbHoj.js +761 -0
  6. package/dist/calendar-DSlrbHoj.js.map +1 -0
  7. package/dist/calendar.d.ts +3 -0
  8. package/dist/calendar.js +3 -0
  9. package/dist/contacts-DQXTZzHc.js +539 -0
  10. package/dist/contacts-DQXTZzHc.js.map +1 -0
  11. package/dist/contacts.d.ts +3 -0
  12. package/dist/contacts.js +3 -0
  13. package/dist/file-browser-m5atC3kF.js +6755 -0
  14. package/dist/file-browser-m5atC3kF.js.map +1 -0
  15. package/dist/file-browser.d.ts +11 -0
  16. package/dist/file-browser.js +9 -0
  17. package/dist/git-B55e6LL-.js +561 -0
  18. package/dist/git-B55e6LL-.js.map +1 -0
  19. package/dist/git.d.ts +2 -0
  20. package/dist/git.js +3 -0
  21. package/dist/iconMap-V4B8P-Uh.js +206 -0
  22. package/dist/iconMap-V4B8P-Uh.js.map +1 -0
  23. package/dist/icons-CIsIOZXR.js +0 -0
  24. package/dist/icons.d.ts +2 -0
  25. package/dist/icons.js +4 -0
  26. package/dist/index-BNmNIWBL.d.ts +71 -0
  27. package/dist/index-BNmNIWBL.d.ts.map +1 -0
  28. package/dist/index-Bryv_GCG.d.ts +1481 -0
  29. package/dist/index-Bryv_GCG.d.ts.map +1 -0
  30. package/dist/index-CuQIjSXs.d.ts +134 -0
  31. package/dist/index-CuQIjSXs.d.ts.map +1 -0
  32. package/dist/index-DSu19mq0.d.ts +153 -0
  33. package/dist/index-DSu19mq0.d.ts.map +1 -0
  34. package/dist/index-DmsyeHFr.d.ts +149 -0
  35. package/dist/index-DmsyeHFr.d.ts.map +1 -0
  36. package/dist/index-DxnJ8FYM.d.ts +17 -0
  37. package/dist/index-DxnJ8FYM.d.ts.map +1 -0
  38. package/dist/index-DzfY1Tok.d.ts +32 -0
  39. package/dist/index-DzfY1Tok.d.ts.map +1 -0
  40. package/dist/index-Ml_SgiKa.d.ts +1847 -0
  41. package/dist/index-Ml_SgiKa.d.ts.map +1 -0
  42. package/dist/index-kHr9udZD.d.ts +1025 -0
  43. package/dist/index-kHr9udZD.d.ts.map +1 -0
  44. package/dist/index.d.ts +11 -0
  45. package/dist/index.js +15 -0
  46. package/dist/layout-Ca_4r8ka.js +89 -0
  47. package/dist/layout-Ca_4r8ka.js.map +1 -0
  48. package/dist/layout.d.ts +2 -0
  49. package/dist/layout.js +5 -0
  50. package/dist/list-CxfT6hix.js +6831 -0
  51. package/dist/list-CxfT6hix.js.map +1 -0
  52. package/dist/list.d.ts +2 -0
  53. package/dist/list.js +5 -0
  54. package/dist/media-DZ292aKK.js +557 -0
  55. package/dist/media-DZ292aKK.js.map +1 -0
  56. package/dist/media.d.ts +3 -0
  57. package/dist/media.js +3 -0
  58. package/dist/tree-Dd9Z0Aso.js +3351 -0
  59. package/dist/tree-Dd9Z0Aso.js.map +1 -0
  60. package/dist/tree.d.ts +2 -0
  61. package/dist/tree.js +6 -0
  62. package/dist/types-common-CB3kRek8.d.ts +26 -0
  63. package/dist/types-common-CB3kRek8.d.ts.map +1 -0
  64. package/dist/utils-B4fdKKsy.js +3 -0
  65. package/package.json +109 -0
  66. package/src/calendar/AgendaView.tsx +37 -0
  67. package/src/calendar/CalendarBrowser.tsx +90 -0
  68. package/src/calendar/CalendarModel.ts +142 -0
  69. package/src/calendar/CalendarSidebar.tsx +81 -0
  70. package/src/calendar/DayView.tsx +76 -0
  71. package/src/calendar/EventCard.tsx +51 -0
  72. package/src/calendar/MockCalendarProvider.ts +98 -0
  73. package/src/calendar/MonthView.tsx +77 -0
  74. package/src/calendar/WeekView.tsx +129 -0
  75. package/src/calendar/index.ts +18 -0
  76. package/src/calendar/types.ts +25 -0
  77. package/src/contacts/ContactAvatar.tsx +35 -0
  78. package/src/contacts/ContactBrowser.tsx +56 -0
  79. package/src/contacts/ContactCard.tsx +37 -0
  80. package/src/contacts/ContactDetail.tsx +63 -0
  81. package/src/contacts/ContactGroupSidebar.tsx +40 -0
  82. package/src/contacts/ContactList.tsx +32 -0
  83. package/src/contacts/ContactListModel.ts +120 -0
  84. package/src/contacts/MockContactProvider.ts +77 -0
  85. package/src/contacts/index.ts +17 -0
  86. package/src/contacts/types.ts +26 -0
  87. package/src/demos/CalendarBrowserDemo.tsx +15 -0
  88. package/src/demos/ContactBrowserDemo.tsx +15 -0
  89. package/src/demos/MediaBrowserDemo.tsx +15 -0
  90. package/src/file-browser/adapters/DocumentViewerAdapter.ts +371 -0
  91. package/src/file-browser/adapters/FileSystemBridge.ts +168 -0
  92. package/src/file-browser/adapters/GitBrowserAdapter.ts +546 -0
  93. package/src/file-browser/adapters/README.md +504 -0
  94. package/src/file-browser/adapters/index.ts +27 -0
  95. package/src/file-browser/adapters/types.ts +70 -0
  96. package/src/file-browser/architecture.md +645 -0
  97. package/src/file-browser/components/CreateItemDialog.tsx +71 -0
  98. package/src/file-browser/components/DeleteConfirmDialog.tsx +58 -0
  99. package/src/file-browser/components/FileBrowser.tsx +473 -0
  100. package/src/file-browser/components/FileBrowserContent.tsx +209 -0
  101. package/src/file-browser/components/FileBrowserHeader.tsx +151 -0
  102. package/src/file-browser/components/FileBrowserToolbar.tsx +145 -0
  103. package/src/file-browser/components/LeftPanel/LeftPanel.tsx +103 -0
  104. package/src/file-browser/components/LeftPanel/LeftPanelTabs.tsx +70 -0
  105. package/src/file-browser/components/LeftPanel/TreeNavigationView.tsx +256 -0
  106. package/src/file-browser/components/PreviewPane.tsx +146 -0
  107. package/src/file-browser/components/RightPanel/FilePreview.tsx +219 -0
  108. package/src/file-browser/components/RightPanel/RightPanel.tsx +186 -0
  109. package/src/file-browser/components/RightPanel/RightPanelToolbar.tsx +113 -0
  110. package/src/file-browser/components/UploadProgress.tsx +123 -0
  111. package/src/file-browser/components/ViewerHost.tsx +208 -0
  112. package/src/file-browser/components/mobile/MobileNavigation.tsx +227 -0
  113. package/src/file-browser/components/navigation/NavigationButtons.tsx +171 -0
  114. package/src/file-browser/components/shared/ErrorBoundary.tsx +116 -0
  115. package/src/file-browser/components/shared/FileBrowserItem.tsx +195 -0
  116. package/src/file-browser/components/shared/FileIcon.tsx +169 -0
  117. package/src/file-browser/components/toolbar/ViewModeToggle.tsx +200 -0
  118. package/src/file-browser/components/views/ListView/ListView.tsx +484 -0
  119. package/src/file-browser/components/views/ThumbnailView/ThumbnailView.tsx +323 -0
  120. package/src/file-browser/components/views/TreeView/TreeNode.tsx +186 -0
  121. package/src/file-browser/components/views/TreeView/TreeNodeList.tsx +191 -0
  122. package/src/file-browser/components/views/TreeView/TreeView.tsx +200 -0
  123. package/src/file-browser/components/views/TreemapView/TreemapView.tsx +339 -0
  124. package/src/file-browser/context/FileBrowserContext.tsx +13 -0
  125. package/src/file-browser/examples/BasicUsage.tsx +20 -0
  126. package/src/file-browser/index.ts +98 -0
  127. package/src/file-browser/models/FileBrowserModel.ts +623 -0
  128. package/src/file-browser/models/LeftPanelManagerModel.ts +105 -0
  129. package/src/file-browser/models/NavigationManagerModel.ts +312 -0
  130. package/src/file-browser/models/ResponsiveLayoutManagerModel.ts +437 -0
  131. package/src/file-browser/models/RightPanelManagerModel.ts +190 -0
  132. package/src/file-browser/models/SelectionManagerModel.ts +252 -0
  133. package/src/file-browser/models/ToolbarManagerModel.ts +144 -0
  134. package/src/file-browser/models/UploadModel.ts +147 -0
  135. package/src/file-browser/models/ViewModeManagerModel.ts +185 -0
  136. package/src/file-browser/models/ViewerHostModel.ts +44 -0
  137. package/src/file-browser/models/ui/ListViewUIModel.ts +265 -0
  138. package/src/file-browser/models/ui/PreviewUIModel.ts +297 -0
  139. package/src/file-browser/models/ui/ThumbnailViewUIModel.ts +254 -0
  140. package/src/file-browser/models/ui/TreeViewUIModel.ts +128 -0
  141. package/src/file-browser/models/ui/TreemapViewUIModel.ts +350 -0
  142. package/src/file-browser/providers/FileSystemListProvider.ts +552 -0
  143. package/src/file-browser/providers/FileSystemProvider.ts +401 -0
  144. package/src/file-browser/providers/FileSystemTreeProvider.ts +231 -0
  145. package/src/file-browser/providers/GitProvider.ts +337 -0
  146. package/src/file-browser/providers/GitRepositoryProvider.ts +376 -0
  147. package/src/file-browser/providers/IFileBrowserProvider.ts +56 -0
  148. package/src/file-browser/providers/MemoryProvider.ts +303 -0
  149. package/src/file-browser/providers/index.ts +4 -0
  150. package/src/file-browser/registry/ViewerRegistry.ts +551 -0
  151. package/src/file-browser/registry/types.ts +144 -0
  152. package/src/file-browser/scripts/performanceBenchmark.ts +553 -0
  153. package/src/file-browser/services/ThumbnailCacheService.ts +128 -0
  154. package/src/file-browser/tasks.md +537 -0
  155. package/src/file-browser/types/FileBrowserTypes.ts +126 -0
  156. package/src/file-browser/types/ProviderTypes.ts +155 -0
  157. package/src/file-browser/types/UITypes.ts +235 -0
  158. package/src/file-browser/types/ViewModeTypes.ts +150 -0
  159. package/src/file-browser/utils/gestures.ts +327 -0
  160. package/src/file-browser/utils/performance.ts +563 -0
  161. package/src/file-browser/viewers/ImageViewer.tsx +163 -0
  162. package/src/file-browser/viewers/ImageViewerModel.ts +79 -0
  163. package/src/file-browser/viewers/TextViewer.tsx +95 -0
  164. package/src/file-browser/viewers/UnsupportedFileViewer.tsx +57 -0
  165. package/src/file-browser/viewers/index.ts +61 -0
  166. package/src/git/BranchList.tsx +128 -0
  167. package/src/git/CommitGraph.tsx +239 -0
  168. package/src/git/CommitList.tsx +258 -0
  169. package/src/git/DiffViewer.tsx +219 -0
  170. package/src/git/index.ts +4 -0
  171. package/src/icons/iconMap.ts +146 -0
  172. package/src/icons/index.ts +9 -0
  173. package/src/index.ts +13 -0
  174. package/src/layout/README.md +307 -0
  175. package/src/layout/components/ExplorerLayout/ExplorerLayout.tsx +178 -0
  176. package/src/layout/examples/SimpleExample.tsx +60 -0
  177. package/src/layout/index.ts +6 -0
  178. package/src/lib/utils.ts +1 -0
  179. package/src/list/README.md +303 -0
  180. package/src/list/architecture.md +807 -0
  181. package/src/list/components/CalculatedGridView.tsx +252 -0
  182. package/src/list/components/DragPreview.tsx +102 -0
  183. package/src/list/components/ListContextMenu.tsx +274 -0
  184. package/src/list/components/ListItem.tsx +761 -0
  185. package/src/list/components/ListItems.tsx +919 -0
  186. package/src/list/components/MasonryView.tsx +241 -0
  187. package/src/list/components/SearchFilter.tsx +44 -0
  188. package/src/list/components/TreemapView.tsx +709 -0
  189. package/src/list/components/ViewSizeControls.tsx +205 -0
  190. package/src/list/components/ViewTypeSelector.tsx +312 -0
  191. package/src/list/components/VirtualizedDetailsView.tsx +231 -0
  192. package/src/list/components/VirtualizedGrid.tsx +164 -0
  193. package/src/list/components/VirtualizedList.tsx +154 -0
  194. package/src/list/components/VirtualizedMasonryView.tsx +344 -0
  195. package/src/list/components/shared/EmptyState.tsx +103 -0
  196. package/src/list/components/shared/ErrorBoundary.tsx +123 -0
  197. package/src/list/components/shared/ErrorDisplay.tsx +100 -0
  198. package/src/list/components/shared/ListLoader.tsx +146 -0
  199. package/src/list/components/shared/LoadingIndicator.tsx +80 -0
  200. package/src/list/index.ts +92 -0
  201. package/src/list/models/ListItemsModel.ts +1301 -0
  202. package/src/list/models/TreemapModel.ts +204 -0
  203. package/src/list/providers/ListItemsProvider.ts +313 -0
  204. package/src/list/providers/TestListProvider.ts +604 -0
  205. package/src/list/tasks.md +937 -0
  206. package/src/list/types/ListTypes.ts +178 -0
  207. package/src/list/utils/BenchmarkLogger.ts +243 -0
  208. package/src/list/utils/DragDropManager.ts +320 -0
  209. package/src/list/utils/GridLayoutCalculator.ts +290 -0
  210. package/src/list/utils/ListAccessibility.ts +367 -0
  211. package/src/list/utils/ListKeyboard.ts +414 -0
  212. package/src/list/utils/MasonryLayoutCalculator.ts +302 -0
  213. package/src/list/utils/MasonryLayoutEngine.ts +401 -0
  214. package/src/list/utils/__tests__/MasonryLayoutEngine.test.ts +157 -0
  215. package/src/list/utils/__tests__/VirtualizedMasonryView.test.tsx +251 -0
  216. package/src/media/AlbumSidebar.tsx +48 -0
  217. package/src/media/MediaBrowser.tsx +92 -0
  218. package/src/media/MediaBrowserModel.ts +138 -0
  219. package/src/media/MediaGrid.tsx +50 -0
  220. package/src/media/MediaList.tsx +49 -0
  221. package/src/media/MediaPreview.tsx +63 -0
  222. package/src/media/MediaTimeline.tsx +38 -0
  223. package/src/media/MockMediaProvider.ts +70 -0
  224. package/src/media/index.ts +18 -0
  225. package/src/media/types.ts +21 -0
  226. package/src/styles/variables.css +60 -0
  227. package/src/tree/DEVELOPMENT_SUMMARY.md +170 -0
  228. package/src/tree/__tests__/TreeModel.test.ts +16 -0
  229. package/src/tree/architecture.md +530 -0
  230. package/src/tree/components/Tree.tsx +283 -0
  231. package/src/tree/components/TreeCheckbox.tsx +147 -0
  232. package/src/tree/components/TreeContextMenu.tsx +139 -0
  233. package/src/tree/components/TreeNodeList.tsx +329 -0
  234. package/src/tree/components/TreeTable.tsx +382 -0
  235. package/src/tree/index.ts +58 -0
  236. package/src/tree/models/TreeModel.ts +839 -0
  237. package/src/tree/providers/SimpleTreeProvider.ts +463 -0
  238. package/src/tree/providers/TestTreeProvider.ts +946 -0
  239. package/src/tree/providers/TreeProvider.ts +308 -0
  240. package/src/tree/tasks.md +2046 -0
  241. package/src/tree/types/TreeTypes.ts +279 -0
  242. package/src/tree/utils/SelectionTheme.ts +150 -0
  243. package/src/tree/utils/logger.ts +203 -0
  244. package/src/types-common.ts +21 -0
@@ -0,0 +1,3351 @@
1
+ import { cn$1 as cn } from "./utils-B4fdKKsy.js";
2
+ import { resolveIcon$1 as resolveIcon } from "./iconMap-V4B8P-Uh.js";
3
+ import { useFileBrowserContext } from "./FileBrowserContext-B6jixa2j.js";
4
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
5
+ import { observer } from "mobx-react-lite";
6
+ import { Archive, ArrowDown, ArrowUp, ArrowUpDown, BookOpen, Braces, Check, ChevronDown, ChevronRight, Code, Component as Component$1, Database, Download, FileText, GitBranch, Globe, ImageIcon, Key, Loader2, Minus, MoreVertical, Music, Package, Palette, Play, Search, Settings, TestTube, Zap } from "lucide-react";
7
+ import { flow, makeAutoObservable, observable } from "mobx";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+
10
+ //#region src/tree/utils/SelectionTheme.ts
11
+ /**
12
+ * Default selection theme configuration
13
+ */
14
+ const DEFAULT_SELECTION_THEME = {
15
+ colorScheme: "primary",
16
+ intensity: "subtle",
17
+ focusStyle: "ring",
18
+ animated: true,
19
+ persistSelection: true
20
+ };
21
+ /**
22
+ * Generate selection background classes based on color scheme and intensity
23
+ */
24
+ function getSelectionBackground(colorScheme, intensity) {
25
+ if (colorScheme === "custom") return "";
26
+ const intensityMap = {
27
+ subtle: {
28
+ primary: "bg-primary/10",
29
+ secondary: "bg-secondary/10",
30
+ accent: "bg-accent/20",
31
+ muted: "bg-muted"
32
+ },
33
+ prominent: {
34
+ primary: "bg-primary/20",
35
+ secondary: "bg-secondary/20",
36
+ accent: "bg-accent/40",
37
+ muted: "bg-muted/60"
38
+ },
39
+ "high-contrast": {
40
+ primary: "bg-primary text-primary-foreground",
41
+ secondary: "bg-secondary text-secondary-foreground",
42
+ accent: "bg-accent text-accent-foreground",
43
+ muted: "bg-muted-foreground text-muted"
44
+ }
45
+ };
46
+ return intensityMap[intensity]?.[colorScheme] || intensityMap.subtle.primary;
47
+ }
48
+ /**
49
+ * Generate hover background classes
50
+ */
51
+ function getHoverBackground(colorScheme) {
52
+ if (colorScheme === "custom") return "";
53
+ const hoverMap = {
54
+ primary: "hover:bg-primary/5",
55
+ secondary: "hover:bg-secondary/5",
56
+ accent: "hover:bg-accent/10",
57
+ muted: "hover:bg-muted/30"
58
+ };
59
+ return hoverMap[colorScheme] || hoverMap.primary;
60
+ }
61
+ /**
62
+ * Generate focus indicator classes based on focus style
63
+ */
64
+ function getFocusIndicator(focusStyle, colorScheme) {
65
+ if (colorScheme === "custom") return focusStyle === "ring" ? "ring-2 ring-[var(--tree-focus-ring)] ring-offset-1" : "border-l-2 border-[var(--tree-focus-border)]";
66
+ const focusMap = {
67
+ ring: {
68
+ primary: "ring-2 ring-primary ring-offset-1",
69
+ secondary: "ring-2 ring-secondary ring-offset-1",
70
+ accent: "ring-2 ring-accent ring-offset-1",
71
+ muted: "ring-2 ring-muted-foreground ring-offset-1"
72
+ },
73
+ outline: {
74
+ primary: "outline-2 outline-primary",
75
+ secondary: "outline-2 outline-secondary",
76
+ accent: "outline-2 outline-accent",
77
+ muted: "outline-2 outline-muted-foreground"
78
+ },
79
+ border: {
80
+ primary: "border-l-2 border-primary",
81
+ secondary: "border-l-2 border-secondary",
82
+ accent: "border-l-2 border-accent",
83
+ muted: "border-l-2 border-muted-foreground"
84
+ },
85
+ underline: {
86
+ primary: "border-b-2 border-primary",
87
+ secondary: "border-b-2 border-secondary",
88
+ accent: "border-b-2 border-accent",
89
+ muted: "border-b-2 border-muted-foreground"
90
+ }
91
+ };
92
+ return focusMap[focusStyle]?.[colorScheme] || focusMap.ring.primary;
93
+ }
94
+ /**
95
+ * Generate complete selection classes for a tree node
96
+ */
97
+ function getSelectionClasses(theme, isSelected, isFocused, useCheckboxes = false) {
98
+ const baseClasses = [
99
+ "transition-colors",
100
+ theme.animated ? "duration-150" : "",
101
+ getHoverBackground(theme.colorScheme)
102
+ ];
103
+ if (isSelected) baseClasses.push(getSelectionBackground(theme.colorScheme, theme.intensity));
104
+ if (isFocused && !useCheckboxes) baseClasses.push(getFocusIndicator(theme.focusStyle, theme.colorScheme));
105
+ return cn(baseClasses);
106
+ }
107
+ /**
108
+ * Generate CSS custom properties for custom color schemes
109
+ * NOTE: This function is now simplified since we handle custom colors with inline styles
110
+ */
111
+ function getCustomColorVariables(theme) {
112
+ return {};
113
+ }
114
+
115
+ //#endregion
116
+ //#region src/tree/providers/TestTreeProvider.ts
117
+ var TestTreeProvider = class {
118
+ constructor() {
119
+ this.id = "test-tree-provider";
120
+ this.name = "Test Tree Provider";
121
+ this.version = "1.0.0";
122
+ this.isMultiSelectEnabled = true;
123
+ this.isDragDropEnabled = false;
124
+ this.isVirtualizationEnabled = false;
125
+ this.isTableViewEnabled = true;
126
+ this.useCheckboxSelection = false;
127
+ this.allowPartialSelection = true;
128
+ this.config = {};
129
+ this.selectionTheme = {
130
+ ...DEFAULT_SELECTION_THEME,
131
+ colorScheme: "primary",
132
+ intensity: "prominent",
133
+ focusStyle: "ring",
134
+ animated: true,
135
+ persistSelection: true
136
+ };
137
+ this.slowLoading = false;
138
+ this.simulateErrors = false;
139
+ this.maxDepth = 3;
140
+ this.nodesPerLevel = 5;
141
+ this.loadingDelay = 300;
142
+ this.updateSelectionTheme = (theme) => {
143
+ this.selectionTheme = {
144
+ ...this.selectionTheme,
145
+ ...theme
146
+ };
147
+ };
148
+ this.setMultiSelectEnabled = (enabled) => {
149
+ this.isMultiSelectEnabled = enabled;
150
+ };
151
+ this.setCheckboxSelection = (enabled) => {
152
+ this.useCheckboxSelection = enabled;
153
+ };
154
+ makeAutoObservable(this, {
155
+ id: false,
156
+ name: false,
157
+ version: false,
158
+ isDragDropEnabled: false,
159
+ isVirtualizationEnabled: false,
160
+ isTableViewEnabled: false,
161
+ config: false
162
+ });
163
+ }
164
+ /**
165
+ * Get selection theme configuration
166
+ */
167
+ getSelectionTheme() {
168
+ return this.selectionTheme;
169
+ }
170
+ /**
171
+ * Load tree nodes
172
+ */
173
+ async loadNodes(options) {
174
+ await this.simulateDelay();
175
+ if (this.simulateErrors && Math.random() < .3) throw new Error("Simulated loading error - try again!");
176
+ const nodes = this.generateMockNodes("", 0);
177
+ return {
178
+ nodes,
179
+ hasMore: false,
180
+ totalCount: nodes.length
181
+ };
182
+ }
183
+ /**
184
+ * Load child nodes for a given parent
185
+ */
186
+ async loadChildren(node, options) {
187
+ await this.simulateDelay();
188
+ if (this.simulateErrors && Math.random() < .3) throw new Error("Simulated loading error - try again!");
189
+ const pathParts = node.path.split("/").filter(Boolean);
190
+ const depth = pathParts.length;
191
+ if (depth >= this.maxDepth) return {
192
+ nodes: [],
193
+ hasMore: false,
194
+ totalCount: 0
195
+ };
196
+ const children = this.generateMockNodes(node.path, depth);
197
+ return {
198
+ nodes: children,
199
+ hasMore: false,
200
+ totalCount: children.length
201
+ };
202
+ }
203
+ /**
204
+ * Refresh tree data
205
+ */
206
+ async refresh(path) {
207
+ return path ? this.loadChildren({ path }) : this.loadNodes();
208
+ }
209
+ canExpand(node) {
210
+ return node.type === "directory" && (node.hasChildren ?? true);
211
+ }
212
+ canSelect(node) {
213
+ return !node.disabled;
214
+ }
215
+ getNodeContextMenu(node) {
216
+ const baseItems = [];
217
+ if (node.type === "directory") if (node.name.includes("components")) return [
218
+ {
219
+ id: "open",
220
+ label: "Open Folder"
221
+ },
222
+ {
223
+ id: "separator-1",
224
+ label: "",
225
+ type: "separator"
226
+ },
227
+ {
228
+ id: "new-component",
229
+ label: "New React Component"
230
+ },
231
+ {
232
+ id: "new-hook",
233
+ label: "New Custom Hook"
234
+ },
235
+ {
236
+ id: "new-story",
237
+ label: "New Storybook Story"
238
+ },
239
+ {
240
+ id: "separator-2",
241
+ label: "",
242
+ type: "separator"
243
+ },
244
+ {
245
+ id: "run-tests",
246
+ label: "Run Component Tests"
247
+ },
248
+ {
249
+ id: "build-docs",
250
+ label: "Generate Docs"
251
+ },
252
+ {
253
+ id: "separator-3",
254
+ label: "",
255
+ type: "separator"
256
+ },
257
+ {
258
+ id: "rename",
259
+ label: "Rename Folder"
260
+ },
261
+ {
262
+ id: "delete",
263
+ label: "Delete Folder"
264
+ }
265
+ ];
266
+ else if (node.name.includes("config")) return [
267
+ {
268
+ id: "open",
269
+ label: "Open Config Folder"
270
+ },
271
+ {
272
+ id: "separator-1",
273
+ label: "",
274
+ type: "separator"
275
+ },
276
+ {
277
+ id: "new-env",
278
+ label: "New Environment File"
279
+ },
280
+ {
281
+ id: "new-config",
282
+ label: "New Config File"
283
+ },
284
+ {
285
+ id: "validate-config",
286
+ label: "Validate Configuration"
287
+ },
288
+ {
289
+ id: "separator-2",
290
+ label: "",
291
+ type: "separator"
292
+ },
293
+ {
294
+ id: "backup-config",
295
+ label: "Backup Configuration"
296
+ },
297
+ {
298
+ id: "restore-config",
299
+ label: "Restore from Backup"
300
+ },
301
+ {
302
+ id: "separator-3",
303
+ label: "",
304
+ type: "separator"
305
+ },
306
+ {
307
+ id: "rename",
308
+ label: "Rename Folder"
309
+ },
310
+ {
311
+ id: "delete",
312
+ label: "Delete Folder"
313
+ }
314
+ ];
315
+ else return [
316
+ {
317
+ id: "open",
318
+ label: "Open Folder"
319
+ },
320
+ {
321
+ id: "separator-1",
322
+ label: "",
323
+ type: "separator"
324
+ },
325
+ {
326
+ id: "new-file",
327
+ label: "New File"
328
+ },
329
+ {
330
+ id: "new-folder",
331
+ label: "New Folder"
332
+ },
333
+ {
334
+ id: "separator-2",
335
+ label: "",
336
+ type: "separator"
337
+ },
338
+ {
339
+ id: "import-files",
340
+ label: "Import Files"
341
+ },
342
+ {
343
+ id: "export-folder",
344
+ label: "Export Folder"
345
+ },
346
+ {
347
+ id: "separator-3",
348
+ label: "",
349
+ type: "separator"
350
+ },
351
+ {
352
+ id: "rename",
353
+ label: "Rename Folder"
354
+ },
355
+ {
356
+ id: "delete",
357
+ label: "Delete Folder"
358
+ },
359
+ {
360
+ id: "separator-4",
361
+ label: "",
362
+ type: "separator"
363
+ },
364
+ {
365
+ id: "copy",
366
+ label: "Copy Folder"
367
+ },
368
+ {
369
+ id: "cut",
370
+ label: "Cut Folder"
371
+ }
372
+ ];
373
+ const fileName = node.name;
374
+ const fileExt = fileName.split(".").pop()?.toLowerCase() || "";
375
+ switch (fileExt) {
376
+ case "js":
377
+ case "jsx": return [
378
+ {
379
+ id: "open",
380
+ label: "Open in Editor"
381
+ },
382
+ {
383
+ id: "open-preview",
384
+ label: "Open in Preview"
385
+ },
386
+ {
387
+ id: "separator-1",
388
+ label: "",
389
+ type: "separator"
390
+ },
391
+ {
392
+ id: "run-file",
393
+ label: "Run JavaScript"
394
+ },
395
+ {
396
+ id: "debug-file",
397
+ label: "Debug in Browser"
398
+ },
399
+ {
400
+ id: "format-code",
401
+ label: "Format with Prettier"
402
+ },
403
+ {
404
+ id: "separator-2",
405
+ label: "",
406
+ type: "separator"
407
+ },
408
+ {
409
+ id: "create-test",
410
+ label: "Create Test File"
411
+ },
412
+ {
413
+ id: "run-tests",
414
+ label: "Run Tests"
415
+ },
416
+ {
417
+ id: "separator-3",
418
+ label: "",
419
+ type: "separator"
420
+ },
421
+ {
422
+ id: "rename",
423
+ label: "Rename File"
424
+ },
425
+ {
426
+ id: "duplicate",
427
+ label: "Duplicate File"
428
+ },
429
+ {
430
+ id: "delete",
431
+ label: "Delete File"
432
+ }
433
+ ];
434
+ case "ts":
435
+ case "tsx": return [
436
+ {
437
+ id: "open",
438
+ label: "Open in Editor"
439
+ },
440
+ {
441
+ id: "open-preview",
442
+ label: "Open in Preview"
443
+ },
444
+ {
445
+ id: "separator-1",
446
+ label: "",
447
+ type: "separator"
448
+ },
449
+ {
450
+ id: "compile-ts",
451
+ label: "Compile TypeScript"
452
+ },
453
+ {
454
+ id: "type-check",
455
+ label: "Run Type Check"
456
+ },
457
+ {
458
+ id: "generate-types",
459
+ label: "Generate Type Definitions"
460
+ },
461
+ {
462
+ id: "separator-2",
463
+ label: "",
464
+ type: "separator"
465
+ },
466
+ {
467
+ id: "create-test",
468
+ label: "Create Test File"
469
+ },
470
+ {
471
+ id: "run-tests",
472
+ label: "Run Tests"
473
+ },
474
+ {
475
+ id: "separator-3",
476
+ label: "",
477
+ type: "separator"
478
+ },
479
+ {
480
+ id: "rename",
481
+ label: "Rename File"
482
+ },
483
+ {
484
+ id: "duplicate",
485
+ label: "Duplicate File"
486
+ },
487
+ {
488
+ id: "delete",
489
+ label: "Delete File"
490
+ }
491
+ ];
492
+ case "css":
493
+ case "scss":
494
+ case "sass":
495
+ case "less": return [
496
+ {
497
+ id: "open",
498
+ label: "Open in Editor"
499
+ },
500
+ {
501
+ id: "open-preview",
502
+ label: "Live Preview"
503
+ },
504
+ {
505
+ id: "separator-1",
506
+ label: "",
507
+ type: "separator"
508
+ },
509
+ {
510
+ id: "compile-css",
511
+ label: "Compile Stylesheet"
512
+ },
513
+ {
514
+ id: "optimize-css",
515
+ label: "Optimize CSS"
516
+ },
517
+ {
518
+ id: "validate-css",
519
+ label: "Validate CSS"
520
+ },
521
+ {
522
+ id: "separator-2",
523
+ label: "",
524
+ type: "separator"
525
+ },
526
+ {
527
+ id: "extract-vars",
528
+ label: "Extract CSS Variables"
529
+ },
530
+ {
531
+ id: "generate-rtl",
532
+ label: "Generate RTL Version"
533
+ },
534
+ {
535
+ id: "separator-3",
536
+ label: "",
537
+ type: "separator"
538
+ },
539
+ {
540
+ id: "rename",
541
+ label: "Rename File"
542
+ },
543
+ {
544
+ id: "duplicate",
545
+ label: "Duplicate File"
546
+ },
547
+ {
548
+ id: "delete",
549
+ label: "Delete File"
550
+ }
551
+ ];
552
+ case "md":
553
+ case "mdx": return [
554
+ {
555
+ id: "open",
556
+ label: "Open in Editor"
557
+ },
558
+ {
559
+ id: "open-preview",
560
+ label: "Preview Markdown"
561
+ },
562
+ {
563
+ id: "separator-1",
564
+ label: "",
565
+ type: "separator"
566
+ },
567
+ {
568
+ id: "export-html",
569
+ label: "Export to HTML"
570
+ },
571
+ {
572
+ id: "export-pdf",
573
+ label: "Export to PDF"
574
+ },
575
+ {
576
+ id: "check-links",
577
+ label: "Check Links"
578
+ },
579
+ {
580
+ id: "separator-2",
581
+ label: "",
582
+ type: "separator"
583
+ },
584
+ {
585
+ id: "toc-generate",
586
+ label: "Generate Table of Contents"
587
+ },
588
+ {
589
+ id: "word-count",
590
+ label: "Word Count & Stats"
591
+ },
592
+ {
593
+ id: "separator-3",
594
+ label: "",
595
+ type: "separator"
596
+ },
597
+ {
598
+ id: "rename",
599
+ label: "Rename File"
600
+ },
601
+ {
602
+ id: "duplicate",
603
+ label: "Duplicate File"
604
+ },
605
+ {
606
+ id: "delete",
607
+ label: "Delete File"
608
+ }
609
+ ];
610
+ case "json": return [
611
+ {
612
+ id: "open",
613
+ label: "Open in Editor"
614
+ },
615
+ {
616
+ id: "open-viewer",
617
+ label: "Open JSON Viewer"
618
+ },
619
+ {
620
+ id: "separator-1",
621
+ label: "",
622
+ type: "separator"
623
+ },
624
+ {
625
+ id: "validate-json",
626
+ label: "Validate JSON"
627
+ },
628
+ {
629
+ id: "format-json",
630
+ label: "Format JSON"
631
+ },
632
+ {
633
+ id: "minify-json",
634
+ label: "Minify JSON"
635
+ },
636
+ {
637
+ id: "separator-2",
638
+ label: "",
639
+ type: "separator"
640
+ },
641
+ {
642
+ id: "compare-json",
643
+ label: "Compare with..."
644
+ },
645
+ {
646
+ id: "schema-validate",
647
+ label: "Validate Against Schema"
648
+ },
649
+ {
650
+ id: "separator-3",
651
+ label: "",
652
+ type: "separator"
653
+ },
654
+ {
655
+ id: "rename",
656
+ label: "Rename File"
657
+ },
658
+ {
659
+ id: "duplicate",
660
+ label: "Duplicate File"
661
+ },
662
+ {
663
+ id: "delete",
664
+ label: "Delete File"
665
+ }
666
+ ];
667
+ case "jpg":
668
+ case "jpeg":
669
+ case "png":
670
+ case "gif":
671
+ case "svg":
672
+ case "webp": return [
673
+ {
674
+ id: "open",
675
+ label: "Open Image"
676
+ },
677
+ {
678
+ id: "open-editor",
679
+ label: "Edit in Image Editor"
680
+ },
681
+ {
682
+ id: "separator-1",
683
+ label: "",
684
+ type: "separator"
685
+ },
686
+ {
687
+ id: "resize-image",
688
+ label: "Resize Image"
689
+ },
690
+ {
691
+ id: "compress-image",
692
+ label: "Compress Image"
693
+ },
694
+ {
695
+ id: "convert-format",
696
+ label: "Convert Format"
697
+ },
698
+ {
699
+ id: "separator-2",
700
+ label: "",
701
+ type: "separator"
702
+ },
703
+ {
704
+ id: "image-info",
705
+ label: "Image Properties"
706
+ },
707
+ {
708
+ id: "copy-url",
709
+ label: "Copy Image URL"
710
+ },
711
+ {
712
+ id: "separator-3",
713
+ label: "",
714
+ type: "separator"
715
+ },
716
+ {
717
+ id: "rename",
718
+ label: "Rename File"
719
+ },
720
+ {
721
+ id: "duplicate",
722
+ label: "Duplicate File"
723
+ },
724
+ {
725
+ id: "delete",
726
+ label: "Delete File"
727
+ }
728
+ ];
729
+ default: return [
730
+ {
731
+ id: "open",
732
+ label: "Open File"
733
+ },
734
+ {
735
+ id: "open-with",
736
+ label: "Open With..."
737
+ },
738
+ {
739
+ id: "separator-1",
740
+ label: "",
741
+ type: "separator"
742
+ },
743
+ {
744
+ id: "properties",
745
+ label: "File Properties"
746
+ },
747
+ {
748
+ id: "permissions",
749
+ label: "Change Permissions"
750
+ },
751
+ {
752
+ id: "separator-2",
753
+ label: "",
754
+ type: "separator"
755
+ },
756
+ {
757
+ id: "rename",
758
+ label: "Rename File"
759
+ },
760
+ {
761
+ id: "duplicate",
762
+ label: "Duplicate File"
763
+ },
764
+ {
765
+ id: "delete",
766
+ label: "Delete File"
767
+ },
768
+ {
769
+ id: "separator-3",
770
+ label: "",
771
+ type: "separator"
772
+ },
773
+ {
774
+ id: "copy",
775
+ label: "Copy File"
776
+ },
777
+ {
778
+ id: "cut",
779
+ label: "Cut File"
780
+ }
781
+ ];
782
+ }
783
+ }
784
+ getMultiNodeContextMenu(nodes) {
785
+ const hasFiles = nodes.some((n) => n.type === "file");
786
+ const hasFolders = nodes.some((n) => n.type === "directory");
787
+ const allSameType = hasFiles && !hasFolders || hasFolders && !hasFiles;
788
+ if (allSameType && hasFiles) return [
789
+ {
790
+ id: "open-all",
791
+ label: `Open ${nodes.length} Files`
792
+ },
793
+ {
794
+ id: "separator-1",
795
+ label: "",
796
+ type: "separator"
797
+ },
798
+ {
799
+ id: "compress-files",
800
+ label: `Create Archive (${nodes.length} files)`
801
+ },
802
+ {
803
+ id: "copy-paths",
804
+ label: "Copy File Paths"
805
+ },
806
+ {
807
+ id: "separator-2",
808
+ label: "",
809
+ type: "separator"
810
+ },
811
+ {
812
+ id: "copy-all",
813
+ label: `Copy ${nodes.length} Files`
814
+ },
815
+ {
816
+ id: "cut-all",
817
+ label: `Cut ${nodes.length} Files`
818
+ },
819
+ {
820
+ id: "delete-all",
821
+ label: `Delete ${nodes.length} Files`
822
+ }
823
+ ];
824
+ else if (allSameType && hasFolders) return [
825
+ {
826
+ id: "open-all",
827
+ label: `Open ${nodes.length} Folders`
828
+ },
829
+ {
830
+ id: "separator-1",
831
+ label: "",
832
+ type: "separator"
833
+ },
834
+ {
835
+ id: "merge-folders",
836
+ label: "Merge Folders"
837
+ },
838
+ {
839
+ id: "compare-folders",
840
+ label: "Compare Folders"
841
+ },
842
+ {
843
+ id: "separator-2",
844
+ label: "",
845
+ type: "separator"
846
+ },
847
+ {
848
+ id: "copy-all",
849
+ label: `Copy ${nodes.length} Folders`
850
+ },
851
+ {
852
+ id: "cut-all",
853
+ label: `Cut ${nodes.length} Folders`
854
+ },
855
+ {
856
+ id: "delete-all",
857
+ label: `Delete ${nodes.length} Folders`
858
+ }
859
+ ];
860
+ else return [
861
+ {
862
+ id: "open-all",
863
+ label: `Open ${nodes.length} Items`
864
+ },
865
+ {
866
+ id: "separator-1",
867
+ label: "",
868
+ type: "separator"
869
+ },
870
+ {
871
+ id: "compress-all",
872
+ label: `Create Archive (${nodes.length} items)`
873
+ },
874
+ {
875
+ id: "copy-info",
876
+ label: "Copy Selection Info"
877
+ },
878
+ {
879
+ id: "separator-2",
880
+ label: "",
881
+ type: "separator"
882
+ },
883
+ {
884
+ id: "copy-all",
885
+ label: `Copy ${nodes.length} Items`
886
+ },
887
+ {
888
+ id: "cut-all",
889
+ label: `Cut ${nodes.length} Items`
890
+ },
891
+ {
892
+ id: "delete-all",
893
+ label: `Delete ${nodes.length} Items`
894
+ }
895
+ ];
896
+ }
897
+ onContextMenuAction(menuItemId, nodes) {
898
+ const nodeNames = nodes.map((n) => n.name).join(", ");
899
+ console.log(`🎯 Context menu action: ${menuItemId} on nodes: ${nodeNames}`);
900
+ switch (menuItemId) {
901
+ case "open":
902
+ case "open-all":
903
+ console.log(`📂 Opening: ${nodeNames}`);
904
+ alert(`Opening: ${nodeNames}`);
905
+ break;
906
+ case "open-preview":
907
+ console.log(`👁️ Opening preview for: ${nodeNames}`);
908
+ alert(`Preview mode for: ${nodeNames}`);
909
+ break;
910
+ case "open-editor":
911
+ console.log(`✏️ Opening in editor: ${nodeNames}`);
912
+ alert(`Editor opened for: ${nodeNames}`);
913
+ break;
914
+ case "run-file":
915
+ case "run-tests":
916
+ console.log(`▶️ Running: ${nodeNames}`);
917
+ alert(`Executing: ${nodeNames}`);
918
+ break;
919
+ case "debug-file":
920
+ console.log(`🐛 Debugging: ${nodeNames}`);
921
+ alert(`Debug session started for: ${nodeNames}`);
922
+ break;
923
+ case "compile-ts":
924
+ case "compile-css":
925
+ console.log(`🔨 Compiling: ${nodeNames}`);
926
+ alert(`Compilation started for: ${nodeNames}`);
927
+ break;
928
+ case "format-code":
929
+ case "format-json":
930
+ console.log(`🎨 Formatting: ${nodeNames}`);
931
+ alert(`Code formatted: ${nodeNames}`);
932
+ break;
933
+ case "type-check":
934
+ console.log(`🔍 Type checking: ${nodeNames}`);
935
+ alert(`Type check completed for: ${nodeNames}`);
936
+ break;
937
+ case "new-file":
938
+ case "new-folder":
939
+ case "new-component":
940
+ case "new-hook":
941
+ case "new-story":
942
+ console.log(`➕ Creating new ${menuItemId.replace("new-", "")} in: ${nodeNames}`);
943
+ alert(`Creating new ${menuItemId.replace("new-", "")} in: ${nodeNames}`);
944
+ break;
945
+ case "export-html":
946
+ case "export-pdf":
947
+ console.log(`📤 Exporting ${menuItemId.replace("export-", "")}: ${nodeNames}`);
948
+ alert(`Exporting ${menuItemId.replace("export-", "").toUpperCase()}: ${nodeNames}`);
949
+ break;
950
+ case "import-files":
951
+ console.log(`📥 Importing files to: ${nodeNames}`);
952
+ alert(`Import dialog opened for: ${nodeNames}`);
953
+ break;
954
+ case "rename":
955
+ console.log(`✏️ Renaming: ${nodeNames}`);
956
+ alert(`Rename dialog for: ${nodeNames}`);
957
+ break;
958
+ case "duplicate":
959
+ console.log(`📋 Duplicating: ${nodeNames}`);
960
+ alert(`Duplicated: ${nodeNames}`);
961
+ break;
962
+ case "delete":
963
+ case "delete-all":
964
+ console.log(`🗑️ Deleting: ${nodeNames}`);
965
+ alert(`Deleted: ${nodeNames}`);
966
+ break;
967
+ case "copy":
968
+ case "copy-all":
969
+ console.log(`📋 Copying: ${nodeNames}`);
970
+ alert(`Copied to clipboard: ${nodeNames}`);
971
+ break;
972
+ case "cut":
973
+ case "cut-all":
974
+ console.log(`✂️ Cutting: ${nodeNames}`);
975
+ alert(`Cut to clipboard: ${nodeNames}`);
976
+ break;
977
+ case "validate-json":
978
+ case "validate-css":
979
+ case "validate-config":
980
+ console.log(`✅ Validating: ${nodeNames}`);
981
+ alert(`Validation passed for: ${nodeNames}`);
982
+ break;
983
+ case "compress-image":
984
+ case "compress-files":
985
+ case "compress-all":
986
+ console.log(`🗜️ Compressing: ${nodeNames}`);
987
+ alert(`Compression completed for: ${nodeNames}`);
988
+ break;
989
+ case "image-info":
990
+ case "properties":
991
+ console.log(`ℹ️ Showing properties for: ${nodeNames}`);
992
+ alert(`Properties: ${nodeNames}\nSize: 1.2MB\nType: ${nodes[0]?.type || "unknown"}\nModified: ${nodes[0]?.lastModified?.toLocaleDateString() || "unknown"}`);
993
+ break;
994
+ default:
995
+ console.log(`❓ Unknown action: ${menuItemId}`);
996
+ alert(`Action executed: ${menuItemId} on ${nodeNames}`);
997
+ }
998
+ }
999
+ getTableColumns() {
1000
+ return [
1001
+ {
1002
+ id: "name",
1003
+ label: "Name",
1004
+ dataKey: "name",
1005
+ width: "300px",
1006
+ sortable: true,
1007
+ filterable: true,
1008
+ isTreeColumn: true,
1009
+ align: "left"
1010
+ },
1011
+ {
1012
+ id: "type",
1013
+ label: "Type",
1014
+ dataKey: "type",
1015
+ width: "100px",
1016
+ sortable: true,
1017
+ filterable: true,
1018
+ align: "left",
1019
+ formatValue: (value) => value === "directory" ? "Folder" : "File"
1020
+ },
1021
+ {
1022
+ id: "size",
1023
+ label: "Size",
1024
+ dataKey: "size",
1025
+ width: "100px",
1026
+ sortable: true,
1027
+ align: "right",
1028
+ formatValue: (value, node) => {
1029
+ if (node.type === "directory") return "-";
1030
+ if (!value) return "-";
1031
+ if (value < 1024) return `${value} B`;
1032
+ if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`;
1033
+ if (value < 1024 * 1024 * 1024) return `${(value / (1024 * 1024)).toFixed(1)} MB`;
1034
+ return `${(value / (1024 * 1024 * 1024)).toFixed(1)} GB`;
1035
+ }
1036
+ },
1037
+ {
1038
+ id: "lastModified",
1039
+ label: "Modified",
1040
+ dataKey: "lastModified",
1041
+ width: "150px",
1042
+ sortable: true,
1043
+ align: "left",
1044
+ formatValue: (value) => {
1045
+ if (!value) return "-";
1046
+ return value.toLocaleDateString();
1047
+ }
1048
+ },
1049
+ {
1050
+ id: "path",
1051
+ label: "Path",
1052
+ dataKey: "path",
1053
+ width: "200px",
1054
+ sortable: true,
1055
+ filterable: true,
1056
+ align: "left"
1057
+ }
1058
+ ];
1059
+ }
1060
+ getDefaultTableSort() {
1061
+ return {
1062
+ columnId: "name",
1063
+ direction: "asc"
1064
+ };
1065
+ }
1066
+ onTableSort(sort) {
1067
+ console.log("Table sort changed:", sort);
1068
+ }
1069
+ async onTableExport(options) {
1070
+ console.log("Table export requested:", options);
1071
+ alert(`Export to ${options.format.toUpperCase()} format requested`);
1072
+ }
1073
+ async simulateDelay() {
1074
+ const delay = this.slowLoading ? 1e3 : this.loadingDelay;
1075
+ await new Promise((resolve) => setTimeout(resolve, delay));
1076
+ }
1077
+ generateMockNodes(parentPath, depth) {
1078
+ if (depth >= this.maxDepth) return [];
1079
+ const nodes = [];
1080
+ const fileTypes = [
1081
+ "js",
1082
+ "jsx",
1083
+ "ts",
1084
+ "tsx",
1085
+ "html",
1086
+ "css",
1087
+ "scss",
1088
+ "sass",
1089
+ "less",
1090
+ "md",
1091
+ "mdx",
1092
+ "txt",
1093
+ "pdf",
1094
+ "json",
1095
+ "yml",
1096
+ "yaml",
1097
+ "xml",
1098
+ "env",
1099
+ "config",
1100
+ "jpg",
1101
+ "jpeg",
1102
+ "png",
1103
+ "gif",
1104
+ "svg",
1105
+ "webp",
1106
+ "ico",
1107
+ "mp4",
1108
+ "mp3",
1109
+ "wav",
1110
+ "avi",
1111
+ "zip",
1112
+ "tar",
1113
+ "gz",
1114
+ "exe",
1115
+ "app",
1116
+ "dmg",
1117
+ "py",
1118
+ "php",
1119
+ "java",
1120
+ "cpp",
1121
+ "c",
1122
+ "go",
1123
+ "rs",
1124
+ "rb"
1125
+ ];
1126
+ const folderTypes = [
1127
+ {
1128
+ name: "components",
1129
+ type: "react"
1130
+ },
1131
+ {
1132
+ name: "hooks",
1133
+ type: "react"
1134
+ },
1135
+ {
1136
+ name: "pages",
1137
+ type: "react"
1138
+ },
1139
+ {
1140
+ name: "utils",
1141
+ type: "code"
1142
+ },
1143
+ {
1144
+ name: "lib",
1145
+ type: "code"
1146
+ },
1147
+ {
1148
+ name: "src",
1149
+ type: "source"
1150
+ },
1151
+ {
1152
+ name: "config",
1153
+ type: "config"
1154
+ },
1155
+ {
1156
+ name: "build",
1157
+ type: "build"
1158
+ },
1159
+ {
1160
+ name: "dist",
1161
+ type: "build"
1162
+ },
1163
+ {
1164
+ name: ".github",
1165
+ type: "git"
1166
+ },
1167
+ {
1168
+ name: ".vscode",
1169
+ type: "vscode"
1170
+ },
1171
+ {
1172
+ name: "assets",
1173
+ type: "assets"
1174
+ },
1175
+ {
1176
+ name: "images",
1177
+ type: "images"
1178
+ },
1179
+ {
1180
+ name: "media",
1181
+ type: "media"
1182
+ },
1183
+ {
1184
+ name: "public",
1185
+ type: "public"
1186
+ },
1187
+ {
1188
+ name: "docs",
1189
+ type: "docs"
1190
+ },
1191
+ {
1192
+ name: "tests",
1193
+ type: "test"
1194
+ },
1195
+ {
1196
+ name: "__tests__",
1197
+ type: "test"
1198
+ },
1199
+ {
1200
+ name: "node_modules",
1201
+ type: "node_modules"
1202
+ }
1203
+ ];
1204
+ for (let i = 0; i < this.nodesPerLevel; i++) {
1205
+ const nodeId = `${parentPath || "root"}-${depth}-${i}`.replace(/[^a-zA-Z0-9-_]/g, "-");
1206
+ const shouldBeFolder = depth < this.maxDepth - 1 && (i < this.nodesPerLevel / 2 || Math.random() < .4);
1207
+ if (shouldBeFolder) {
1208
+ const folderType = folderTypes[i % folderTypes.length] || {
1209
+ name: `folder-${i}`,
1210
+ type: "default"
1211
+ };
1212
+ const folderPath = parentPath ? `${parentPath}/${folderType.name}` : `/${folderType.name}`;
1213
+ nodes.push({
1214
+ id: nodeId,
1215
+ name: folderType.name,
1216
+ path: folderPath,
1217
+ type: "directory",
1218
+ hasChildren: depth < this.maxDepth - 1,
1219
+ lastModified: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1e3),
1220
+ metadata: {
1221
+ folderType: folderType.type,
1222
+ description: this.getFolderDescription(folderType.type)
1223
+ }
1224
+ });
1225
+ } else {
1226
+ const fileType = fileTypes[i % fileTypes.length] || "txt";
1227
+ const fileName = this.generateFileName(fileType, i);
1228
+ const filePath = parentPath ? `${parentPath}/${fileName}` : `/${fileName}`;
1229
+ nodes.push({
1230
+ id: nodeId,
1231
+ name: fileName,
1232
+ path: filePath,
1233
+ type: "file",
1234
+ hasChildren: false,
1235
+ size: Math.floor(Math.random() * 1e6) + 1024,
1236
+ lastModified: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1e3),
1237
+ metadata: {
1238
+ fileType,
1239
+ category: this.getFileCategory(fileType),
1240
+ description: this.getFileDescription(fileType)
1241
+ }
1242
+ });
1243
+ }
1244
+ }
1245
+ return nodes;
1246
+ }
1247
+ generateFileName(fileType, index) {
1248
+ const fileNames = {
1249
+ "js": [
1250
+ "utils",
1251
+ "helpers",
1252
+ "config",
1253
+ "index",
1254
+ "main"
1255
+ ],
1256
+ "jsx": [
1257
+ "App",
1258
+ "Component",
1259
+ "Button",
1260
+ "Modal",
1261
+ "Form"
1262
+ ],
1263
+ "ts": [
1264
+ "types",
1265
+ "interfaces",
1266
+ "utils",
1267
+ "service",
1268
+ "api"
1269
+ ],
1270
+ "tsx": [
1271
+ "HomePage",
1272
+ "UserCard",
1273
+ "Navigation",
1274
+ "Layout",
1275
+ "Dashboard"
1276
+ ],
1277
+ "css": [
1278
+ "styles",
1279
+ "main",
1280
+ "theme",
1281
+ "reset",
1282
+ "variables"
1283
+ ],
1284
+ "scss": [
1285
+ "app",
1286
+ "components",
1287
+ "layout",
1288
+ "mixins",
1289
+ "variables"
1290
+ ],
1291
+ "md": [
1292
+ "README",
1293
+ "CHANGELOG",
1294
+ "CONTRIBUTING",
1295
+ "DOCS",
1296
+ "API"
1297
+ ],
1298
+ "json": [
1299
+ "package",
1300
+ "tsconfig",
1301
+ "config",
1302
+ "settings",
1303
+ "data"
1304
+ ],
1305
+ "yml": [
1306
+ "docker-compose",
1307
+ "config",
1308
+ "ci",
1309
+ "deployment",
1310
+ "settings"
1311
+ ],
1312
+ "py": [
1313
+ "main",
1314
+ "utils",
1315
+ "models",
1316
+ "views",
1317
+ "tests"
1318
+ ],
1319
+ "jpg": [
1320
+ "hero-image",
1321
+ "profile",
1322
+ "banner",
1323
+ "thumbnail",
1324
+ "cover"
1325
+ ],
1326
+ "png": [
1327
+ "logo",
1328
+ "icon",
1329
+ "screenshot",
1330
+ "diagram",
1331
+ "chart"
1332
+ ]
1333
+ };
1334
+ const names = fileNames[fileType] || ["file"];
1335
+ const baseName = names[index % names.length];
1336
+ return `${baseName}.${fileType}`;
1337
+ }
1338
+ getFolderDescription(folderType) {
1339
+ const descriptions = {
1340
+ "react": "React components and hooks",
1341
+ "source": "Source code files",
1342
+ "code": "Utility functions and libraries",
1343
+ "config": "Configuration files",
1344
+ "build": "Build output and artifacts",
1345
+ "git": "Git workflow configurations",
1346
+ "vscode": "VS Code editor settings",
1347
+ "assets": "Project assets and resources",
1348
+ "images": "Image files and graphics",
1349
+ "media": "Media files (video, audio)",
1350
+ "public": "Public static files",
1351
+ "docs": "Documentation files",
1352
+ "test": "Test files and specs",
1353
+ "node_modules": "Node.js dependencies",
1354
+ "default": "General folder"
1355
+ };
1356
+ return descriptions[folderType] || "General folder";
1357
+ }
1358
+ getFileCategory(fileType) {
1359
+ const categories = {
1360
+ "js": "JavaScript",
1361
+ "jsx": "React",
1362
+ "ts": "TypeScript",
1363
+ "tsx": "React TypeScript",
1364
+ "css": "Stylesheet",
1365
+ "scss": "Sass",
1366
+ "sass": "Sass",
1367
+ "less": "Less",
1368
+ "html": "Markup",
1369
+ "xml": "Markup",
1370
+ "svg": "Vector Graphics",
1371
+ "md": "Markdown",
1372
+ "txt": "Text",
1373
+ "pdf": "Document",
1374
+ "json": "Data",
1375
+ "yml": "Config",
1376
+ "yaml": "Config",
1377
+ "env": "Environment",
1378
+ "jpg": "Image",
1379
+ "png": "Image",
1380
+ "gif": "Image",
1381
+ "mp3": "Audio",
1382
+ "mp4": "Video",
1383
+ "py": "Python",
1384
+ "php": "PHP",
1385
+ "java": "Java",
1386
+ "cpp": "C++",
1387
+ "go": "Go",
1388
+ "zip": "Archive",
1389
+ "tar": "Archive",
1390
+ "default": "File"
1391
+ };
1392
+ return categories[fileType] || categories["default"] || "File";
1393
+ }
1394
+ getFileDescription(fileType) {
1395
+ const descriptions = {
1396
+ "js": "JavaScript source file",
1397
+ "jsx": "React component file",
1398
+ "ts": "TypeScript source file",
1399
+ "tsx": "React TypeScript component",
1400
+ "css": "Cascading Style Sheet",
1401
+ "scss": "Sass stylesheet",
1402
+ "html": "HTML markup file",
1403
+ "md": "Markdown documentation",
1404
+ "json": "JSON data file",
1405
+ "py": "Python script",
1406
+ "jpg": "JPEG image file",
1407
+ "png": "PNG image file",
1408
+ "mp3": "MP3 audio file",
1409
+ "zip": "Compressed archive"
1410
+ };
1411
+ return descriptions[fileType] || `${fileType.toUpperCase()} file`;
1412
+ }
1413
+ /**
1414
+ * Get custom icon for a node based on file type and category
1415
+ */
1416
+ getNodeIcon(node) {
1417
+ if (node.type === "directory") {
1418
+ if (node.name.includes("components")) return Component$1;
1419
+ if (node.name.includes("assets") || node.name.includes("images")) return ImageIcon;
1420
+ if (node.name.includes("config") || node.name.includes("settings")) return Settings;
1421
+ if (node.name.includes("docs") || node.name.includes("documentation")) return BookOpen;
1422
+ if (node.name.includes("test") || node.name.includes("__tests__")) return TestTube;
1423
+ if (node.name.includes("lib") || node.name.includes("library")) return Package;
1424
+ return "folder";
1425
+ }
1426
+ const extension = node.name.split(".").pop() || "";
1427
+ switch (extension.toLowerCase()) {
1428
+ case "js":
1429
+ case "jsx": return FileText;
1430
+ case "ts":
1431
+ case "tsx": return Code;
1432
+ case "html":
1433
+ case "htm": return Globe;
1434
+ case "css":
1435
+ case "scss":
1436
+ case "sass":
1437
+ case "less": return Palette;
1438
+ case "md":
1439
+ case "mdx": return FileText;
1440
+ case "txt": return FileText;
1441
+ case "pdf": return FileText;
1442
+ case "json": return Braces;
1443
+ case "yml":
1444
+ case "yaml": return Settings;
1445
+ case "xml": return Code;
1446
+ case "env": return Key;
1447
+ case "config": return Settings;
1448
+ case "jpg":
1449
+ case "jpeg":
1450
+ case "png":
1451
+ case "gif":
1452
+ case "webp":
1453
+ case "bmp":
1454
+ case "tiff":
1455
+ case "ico": return ImageIcon;
1456
+ case "svg": return Zap;
1457
+ case "mp4":
1458
+ case "avi":
1459
+ case "mov":
1460
+ case "mkv": return Play;
1461
+ case "mp3":
1462
+ case "wav":
1463
+ case "flac":
1464
+ case "ogg": return Music;
1465
+ case "zip":
1466
+ case "tar":
1467
+ case "gz":
1468
+ case "7z":
1469
+ case "rar": return Archive;
1470
+ case "exe":
1471
+ case "app":
1472
+ case "deb":
1473
+ case "dmg": return Zap;
1474
+ case "git":
1475
+ case "gitignore": return GitBranch;
1476
+ case "dockerfile": return Package;
1477
+ case "sql": return Database;
1478
+ case "log": return FileText;
1479
+ default: return "file";
1480
+ }
1481
+ }
1482
+ };
1483
+
1484
+ //#endregion
1485
+ //#region src/tree/providers/SimpleTreeProvider.ts
1486
+ var SimpleTreeProvider = class {
1487
+ constructor(options = {}) {
1488
+ this.id = "simple-tree-provider";
1489
+ this.name = "Simple Tree Provider";
1490
+ this.version = "1.0.0";
1491
+ this.isMultiSelectEnabled = true;
1492
+ this.isDragDropEnabled = false;
1493
+ this.isVirtualizationEnabled = false;
1494
+ this.isTableViewEnabled = false;
1495
+ this.useCheckboxSelection = false;
1496
+ this.allowPartialSelection = true;
1497
+ this.config = {};
1498
+ this.selectionTheme = {
1499
+ ...DEFAULT_SELECTION_THEME,
1500
+ colorScheme: "primary",
1501
+ intensity: "prominent",
1502
+ focusStyle: "ring",
1503
+ animated: true,
1504
+ persistSelection: true
1505
+ };
1506
+ this.nodes = [];
1507
+ this.contextMenuItems = [];
1508
+ this.updateSelectionTheme = (theme) => {
1509
+ this.selectionTheme = {
1510
+ ...this.selectionTheme,
1511
+ ...theme
1512
+ };
1513
+ };
1514
+ this.setMultiSelectEnabled = (enabled) => {
1515
+ this.isMultiSelectEnabled = enabled;
1516
+ };
1517
+ this.setCheckboxSelection = (enabled) => {
1518
+ this.useCheckboxSelection = enabled;
1519
+ };
1520
+ this.nodes = options.data ?? this.createDefaultData();
1521
+ this.contextMenuItems = options.contextMenuItems ?? this.createDefaultContextMenu();
1522
+ this.onSelectionChange = options.onSelectionChange;
1523
+ this.onContextMenuAction = options.onContextMenuAction;
1524
+ makeAutoObservable(this, {
1525
+ id: false,
1526
+ name: false,
1527
+ version: false,
1528
+ isDragDropEnabled: false,
1529
+ isVirtualizationEnabled: false,
1530
+ isTableViewEnabled: false,
1531
+ config: false
1532
+ });
1533
+ }
1534
+ /**
1535
+ * Load root nodes
1536
+ */
1537
+ async loadNodes(options) {
1538
+ return {
1539
+ nodes: this.nodes,
1540
+ hasMore: false,
1541
+ totalCount: this.nodes.length
1542
+ };
1543
+ }
1544
+ /**
1545
+ * Load children for a specific node
1546
+ */
1547
+ async loadChildren(node, options) {
1548
+ const children = node.children || [];
1549
+ return {
1550
+ nodes: children,
1551
+ hasMore: false,
1552
+ totalCount: children.length
1553
+ };
1554
+ }
1555
+ /**
1556
+ * Refresh/reload nodes
1557
+ */
1558
+ async refresh(path) {
1559
+ if (path) {
1560
+ const node = this.findNodeByPath(path);
1561
+ if (node) return this.loadChildren(node);
1562
+ }
1563
+ return this.loadNodes();
1564
+ }
1565
+ /**
1566
+ * Get context menu for a single node
1567
+ */
1568
+ getNodeContextMenu(node) {
1569
+ return this.contextMenuItems.filter((item) => {
1570
+ if (item.id === "expand" || item.id === "collapse") return node.hasChildren || node.children && node.children.length > 0;
1571
+ if (item.id === "open") return node.type === "file";
1572
+ return true;
1573
+ });
1574
+ }
1575
+ /**
1576
+ * Get context menu for multiple selected nodes
1577
+ */
1578
+ getMultiNodeContextMenu(nodes) {
1579
+ return this.contextMenuItems.filter((item) => {
1580
+ return [
1581
+ "delete",
1582
+ "copy",
1583
+ "cut",
1584
+ "properties"
1585
+ ].includes(item.id);
1586
+ });
1587
+ }
1588
+ /**
1589
+ * Get selection theme configuration
1590
+ */
1591
+ getSelectionTheme() {
1592
+ return this.selectionTheme;
1593
+ }
1594
+ /**
1595
+ * Update the static data
1596
+ */
1597
+ setData(data) {
1598
+ this.nodes = data;
1599
+ }
1600
+ /**
1601
+ * Get current data
1602
+ */
1603
+ getData() {
1604
+ return this.nodes;
1605
+ }
1606
+ /**
1607
+ * Add a new node to the tree
1608
+ */
1609
+ addNode(parentPath, newNode) {
1610
+ if (parentPath === null) {
1611
+ this.nodes.push(newNode);
1612
+ return true;
1613
+ }
1614
+ const parent = this.findNodeByPath(parentPath);
1615
+ if (parent) {
1616
+ if (!parent.children) parent.children = [];
1617
+ parent.children.push(newNode);
1618
+ parent.hasChildren = true;
1619
+ return true;
1620
+ }
1621
+ return false;
1622
+ }
1623
+ /**
1624
+ * Remove a node from the tree
1625
+ */
1626
+ removeNode(nodePath) {
1627
+ const rootIndex = this.nodes.findIndex((node) => node.path === nodePath);
1628
+ if (rootIndex >= 0) {
1629
+ this.nodes.splice(rootIndex, 1);
1630
+ return true;
1631
+ }
1632
+ return this.removeNodeFromChildren(this.nodes, nodePath);
1633
+ }
1634
+ /**
1635
+ * Find a node by its path
1636
+ */
1637
+ findNodeByPath(path) {
1638
+ const searchNodes = (nodes) => {
1639
+ for (const node of nodes) {
1640
+ if (node.path === path) return node;
1641
+ if (node.children) {
1642
+ const found = searchNodes(node.children);
1643
+ if (found) return found;
1644
+ }
1645
+ }
1646
+ return null;
1647
+ };
1648
+ return searchNodes(this.nodes);
1649
+ }
1650
+ /**
1651
+ * Create default sample data
1652
+ */
1653
+ createDefaultData() {
1654
+ return [
1655
+ {
1656
+ id: "documents",
1657
+ name: "Documents",
1658
+ path: "/documents",
1659
+ type: "directory",
1660
+ hasChildren: true,
1661
+ children: [
1662
+ {
1663
+ id: "reports",
1664
+ name: "Reports",
1665
+ path: "/documents/reports",
1666
+ type: "directory",
1667
+ hasChildren: true,
1668
+ children: [{
1669
+ id: "quarterly-report",
1670
+ name: "Quarterly Report.pdf",
1671
+ path: "/documents/reports/quarterly-report.pdf",
1672
+ type: "file",
1673
+ size: 1024e3,
1674
+ lastModified: new Date("2024-01-15")
1675
+ }, {
1676
+ id: "annual-report",
1677
+ name: "Annual Report.pdf",
1678
+ path: "/documents/reports/annual-report.pdf",
1679
+ type: "file",
1680
+ size: 2048e3,
1681
+ lastModified: new Date("2024-01-10")
1682
+ }]
1683
+ },
1684
+ {
1685
+ id: "presentations",
1686
+ name: "Presentations",
1687
+ path: "/documents/presentations",
1688
+ type: "directory",
1689
+ hasChildren: true,
1690
+ children: [{
1691
+ id: "product-demo",
1692
+ name: "Product Demo.pptx",
1693
+ path: "/documents/presentations/product-demo.pptx",
1694
+ type: "file",
1695
+ size: 512e4,
1696
+ lastModified: new Date("2024-01-20")
1697
+ }]
1698
+ },
1699
+ {
1700
+ id: "readme",
1701
+ name: "README.md",
1702
+ path: "/documents/readme.md",
1703
+ type: "file",
1704
+ size: 2048,
1705
+ lastModified: new Date("2024-01-25")
1706
+ }
1707
+ ]
1708
+ },
1709
+ {
1710
+ id: "projects",
1711
+ name: "Projects",
1712
+ path: "/projects",
1713
+ type: "directory",
1714
+ hasChildren: true,
1715
+ children: [{
1716
+ id: "web-app",
1717
+ name: "Web Application",
1718
+ path: "/projects/web-app",
1719
+ type: "directory",
1720
+ hasChildren: true,
1721
+ children: [{
1722
+ id: "src",
1723
+ name: "src",
1724
+ path: "/projects/web-app/src",
1725
+ type: "directory",
1726
+ hasChildren: true,
1727
+ children: [{
1728
+ id: "app-ts",
1729
+ name: "app.ts",
1730
+ path: "/projects/web-app/src/app.ts",
1731
+ type: "file",
1732
+ size: 4096,
1733
+ lastModified: new Date("2024-01-24")
1734
+ }, {
1735
+ id: "components",
1736
+ name: "components",
1737
+ path: "/projects/web-app/src/components",
1738
+ type: "directory",
1739
+ hasChildren: false,
1740
+ children: []
1741
+ }]
1742
+ }, {
1743
+ id: "package-json",
1744
+ name: "package.json",
1745
+ path: "/projects/web-app/package.json",
1746
+ type: "file",
1747
+ size: 1024,
1748
+ lastModified: new Date("2024-01-23")
1749
+ }]
1750
+ }]
1751
+ },
1752
+ {
1753
+ id: "downloads",
1754
+ name: "Downloads",
1755
+ path: "/downloads",
1756
+ type: "directory",
1757
+ hasChildren: false,
1758
+ children: []
1759
+ }
1760
+ ];
1761
+ }
1762
+ /**
1763
+ * Create default context menu items
1764
+ */
1765
+ createDefaultContextMenu() {
1766
+ return [
1767
+ {
1768
+ id: "open",
1769
+ label: "Open",
1770
+ icon: "file-text",
1771
+ handler: (node) => this.onContextMenuAction?.("open", [node])
1772
+ },
1773
+ {
1774
+ id: "expand",
1775
+ label: "Expand",
1776
+ icon: "chevron-down",
1777
+ handler: (node) => this.onContextMenuAction?.("expand", [node])
1778
+ },
1779
+ {
1780
+ id: "collapse",
1781
+ label: "Collapse",
1782
+ icon: "chevron-right",
1783
+ handler: (node) => this.onContextMenuAction?.("collapse", [node])
1784
+ },
1785
+ {
1786
+ id: "separator-1",
1787
+ label: "",
1788
+ type: "separator"
1789
+ },
1790
+ {
1791
+ id: "copy",
1792
+ label: "Copy",
1793
+ icon: "copy",
1794
+ handler: (node) => this.onContextMenuAction?.("copy", [node])
1795
+ },
1796
+ {
1797
+ id: "cut",
1798
+ label: "Cut",
1799
+ icon: "scissors",
1800
+ handler: (node) => this.onContextMenuAction?.("cut", [node])
1801
+ },
1802
+ {
1803
+ id: "delete",
1804
+ label: "Delete",
1805
+ icon: "trash-2",
1806
+ handler: (node) => this.onContextMenuAction?.("delete", [node])
1807
+ },
1808
+ {
1809
+ id: "separator-2",
1810
+ label: "",
1811
+ type: "separator"
1812
+ },
1813
+ {
1814
+ id: "properties",
1815
+ label: "Properties",
1816
+ icon: "info",
1817
+ handler: (node) => this.onContextMenuAction?.("properties", [node])
1818
+ }
1819
+ ];
1820
+ }
1821
+ /**
1822
+ * Helper method to remove node from children recursively
1823
+ */
1824
+ removeNodeFromChildren(nodes, targetPath) {
1825
+ for (const node of nodes) if (node.children) {
1826
+ const childIndex = node.children.findIndex((child) => child.path === targetPath);
1827
+ if (childIndex >= 0) {
1828
+ node.children.splice(childIndex, 1);
1829
+ node.hasChildren = node.children.length > 0;
1830
+ return true;
1831
+ }
1832
+ if (this.removeNodeFromChildren(node.children, targetPath)) return true;
1833
+ }
1834
+ return false;
1835
+ }
1836
+ };
1837
+
1838
+ //#endregion
1839
+ //#region src/tree/utils/logger.ts
1840
+ var TreeLogger = class {
1841
+ constructor() {
1842
+ this.isEnabled = true;
1843
+ this.logs = [];
1844
+ this.maxLogs = 1e3;
1845
+ }
1846
+ enable() {
1847
+ this.isEnabled = true;
1848
+ console.log("🌳 TreeComponent logging enabled");
1849
+ }
1850
+ disable() {
1851
+ this.isEnabled = false;
1852
+ console.log("🌳 TreeComponent logging disabled");
1853
+ }
1854
+ log(level, message, context, data) {
1855
+ if (!this.isEnabled) return;
1856
+ const entry = {
1857
+ timestamp: new Date().toISOString(),
1858
+ level,
1859
+ message,
1860
+ context,
1861
+ data
1862
+ };
1863
+ this.logs.push(entry);
1864
+ if (this.logs.length > this.maxLogs) this.logs.shift();
1865
+ const prefix = this.getPrefix(level);
1866
+ const contextStr = context ? this.formatContext(context) : "";
1867
+ const fullMessage = `${prefix} ${message}${contextStr}`;
1868
+ switch (level) {
1869
+ case "debug":
1870
+ console.debug(fullMessage, data || "");
1871
+ break;
1872
+ case "info":
1873
+ console.info(fullMessage, data || "");
1874
+ break;
1875
+ case "warn":
1876
+ console.warn(fullMessage, data || "");
1877
+ break;
1878
+ case "error":
1879
+ console.error(fullMessage, data || "");
1880
+ break;
1881
+ }
1882
+ }
1883
+ getPrefix(level) {
1884
+ const timestamp = new Date().toLocaleTimeString();
1885
+ switch (level) {
1886
+ case "debug": return `🔍 [${timestamp}] TreeComponent:`;
1887
+ case "info": return `ℹ️ [${timestamp}] TreeComponent:`;
1888
+ case "warn": return `⚠️ [${timestamp}] TreeComponent:`;
1889
+ case "error": return `❌ [${timestamp}] TreeComponent:`;
1890
+ }
1891
+ }
1892
+ formatContext(context) {
1893
+ const parts = [];
1894
+ if (context.component) parts.push(`[${context.component}]`);
1895
+ if (context.nodeId) parts.push(`(node: ${context.nodeId})`);
1896
+ if (context.operation) parts.push(`{${context.operation}}`);
1897
+ Object.entries(context).forEach(([key, value]) => {
1898
+ if (![
1899
+ "component",
1900
+ "nodeId",
1901
+ "operation"
1902
+ ].includes(key)) parts.push(`${key}=${value}`);
1903
+ });
1904
+ return parts.length > 0 ? ` ${parts.join(" ")}` : "";
1905
+ }
1906
+ debug(message, context, data) {
1907
+ this.log("debug", message, context, data);
1908
+ }
1909
+ info(message, context, data) {
1910
+ this.log("info", message, context, data);
1911
+ }
1912
+ warn(message, context, data) {
1913
+ this.log("warn", message, context, data);
1914
+ }
1915
+ error(message, context, data) {
1916
+ this.log("error", message, context, data);
1917
+ }
1918
+ selection(operation, nodeId, data) {
1919
+ this.debug(`Selection ${operation}`, {
1920
+ component: "Selection",
1921
+ nodeId,
1922
+ operation
1923
+ }, data);
1924
+ }
1925
+ expansion(operation, nodeId, data) {
1926
+ this.debug(`Expansion ${operation}`, {
1927
+ component: "Expansion",
1928
+ nodeId,
1929
+ operation
1930
+ }, data);
1931
+ }
1932
+ interaction(operation, nodeId, data) {
1933
+ this.debug(`User interaction: ${operation}`, {
1934
+ component: "Interaction",
1935
+ nodeId,
1936
+ operation
1937
+ }, data);
1938
+ }
1939
+ rendering(component, nodeId, data) {
1940
+ this.debug(`Rendering`, {
1941
+ component,
1942
+ nodeId,
1943
+ operation: "render"
1944
+ }, data);
1945
+ }
1946
+ cssClasses(nodeId, classes, selected, focused) {
1947
+ this.debug(`CSS classes applied`, {
1948
+ component: "Styling",
1949
+ nodeId,
1950
+ selected: selected.toString(),
1951
+ focused: focused.toString()
1952
+ }, { classes });
1953
+ }
1954
+ providerCall(method, data) {
1955
+ this.debug(`Provider method called`, {
1956
+ component: "Provider",
1957
+ operation: method
1958
+ }, data);
1959
+ }
1960
+ stateChange(component, property, oldValue, newValue) {
1961
+ this.debug(`State change`, {
1962
+ component,
1963
+ operation: "state-change",
1964
+ property
1965
+ }, {
1966
+ oldValue,
1967
+ newValue
1968
+ });
1969
+ }
1970
+ getLogs(filter) {
1971
+ if (!filter) return [...this.logs];
1972
+ return this.logs.filter((log) => {
1973
+ if (filter.level && log.level !== filter.level) return false;
1974
+ if (filter.component && log.context?.component !== filter.component) return false;
1975
+ return true;
1976
+ });
1977
+ }
1978
+ clearLogs() {
1979
+ this.logs = [];
1980
+ console.log("🌳 TreeComponent logs cleared");
1981
+ }
1982
+ exportLogs() {
1983
+ return JSON.stringify(this.logs, null, 2);
1984
+ }
1985
+ };
1986
+ const logger = new TreeLogger();
1987
+ if (typeof window !== "undefined" && process.env.NODE_ENV === "development") {
1988
+ logger.enable();
1989
+ window.treeLogger = logger;
1990
+ }
1991
+
1992
+ //#endregion
1993
+ //#region src/tree/models/TreeModel.ts
1994
+ var TreeModel = class {
1995
+ constructor(provider) {
1996
+ this.provider = provider;
1997
+ this.nodes = [];
1998
+ this.nodeMap = observable.map();
1999
+ this.isLoading = false;
2000
+ this.errors = observable.map();
2001
+ this.selectedNodes = observable.map();
2002
+ this.focusedNode = null;
2003
+ this.checkboxStates = observable.map();
2004
+ this.expandedNodes = observable.map();
2005
+ this.loadingNodes = observable.map();
2006
+ this.contextMenuVisible = false;
2007
+ this.contextMenuPosition = {
2008
+ x: 0,
2009
+ y: 0
2010
+ };
2011
+ this.contextMenuItems = [];
2012
+ this.contextMenuNodes = [];
2013
+ this.loadNodes = flow(function* (options) {
2014
+ this.isLoading = true;
2015
+ this.errors.clear();
2016
+ try {
2017
+ const result = yield this.provider.loadNodes(options);
2018
+ this.nodes = result.nodes;
2019
+ this.nodeMap.clear();
2020
+ result.nodes.forEach((node) => this.nodeMap.set(node.id, node));
2021
+ } catch (error) {
2022
+ const errorKey = "loadNodes";
2023
+ this.errors.set(errorKey, error instanceof Error ? error : new Error(String(error)));
2024
+ } finally {
2025
+ this.isLoading = false;
2026
+ }
2027
+ });
2028
+ this.expandNode = flow(function* (node) {
2029
+ logger.expansion("expandNode called", node.id, {
2030
+ hasChildren: node.hasChildren,
2031
+ currentlyExpanded: this.isNodeExpanded(node.id),
2032
+ hasLoadedChildren: !!(node.children && node.children.length > 0)
2033
+ });
2034
+ if (!this.nodeMap.has(node.id)) {
2035
+ logger.expansion("expandNode skipped - node not in tree", node.id);
2036
+ return;
2037
+ }
2038
+ this.expandedNodes.set(node.id, true);
2039
+ logger.expansion("expansion state set to true", node.id);
2040
+ if (this.provider.onNodeExpansion) {
2041
+ logger.providerCall("onNodeExpansion", {
2042
+ nodeId: node.id,
2043
+ expanded: true
2044
+ });
2045
+ this.provider.onNodeExpansion(node, true);
2046
+ }
2047
+ if (node.hasChildren && (!node.children || node.children.length === 0)) {
2048
+ logger.expansion("loading children", node.id, { hasChildren: node.hasChildren });
2049
+ this.loadingNodes.set(node.id, true);
2050
+ try {
2051
+ logger.providerCall("loadChildren", { nodeId: node.id });
2052
+ const result = yield this.provider.loadChildren(node);
2053
+ logger.expansion("children loaded successfully", node.id, { childCount: result.nodes.length });
2054
+ const existingNode = this.nodeMap.get(node.id);
2055
+ if (existingNode) {
2056
+ existingNode.children = result.nodes;
2057
+ result.nodes.forEach((child) => {
2058
+ this.nodeMap.set(child.id, child);
2059
+ });
2060
+ }
2061
+ node.children = result.nodes;
2062
+ const updateNodeInTree = (nodes) => {
2063
+ for (const treeNode of nodes) {
2064
+ if (treeNode.id === node.id) treeNode.children = result.nodes;
2065
+ if (treeNode.children) updateNodeInTree(treeNode.children);
2066
+ }
2067
+ };
2068
+ updateNodeInTree(this.nodes);
2069
+ if (this.provider.allowPartialSelection && this.provider.useCheckboxSelection) {
2070
+ const parentCheckboxState = this.getCheckboxState(node.id);
2071
+ logger.debug("Applying parent checkbox state to newly loaded children", {
2072
+ component: "Checkbox",
2073
+ nodeId: node.id,
2074
+ operation: "children-loaded-inheritance"
2075
+ }, {
2076
+ parentState: parentCheckboxState,
2077
+ childrenCount: result.nodes.length,
2078
+ childIds: result.nodes.map((child) => child.id)
2079
+ });
2080
+ if (parentCheckboxState === "checked" || parentCheckboxState === "unchecked") result.nodes.forEach((child) => {
2081
+ const previousChildState = this.checkboxStates.get(child.id);
2082
+ this.checkboxStates.set(child.id, parentCheckboxState);
2083
+ logger.debug("Child checkbox state inherited from parent", {
2084
+ component: "Checkbox",
2085
+ nodeId: child.id
2086
+ }, {
2087
+ parentNode: node.id,
2088
+ parentState: parentCheckboxState,
2089
+ previousChildState,
2090
+ newChildState: parentCheckboxState
2091
+ });
2092
+ if (parentCheckboxState === "checked") {
2093
+ this.selectedNodes.set(child.id, child);
2094
+ logger.selection("child auto-selected via parent inheritance", child.id, { parentNode: node.id });
2095
+ } else {
2096
+ this.selectedNodes.delete(child.id);
2097
+ logger.selection("child auto-deselected via parent inheritance", child.id, { parentNode: node.id });
2098
+ }
2099
+ if (child.children && child.children.length > 0) this.updateChildCheckboxes(child, parentCheckboxState);
2100
+ });
2101
+ }
2102
+ } catch (error) {
2103
+ const errorKey = `loadChildren-${node.id}`;
2104
+ this.errors.set(errorKey, error instanceof Error ? error : new Error(String(error)));
2105
+ this.expandedNodes.set(node.id, false);
2106
+ } finally {
2107
+ this.loadingNodes.delete(node.id);
2108
+ }
2109
+ }
2110
+ });
2111
+ makeAutoObservable(this, {});
2112
+ }
2113
+ /**
2114
+ * Whether the tree has any nodes loaded
2115
+ */
2116
+ get hasNodes() {
2117
+ return this.nodes.length > 0;
2118
+ }
2119
+ /**
2120
+ * Total count of nodes currently loaded
2121
+ */
2122
+ get nodeCount() {
2123
+ return this.nodes.length;
2124
+ }
2125
+ /**
2126
+ * Whether data has been loaded (regardless of success/failure)
2127
+ */
2128
+ get isLoaded() {
2129
+ return !this.isLoading && (this.hasNodes || this.errors.size > 0);
2130
+ }
2131
+ /**
2132
+ * Array of currently selected nodes (computed from selectedNodes map)
2133
+ */
2134
+ get selectedNodesArray() {
2135
+ return Array.from(this.selectedNodes.values());
2136
+ }
2137
+ /**
2138
+ * Whether any nodes are currently selected
2139
+ */
2140
+ get hasSelection() {
2141
+ return this.selectedNodes.size > 0;
2142
+ }
2143
+ /**
2144
+ * Check if a specific node is selected
2145
+ */
2146
+ isNodeSelected(nodeId) {
2147
+ return this.selectedNodes.has(nodeId);
2148
+ }
2149
+ /**
2150
+ * Check if a specific node is expanded
2151
+ */
2152
+ isNodeExpanded(nodeId) {
2153
+ return this.expandedNodes.get(nodeId) ?? false;
2154
+ }
2155
+ /**
2156
+ * Check if a specific node is loading
2157
+ */
2158
+ isNodeLoading(nodeId) {
2159
+ return this.loadingNodes.get(nodeId) ?? false;
2160
+ }
2161
+ /**
2162
+ * Select a specific node
2163
+ * @param node Node to select
2164
+ */
2165
+ selectNode(node) {
2166
+ const previousSelection = this.selectedNodesArray.map((n) => n.id);
2167
+ logger.selection("selectNode called", node.id, {
2168
+ previouslySelected: previousSelection,
2169
+ isMultiSelect: this.provider.isMultiSelectEnabled
2170
+ });
2171
+ if (!this.provider.isMultiSelectEnabled) {
2172
+ logger.selection("clearing selection (single-select mode)", node.id);
2173
+ this.selectedNodes.clear();
2174
+ }
2175
+ this.selectedNodes.set(node.id, node);
2176
+ logger.selection("node added to selection", node.id, { totalSelected: this.selectedNodes.size });
2177
+ const previousFocus = this.focusedNode;
2178
+ this.focusedNode = node.id;
2179
+ logger.stateChange("TreeModel", "focusedNode", previousFocus, this.focusedNode);
2180
+ if (this.provider.onSelectionChange) {
2181
+ logger.providerCall("onSelectionChange", {
2182
+ selectedCount: this.selectedNodesArray.length,
2183
+ selectionType: this.provider.isMultiSelectEnabled ? "multi" : "single"
2184
+ });
2185
+ this.provider.onSelectionChange({
2186
+ selectedNodes: this.selectedNodesArray,
2187
+ previousSelection: [],
2188
+ selectionType: this.provider.isMultiSelectEnabled ? "multi" : "single",
2189
+ trigger: "click"
2190
+ });
2191
+ }
2192
+ }
2193
+ /**
2194
+ * Deselect a specific node
2195
+ * @param node Node to deselect
2196
+ */
2197
+ deselectNode(node) {
2198
+ logger.selection("deselectNode called", node.id, { wasSelected: this.selectedNodes.has(node.id) });
2199
+ if (this.selectedNodes.has(node.id)) {
2200
+ this.selectedNodes.delete(node.id);
2201
+ logger.selection("node removed from selection", node.id, { remainingSelected: this.selectedNodes.size });
2202
+ if (this.focusedNode === node.id) {
2203
+ const previousFocus = this.focusedNode;
2204
+ this.focusedNode = null;
2205
+ logger.stateChange("TreeModel", "focusedNode", previousFocus, this.focusedNode);
2206
+ }
2207
+ if (this.provider.onSelectionChange) {
2208
+ logger.providerCall("onSelectionChange", {
2209
+ selectedCount: this.selectedNodesArray.length,
2210
+ selectionType: this.provider.isMultiSelectEnabled ? "multi" : "single"
2211
+ });
2212
+ this.provider.onSelectionChange({
2213
+ selectedNodes: this.selectedNodesArray,
2214
+ previousSelection: [],
2215
+ selectionType: this.provider.isMultiSelectEnabled ? "multi" : "single",
2216
+ trigger: "click"
2217
+ });
2218
+ }
2219
+ }
2220
+ }
2221
+ /**
2222
+ * Clear all selected nodes
2223
+ */
2224
+ clearSelection() {
2225
+ if (this.selectedNodes.size > 0) {
2226
+ this.selectedNodes.clear();
2227
+ this.focusedNode = null;
2228
+ if (this.provider.onSelectionChange) this.provider.onSelectionChange({
2229
+ selectedNodes: [],
2230
+ previousSelection: [],
2231
+ selectionType: this.provider.isMultiSelectEnabled ? "multi" : "single",
2232
+ trigger: "api"
2233
+ });
2234
+ }
2235
+ }
2236
+ /**
2237
+ * Select all nodes (only if multi-select is enabled)
2238
+ */
2239
+ selectAll() {
2240
+ if (!this.provider.isMultiSelectEnabled) return;
2241
+ this.nodes.forEach((node) => {
2242
+ this.selectedNodes.set(node.id, node);
2243
+ });
2244
+ if (this.nodes.length > 0) {
2245
+ const firstNode = this.nodes[0];
2246
+ if (firstNode) this.focusedNode = firstNode.id;
2247
+ }
2248
+ if (this.provider.onSelectionChange) this.provider.onSelectionChange({
2249
+ selectedNodes: this.selectedNodesArray,
2250
+ previousSelection: [],
2251
+ selectionType: "multi",
2252
+ trigger: "api"
2253
+ });
2254
+ }
2255
+ /**
2256
+ * Get checkbox state for a specific node
2257
+ */
2258
+ getCheckboxState(nodeId) {
2259
+ return this.checkboxStates.get(nodeId) ?? "unchecked";
2260
+ }
2261
+ /**
2262
+ * Handle checkbox click for a node
2263
+ */
2264
+ handleCheckboxChange(node, newState) {
2265
+ logger.interaction("handleCheckboxChange called", node.id, {
2266
+ newState,
2267
+ useCheckboxSelection: this.provider.useCheckboxSelection,
2268
+ allowPartialSelection: this.provider.allowPartialSelection,
2269
+ currentCheckboxState: this.checkboxStates.get(node.id),
2270
+ isCurrentlySelected: this.selectedNodes.has(node.id)
2271
+ });
2272
+ if (!this.provider.useCheckboxSelection) {
2273
+ logger.interaction("handleCheckboxChange aborted - useCheckboxSelection is false", node.id);
2274
+ return;
2275
+ }
2276
+ const previousState = this.checkboxStates.get(node.id);
2277
+ this.checkboxStates.set(node.id, newState);
2278
+ logger.stateChange("TreeModel", "checkboxStates", previousState, newState);
2279
+ const wasSelected = this.selectedNodes.has(node.id);
2280
+ if (newState === "checked") {
2281
+ this.selectedNodes.set(node.id, node);
2282
+ logger.selection("node added via checkbox", node.id, {
2283
+ wasSelected,
2284
+ nowSelected: true
2285
+ });
2286
+ } else {
2287
+ this.selectedNodes.delete(node.id);
2288
+ logger.selection("node removed via checkbox", node.id, {
2289
+ wasSelected,
2290
+ nowSelected: false
2291
+ });
2292
+ }
2293
+ if (this.provider.allowPartialSelection) {
2294
+ logger.debug("Updating parent/child checkbox relationships", {
2295
+ component: "Checkbox",
2296
+ nodeId: node.id
2297
+ });
2298
+ this.updateChildCheckboxes(node, newState);
2299
+ this.updateParentCheckboxes(node);
2300
+ }
2301
+ if (this.provider.onSelectionChange) {
2302
+ const selectionInfo = {
2303
+ selectedNodes: this.selectedNodesArray,
2304
+ previousSelection: [],
2305
+ selectionType: "multi",
2306
+ trigger: "click"
2307
+ };
2308
+ logger.providerCall("onSelectionChange", {
2309
+ selectedCount: selectionInfo.selectedNodes.length,
2310
+ trigger: selectionInfo.trigger
2311
+ });
2312
+ this.provider.onSelectionChange(selectionInfo);
2313
+ }
2314
+ }
2315
+ /**
2316
+ * Update all child checkboxes when parent state changes
2317
+ */
2318
+ updateChildCheckboxes(node, state) {
2319
+ logger.debug("updateChildCheckboxes called", {
2320
+ component: "Checkbox",
2321
+ nodeId: node.id,
2322
+ operation: "update-children"
2323
+ }, {
2324
+ parentState: state,
2325
+ hasChildren: !!node.children,
2326
+ childrenCount: node.children?.length || 0
2327
+ });
2328
+ if (!node.children || state === "indeterminate") {
2329
+ logger.debug("updateChildCheckboxes skipped", {
2330
+ component: "Checkbox",
2331
+ nodeId: node.id
2332
+ }, {
2333
+ reason: !node.children ? "no-children" : "indeterminate-state",
2334
+ hasChildren: !!node.children,
2335
+ state
2336
+ });
2337
+ return;
2338
+ }
2339
+ node.children.forEach((child) => {
2340
+ const previousState = this.checkboxStates.get(child.id);
2341
+ this.checkboxStates.set(child.id, state);
2342
+ logger.debug("updateChildCheckboxes - child updated", {
2343
+ component: "Checkbox",
2344
+ nodeId: child.id
2345
+ }, {
2346
+ previousState,
2347
+ newState: state,
2348
+ parentNode: node.id
2349
+ });
2350
+ if (state === "checked") {
2351
+ this.selectedNodes.set(child.id, child);
2352
+ logger.selection("child selected via parent", child.id, { parentNode: node.id });
2353
+ } else {
2354
+ this.selectedNodes.delete(child.id);
2355
+ logger.selection("child deselected via parent", child.id, { parentNode: node.id });
2356
+ }
2357
+ this.updateChildCheckboxes(child, state);
2358
+ });
2359
+ }
2360
+ /**
2361
+ * Update parent checkbox state based on children
2362
+ */
2363
+ updateParentCheckboxes(node) {
2364
+ const parent = this.findParentNode(node.id);
2365
+ logger.debug("updateParentCheckboxes called", {
2366
+ component: "Checkbox",
2367
+ nodeId: node.id,
2368
+ operation: "update-parent"
2369
+ }, {
2370
+ parentFound: !!parent,
2371
+ parentId: parent?.id
2372
+ });
2373
+ if (!parent || !parent.children) {
2374
+ logger.debug("updateParentCheckboxes skipped", {
2375
+ component: "Checkbox",
2376
+ nodeId: node.id
2377
+ }, { reason: !parent ? "no-parent" : "parent-no-children" });
2378
+ return;
2379
+ }
2380
+ const childStates = parent.children.map((child) => this.getCheckboxState(child.id));
2381
+ const checkedCount = childStates.filter((state) => state === "checked").length;
2382
+ const uncheckedCount = childStates.filter((state) => state === "unchecked").length;
2383
+ const indeterminateCount = childStates.filter((state) => state === "indeterminate").length;
2384
+ logger.debug("updateParentCheckboxes - analyzing children", {
2385
+ component: "Checkbox",
2386
+ nodeId: parent.id
2387
+ }, {
2388
+ totalChildren: parent.children.length,
2389
+ checkedCount,
2390
+ uncheckedCount,
2391
+ indeterminateCount,
2392
+ childStates
2393
+ });
2394
+ let parentState;
2395
+ if (checkedCount === parent.children.length) parentState = "checked";
2396
+ else if (checkedCount === 0 && indeterminateCount === 0) parentState = "unchecked";
2397
+ else parentState = "indeterminate";
2398
+ const previousParentState = this.checkboxStates.get(parent.id);
2399
+ this.checkboxStates.set(parent.id, parentState);
2400
+ logger.debug("updateParentCheckboxes - parent state calculated", {
2401
+ component: "Checkbox",
2402
+ nodeId: parent.id
2403
+ }, {
2404
+ previousState: previousParentState,
2405
+ newState: parentState,
2406
+ logic: checkedCount === parent.children.length ? "all-checked" : checkedCount === 0 && indeterminateCount === 0 ? "none-checked" : "mixed"
2407
+ });
2408
+ if (parentState === "checked") {
2409
+ this.selectedNodes.set(parent.id, parent);
2410
+ logger.selection("parent selected via children", parent.id, { newState: parentState });
2411
+ } else {
2412
+ this.selectedNodes.delete(parent.id);
2413
+ logger.selection("parent deselected via children", parent.id, { newState: parentState });
2414
+ }
2415
+ this.updateParentCheckboxes(parent);
2416
+ }
2417
+ /**
2418
+ * Find parent node of a given node ID
2419
+ */
2420
+ findParentNode(nodeId) {
2421
+ const findParent = (nodes, targetId, parentNode = null) => {
2422
+ for (const node of nodes) {
2423
+ if (node.id === targetId) return parentNode;
2424
+ if (node.children && node.children.length > 0) {
2425
+ const parent = findParent(node.children, targetId, node);
2426
+ if (parent) return parent;
2427
+ }
2428
+ }
2429
+ return null;
2430
+ };
2431
+ return findParent(this.nodes, nodeId);
2432
+ }
2433
+ /**
2434
+ * Set focus to a specific node
2435
+ * @param nodeId ID of node to focus
2436
+ */
2437
+ setFocus(nodeId) {
2438
+ if (this.nodeMap.has(nodeId)) this.focusedNode = nodeId;
2439
+ }
2440
+ /**
2441
+ * Move focus to next visible node
2442
+ */
2443
+ focusNext() {
2444
+ const flatNodes = this.getFlattenedVisibleNodes();
2445
+ const currentIndex = flatNodes.findIndex((node) => node.id === this.focusedNode);
2446
+ if (currentIndex >= 0 && currentIndex < flatNodes.length - 1) {
2447
+ const nextNode = flatNodes[currentIndex + 1];
2448
+ if (nextNode) {
2449
+ this.focusedNode = nextNode.id;
2450
+ return true;
2451
+ }
2452
+ }
2453
+ return false;
2454
+ }
2455
+ /**
2456
+ * Move focus to previous visible node
2457
+ */
2458
+ focusPrevious() {
2459
+ const flatNodes = this.getFlattenedVisibleNodes();
2460
+ const currentIndex = flatNodes.findIndex((node) => node.id === this.focusedNode);
2461
+ if (currentIndex > 0) {
2462
+ const prevNode = flatNodes[currentIndex - 1];
2463
+ if (prevNode) {
2464
+ this.focusedNode = prevNode.id;
2465
+ return true;
2466
+ }
2467
+ }
2468
+ return false;
2469
+ }
2470
+ /**
2471
+ * Move focus to first visible node
2472
+ */
2473
+ focusFirst() {
2474
+ const flatNodes = this.getFlattenedVisibleNodes();
2475
+ if (flatNodes.length > 0) {
2476
+ const firstNode = flatNodes[0];
2477
+ if (firstNode) {
2478
+ this.focusedNode = firstNode.id;
2479
+ return true;
2480
+ }
2481
+ }
2482
+ return false;
2483
+ }
2484
+ /**
2485
+ * Move focus to last visible node
2486
+ */
2487
+ focusLast() {
2488
+ const flatNodes = this.getFlattenedVisibleNodes();
2489
+ if (flatNodes.length > 0) {
2490
+ const lastNode = flatNodes[flatNodes.length - 1];
2491
+ if (lastNode) {
2492
+ this.focusedNode = lastNode.id;
2493
+ return true;
2494
+ }
2495
+ }
2496
+ return false;
2497
+ }
2498
+ /**
2499
+ * Get flattened list of all currently visible nodes
2500
+ * @private
2501
+ */
2502
+ getFlattenedVisibleNodes() {
2503
+ const result = [];
2504
+ const processNodes = (nodes) => {
2505
+ for (const node of nodes) {
2506
+ result.push(node);
2507
+ if (this.isNodeExpanded(node.id) && node.children && node.children.length > 0) processNodes(node.children);
2508
+ }
2509
+ };
2510
+ processNodes(this.nodes);
2511
+ return result;
2512
+ }
2513
+ /**
2514
+ * Collapse a specific node
2515
+ * @param node Node to collapse
2516
+ */
2517
+ collapseNode(node) {
2518
+ logger.expansion("collapseNode called", node.id, { currentlyExpanded: this.isNodeExpanded(node.id) });
2519
+ this.expandedNodes.set(node.id, false);
2520
+ logger.expansion("expansion state set to false", node.id);
2521
+ if (this.provider.onNodeExpansion) {
2522
+ logger.providerCall("onNodeExpansion", {
2523
+ nodeId: node.id,
2524
+ expanded: false
2525
+ });
2526
+ this.provider.onNodeExpansion(node, false);
2527
+ }
2528
+ }
2529
+ /**
2530
+ * Toggle expansion state of a specific node
2531
+ * @param node Node to toggle
2532
+ */
2533
+ toggleExpansion(node) {
2534
+ if (this.isNodeExpanded(node.id)) this.collapseNode(node);
2535
+ else this.expandNode(node);
2536
+ }
2537
+ /**
2538
+ * Show context menu for a specific node
2539
+ * @param node Target node
2540
+ * @param event Mouse event
2541
+ */
2542
+ showContextMenu(node, event) {
2543
+ const menuItems = this.provider.getNodeContextMenu?.(node) || [];
2544
+ if (menuItems.length === 0) return;
2545
+ this.contextMenuVisible = true;
2546
+ this.contextMenuPosition = {
2547
+ x: event.clientX,
2548
+ y: event.clientY
2549
+ };
2550
+ this.contextMenuItems = menuItems;
2551
+ this.contextMenuNodes = [node];
2552
+ }
2553
+ /**
2554
+ * Show context menu for multiple selected nodes
2555
+ * @param nodes Selected nodes
2556
+ * @param event Mouse event
2557
+ */
2558
+ showMultiNodeContextMenu(nodes, event) {
2559
+ const menuItems = this.provider.getMultiNodeContextMenu?.(nodes) || [];
2560
+ if (menuItems.length === 0) return;
2561
+ this.contextMenuVisible = true;
2562
+ this.contextMenuPosition = {
2563
+ x: event.clientX,
2564
+ y: event.clientY
2565
+ };
2566
+ this.contextMenuItems = menuItems;
2567
+ this.contextMenuNodes = nodes;
2568
+ }
2569
+ /**
2570
+ * Hide context menu
2571
+ */
2572
+ hideContextMenu() {
2573
+ this.contextMenuVisible = false;
2574
+ this.contextMenuItems = [];
2575
+ this.contextMenuNodes = [];
2576
+ }
2577
+ /**
2578
+ * Handle context menu item click
2579
+ * @param menuItem Clicked menu item
2580
+ */
2581
+ handleContextMenuAction(menuItem) {
2582
+ if (this.provider.onContextMenuAction) this.provider.onContextMenuAction(menuItem.id, this.contextMenuNodes);
2583
+ this.hideContextMenu();
2584
+ }
2585
+ };
2586
+
2587
+ //#endregion
2588
+ //#region src/tree/components/TreeCheckbox.tsx
2589
+ /**
2590
+ * TreeCheckbox Component - 3-state checkbox for tree selection
2591
+ */
2592
+ const TreeCheckbox = observer(({ state, onChange, disabled = false, className, ariaLabel, size = "md", nodeId = "unknown" }) => {
2593
+ logger.rendering("TreeCheckbox", nodeId, {
2594
+ state,
2595
+ disabled,
2596
+ size,
2597
+ hasOnChange: !!onChange
2598
+ });
2599
+ const handleClick = (event) => {
2600
+ event.stopPropagation();
2601
+ logger.interaction("TreeCheckbox click", nodeId, {
2602
+ currentState: state,
2603
+ disabled,
2604
+ eventType: "click"
2605
+ });
2606
+ if (disabled) {
2607
+ logger.interaction("TreeCheckbox click ignored (disabled)", nodeId);
2608
+ return;
2609
+ }
2610
+ const newState = state === "checked" ? "unchecked" : "checked";
2611
+ logger.interaction("TreeCheckbox state change", nodeId, {
2612
+ from: state,
2613
+ to: newState,
2614
+ trigger: "click"
2615
+ });
2616
+ onChange(newState);
2617
+ };
2618
+ const handleKeyDown = (event) => {
2619
+ if (event.key === " " || event.key === "Enter") {
2620
+ event.preventDefault();
2621
+ logger.interaction("TreeCheckbox keydown", nodeId, {
2622
+ key: event.key,
2623
+ currentState: state
2624
+ });
2625
+ handleClick(event);
2626
+ }
2627
+ };
2628
+ const sizeClasses = {
2629
+ sm: "w-3 h-3",
2630
+ md: "w-4 h-4",
2631
+ lg: "w-5 h-5"
2632
+ };
2633
+ const iconSizeClasses = {
2634
+ sm: "w-2 h-2",
2635
+ md: "w-3 h-3",
2636
+ lg: "w-4 h-4"
2637
+ };
2638
+ const computedClasses = cn(
2639
+ "flex items-center justify-center rounded border cursor-pointer transition-all",
2640
+ "focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1",
2641
+ sizeClasses[size],
2642
+ // Base styles
2643
+ "border-border bg-background",
2644
+ // State-specific styles
2645
+ state === "checked" && "bg-primary border-primary text-primary-foreground",
2646
+ state === "indeterminate" && "bg-primary border-primary text-primary-foreground",
2647
+ state === "unchecked" && "hover:border-primary/50",
2648
+ // Disabled styles
2649
+ disabled && "opacity-50 cursor-not-allowed pointer-events-none",
2650
+ className
2651
+ );
2652
+ logger.cssClasses(`TreeCheckbox-${nodeId}`, computedClasses, state === "checked", false);
2653
+ return /* @__PURE__ */ jsxs("div", {
2654
+ role: "checkbox",
2655
+ "aria-checked": state === "indeterminate" ? "mixed" : state === "checked",
2656
+ "aria-label": ariaLabel,
2657
+ className: computedClasses,
2658
+ onClick: handleClick,
2659
+ onKeyDown: handleKeyDown,
2660
+ tabIndex: disabled ? -1 : 0,
2661
+ children: [state === "checked" && /* @__PURE__ */ jsx(Check, { className: cn(iconSizeClasses[size], "stroke-[3]") }), state === "indeterminate" && /* @__PURE__ */ jsx(Minus, { className: cn(iconSizeClasses[size], "stroke-[3]") })]
2662
+ });
2663
+ });
2664
+
2665
+ //#endregion
2666
+ //#region src/tree/components/TreeNodeList.tsx
2667
+ const TreeInlineRename = ({ currentName, onCommit, onCancel }) => {
2668
+ const [value, setValue] = useState(currentName);
2669
+ const inputRef = useRef(null);
2670
+ const mountTimeRef = useRef(Date.now());
2671
+ const committedRef = useRef(false);
2672
+ useEffect(() => {
2673
+ mountTimeRef.current = Date.now();
2674
+ const raf = requestAnimationFrame(() => {
2675
+ const input = inputRef.current;
2676
+ if (input) {
2677
+ input.focus();
2678
+ const dotIndex = currentName.lastIndexOf(".");
2679
+ input.setSelectionRange(0, dotIndex > 0 ? dotIndex : currentName.length);
2680
+ }
2681
+ });
2682
+ return () => cancelAnimationFrame(raf);
2683
+ }, [currentName]);
2684
+ const commit = () => {
2685
+ if (committedRef.current) return;
2686
+ committedRef.current = true;
2687
+ const trimmed = value.trim();
2688
+ if (trimmed && trimmed !== currentName) onCommit(trimmed);
2689
+ else onCancel();
2690
+ };
2691
+ const handleBlur = () => {
2692
+ if (Date.now() - mountTimeRef.current < 200) return;
2693
+ commit();
2694
+ };
2695
+ return /* @__PURE__ */ jsx("input", {
2696
+ ref: inputRef,
2697
+ value,
2698
+ onChange: (e) => setValue(e.target.value),
2699
+ onKeyDown: (e) => {
2700
+ e.stopPropagation();
2701
+ if (e.key === "Enter") {
2702
+ e.preventDefault();
2703
+ commit();
2704
+ }
2705
+ if (e.key === "Escape") {
2706
+ e.preventDefault();
2707
+ onCancel();
2708
+ }
2709
+ },
2710
+ onBlur: handleBlur,
2711
+ onClick: (e) => e.stopPropagation(),
2712
+ onDoubleClick: (e) => e.stopPropagation(),
2713
+ className: "flex-1 bg-background border border-primary rounded px-1 py-0 text-[13px] outline-none min-w-0"
2714
+ });
2715
+ };
2716
+ /**
2717
+ * TreeNodeList component renders a list of tree nodes with proper nesting and interaction
2718
+ */
2719
+ const TreeNodeList = observer(({ nodes, treeModel, depth = 0, className }) => {
2720
+ const fileBrowserCtx = useFileBrowserContext();
2721
+ const renderNode = (node) => {
2722
+ const isSelected = treeModel.isNodeSelected(node.id);
2723
+ const isExpanded = treeModel.isNodeExpanded(node.id);
2724
+ const isFocused = treeModel.focusedNode === node.id;
2725
+ const isLoading = treeModel.isNodeLoading(node.id);
2726
+ const isDirectory = node.type === "directory";
2727
+ const hasChildren = node.hasChildren || node.children && node.children.length > 0;
2728
+ const showCheckbox = treeModel.provider.useCheckboxSelection;
2729
+ const [isHovered, setIsHovered] = useState(false);
2730
+ const isRenaming = fileBrowserCtx?.renameState?.itemId === node.id && fileBrowserCtx?.renameState?.source === "tree";
2731
+ const selectionTheme = treeModel.provider.getSelectionTheme?.() || DEFAULT_SELECTION_THEME;
2732
+ const nodeClasses = cn(
2733
+ // Base styles
2734
+ "group flex items-center gap-2 py-1 px-2 text-[13px] cursor-default select-none transition-colors duration-150",
2735
+ "focus:outline-none",
2736
+ // Selection styling - must come before hover so selected state is visible
2737
+ isSelected ? "bg-accent text-accent-foreground" : "hover:bg-muted/50",
2738
+ // Focus styles
2739
+ isFocused && !isSelected && "ring-1 ring-primary/50"
2740
+ );
2741
+ const handleNodeClick = (event) => {
2742
+ event.stopPropagation();
2743
+ logger.interaction("TreeNode clicked", node.id, { name: node.name });
2744
+ if (event.ctrlKey || event.metaKey) {
2745
+ if (treeModel.provider.isMultiSelectEnabled) if (isSelected) treeModel.deselectNode(node);
2746
+ else treeModel.selectNode(node);
2747
+ } else if (!isSelected) treeModel.selectNode(node);
2748
+ };
2749
+ const handleExpandClick = (event) => {
2750
+ event.stopPropagation();
2751
+ logger.interaction("TreeNode expand/collapse clicked", node.id, { name: node.name });
2752
+ if (hasChildren) treeModel.toggleExpansion(node);
2753
+ };
2754
+ const handleContextMenu = (event) => {
2755
+ event.preventDefault();
2756
+ event.stopPropagation();
2757
+ logger.interaction("TreeNode context menu", node.id, { name: node.name });
2758
+ if (!isSelected && treeModel.selectedNodesArray.length <= 1) treeModel.selectNode(node);
2759
+ const rect = {
2760
+ x: event.clientX,
2761
+ y: event.clientY
2762
+ };
2763
+ const selectedNodes = treeModel.selectedNodesArray;
2764
+ if (selectedNodes.length > 1 && isSelected) treeModel.showMultiNodeContextMenu(selectedNodes, event.nativeEvent);
2765
+ else treeModel.showContextMenu(node, event.nativeEvent);
2766
+ };
2767
+ const handleMenuButtonClick = (event) => {
2768
+ event.stopPropagation();
2769
+ logger.interaction("TreeNode menu button clicked", node.id, { name: node.name });
2770
+ const rect = event.currentTarget.getBoundingClientRect();
2771
+ const fakeEvent = new MouseEvent("contextmenu", {
2772
+ clientX: rect.right - 10,
2773
+ clientY: rect.bottom,
2774
+ bubbles: true,
2775
+ cancelable: true
2776
+ });
2777
+ const selectedNodes = treeModel.selectedNodesArray;
2778
+ if (selectedNodes.length > 1 && isSelected) treeModel.showMultiNodeContextMenu(selectedNodes, fakeEvent);
2779
+ else treeModel.showContextMenu(node, fakeEvent);
2780
+ };
2781
+ const iconSize = "w-4 h-4";
2782
+ const renderIcon = () => {
2783
+ const customIcon = treeModel.provider.getNodeIcon?.(node);
2784
+ if (customIcon) if (typeof customIcon === "string") return resolveIcon(customIcon, iconSize);
2785
+ else {
2786
+ const IconComponent = customIcon;
2787
+ return /* @__PURE__ */ jsx(IconComponent, { className: iconSize });
2788
+ }
2789
+ if (isDirectory) return resolveIcon(isExpanded ? "folder-open" : "folder", iconSize);
2790
+ else return resolveIcon("file", iconSize);
2791
+ };
2792
+ return /* @__PURE__ */ jsxs("div", {
2793
+ className: "w-full",
2794
+ children: [/* @__PURE__ */ jsxs("div", {
2795
+ className: nodeClasses,
2796
+ style: { paddingLeft: `${depth * 20 + 8}px` },
2797
+ onClick: handleNodeClick,
2798
+ onContextMenu: handleContextMenu,
2799
+ onMouseEnter: () => setIsHovered(true),
2800
+ onMouseLeave: () => setIsHovered(false),
2801
+ tabIndex: isFocused ? 0 : -1,
2802
+ role: "treeitem",
2803
+ "aria-expanded": hasChildren ? isExpanded : void 0,
2804
+ "aria-selected": isSelected,
2805
+ "aria-level": depth + 1,
2806
+ "aria-label": `${node.type === "directory" ? "Folder" : "File"}: ${node.name}`,
2807
+ children: [
2808
+ /* @__PURE__ */ jsx("div", {
2809
+ className: "flex-shrink-0 w-4 h-4",
2810
+ children: hasChildren && /* @__PURE__ */ jsx("button", {
2811
+ className: "w-4 h-4 flex items-center justify-center hover:bg-muted rounded-sm transition-colors",
2812
+ onClick: handleExpandClick,
2813
+ "aria-label": isExpanded ? "Collapse" : "Expand",
2814
+ children: isLoading ? /* @__PURE__ */ jsx(Loader2, { className: "w-3 h-3 animate-spin" }) : isExpanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "w-3 h-3" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "w-3 h-3" })
2815
+ })
2816
+ }),
2817
+ showCheckbox && /* @__PURE__ */ jsx(TreeCheckbox, {
2818
+ state: treeModel.getCheckboxState(node.id),
2819
+ onChange: (newState) => {
2820
+ logger.interaction("TreeCheckbox changed", node.id, {
2821
+ name: node.name,
2822
+ newState
2823
+ });
2824
+ treeModel.handleCheckboxChange(node, newState);
2825
+ },
2826
+ nodeId: node.id
2827
+ }),
2828
+ /* @__PURE__ */ jsx("div", {
2829
+ className: "flex-shrink-0 w-4 h-4",
2830
+ children: renderIcon()
2831
+ }),
2832
+ isRenaming && fileBrowserCtx ? /* @__PURE__ */ jsx(TreeInlineRename, {
2833
+ currentName: fileBrowserCtx.renameState.currentName,
2834
+ onCommit: fileBrowserCtx.onRenameCommit,
2835
+ onCancel: fileBrowserCtx.onRenameCancel
2836
+ }) : /* @__PURE__ */ jsx("span", {
2837
+ className: "flex-1 truncate",
2838
+ children: node.name
2839
+ }),
2840
+ /* @__PURE__ */ jsx("button", {
2841
+ className: cn(
2842
+ "flex-shrink-0 w-6 h-6 flex items-center justify-center rounded hover:bg-muted/70 transition-colors cursor-pointer",
2843
+ // Show on hover or when node is selected
2844
+ "opacity-0 group-hover:opacity-100",
2845
+ (isSelected || isHovered) && "opacity-100"
2846
+ ),
2847
+ onClick: handleMenuButtonClick,
2848
+ "aria-label": "Show context menu",
2849
+ children: /* @__PURE__ */ jsx(MoreVertical, { className: "w-4 h-4" })
2850
+ })
2851
+ ]
2852
+ }), isExpanded && hasChildren && node.children && /* @__PURE__ */ jsx(TreeNodeList, {
2853
+ nodes: node.children,
2854
+ treeModel,
2855
+ depth: depth + 1,
2856
+ className
2857
+ })]
2858
+ }, node.id);
2859
+ };
2860
+ return /* @__PURE__ */ jsx("div", {
2861
+ className: cn("w-full", className),
2862
+ children: nodes.map(renderNode)
2863
+ });
2864
+ });
2865
+
2866
+ //#endregion
2867
+ //#region src/tree/components/TreeContextMenu.tsx
2868
+ /**
2869
+ * TreeContextMenu - Context menu component for tree nodes
2870
+ *
2871
+ * Features:
2872
+ * - Renders context menu items with proper styling (no icons)
2873
+ * - Handles click outside to close
2874
+ * - Supports separators and disabled items
2875
+ * - Positioned absolutely at cursor position
2876
+ * - Keyboard navigation support
2877
+ */
2878
+ const TreeContextMenu = observer(({ items, position, visible, onClose, onItemClick, className = "" }) => {
2879
+ const menuRef = useRef(null);
2880
+ useEffect(() => {
2881
+ if (!visible) return;
2882
+ const handleClickOutside = (event) => {
2883
+ if (menuRef.current && !menuRef.current.contains(event.target)) onClose();
2884
+ };
2885
+ const handleEscape = (event) => {
2886
+ if (event.key === "Escape") onClose();
2887
+ };
2888
+ document.addEventListener("mousedown", handleClickOutside);
2889
+ document.addEventListener("keydown", handleEscape);
2890
+ return () => {
2891
+ document.removeEventListener("mousedown", handleClickOutside);
2892
+ document.removeEventListener("keydown", handleEscape);
2893
+ };
2894
+ }, [visible, onClose]);
2895
+ const handleItemClick = (item, event) => {
2896
+ event.preventDefault();
2897
+ event.stopPropagation();
2898
+ if (item.disabled) return;
2899
+ onItemClick(item);
2900
+ onClose();
2901
+ };
2902
+ if (!visible) return null;
2903
+ return /* @__PURE__ */ jsx("div", {
2904
+ ref: menuRef,
2905
+ className: `
2906
+ bg-popover text-popover-foreground rounded-md border p-1 shadow-md min-w-[160px]
2907
+ fixed z-[100]
2908
+ ${className}
2909
+ `,
2910
+ style: {
2911
+ left: position.x,
2912
+ top: position.y
2913
+ },
2914
+ role: "menu",
2915
+ "aria-label": "Context menu",
2916
+ children: items.map((item, index) => /* @__PURE__ */ jsxs(React.Fragment, { children: [/* @__PURE__ */ jsx("div", {
2917
+ className: `
2918
+ px-2 py-1.5 text-sm cursor-default hover:bg-accent hover:text-accent-foreground
2919
+ flex items-center rounded-sm transition-colors duration-150
2920
+ ${item.disabled ? "opacity-50 cursor-not-allowed" : ""}
2921
+ `,
2922
+ onClick: (e) => handleItemClick(item, e),
2923
+ role: "menuitem",
2924
+ tabIndex: item.disabled ? -1 : 0,
2925
+ "aria-disabled": item.disabled,
2926
+ children: /* @__PURE__ */ jsx("span", {
2927
+ className: "flex-1",
2928
+ children: item.label
2929
+ })
2930
+ }), item.separator && index < items.length - 1 && /* @__PURE__ */ jsx("div", {
2931
+ className: "h-px bg-border my-1",
2932
+ role: "separator"
2933
+ })] }, item.id))
2934
+ });
2935
+ });
2936
+ TreeContextMenu.displayName = "TreeContextMenu";
2937
+
2938
+ //#endregion
2939
+ //#region src/tree/components/Tree.tsx
2940
+ /**
2941
+ * Tree Component - Reactive tree display with loading states
2942
+ *
2943
+ * This is the main tree component that:
2944
+ * - Creates and manages TreeModel
2945
+ * - Shows loading/error states
2946
+ * - Renders tree nodes
2947
+ * - Follows MobX observer pattern for reactivity
2948
+ * - Handles keyboard navigation
2949
+ */
2950
+ const Tree = observer(({ provider, model, loadOptions, className }) => {
2951
+ const [internalModel] = useState(() => model || new TreeModel(provider));
2952
+ const treeModel = model || internalModel;
2953
+ const treeRef = useRef(null);
2954
+ useEffect(() => {
2955
+ if (!model) treeModel.loadNodes(loadOptions);
2956
+ }, [
2957
+ treeModel,
2958
+ loadOptions,
2959
+ model
2960
+ ]);
2961
+ const getFlattenedNodes = () => {
2962
+ const result = [];
2963
+ const processNodes = (nodes) => {
2964
+ for (const node of nodes) {
2965
+ result.push(node);
2966
+ if (treeModel.isNodeExpanded(node.id) && node.children && node.children.length > 0) processNodes(node.children);
2967
+ }
2968
+ };
2969
+ processNodes(treeModel.nodes);
2970
+ return result;
2971
+ };
2972
+ const navigateToNode = (targetIndex) => {
2973
+ const flatNodes = getFlattenedNodes();
2974
+ if (targetIndex >= 0 && targetIndex < flatNodes.length) {
2975
+ const targetNode = flatNodes[targetIndex];
2976
+ if (targetNode) {
2977
+ treeModel.focusedNode = targetNode.id;
2978
+ treeModel.selectNode(targetNode);
2979
+ }
2980
+ }
2981
+ };
2982
+ const getCurrentNodeIndex = () => {
2983
+ if (!treeModel.focusedNode) return -1;
2984
+ const flatNodes = getFlattenedNodes();
2985
+ return flatNodes.findIndex((node) => node.id === treeModel.focusedNode);
2986
+ };
2987
+ const getParentNode = (nodeId) => {
2988
+ const findParent = (nodes, targetId, parentNode = null) => {
2989
+ for (const node of nodes) {
2990
+ if (node.id === targetId) return parentNode;
2991
+ if (node.children && node.children.length > 0) {
2992
+ const parent = findParent(node.children, targetId, node);
2993
+ if (parent) return parent;
2994
+ }
2995
+ }
2996
+ return null;
2997
+ };
2998
+ return findParent(treeModel.nodes, nodeId);
2999
+ };
3000
+ useEffect(() => {
3001
+ const handleKeyDown = (event) => {
3002
+ if (!treeRef.current?.contains(document.activeElement) && !treeModel.focusedNode) return;
3003
+ const currentIndex = getCurrentNodeIndex();
3004
+ const flatNodes = getFlattenedNodes();
3005
+ const currentNode = treeModel.focusedNode ? treeModel.nodeMap.get(treeModel.focusedNode) : null;
3006
+ switch (event.key) {
3007
+ case "ArrowDown":
3008
+ event.preventDefault();
3009
+ if (currentIndex < flatNodes.length - 1) navigateToNode(currentIndex + 1);
3010
+ break;
3011
+ case "ArrowUp":
3012
+ event.preventDefault();
3013
+ if (currentIndex > 0) navigateToNode(currentIndex - 1);
3014
+ break;
3015
+ case "ArrowRight":
3016
+ event.preventDefault();
3017
+ if (currentNode) {
3018
+ if (currentNode.hasChildren || currentNode.children && currentNode.children.length > 0) {
3019
+ if (!treeModel.isNodeExpanded(currentNode.id)) treeModel.expandNode(currentNode);
3020
+ else if (currentNode.children && currentNode.children.length > 0) {
3021
+ const firstChild = currentNode.children[0];
3022
+ if (firstChild) {
3023
+ treeModel.focusedNode = firstChild.id;
3024
+ treeModel.selectNode(firstChild);
3025
+ }
3026
+ }
3027
+ }
3028
+ }
3029
+ break;
3030
+ case "ArrowLeft":
3031
+ event.preventDefault();
3032
+ if (currentNode) if (treeModel.isNodeExpanded(currentNode.id) && (currentNode.hasChildren || currentNode.children && currentNode.children.length > 0)) treeModel.collapseNode(currentNode);
3033
+ else {
3034
+ const parentNode = getParentNode(currentNode.id);
3035
+ if (parentNode) {
3036
+ treeModel.focusedNode = parentNode.id;
3037
+ treeModel.selectNode(parentNode);
3038
+ }
3039
+ }
3040
+ break;
3041
+ case "Home":
3042
+ event.preventDefault();
3043
+ if (flatNodes.length > 0) navigateToNode(0);
3044
+ break;
3045
+ case "End":
3046
+ event.preventDefault();
3047
+ if (flatNodes.length > 0) navigateToNode(flatNodes.length - 1);
3048
+ break;
3049
+ case "Enter":
3050
+ case " ":
3051
+ event.preventDefault();
3052
+ if (currentNode) {
3053
+ if (currentNode.hasChildren || currentNode.children && currentNode.children.length > 0) treeModel.toggleExpansion(currentNode);
3054
+ }
3055
+ break;
3056
+ case "a":
3057
+ if ((event.ctrlKey || event.metaKey) && treeModel.provider.isMultiSelectEnabled) {
3058
+ event.preventDefault();
3059
+ treeModel.selectAll();
3060
+ }
3061
+ break;
3062
+ case "Escape":
3063
+ event.preventDefault();
3064
+ treeModel.clearSelection();
3065
+ treeModel.hideContextMenu();
3066
+ break;
3067
+ default: break;
3068
+ }
3069
+ };
3070
+ document.addEventListener("keydown", handleKeyDown);
3071
+ return () => document.removeEventListener("keydown", handleKeyDown);
3072
+ }, [treeModel]);
3073
+ useEffect(() => {
3074
+ if (!treeModel.focusedNode && treeModel.hasNodes && treeModel.nodes.length > 0) {
3075
+ const firstNode = treeModel.nodes[0];
3076
+ if (firstNode) treeModel.focusedNode = firstNode.id;
3077
+ }
3078
+ }, [
3079
+ treeModel.hasNodes,
3080
+ treeModel.nodes.length,
3081
+ treeModel.focusedNode
3082
+ ]);
3083
+ return /* @__PURE__ */ jsxs("div", {
3084
+ ref: treeRef,
3085
+ className: `h-full overflow-hidden focus-within:outline-none ${className || ""}`,
3086
+ tabIndex: 0,
3087
+ role: "tree",
3088
+ "aria-label": "File tree",
3089
+ children: [
3090
+ treeModel.isLoading && /* @__PURE__ */ jsx("div", {
3091
+ className: "p-4 text-center text-muted-foreground",
3092
+ role: "status",
3093
+ "aria-live": "polite",
3094
+ children: /* @__PURE__ */ jsxs("div", {
3095
+ className: "inline-flex items-center gap-2",
3096
+ children: [/* @__PURE__ */ jsx("div", { className: "w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin" }), /* @__PURE__ */ jsx("span", { children: "Loading tree..." })]
3097
+ })
3098
+ }),
3099
+ !treeModel.isLoading && treeModel.errors.size > 0 && /* @__PURE__ */ jsx("div", {
3100
+ className: "p-4 text-red-600 bg-red-50 border-l-4 border-red-400",
3101
+ role: "alert",
3102
+ children: Array.from(treeModel.errors.entries()).map(([key, error]) => /* @__PURE__ */ jsxs("div", {
3103
+ className: "flex items-start gap-2",
3104
+ children: [/* @__PURE__ */ jsx("span", {
3105
+ className: "font-semibold",
3106
+ children: "Error:"
3107
+ }), /* @__PURE__ */ jsx("span", { children: error.message })]
3108
+ }, key))
3109
+ }),
3110
+ !treeModel.isLoading && treeModel.errors.size === 0 && !treeModel.hasNodes && /* @__PURE__ */ jsx("div", {
3111
+ className: "p-4 text-center text-muted-foreground",
3112
+ role: "status",
3113
+ children: /* @__PURE__ */ jsx("span", { children: "No items to display" })
3114
+ }),
3115
+ !treeModel.isLoading && treeModel.hasNodes && /* @__PURE__ */ jsx("div", {
3116
+ className: "h-full overflow-auto",
3117
+ children: /* @__PURE__ */ jsx(TreeNodeList, {
3118
+ treeModel,
3119
+ nodes: treeModel.nodes,
3120
+ depth: 0
3121
+ })
3122
+ }),
3123
+ /* @__PURE__ */ jsx(TreeContextMenu, {
3124
+ items: treeModel.contextMenuItems,
3125
+ position: treeModel.contextMenuPosition,
3126
+ visible: treeModel.contextMenuVisible,
3127
+ onClose: () => treeModel.hideContextMenu(),
3128
+ onItemClick: (menuItem) => treeModel.handleContextMenuAction(menuItem)
3129
+ })
3130
+ ]
3131
+ });
3132
+ });
3133
+
3134
+ //#endregion
3135
+ //#region src/tree/components/TreeTable.tsx
3136
+ /**
3137
+ * TreeTable Component - Table view of tree data
3138
+ */
3139
+ const TreeTable = observer(({ treeModel, columns: propColumns, enableAnimations = true, className, enableExport = true, enableFiltering = false, enableSorting = true }) => {
3140
+ const [sortColumn, setSortColumn] = useState(null);
3141
+ const [sortDirection, setSortDirection] = useState("asc");
3142
+ const [filters, setFilters] = useState({});
3143
+ const defaultColumns = useMemo(() => [{
3144
+ id: "name",
3145
+ label: "Name",
3146
+ dataKey: "name",
3147
+ width: "100%",
3148
+ sortable: true,
3149
+ filterable: true,
3150
+ isTreeColumn: true
3151
+ }], []);
3152
+ const columns = propColumns || defaultColumns;
3153
+ const isHeaderVisible = columns.length > 1 || enableFiltering || enableExport;
3154
+ const tableData = useMemo(() => {
3155
+ const flattenNode = (node, level = 0) => {
3156
+ const result = [{
3157
+ node,
3158
+ level
3159
+ }];
3160
+ if (treeModel.isNodeExpanded(node.id) && node.children) for (const child of node.children) result.push(...flattenNode(child, level + 1));
3161
+ return result;
3162
+ };
3163
+ let data = [];
3164
+ const rootNodes = treeModel.nodes;
3165
+ for (const node of rootNodes) data.push(...flattenNode(node));
3166
+ if (Object.values(filters).some((f) => f.trim())) data = data.filter(({ node }) => {
3167
+ return Object.entries(filters).every(([columnKey, filterValue]) => {
3168
+ if (!filterValue.trim()) return true;
3169
+ const column = columns.find((c) => c.id === columnKey);
3170
+ if (!column) return true;
3171
+ let cellValue = "";
3172
+ if (column.renderCell) {
3173
+ const rendered = column.renderCell(node[column.dataKey], node);
3174
+ cellValue = typeof rendered === "string" ? rendered : String(rendered || "");
3175
+ } else if (column.formatValue) cellValue = column.formatValue(node[column.dataKey], node);
3176
+ else cellValue = node[column.dataKey]?.toString() || "";
3177
+ return cellValue.toLowerCase().includes(filterValue.toLowerCase());
3178
+ });
3179
+ });
3180
+ if (sortColumn) {
3181
+ const column = columns.find((c) => c.id === sortColumn);
3182
+ if (column) data.sort((a, b) => {
3183
+ let aValue = "";
3184
+ let bValue = "";
3185
+ if (column.formatValue) {
3186
+ aValue = column.formatValue(a.node[column.dataKey], a.node);
3187
+ bValue = column.formatValue(b.node[column.dataKey], b.node);
3188
+ } else {
3189
+ aValue = a.node[column.dataKey]?.toString() || "";
3190
+ bValue = b.node[column.dataKey]?.toString() || "";
3191
+ }
3192
+ const compareResult = aValue.localeCompare(bValue);
3193
+ return sortDirection === "asc" ? compareResult : -compareResult;
3194
+ });
3195
+ }
3196
+ return data;
3197
+ }, [
3198
+ treeModel.nodes,
3199
+ sortColumn,
3200
+ sortDirection,
3201
+ filters,
3202
+ columns,
3203
+ treeModel
3204
+ ]);
3205
+ const handleSort = useCallback((columnKey) => {
3206
+ if (sortColumn === columnKey) setSortDirection((prev) => prev === "asc" ? "desc" : "asc");
3207
+ else {
3208
+ setSortColumn(columnKey);
3209
+ setSortDirection("asc");
3210
+ }
3211
+ }, [sortColumn]);
3212
+ const handleExport = useCallback(() => {
3213
+ const csvContent = [columns.map((col) => col.label).join(","), ...tableData.map(({ node }) => columns.map((col) => {
3214
+ let value = "";
3215
+ if (col.formatValue) value = col.formatValue(node[col.dataKey], node);
3216
+ else value = node[col.dataKey]?.toString() || "";
3217
+ return `"${value.replace(/"/g, "\"\"")}"`;
3218
+ }).join(","))].join("\n");
3219
+ const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
3220
+ const link = document.createElement("a");
3221
+ link.href = URL.createObjectURL(blob);
3222
+ link.download = "tree-data.csv";
3223
+ link.click();
3224
+ }, [tableData, columns]);
3225
+ const selectionTheme = treeModel.provider.getSelectionTheme?.() || DEFAULT_SELECTION_THEME;
3226
+ return /* @__PURE__ */ jsxs("div", {
3227
+ className: cn("flex flex-col h-full", className),
3228
+ children: [isHeaderVisible && /* @__PURE__ */ jsx("div", {
3229
+ className: "flex-shrink-0 p-3 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60",
3230
+ children: /* @__PURE__ */ jsxs("div", {
3231
+ className: "flex items-center justify-between gap-4",
3232
+ children: [/* @__PURE__ */ jsx("div", {
3233
+ className: "flex items-center gap-2",
3234
+ children: enableFiltering && /* @__PURE__ */ jsxs("div", {
3235
+ className: "flex items-center gap-2",
3236
+ children: [
3237
+ /* @__PURE__ */ jsx(Search, { className: "w-4 h-4 text-muted-foreground" }),
3238
+ /* @__PURE__ */ jsx("span", {
3239
+ className: "text-sm text-muted-foreground",
3240
+ children: "Filter:"
3241
+ }),
3242
+ columns.filter((c) => c.filterable).map((column) => /* @__PURE__ */ jsx("input", {
3243
+ type: "text",
3244
+ placeholder: `Filter ${column.label}`,
3245
+ value: filters[column.id] || "",
3246
+ onChange: (e) => setFilters((prev) => ({
3247
+ ...prev,
3248
+ [column.id]: e.target.value
3249
+ })),
3250
+ className: "px-2 py-1 text-sm border rounded-md w-32"
3251
+ }, column.id))
3252
+ ]
3253
+ })
3254
+ }), enableExport && /* @__PURE__ */ jsxs("button", {
3255
+ onClick: handleExport,
3256
+ className: "flex items-center gap-2 px-3 py-1 text-sm bg-primary text-primary-foreground rounded-md hover:bg-primary/90",
3257
+ children: [/* @__PURE__ */ jsx(Download, { className: "w-4 h-4" }), "Export CSV"]
3258
+ })]
3259
+ })
3260
+ }), /* @__PURE__ */ jsx("div", {
3261
+ className: "flex-1 overflow-auto",
3262
+ children: /* @__PURE__ */ jsxs("div", {
3263
+ className: "min-w-full",
3264
+ children: [columns.length > 1 && /* @__PURE__ */ jsx("div", {
3265
+ className: "sticky top-0 bg-background border-b z-10",
3266
+ children: /* @__PURE__ */ jsx("div", {
3267
+ className: "flex",
3268
+ children: columns.map((column) => /* @__PURE__ */ jsxs("div", {
3269
+ className: cn("px-2 py-2 text-sm font-medium text-muted-foreground", column.sortable && enableSorting && "cursor-pointer hover:text-foreground", "flex items-center gap-1"),
3270
+ style: { width: column.width },
3271
+ onClick: () => column.sortable && enableSorting && handleSort(column.id),
3272
+ children: [/* @__PURE__ */ jsx("span", { children: column.label }), column.sortable && enableSorting && /* @__PURE__ */ jsx("div", {
3273
+ className: "ml-auto",
3274
+ children: sortColumn === column.id ? sortDirection === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "w-3 h-3" }) : /* @__PURE__ */ jsx(ArrowDown, { className: "w-3 h-3" }) : /* @__PURE__ */ jsx(ArrowUpDown, { className: "w-3 h-3 opacity-50" })
3275
+ })]
3276
+ }, column.id))
3277
+ })
3278
+ }), /* @__PURE__ */ jsx("div", { children: tableData.length === 0 ? /* @__PURE__ */ jsx("div", {
3279
+ className: "p-8 text-center",
3280
+ children: /* @__PURE__ */ jsx("p", {
3281
+ className: "text-sm text-muted-foreground",
3282
+ children: Object.values(filters).some((f) => f.trim()) ? "No results found for current filters" : "No data available"
3283
+ })
3284
+ }) : tableData.map(({ node, level }) => {
3285
+ const isSelected = treeModel.isNodeSelected(node.id);
3286
+ const isFocused = treeModel.focusedNode === node.id;
3287
+ const isExpanded = treeModel.isNodeExpanded(node.id);
3288
+ const hasChildren = node.hasChildren || node.children && node.children.length > 0;
3289
+ const useCheckboxes = treeModel.provider.useCheckboxSelection;
3290
+ const customColorVars = getCustomColorVariables(selectionTheme);
3291
+ return /* @__PURE__ */ jsx("div", {
3292
+ className: cn("flex w-full cursor-pointer group", getSelectionClasses(selectionTheme, isSelected, isFocused, !!useCheckboxes), useCheckboxes && "select-none"),
3293
+ style: customColorVars,
3294
+ onClick: (event) => {
3295
+ if (useCheckboxes) return;
3296
+ const isMultiSelectKeyPressed = event.ctrlKey || event.metaKey;
3297
+ if (treeModel.provider.isMultiSelectEnabled && isMultiSelectKeyPressed) if (isSelected) treeModel.deselectNode(node);
3298
+ else treeModel.selectNode(node);
3299
+ else if (treeModel.provider.isMultiSelectEnabled) {
3300
+ treeModel.clearSelection();
3301
+ treeModel.selectNode(node);
3302
+ } else if (isSelected) treeModel.clearSelection();
3303
+ else treeModel.selectNode(node);
3304
+ },
3305
+ onDoubleClick: () => hasChildren && treeModel.toggleExpansion(node),
3306
+ children: columns.map((column, columnIndex) => {
3307
+ let cellContent = "";
3308
+ if (column.renderCell) cellContent = column.renderCell(node[column.dataKey], node);
3309
+ else if (column.formatValue) cellContent = column.formatValue(node[column.dataKey], node);
3310
+ else cellContent = node[column.dataKey]?.toString() || "";
3311
+ if (column.isTreeColumn) return /* @__PURE__ */ jsxs("div", {
3312
+ className: "flex items-center px-2 py-1 text-sm",
3313
+ style: { width: column.width },
3314
+ children: [
3315
+ /* @__PURE__ */ jsx("div", {
3316
+ style: { width: level * 16 },
3317
+ className: "flex-shrink-0"
3318
+ }),
3319
+ /* @__PURE__ */ jsx("div", {
3320
+ className: "flex-shrink-0 w-4 h-4 mr-1",
3321
+ children: hasChildren && /* @__PURE__ */ jsx("button", {
3322
+ onClick: (e) => {
3323
+ e.stopPropagation();
3324
+ treeModel.toggleExpansion(node);
3325
+ },
3326
+ className: "w-4 h-4 flex items-center justify-center transition-transform duration-200 hover:bg-accent/20",
3327
+ children: isExpanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "w-3 h-3" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "w-3 h-3" })
3328
+ })
3329
+ }),
3330
+ /* @__PURE__ */ jsx("span", {
3331
+ className: "flex-1 truncate",
3332
+ children: cellContent
3333
+ })
3334
+ ]
3335
+ }, column.id);
3336
+ return /* @__PURE__ */ jsx("div", {
3337
+ className: "px-2 py-1 text-sm truncate",
3338
+ style: { width: column.width },
3339
+ children: cellContent
3340
+ }, column.id);
3341
+ })
3342
+ }, node.id);
3343
+ }) })]
3344
+ })
3345
+ })]
3346
+ });
3347
+ });
3348
+
3349
+ //#endregion
3350
+ export { DEFAULT_SELECTION_THEME, SimpleTreeProvider, TestTreeProvider, Tree, TreeCheckbox, TreeContextMenu, TreeModel, TreeNodeList, TreeTable, getCustomColorVariables, getSelectionClasses, logger };
3351
+ //# sourceMappingURL=tree-Dd9Z0Aso.js.map