meno-core 1.0.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 (231) hide show
  1. package/bin/cli.ts +281 -0
  2. package/build-static.ts +298 -0
  3. package/bunfig.toml +39 -0
  4. package/entries/client-router.tsx +111 -0
  5. package/entries/server-router.tsx +71 -0
  6. package/lib/client/ClientInitializer.test.ts +9 -0
  7. package/lib/client/ClientInitializer.test.ts.skip +92 -0
  8. package/lib/client/ClientInitializer.ts +60 -0
  9. package/lib/client/ErrorBoundary.test.tsx +595 -0
  10. package/lib/client/ErrorBoundary.tsx +230 -0
  11. package/lib/client/componentRegistry.test.ts +165 -0
  12. package/lib/client/componentRegistry.ts +18 -0
  13. package/lib/client/contexts/ThemeContext.tsx +73 -0
  14. package/lib/client/core/ComponentBuilder.test.ts +677 -0
  15. package/lib/client/core/ComponentBuilder.ts +660 -0
  16. package/lib/client/core/ComponentRenderer.test.tsx +176 -0
  17. package/lib/client/core/ComponentRenderer.tsx +83 -0
  18. package/lib/client/core/cmsTemplateProcessor.ts +129 -0
  19. package/lib/client/elementRegistry.ts +81 -0
  20. package/lib/client/hmr/HMRManager.tsx +179 -0
  21. package/lib/client/hmr/index.ts +5 -0
  22. package/lib/client/hmrWebSocket.test.ts +9 -0
  23. package/lib/client/hmrWebSocket.ts +250 -0
  24. package/lib/client/hooks/useColorVariables.test.ts +166 -0
  25. package/lib/client/hooks/useColorVariables.ts +249 -0
  26. package/lib/client/hooks/usePropertyAutocomplete.test.ts +9 -0
  27. package/lib/client/hooks/usePropertyAutocomplete.ts +40 -0
  28. package/lib/client/hydration/HydrationUtils.test.ts +154 -0
  29. package/lib/client/hydration/HydrationUtils.ts +35 -0
  30. package/lib/client/i18nConfigService.test.ts +74 -0
  31. package/lib/client/i18nConfigService.ts +78 -0
  32. package/lib/client/index.ts +56 -0
  33. package/lib/client/navigation.test.ts +441 -0
  34. package/lib/client/navigation.ts +23 -0
  35. package/lib/client/responsiveStyleResolver.test.ts +491 -0
  36. package/lib/client/responsiveStyleResolver.ts +184 -0
  37. package/lib/client/routing/RouteLoader.test.ts +635 -0
  38. package/lib/client/routing/RouteLoader.ts +347 -0
  39. package/lib/client/routing/Router.tsx +382 -0
  40. package/lib/client/scripts/ScriptExecutor.test.ts +489 -0
  41. package/lib/client/scripts/ScriptExecutor.ts +171 -0
  42. package/lib/client/scripts/formHandler.ts +103 -0
  43. package/lib/client/styleProcessor.test.ts +126 -0
  44. package/lib/client/styleProcessor.ts +92 -0
  45. package/lib/client/styles/StyleInjector.test.ts +354 -0
  46. package/lib/client/styles/StyleInjector.ts +154 -0
  47. package/lib/client/templateEngine.test.ts +660 -0
  48. package/lib/client/templateEngine.ts +667 -0
  49. package/lib/client/theme.test.ts +173 -0
  50. package/lib/client/theme.ts +159 -0
  51. package/lib/client/utils/toast.ts +46 -0
  52. package/lib/server/createServer.ts +170 -0
  53. package/lib/server/cssGenerator.test.ts +172 -0
  54. package/lib/server/cssGenerator.ts +58 -0
  55. package/lib/server/fileWatcher.ts +134 -0
  56. package/lib/server/index.ts +55 -0
  57. package/lib/server/jsonLoader.test.ts +103 -0
  58. package/lib/server/jsonLoader.ts +350 -0
  59. package/lib/server/middleware/cors.test.ts +177 -0
  60. package/lib/server/middleware/cors.ts +69 -0
  61. package/lib/server/middleware/errorHandler.test.ts +208 -0
  62. package/lib/server/middleware/errorHandler.ts +63 -0
  63. package/lib/server/middleware/index.ts +9 -0
  64. package/lib/server/middleware/logger.test.ts +233 -0
  65. package/lib/server/middleware/logger.ts +99 -0
  66. package/lib/server/pageCache.test.ts +167 -0
  67. package/lib/server/pageCache.ts +97 -0
  68. package/lib/server/projectContext.ts +51 -0
  69. package/lib/server/providers/fileSystemCMSProvider.test.ts +292 -0
  70. package/lib/server/providers/fileSystemCMSProvider.ts +227 -0
  71. package/lib/server/providers/fileSystemPageProvider.ts +83 -0
  72. package/lib/server/routes/api/cms.test.ts +177 -0
  73. package/lib/server/routes/api/cms.ts +82 -0
  74. package/lib/server/routes/api/colors.ts +59 -0
  75. package/lib/server/routes/api/components.ts +70 -0
  76. package/lib/server/routes/api/config.test.ts +9 -0
  77. package/lib/server/routes/api/config.ts +28 -0
  78. package/lib/server/routes/api/core-routes.ts +182 -0
  79. package/lib/server/routes/api/functions.ts +170 -0
  80. package/lib/server/routes/api/index.ts +69 -0
  81. package/lib/server/routes/api/pages.ts +95 -0
  82. package/lib/server/routes/api/shared.test.ts +81 -0
  83. package/lib/server/routes/api/shared.ts +31 -0
  84. package/lib/server/routes/editor.test.ts +9 -0
  85. package/lib/server/routes/index.ts +104 -0
  86. package/lib/server/routes/pages.ts +161 -0
  87. package/lib/server/routes/static.ts +107 -0
  88. package/lib/server/services/ColorService.ts +193 -0
  89. package/lib/server/services/cmsService.test.ts +388 -0
  90. package/lib/server/services/cmsService.ts +296 -0
  91. package/lib/server/services/componentService.test.ts +276 -0
  92. package/lib/server/services/componentService.ts +346 -0
  93. package/lib/server/services/configService.ts +156 -0
  94. package/lib/server/services/fileWatcherService.ts +67 -0
  95. package/lib/server/services/index.ts +10 -0
  96. package/lib/server/services/pageService.test.ts +258 -0
  97. package/lib/server/services/pageService.ts +240 -0
  98. package/lib/server/ssrRenderer.test.ts +1005 -0
  99. package/lib/server/ssrRenderer.ts +878 -0
  100. package/lib/server/utilityClassGenerator.ts +11 -0
  101. package/lib/server/utils/index.ts +5 -0
  102. package/lib/server/utils/jsonLineMapper.test.ts +100 -0
  103. package/lib/server/utils/jsonLineMapper.ts +166 -0
  104. package/lib/server/validateStyleCoverage.test.ts +9 -0
  105. package/lib/server/validateStyleCoverage.ts +167 -0
  106. package/lib/server/websocketManager.test.ts +9 -0
  107. package/lib/server/websocketManager.ts +95 -0
  108. package/lib/shared/attributeNodeUtils.test.ts +152 -0
  109. package/lib/shared/attributeNodeUtils.ts +50 -0
  110. package/lib/shared/breakpoints.test.ts +166 -0
  111. package/lib/shared/breakpoints.ts +65 -0
  112. package/lib/shared/colorProperties.test.ts +111 -0
  113. package/lib/shared/colorProperties.ts +40 -0
  114. package/lib/shared/colorVariableUtils.test.ts +319 -0
  115. package/lib/shared/colorVariableUtils.ts +97 -0
  116. package/lib/shared/constants.test.ts +175 -0
  117. package/lib/shared/constants.ts +116 -0
  118. package/lib/shared/cssGeneration.ts +481 -0
  119. package/lib/shared/cssProperties.test.ts +252 -0
  120. package/lib/shared/cssProperties.ts +338 -0
  121. package/lib/shared/elementUtils.test.ts +245 -0
  122. package/lib/shared/elementUtils.ts +90 -0
  123. package/lib/shared/fontLoader.ts +97 -0
  124. package/lib/shared/i18n.test.ts +313 -0
  125. package/lib/shared/i18n.ts +286 -0
  126. package/lib/shared/index.ts +50 -0
  127. package/lib/shared/interfaces/contentProvider.test.ts +9 -0
  128. package/lib/shared/interfaces/contentProvider.ts +121 -0
  129. package/lib/shared/nodeUtils.test.ts +320 -0
  130. package/lib/shared/nodeUtils.ts +220 -0
  131. package/lib/shared/pathArrayUtils.test.ts +315 -0
  132. package/lib/shared/pathArrayUtils.ts +17 -0
  133. package/lib/shared/pathUtils.test.ts +260 -0
  134. package/lib/shared/pathUtils.ts +244 -0
  135. package/lib/shared/paths/Path.test.ts +74 -0
  136. package/lib/shared/paths/Path.ts +23 -0
  137. package/lib/shared/paths/PathConverter.test.ts +232 -0
  138. package/lib/shared/paths/PathConverter.ts +141 -0
  139. package/lib/shared/paths/PathUtils.ts +290 -0
  140. package/lib/shared/paths/PathValidator.test.ts +193 -0
  141. package/lib/shared/paths/PathValidator.ts +53 -0
  142. package/lib/shared/paths/index.ts +48 -0
  143. package/lib/shared/propResolver.test.ts +639 -0
  144. package/lib/shared/propResolver.ts +124 -0
  145. package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +190 -0
  146. package/lib/shared/registry/BaseNodeTypeRegistry.ts +200 -0
  147. package/lib/shared/registry/ClientNodeTypeRegistry.ts +34 -0
  148. package/lib/shared/registry/ClientRegistry.test.ts +26 -0
  149. package/lib/shared/registry/ClientRegistry.ts +15 -0
  150. package/lib/shared/registry/ComponentRegistry.test.ts +293 -0
  151. package/lib/shared/registry/ComponentRegistry.ts +100 -0
  152. package/lib/shared/registry/NodeTypeDefinition.ts +198 -0
  153. package/lib/shared/registry/NodeTypeManager.ts +94 -0
  154. package/lib/shared/registry/RegistryManager.test.ts +58 -0
  155. package/lib/shared/registry/RegistryManager.ts +60 -0
  156. package/lib/shared/registry/SSRNodeTypeRegistry.ts +33 -0
  157. package/lib/shared/registry/SSRRegistry.test.ts +26 -0
  158. package/lib/shared/registry/SSRRegistry.ts +15 -0
  159. package/lib/shared/registry/createNodeType.ts +175 -0
  160. package/lib/shared/registry/defineNodeType.ts +73 -0
  161. package/lib/shared/registry/fieldPresets.ts +109 -0
  162. package/lib/shared/registry/index.ts +50 -0
  163. package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +71 -0
  164. package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +61 -0
  165. package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +88 -0
  166. package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +66 -0
  167. package/lib/shared/registry/nodeTypes/ObjectLinkNodeType.ts +75 -0
  168. package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +49 -0
  169. package/lib/shared/registry/nodeTypes/TextNodeType.ts +52 -0
  170. package/lib/shared/registry/nodeTypes/index.ts +75 -0
  171. package/lib/shared/responsiveScaling.test.ts +268 -0
  172. package/lib/shared/responsiveScaling.ts +194 -0
  173. package/lib/shared/responsiveStyleUtils.test.ts +300 -0
  174. package/lib/shared/responsiveStyleUtils.ts +139 -0
  175. package/lib/shared/slugTranslator.test.ts +325 -0
  176. package/lib/shared/slugTranslator.ts +177 -0
  177. package/lib/shared/styleNodeUtils.test.ts +132 -0
  178. package/lib/shared/styleNodeUtils.ts +102 -0
  179. package/lib/shared/styleUtils.test.ts +238 -0
  180. package/lib/shared/styleUtils.ts +63 -0
  181. package/lib/shared/themeDefaults.test.ts +113 -0
  182. package/lib/shared/themeDefaults.ts +103 -0
  183. package/lib/shared/tree/PathBuilder.ts +383 -0
  184. package/lib/shared/treePathUtils.test.ts +539 -0
  185. package/lib/shared/treePathUtils.ts +339 -0
  186. package/lib/shared/types/api.ts +58 -0
  187. package/lib/shared/types/cms.ts +95 -0
  188. package/lib/shared/types/colors.ts +45 -0
  189. package/lib/shared/types/components.ts +121 -0
  190. package/lib/shared/types/errors.test.ts +103 -0
  191. package/lib/shared/types/errors.ts +69 -0
  192. package/lib/shared/types/index.ts +96 -0
  193. package/lib/shared/types/nodes.ts +20 -0
  194. package/lib/shared/types/rendering.ts +61 -0
  195. package/lib/shared/types/styles.ts +38 -0
  196. package/lib/shared/types.ts +11 -0
  197. package/lib/shared/utilityClassConfig.ts +287 -0
  198. package/lib/shared/utilityClassMapper.test.ts +140 -0
  199. package/lib/shared/utilityClassMapper.ts +229 -0
  200. package/lib/shared/utils/fileUtils.test.ts +99 -0
  201. package/lib/shared/utils/fileUtils.ts +56 -0
  202. package/lib/shared/utils.test.ts +261 -0
  203. package/lib/shared/utils.ts +84 -0
  204. package/lib/shared/validation/index.ts +7 -0
  205. package/lib/shared/validation/propValidator.test.ts +178 -0
  206. package/lib/shared/validation/propValidator.ts +238 -0
  207. package/lib/shared/validation/schemas.test.ts +177 -0
  208. package/lib/shared/validation/schemas.ts +401 -0
  209. package/lib/shared/validation/validators.test.ts +109 -0
  210. package/lib/shared/validation/validators.ts +304 -0
  211. package/lib/test-utils/dom-setup.ts +55 -0
  212. package/lib/test-utils/factories/ConsoleMockFactory.ts +200 -0
  213. package/lib/test-utils/factories/DomMockFactory.ts +487 -0
  214. package/lib/test-utils/factories/EventMockFactory.ts +244 -0
  215. package/lib/test-utils/factories/FetchMockFactory.ts +210 -0
  216. package/lib/test-utils/factories/ServerMockFactory.ts +223 -0
  217. package/lib/test-utils/factories/StoreMockFactory.ts +370 -0
  218. package/lib/test-utils/factories/index.ts +11 -0
  219. package/lib/test-utils/fixtures.ts +134 -0
  220. package/lib/test-utils/helpers/asyncHelpers.test.ts +112 -0
  221. package/lib/test-utils/helpers/asyncHelpers.ts +196 -0
  222. package/lib/test-utils/helpers/index.ts +6 -0
  223. package/lib/test-utils/helpers.test.ts +73 -0
  224. package/lib/test-utils/helpers.ts +90 -0
  225. package/lib/test-utils/index.ts +17 -0
  226. package/lib/test-utils/mockFactories.ts +92 -0
  227. package/lib/test-utils/mocks.ts +341 -0
  228. package/package.json +38 -0
  229. package/templates/index-router.html +34 -0
  230. package/tsconfig.json +14 -0
  231. package/vite.config.ts +43 -0
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Shared path utility functions
3
+ * Used by both build system and server router
4
+ */
5
+
6
+ import type { Path } from './paths';
7
+ import {
8
+ stringToPath,
9
+ pathToLegacyString,
10
+ getParentPath as getParentPathArray,
11
+ buildParentPaths as buildParentPathsArray,
12
+ pathsEqual,
13
+ isRootPath as isRootPathArray,
14
+ ROOT_STRING,
15
+ domPathStringToTreePath,
16
+ treePathToDomPathString,
17
+ isAncestorPath
18
+ } from './paths';
19
+
20
+ /**
21
+ * Convert a page path to static HTML file path
22
+ *
23
+ * @example
24
+ * getStaticFilePath("/") -> "./dist/index.html"
25
+ * getStaticFilePath("/about") -> "./dist/about.html"
26
+ * getStaticFilePath("/blog/post-1") -> "./dist/blog-post-1.html"
27
+ */
28
+ export function getStaticFilePath(pagePath: string, distDir: string = "./dist"): string {
29
+ if (pagePath === "/") {
30
+ return `${distDir}/index.html`;
31
+ }
32
+
33
+ // Convert /about -> about.html
34
+ // Convert /blog/post-1 -> blog-post-1.html (flat structure)
35
+ const pathParts = pagePath.substring(1).split("/");
36
+ const fileName = pathParts.join("-") + ".html";
37
+
38
+ return `${distDir}/${fileName}`;
39
+ }
40
+
41
+ /**
42
+ * Convert a page path to a page name (for file system)
43
+ *
44
+ * @example
45
+ * pathToPageName("/") -> "index"
46
+ * pathToPageName("/about") -> "about"
47
+ */
48
+ export function pathToPageName(pagePath: string): string {
49
+ return pagePath === "/" ? "index" : pagePath.substring(1);
50
+ }
51
+
52
+ /**
53
+ * Convert a page name to a page path
54
+ *
55
+ * @example
56
+ * pageNameToPath("index") -> "/"
57
+ * pageNameToPath("about") -> "/about"
58
+ */
59
+ export function pageNameToPath(pageName: string): string {
60
+ return pageName === "index" ? "/" : `/${pageName}`;
61
+ }
62
+
63
+ /**
64
+ * Convert tree path to DOM path
65
+ * The tree adds a synthetic "root" container, so "root_0" in the tree corresponds to "root" in the DOM
66
+ * Also accepts array paths: [0] -> "root", [0,0] -> "root_children_0"
67
+ *
68
+ * Uses array operations internally for conversion.
69
+ *
70
+ * @example
71
+ * treePathToDomPath("root_0") -> "root"
72
+ * treePathToDomPath("root_0_children_0") -> "root_children_0"
73
+ * treePathToDomPath([0]) -> "root"
74
+ * treePathToDomPath([0,0]) -> "root_children_0"
75
+ */
76
+ export function treePathToDomPath(treePath: Path | string): string {
77
+ // Convert to array if needed, then use array-based conversion
78
+ const pathArray = typeof treePath === 'string' ? stringToPath(treePath) : treePath;
79
+ return treePathToDomPathString(pathArray);
80
+ }
81
+
82
+ /**
83
+ * Convert DOM path back to tree path
84
+ * The tree adds a synthetic "root" container, so "root" in the DOM corresponds to "root_0" in the tree
85
+ * Returns legacy string format for backward compatibility
86
+ *
87
+ * Uses array operations internally for conversion.
88
+ *
89
+ * @example
90
+ * domPathToTreePath("root") -> "root_0"
91
+ * domPathToTreePath("root_children_0") -> "root_0_children_0"
92
+ */
93
+ export function domPathToTreePath(domPath: string): string {
94
+ // Convert DOM path to array, then to legacy tree path string
95
+ const pathArray = domPathStringToTreePath(domPath);
96
+ return pathToLegacyString(pathArray);
97
+ }
98
+
99
+ /**
100
+ * Build parent paths for expanding tree nodes
101
+ * Returns all parent path segments for a given tree path
102
+ * Also accepts array paths
103
+ *
104
+ * @example
105
+ * buildParentPaths("root_0_children_0_children_1") -> ["root_0", "root_0_children_0"]
106
+ * buildParentPaths([0,0,1]) -> ["root_0", "root_0_children_0"]
107
+ */
108
+ export function buildParentPaths(treePath: Path | string): string[] {
109
+ const pathArray = typeof treePath === 'string' ? stringToPath(treePath) : treePath;
110
+
111
+ if (isRootPathArray(pathArray)) {
112
+ return [];
113
+ }
114
+
115
+ const parentArrays = buildParentPathsArray(pathArray);
116
+ return parentArrays.map(pathToLegacyString);
117
+ }
118
+
119
+ /**
120
+ * Calculate parent path from a child path
121
+ * Also accepts array paths
122
+ * Example: root_0_children_0 -> root_0
123
+ * Example: root_0_children_0_children_1 -> root_0_children_0
124
+ * Example: [0,0] -> "root_0"
125
+ * Example: [0,0,1] -> "root_0_children_0"
126
+ */
127
+ export function getParentPathFromChildPath(childPath: Path | string): string {
128
+ const pathArray = typeof childPath === 'string' ? stringToPath(childPath) : childPath;
129
+ const parentArray = getParentPathArray(pathArray);
130
+ return pathToLegacyString(parentArray);
131
+ }
132
+
133
+ /**
134
+ * Convert component instance path to component structure path
135
+ * When editing a component, elements clicked inside the component instance on the page
136
+ * need to be converted to their paths within the component structure.
137
+ *
138
+ * Uses array operations internally for path manipulation.
139
+ *
140
+ * @example
141
+ * componentInstancePath: "root_children_0" (component instance on page)
142
+ * instancePath: "root_children_0_children_1" (element inside instance)
143
+ * Returns: "root_children_1" (component structure path)
144
+ *
145
+ * @param instancePath - Path of element within component instance on page (DOM path string)
146
+ * @param componentInstancePath - Path of the component instance root on the page (DOM path string)
147
+ * @returns Component structure path (always starts with "root")
148
+ */
149
+ export function componentInstancePathToStructurePath(
150
+ instancePath: string,
151
+ componentInstancePath: string
152
+ ): string {
153
+ // Convert DOM paths to arrays for manipulation
154
+ const instancePathArray = domPathStringToTreePath(instancePath);
155
+ const componentInstancePathArray = domPathStringToTreePath(componentInstancePath);
156
+
157
+ // Check if instancePath is inside componentInstancePath using array operations
158
+ if (!isAncestorPath(componentInstancePathArray, instancePathArray)) {
159
+ // Not inside component instance - return as-is (shouldn't happen, but handle gracefully)
160
+ return instancePath;
161
+ }
162
+
163
+ // Extract relative path using array operations
164
+ // componentInstancePathArray: [0, 0] -> instancePathArray: [0, 0, 1]
165
+ // relative: [1] -> convert to DOM path: "root_children_1"
166
+ const relativePathArray = instancePathArray.slice(componentInstancePathArray.length);
167
+
168
+ // Convert relative path array back to DOM path string
169
+ // If relative is empty, return root
170
+ if (relativePathArray.length === 0) {
171
+ return ROOT_STRING;
172
+ }
173
+
174
+ // Build component structure path: root + relative path
175
+ return treePathToDomPathString(relativePathArray);
176
+ }
177
+
178
+ /**
179
+ * Convert component structure path to component instance path
180
+ * When selecting an element in the component structure tree, convert it to
181
+ * the path within the component instance on the page.
182
+ *
183
+ * Uses array operations internally for path manipulation.
184
+ *
185
+ * @example
186
+ * componentInstancePath: "root_children_0" (component instance on page)
187
+ * structurePath: "root_children_1" (element in component structure)
188
+ * Returns: "root_children_0_children_1" (path within instance on page)
189
+ *
190
+ * @param structurePath - Path within component structure (starts with "root", DOM path string)
191
+ * @param componentInstancePath - Path of the component instance root on the page (DOM path string)
192
+ * @returns Path within component instance on page (DOM path string)
193
+ */
194
+ export function componentStructurePathToInstancePath(
195
+ structurePath: string,
196
+ componentInstancePath: string
197
+ ): string {
198
+ // Convert DOM paths to arrays for manipulation
199
+ const structurePathArray = domPathStringToTreePath(structurePath);
200
+ const componentInstancePathArray = domPathStringToTreePath(componentInstancePath);
201
+
202
+ // Extract relative path from structure path (skip root [0])
203
+ // structurePathArray: [0, 1] -> relative: [1]
204
+ const relativePathArray = structurePathArray.slice(1);
205
+
206
+ // Combine component instance path with relative path using array operations
207
+ // componentInstancePathArray: [0, 0] + relativePathArray: [1] -> [0, 0, 1]
208
+ const instancePathArray: Path = [...componentInstancePathArray, ...relativePathArray];
209
+
210
+ // Convert back to DOM path string
211
+ return treePathToDomPathString(instancePathArray);
212
+ }
213
+
214
+ /**
215
+ * Convert path for component editing context
216
+ * When editing a component, converts component instance paths to component structure paths
217
+ *
218
+ * Uses array operations internally for path manipulation.
219
+ *
220
+ * @param path - Path array from canvas
221
+ * @param isEditingComponent - Whether we're editing a component
222
+ * @param componentInstancePath - Path of component instance on page (if editing component, Path array)
223
+ * @returns Path array for selection/hover (converted for component context if needed)
224
+ */
225
+ export function convertPathForComponentContext(
226
+ path: Path,
227
+ isEditingComponent: boolean,
228
+ componentInstancePath: Path | null
229
+ ): Path {
230
+ if (isEditingComponent && componentInstancePath) {
231
+ // Check if path is inside componentInstancePath using array operations
232
+ if (isAncestorPath(componentInstancePath, path)) {
233
+ // Extract relative path: componentInstancePath: [0, 0] -> path: [0, 0, 1]
234
+ // relative: [1] -> component structure path: [0, 1]
235
+ const relativePath = path.slice(componentInstancePath.length);
236
+
237
+ // Build component structure path: [0] + relative path
238
+ return [0, ...relativePath];
239
+ }
240
+ }
241
+
242
+ // Regular page editing - return path as-is
243
+ return path;
244
+ }
@@ -0,0 +1,74 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import {
3
+ ROOT_STRING,
4
+ ROOT_0_STRING,
5
+ CHILDREN_SEPARATOR,
6
+ ROOT_PREFIX,
7
+ CHILDREN_TOKEN,
8
+ PATH_SEPARATOR,
9
+ type Path
10
+ } from './Path';
11
+
12
+ describe('Path types and constants', () => {
13
+ describe('Path type', () => {
14
+ test('Path is a number array', () => {
15
+ const path: Path = [0, 1, 2];
16
+ expect(Array.isArray(path)).toBe(true);
17
+ expect(path.every(n => typeof n === 'number')).toBe(true);
18
+ });
19
+
20
+ test('Root path is [0]', () => {
21
+ const root: Path = [0];
22
+ expect(root).toEqual([0]);
23
+ expect(root.length).toBe(1);
24
+ });
25
+
26
+ test('Nested path has multiple indices', () => {
27
+ const nested: Path = [0, 0, 1, 2];
28
+ expect(nested.length).toBe(4);
29
+ });
30
+ });
31
+
32
+ describe('Path constants', () => {
33
+ test('ROOT_STRING is "root"', () => {
34
+ expect(ROOT_STRING).toBe('root');
35
+ });
36
+
37
+ test('ROOT_0_STRING is "root_0"', () => {
38
+ expect(ROOT_0_STRING).toBe('root_0');
39
+ });
40
+
41
+ test('CHILDREN_SEPARATOR is "_children_"', () => {
42
+ expect(CHILDREN_SEPARATOR).toBe('_children_');
43
+ });
44
+
45
+ test('ROOT_PREFIX is "root_"', () => {
46
+ expect(ROOT_PREFIX).toBe('root_');
47
+ });
48
+
49
+ test('CHILDREN_TOKEN is "children"', () => {
50
+ expect(CHILDREN_TOKEN).toBe('children');
51
+ });
52
+
53
+ test('PATH_SEPARATOR is "_"', () => {
54
+ expect(PATH_SEPARATOR).toBe('_');
55
+ });
56
+ });
57
+
58
+ describe('Path string format', () => {
59
+ test('constants can build root path strings', () => {
60
+ expect(ROOT_0_STRING).toBe('root_0');
61
+ });
62
+
63
+ test('constants can build nested path strings', () => {
64
+ const path = `${ROOT_0_STRING}${CHILDREN_SEPARATOR}0${CHILDREN_SEPARATOR}1`;
65
+ expect(path).toBe('root_0_children_0_children_1');
66
+ });
67
+
68
+ test('CHILDREN_SEPARATOR connects indices', () => {
69
+ const parts = ['root_0', '0', '1'];
70
+ const joined = parts.join(CHILDREN_SEPARATOR);
71
+ expect(joined).toBe('root_0_children_0_children_1');
72
+ });
73
+ });
74
+ });
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Path Type Definition
3
+ * Single source of truth for Path type
4
+ */
5
+
6
+ /**
7
+ * Path is represented as a number array: [0,0,1]
8
+ * Root is represented as [0]
9
+ */
10
+ export type Path = number[];
11
+
12
+ /**
13
+ * Path string constants
14
+ */
15
+ export const ROOT_STRING = 'root';
16
+ export const ROOT_0_STRING = 'root_0';
17
+ export const CHILDREN_SEPARATOR = '_children_';
18
+ const ROOT_PREFIX = 'root_';
19
+ const CHILDREN_TOKEN = 'children';
20
+ const PATH_SEPARATOR = '_';
21
+
22
+ export { ROOT_PREFIX, CHILDREN_TOKEN, PATH_SEPARATOR };
23
+
@@ -0,0 +1,232 @@
1
+ import { describe, test, expect } from 'bun:test';
2
+ import {
3
+ normalizePathInput,
4
+ pathToString,
5
+ stringToPath,
6
+ pathToLegacyString,
7
+ domPathStringToTreePath,
8
+ treePathToDomPathString
9
+ } from './PathConverter';
10
+ import { PathConversionError } from './PathValidator';
11
+
12
+ describe('PathConverter', () => {
13
+ describe('normalizePathInput', () => {
14
+ test('returns path array unchanged', () => {
15
+ const path = [0, 1, 2];
16
+ expect(normalizePathInput(path)).toEqual([0, 1, 2]);
17
+ });
18
+
19
+ test('converts string to path array', () => {
20
+ expect(normalizePathInput('0,1,2')).toEqual([0, 1, 2]);
21
+ });
22
+
23
+ test('converts root string to [0]', () => {
24
+ expect(normalizePathInput('root')).toEqual([0]);
25
+ });
26
+
27
+ test('throws on non-array non-string input', () => {
28
+ expect(() => normalizePathInput(123 as any)).toThrow(PathConversionError);
29
+ expect(() => normalizePathInput({} as any)).toThrow(PathConversionError);
30
+ });
31
+ });
32
+
33
+ describe('pathToString', () => {
34
+ test('converts path to comma-separated string', () => {
35
+ expect(pathToString([0, 1, 2])).toBe('0,1,2');
36
+ });
37
+
38
+ test('converts single element path', () => {
39
+ expect(pathToString([0])).toBe('0');
40
+ });
41
+
42
+ test('converts empty path', () => {
43
+ expect(pathToString([])).toBe('');
44
+ });
45
+
46
+ test('handles path with zeros', () => {
47
+ expect(pathToString([0, 0, 0])).toBe('0,0,0');
48
+ });
49
+
50
+ test('handles path with large numbers', () => {
51
+ expect(pathToString([100, 200, 300])).toBe('100,200,300');
52
+ });
53
+ });
54
+
55
+ describe('stringToPath', () => {
56
+ test('converts comma-separated string to path', () => {
57
+ expect(stringToPath('0,1,2')).toEqual([0, 1, 2]);
58
+ });
59
+
60
+ test('converts single number string', () => {
61
+ expect(stringToPath('5')).toEqual([5]);
62
+ });
63
+
64
+ test('converts root string to [0]', () => {
65
+ expect(stringToPath('root')).toEqual([0]);
66
+ expect(stringToPath('root_0')).toEqual([0]);
67
+ });
68
+
69
+ test('converts empty string to [0]', () => {
70
+ expect(stringToPath('')).toEqual([0]);
71
+ });
72
+
73
+ test('throws on invalid numeric string', () => {
74
+ expect(() => stringToPath('0,abc,2')).toThrow(PathConversionError);
75
+ });
76
+
77
+ test('throws on non-numeric values', () => {
78
+ expect(() => stringToPath('a,b,c')).toThrow(PathConversionError);
79
+ });
80
+
81
+ test('handles path with zeros', () => {
82
+ expect(stringToPath('0,0,0')).toEqual([0, 0, 0]);
83
+ });
84
+
85
+ test('parses negative numbers', () => {
86
+ expect(stringToPath('0,-1,2')).toEqual([0, -1, 2]);
87
+ });
88
+ });
89
+
90
+ describe('pathToLegacyString', () => {
91
+ test('converts [0] to root_0', () => {
92
+ expect(pathToLegacyString([0])).toBe('root_0');
93
+ });
94
+
95
+ test('converts empty path to root_0', () => {
96
+ expect(pathToLegacyString([])).toBe('root_0');
97
+ });
98
+
99
+ test('converts [0,0] to root_0_children_0', () => {
100
+ expect(pathToLegacyString([0, 0])).toBe('root_0_children_0');
101
+ });
102
+
103
+ test('converts [0,1,2] to root_0_children_1_children_2', () => {
104
+ expect(pathToLegacyString([0, 1, 2])).toBe('root_0_children_1_children_2');
105
+ });
106
+
107
+ test('handles deep paths', () => {
108
+ expect(pathToLegacyString([0, 0, 1, 2, 3])).toBe('root_0_children_0_children_1_children_2_children_3');
109
+ });
110
+
111
+ test('handles path with large indices', () => {
112
+ expect(pathToLegacyString([0, 100])).toBe('root_0_children_100');
113
+ });
114
+ });
115
+
116
+ describe('domPathStringToTreePath', () => {
117
+ test('converts root to [0]', () => {
118
+ expect(domPathStringToTreePath('root')).toEqual([0]);
119
+ });
120
+
121
+ test('converts empty string to [0]', () => {
122
+ expect(domPathStringToTreePath('')).toEqual([0]);
123
+ });
124
+
125
+ test('converts root_children_0 to [0,0]', () => {
126
+ expect(domPathStringToTreePath('root_children_0')).toEqual([0, 0]);
127
+ });
128
+
129
+ test('converts root_children_1_children_2 to [0,1,2]', () => {
130
+ expect(domPathStringToTreePath('root_children_1_children_2')).toEqual([0, 1, 2]);
131
+ });
132
+
133
+ test('handles deep DOM paths', () => {
134
+ expect(domPathStringToTreePath('root_children_0_children_1_children_2')).toEqual([0, 0, 1, 2]);
135
+ });
136
+
137
+ test('handles DOM path without root_ prefix', () => {
138
+ expect(domPathStringToTreePath('children_0_children_1')).toEqual([0, 0, 1]);
139
+ });
140
+
141
+ test('skips invalid segments', () => {
142
+ expect(domPathStringToTreePath('root_children_0_invalid_children_1')).toEqual([0, 0, 1]);
143
+ });
144
+
145
+ test('handles path with large indices', () => {
146
+ expect(domPathStringToTreePath('root_children_100')).toEqual([0, 100]);
147
+ });
148
+
149
+ test('handles malformed paths gracefully', () => {
150
+ expect(domPathStringToTreePath('root_children_abc')).toEqual([0]);
151
+ });
152
+ });
153
+
154
+ describe('treePathToDomPathString', () => {
155
+ test('converts [0] to root', () => {
156
+ expect(treePathToDomPathString([0])).toBe('root');
157
+ });
158
+
159
+ test('converts empty path to root', () => {
160
+ expect(treePathToDomPathString([])).toBe('root');
161
+ });
162
+
163
+ test('converts [0,0] to root_children_0', () => {
164
+ expect(treePathToDomPathString([0, 0])).toBe('root_children_0');
165
+ });
166
+
167
+ test('converts [0,1,2] to root_children_1_children_2', () => {
168
+ expect(treePathToDomPathString([0, 1, 2])).toBe('root_children_1_children_2');
169
+ });
170
+
171
+ test('handles deep paths', () => {
172
+ expect(treePathToDomPathString([0, 0, 1, 2, 3])).toBe('root_children_0_children_1_children_2_children_3');
173
+ });
174
+
175
+ test('handles string input by converting first', () => {
176
+ expect(treePathToDomPathString('0,1,2')).toBe('root_children_1_children_2');
177
+ });
178
+
179
+ test('throws on invalid path values', () => {
180
+ expect(() => treePathToDomPathString([0, NaN, 2])).toThrow();
181
+ });
182
+
183
+ test('throws on non-numeric path values', () => {
184
+ expect(() => treePathToDomPathString([0, 'invalid' as any, 2])).toThrow();
185
+ });
186
+
187
+ test('handles path with large indices', () => {
188
+ expect(treePathToDomPathString([0, 100])).toBe('root_children_100');
189
+ });
190
+
191
+ test('validates path before conversion', () => {
192
+ expect(() => treePathToDomPathString([0, Infinity])).toThrow();
193
+ });
194
+ });
195
+
196
+ describe('round-trip conversions', () => {
197
+ test('pathToString and stringToPath are inverses', () => {
198
+ const path = [0, 1, 2, 3];
199
+ const str = pathToString(path);
200
+ const result = stringToPath(str);
201
+ expect(result).toEqual(path);
202
+ });
203
+
204
+ test('treePathToDomPathString and domPathStringToTreePath are inverses', () => {
205
+ const path = [0, 1, 2, 3];
206
+ const str = treePathToDomPathString(path);
207
+ const result = domPathStringToTreePath(str);
208
+ expect(result).toEqual(path);
209
+ });
210
+
211
+ test('multiple round-trips preserve path', () => {
212
+ let path = [0, 1, 2];
213
+ for (let i = 0; i < 3; i++) {
214
+ const str = treePathToDomPathString(path);
215
+ path = domPathStringToTreePath(str);
216
+ }
217
+ expect(path).toEqual([0, 1, 2]);
218
+ });
219
+
220
+ test('pathToString handles empty path correctly', () => {
221
+ const path: number[] = [];
222
+ const str = pathToString(path);
223
+ expect(str).toBe('');
224
+ });
225
+
226
+ test('root path conversions are consistent', () => {
227
+ expect(treePathToDomPathString([0])).toBe('root');
228
+ expect(domPathStringToTreePath('root')).toEqual([0]);
229
+ expect(treePathToDomPathString([])).toBe('root');
230
+ });
231
+ });
232
+ });