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.
- package/bin/cli.ts +281 -0
- package/build-static.ts +298 -0
- package/bunfig.toml +39 -0
- package/entries/client-router.tsx +111 -0
- package/entries/server-router.tsx +71 -0
- package/lib/client/ClientInitializer.test.ts +9 -0
- package/lib/client/ClientInitializer.test.ts.skip +92 -0
- package/lib/client/ClientInitializer.ts +60 -0
- package/lib/client/ErrorBoundary.test.tsx +595 -0
- package/lib/client/ErrorBoundary.tsx +230 -0
- package/lib/client/componentRegistry.test.ts +165 -0
- package/lib/client/componentRegistry.ts +18 -0
- package/lib/client/contexts/ThemeContext.tsx +73 -0
- package/lib/client/core/ComponentBuilder.test.ts +677 -0
- package/lib/client/core/ComponentBuilder.ts +660 -0
- package/lib/client/core/ComponentRenderer.test.tsx +176 -0
- package/lib/client/core/ComponentRenderer.tsx +83 -0
- package/lib/client/core/cmsTemplateProcessor.ts +129 -0
- package/lib/client/elementRegistry.ts +81 -0
- package/lib/client/hmr/HMRManager.tsx +179 -0
- package/lib/client/hmr/index.ts +5 -0
- package/lib/client/hmrWebSocket.test.ts +9 -0
- package/lib/client/hmrWebSocket.ts +250 -0
- package/lib/client/hooks/useColorVariables.test.ts +166 -0
- package/lib/client/hooks/useColorVariables.ts +249 -0
- package/lib/client/hooks/usePropertyAutocomplete.test.ts +9 -0
- package/lib/client/hooks/usePropertyAutocomplete.ts +40 -0
- package/lib/client/hydration/HydrationUtils.test.ts +154 -0
- package/lib/client/hydration/HydrationUtils.ts +35 -0
- package/lib/client/i18nConfigService.test.ts +74 -0
- package/lib/client/i18nConfigService.ts +78 -0
- package/lib/client/index.ts +56 -0
- package/lib/client/navigation.test.ts +441 -0
- package/lib/client/navigation.ts +23 -0
- package/lib/client/responsiveStyleResolver.test.ts +491 -0
- package/lib/client/responsiveStyleResolver.ts +184 -0
- package/lib/client/routing/RouteLoader.test.ts +635 -0
- package/lib/client/routing/RouteLoader.ts +347 -0
- package/lib/client/routing/Router.tsx +382 -0
- package/lib/client/scripts/ScriptExecutor.test.ts +489 -0
- package/lib/client/scripts/ScriptExecutor.ts +171 -0
- package/lib/client/scripts/formHandler.ts +103 -0
- package/lib/client/styleProcessor.test.ts +126 -0
- package/lib/client/styleProcessor.ts +92 -0
- package/lib/client/styles/StyleInjector.test.ts +354 -0
- package/lib/client/styles/StyleInjector.ts +154 -0
- package/lib/client/templateEngine.test.ts +660 -0
- package/lib/client/templateEngine.ts +667 -0
- package/lib/client/theme.test.ts +173 -0
- package/lib/client/theme.ts +159 -0
- package/lib/client/utils/toast.ts +46 -0
- package/lib/server/createServer.ts +170 -0
- package/lib/server/cssGenerator.test.ts +172 -0
- package/lib/server/cssGenerator.ts +58 -0
- package/lib/server/fileWatcher.ts +134 -0
- package/lib/server/index.ts +55 -0
- package/lib/server/jsonLoader.test.ts +103 -0
- package/lib/server/jsonLoader.ts +350 -0
- package/lib/server/middleware/cors.test.ts +177 -0
- package/lib/server/middleware/cors.ts +69 -0
- package/lib/server/middleware/errorHandler.test.ts +208 -0
- package/lib/server/middleware/errorHandler.ts +63 -0
- package/lib/server/middleware/index.ts +9 -0
- package/lib/server/middleware/logger.test.ts +233 -0
- package/lib/server/middleware/logger.ts +99 -0
- package/lib/server/pageCache.test.ts +167 -0
- package/lib/server/pageCache.ts +97 -0
- package/lib/server/projectContext.ts +51 -0
- package/lib/server/providers/fileSystemCMSProvider.test.ts +292 -0
- package/lib/server/providers/fileSystemCMSProvider.ts +227 -0
- package/lib/server/providers/fileSystemPageProvider.ts +83 -0
- package/lib/server/routes/api/cms.test.ts +177 -0
- package/lib/server/routes/api/cms.ts +82 -0
- package/lib/server/routes/api/colors.ts +59 -0
- package/lib/server/routes/api/components.ts +70 -0
- package/lib/server/routes/api/config.test.ts +9 -0
- package/lib/server/routes/api/config.ts +28 -0
- package/lib/server/routes/api/core-routes.ts +182 -0
- package/lib/server/routes/api/functions.ts +170 -0
- package/lib/server/routes/api/index.ts +69 -0
- package/lib/server/routes/api/pages.ts +95 -0
- package/lib/server/routes/api/shared.test.ts +81 -0
- package/lib/server/routes/api/shared.ts +31 -0
- package/lib/server/routes/editor.test.ts +9 -0
- package/lib/server/routes/index.ts +104 -0
- package/lib/server/routes/pages.ts +161 -0
- package/lib/server/routes/static.ts +107 -0
- package/lib/server/services/ColorService.ts +193 -0
- package/lib/server/services/cmsService.test.ts +388 -0
- package/lib/server/services/cmsService.ts +296 -0
- package/lib/server/services/componentService.test.ts +276 -0
- package/lib/server/services/componentService.ts +346 -0
- package/lib/server/services/configService.ts +156 -0
- package/lib/server/services/fileWatcherService.ts +67 -0
- package/lib/server/services/index.ts +10 -0
- package/lib/server/services/pageService.test.ts +258 -0
- package/lib/server/services/pageService.ts +240 -0
- package/lib/server/ssrRenderer.test.ts +1005 -0
- package/lib/server/ssrRenderer.ts +878 -0
- package/lib/server/utilityClassGenerator.ts +11 -0
- package/lib/server/utils/index.ts +5 -0
- package/lib/server/utils/jsonLineMapper.test.ts +100 -0
- package/lib/server/utils/jsonLineMapper.ts +166 -0
- package/lib/server/validateStyleCoverage.test.ts +9 -0
- package/lib/server/validateStyleCoverage.ts +167 -0
- package/lib/server/websocketManager.test.ts +9 -0
- package/lib/server/websocketManager.ts +95 -0
- package/lib/shared/attributeNodeUtils.test.ts +152 -0
- package/lib/shared/attributeNodeUtils.ts +50 -0
- package/lib/shared/breakpoints.test.ts +166 -0
- package/lib/shared/breakpoints.ts +65 -0
- package/lib/shared/colorProperties.test.ts +111 -0
- package/lib/shared/colorProperties.ts +40 -0
- package/lib/shared/colorVariableUtils.test.ts +319 -0
- package/lib/shared/colorVariableUtils.ts +97 -0
- package/lib/shared/constants.test.ts +175 -0
- package/lib/shared/constants.ts +116 -0
- package/lib/shared/cssGeneration.ts +481 -0
- package/lib/shared/cssProperties.test.ts +252 -0
- package/lib/shared/cssProperties.ts +338 -0
- package/lib/shared/elementUtils.test.ts +245 -0
- package/lib/shared/elementUtils.ts +90 -0
- package/lib/shared/fontLoader.ts +97 -0
- package/lib/shared/i18n.test.ts +313 -0
- package/lib/shared/i18n.ts +286 -0
- package/lib/shared/index.ts +50 -0
- package/lib/shared/interfaces/contentProvider.test.ts +9 -0
- package/lib/shared/interfaces/contentProvider.ts +121 -0
- package/lib/shared/nodeUtils.test.ts +320 -0
- package/lib/shared/nodeUtils.ts +220 -0
- package/lib/shared/pathArrayUtils.test.ts +315 -0
- package/lib/shared/pathArrayUtils.ts +17 -0
- package/lib/shared/pathUtils.test.ts +260 -0
- package/lib/shared/pathUtils.ts +244 -0
- package/lib/shared/paths/Path.test.ts +74 -0
- package/lib/shared/paths/Path.ts +23 -0
- package/lib/shared/paths/PathConverter.test.ts +232 -0
- package/lib/shared/paths/PathConverter.ts +141 -0
- package/lib/shared/paths/PathUtils.ts +290 -0
- package/lib/shared/paths/PathValidator.test.ts +193 -0
- package/lib/shared/paths/PathValidator.ts +53 -0
- package/lib/shared/paths/index.ts +48 -0
- package/lib/shared/propResolver.test.ts +639 -0
- package/lib/shared/propResolver.ts +124 -0
- package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +190 -0
- package/lib/shared/registry/BaseNodeTypeRegistry.ts +200 -0
- package/lib/shared/registry/ClientNodeTypeRegistry.ts +34 -0
- package/lib/shared/registry/ClientRegistry.test.ts +26 -0
- package/lib/shared/registry/ClientRegistry.ts +15 -0
- package/lib/shared/registry/ComponentRegistry.test.ts +293 -0
- package/lib/shared/registry/ComponentRegistry.ts +100 -0
- package/lib/shared/registry/NodeTypeDefinition.ts +198 -0
- package/lib/shared/registry/NodeTypeManager.ts +94 -0
- package/lib/shared/registry/RegistryManager.test.ts +58 -0
- package/lib/shared/registry/RegistryManager.ts +60 -0
- package/lib/shared/registry/SSRNodeTypeRegistry.ts +33 -0
- package/lib/shared/registry/SSRRegistry.test.ts +26 -0
- package/lib/shared/registry/SSRRegistry.ts +15 -0
- package/lib/shared/registry/createNodeType.ts +175 -0
- package/lib/shared/registry/defineNodeType.ts +73 -0
- package/lib/shared/registry/fieldPresets.ts +109 -0
- package/lib/shared/registry/index.ts +50 -0
- package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +71 -0
- package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +61 -0
- package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +88 -0
- package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +66 -0
- package/lib/shared/registry/nodeTypes/ObjectLinkNodeType.ts +75 -0
- package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +49 -0
- package/lib/shared/registry/nodeTypes/TextNodeType.ts +52 -0
- package/lib/shared/registry/nodeTypes/index.ts +75 -0
- package/lib/shared/responsiveScaling.test.ts +268 -0
- package/lib/shared/responsiveScaling.ts +194 -0
- package/lib/shared/responsiveStyleUtils.test.ts +300 -0
- package/lib/shared/responsiveStyleUtils.ts +139 -0
- package/lib/shared/slugTranslator.test.ts +325 -0
- package/lib/shared/slugTranslator.ts +177 -0
- package/lib/shared/styleNodeUtils.test.ts +132 -0
- package/lib/shared/styleNodeUtils.ts +102 -0
- package/lib/shared/styleUtils.test.ts +238 -0
- package/lib/shared/styleUtils.ts +63 -0
- package/lib/shared/themeDefaults.test.ts +113 -0
- package/lib/shared/themeDefaults.ts +103 -0
- package/lib/shared/tree/PathBuilder.ts +383 -0
- package/lib/shared/treePathUtils.test.ts +539 -0
- package/lib/shared/treePathUtils.ts +339 -0
- package/lib/shared/types/api.ts +58 -0
- package/lib/shared/types/cms.ts +95 -0
- package/lib/shared/types/colors.ts +45 -0
- package/lib/shared/types/components.ts +121 -0
- package/lib/shared/types/errors.test.ts +103 -0
- package/lib/shared/types/errors.ts +69 -0
- package/lib/shared/types/index.ts +96 -0
- package/lib/shared/types/nodes.ts +20 -0
- package/lib/shared/types/rendering.ts +61 -0
- package/lib/shared/types/styles.ts +38 -0
- package/lib/shared/types.ts +11 -0
- package/lib/shared/utilityClassConfig.ts +287 -0
- package/lib/shared/utilityClassMapper.test.ts +140 -0
- package/lib/shared/utilityClassMapper.ts +229 -0
- package/lib/shared/utils/fileUtils.test.ts +99 -0
- package/lib/shared/utils/fileUtils.ts +56 -0
- package/lib/shared/utils.test.ts +261 -0
- package/lib/shared/utils.ts +84 -0
- package/lib/shared/validation/index.ts +7 -0
- package/lib/shared/validation/propValidator.test.ts +178 -0
- package/lib/shared/validation/propValidator.ts +238 -0
- package/lib/shared/validation/schemas.test.ts +177 -0
- package/lib/shared/validation/schemas.ts +401 -0
- package/lib/shared/validation/validators.test.ts +109 -0
- package/lib/shared/validation/validators.ts +304 -0
- package/lib/test-utils/dom-setup.ts +55 -0
- package/lib/test-utils/factories/ConsoleMockFactory.ts +200 -0
- package/lib/test-utils/factories/DomMockFactory.ts +487 -0
- package/lib/test-utils/factories/EventMockFactory.ts +244 -0
- package/lib/test-utils/factories/FetchMockFactory.ts +210 -0
- package/lib/test-utils/factories/ServerMockFactory.ts +223 -0
- package/lib/test-utils/factories/StoreMockFactory.ts +370 -0
- package/lib/test-utils/factories/index.ts +11 -0
- package/lib/test-utils/fixtures.ts +134 -0
- package/lib/test-utils/helpers/asyncHelpers.test.ts +112 -0
- package/lib/test-utils/helpers/asyncHelpers.ts +196 -0
- package/lib/test-utils/helpers/index.ts +6 -0
- package/lib/test-utils/helpers.test.ts +73 -0
- package/lib/test-utils/helpers.ts +90 -0
- package/lib/test-utils/index.ts +17 -0
- package/lib/test-utils/mockFactories.ts +92 -0
- package/lib/test-utils/mocks.ts +341 -0
- package/package.json +38 -0
- package/templates/index-router.html +34 -0
- package/tsconfig.json +14 -0
- 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
|
+
});
|