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,245 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
2
|
+
import { findElementWithPath, findComponentInstanceRoot, findComponentInstanceRootByParent } from './elementUtils';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* elementUtils Tests
|
|
6
|
+
* Tests DOM element utility functions
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
describe('elementUtils', () => {
|
|
10
|
+
let container: HTMLDivElement;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
container = document.createElement('div');
|
|
14
|
+
document.body.appendChild(container);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
document.body.removeChild(container);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('findElementWithPath', () => {
|
|
22
|
+
test('should find element with data-element-path attribute', () => {
|
|
23
|
+
const parent = document.createElement('div');
|
|
24
|
+
parent.setAttribute('data-element-path', '0,0');
|
|
25
|
+
const child = document.createElement('span');
|
|
26
|
+
parent.appendChild(child);
|
|
27
|
+
container.appendChild(parent);
|
|
28
|
+
|
|
29
|
+
const result = findElementWithPath(child);
|
|
30
|
+
expect(result).toBe(parent);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('should return the element itself if it has data-element-path', () => {
|
|
34
|
+
const element = document.createElement('div');
|
|
35
|
+
element.setAttribute('data-element-path', '0,0');
|
|
36
|
+
container.appendChild(element);
|
|
37
|
+
|
|
38
|
+
const result = findElementWithPath(element);
|
|
39
|
+
expect(result).toBe(element);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('should return null if no element with path is found', () => {
|
|
43
|
+
const element = document.createElement('div');
|
|
44
|
+
container.appendChild(element);
|
|
45
|
+
|
|
46
|
+
const result = findElementWithPath(element);
|
|
47
|
+
expect(result).toBeNull();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('should return null for null input', () => {
|
|
51
|
+
const result = findElementWithPath(null);
|
|
52
|
+
expect(result).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('should traverse multiple levels up', () => {
|
|
56
|
+
const grandparent = document.createElement('div');
|
|
57
|
+
grandparent.setAttribute('data-element-path', '0,0');
|
|
58
|
+
const parent = document.createElement('div');
|
|
59
|
+
const child = document.createElement('span');
|
|
60
|
+
grandparent.appendChild(parent);
|
|
61
|
+
parent.appendChild(child);
|
|
62
|
+
container.appendChild(grandparent);
|
|
63
|
+
|
|
64
|
+
const result = findElementWithPath(child);
|
|
65
|
+
expect(result).toBe(grandparent);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('should find nearest ancestor with path', () => {
|
|
69
|
+
const grandparent = document.createElement('div');
|
|
70
|
+
grandparent.setAttribute('data-element-path', '0,0');
|
|
71
|
+
const parent = document.createElement('div');
|
|
72
|
+
parent.setAttribute('data-element-path', '0,0,0');
|
|
73
|
+
const child = document.createElement('span');
|
|
74
|
+
grandparent.appendChild(parent);
|
|
75
|
+
parent.appendChild(child);
|
|
76
|
+
container.appendChild(grandparent);
|
|
77
|
+
|
|
78
|
+
const result = findElementWithPath(child);
|
|
79
|
+
expect(result).toBe(parent);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('findComponentInstanceRoot', () => {
|
|
84
|
+
test('should find component root by matching parent-component', () => {
|
|
85
|
+
const root = document.createElement('div');
|
|
86
|
+
root.setAttribute('data-parent-component', 'Button');
|
|
87
|
+
root.setAttribute('data-element-path', '0,0');
|
|
88
|
+
|
|
89
|
+
const child = document.createElement('span');
|
|
90
|
+
child.setAttribute('data-parent-component', 'Button');
|
|
91
|
+
|
|
92
|
+
root.appendChild(child);
|
|
93
|
+
container.appendChild(root);
|
|
94
|
+
|
|
95
|
+
const result = findComponentInstanceRoot(child);
|
|
96
|
+
expect(result).toBe(root);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('should return null if element has no data-parent-component', () => {
|
|
100
|
+
const element = document.createElement('div');
|
|
101
|
+
container.appendChild(element);
|
|
102
|
+
|
|
103
|
+
const result = findComponentInstanceRoot(element);
|
|
104
|
+
expect(result).toBeNull();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('should find topmost element with same component name', () => {
|
|
108
|
+
const root = document.createElement('div');
|
|
109
|
+
root.setAttribute('data-parent-component', 'Button');
|
|
110
|
+
root.setAttribute('data-element-path', '0,0');
|
|
111
|
+
|
|
112
|
+
const middle = document.createElement('div');
|
|
113
|
+
middle.setAttribute('data-parent-component', 'Button');
|
|
114
|
+
middle.setAttribute('data-element-path', '0,0,0');
|
|
115
|
+
|
|
116
|
+
const child = document.createElement('span');
|
|
117
|
+
child.setAttribute('data-parent-component', 'Button');
|
|
118
|
+
|
|
119
|
+
root.appendChild(middle);
|
|
120
|
+
middle.appendChild(child);
|
|
121
|
+
container.appendChild(root);
|
|
122
|
+
|
|
123
|
+
const result = findComponentInstanceRoot(child);
|
|
124
|
+
expect(result).toBe(root);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('should stop at different component boundary', () => {
|
|
128
|
+
const outerComponent = document.createElement('div');
|
|
129
|
+
outerComponent.setAttribute('data-parent-component', 'Container');
|
|
130
|
+
outerComponent.setAttribute('data-element-path', '0,0');
|
|
131
|
+
|
|
132
|
+
const innerComponent = document.createElement('div');
|
|
133
|
+
innerComponent.setAttribute('data-parent-component', 'Button');
|
|
134
|
+
innerComponent.setAttribute('data-element-path', '0,0,0');
|
|
135
|
+
|
|
136
|
+
const child = document.createElement('span');
|
|
137
|
+
child.setAttribute('data-parent-component', 'Button');
|
|
138
|
+
|
|
139
|
+
outerComponent.appendChild(innerComponent);
|
|
140
|
+
innerComponent.appendChild(child);
|
|
141
|
+
container.appendChild(outerComponent);
|
|
142
|
+
|
|
143
|
+
const result = findComponentInstanceRoot(child);
|
|
144
|
+
expect(result).toBe(innerComponent);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('should handle element with path attribute', () => {
|
|
148
|
+
const element = document.createElement('div');
|
|
149
|
+
element.setAttribute('data-parent-component', 'Button');
|
|
150
|
+
element.setAttribute('data-element-path', '0,0');
|
|
151
|
+
container.appendChild(element);
|
|
152
|
+
|
|
153
|
+
const result = findComponentInstanceRoot(element);
|
|
154
|
+
expect(result).toBe(element);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('should return null if no element with path is found in component', () => {
|
|
158
|
+
const parent = document.createElement('div');
|
|
159
|
+
const child = document.createElement('span');
|
|
160
|
+
child.setAttribute('data-parent-component', 'Button');
|
|
161
|
+
|
|
162
|
+
parent.appendChild(child);
|
|
163
|
+
container.appendChild(parent);
|
|
164
|
+
|
|
165
|
+
const result = findComponentInstanceRoot(child);
|
|
166
|
+
expect(result).toBeNull();
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe('findComponentInstanceRootByParent', () => {
|
|
171
|
+
test('should find root element without data-parent-component', () => {
|
|
172
|
+
const root = document.createElement('div');
|
|
173
|
+
|
|
174
|
+
const child = document.createElement('span');
|
|
175
|
+
child.setAttribute('data-parent-component', 'Button');
|
|
176
|
+
|
|
177
|
+
root.appendChild(child);
|
|
178
|
+
container.appendChild(root);
|
|
179
|
+
|
|
180
|
+
const result = findComponentInstanceRootByParent(child);
|
|
181
|
+
expect(result).toBe(root);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('should return element itself if it has no data-parent-component', () => {
|
|
185
|
+
const element = document.createElement('div');
|
|
186
|
+
container.appendChild(element);
|
|
187
|
+
|
|
188
|
+
const result = findComponentInstanceRootByParent(element);
|
|
189
|
+
expect(result).toBe(element);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('should traverse multiple levels', () => {
|
|
193
|
+
const root = document.createElement('div');
|
|
194
|
+
|
|
195
|
+
const middle = document.createElement('div');
|
|
196
|
+
middle.setAttribute('data-parent-component', 'Button');
|
|
197
|
+
|
|
198
|
+
const child = document.createElement('span');
|
|
199
|
+
child.setAttribute('data-parent-component', 'Button');
|
|
200
|
+
|
|
201
|
+
root.appendChild(middle);
|
|
202
|
+
middle.appendChild(child);
|
|
203
|
+
container.appendChild(root);
|
|
204
|
+
|
|
205
|
+
const result = findComponentInstanceRootByParent(child);
|
|
206
|
+
expect(result).toBe(root);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('should return null if no root found', () => {
|
|
210
|
+
// Create a detached element tree where all have data-parent-component
|
|
211
|
+
const parent = document.createElement('div');
|
|
212
|
+
parent.setAttribute('data-parent-component', 'Container');
|
|
213
|
+
|
|
214
|
+
const child = document.createElement('span');
|
|
215
|
+
child.setAttribute('data-parent-component', 'Button');
|
|
216
|
+
|
|
217
|
+
parent.appendChild(child);
|
|
218
|
+
// Don't append to container - it's detached
|
|
219
|
+
|
|
220
|
+
const result = findComponentInstanceRootByParent(child);
|
|
221
|
+
expect(result).toBeNull();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('should find root in nested component structure', () => {
|
|
225
|
+
const root = document.createElement('div');
|
|
226
|
+
|
|
227
|
+
const outerComponent = document.createElement('div');
|
|
228
|
+
outerComponent.setAttribute('data-parent-component', 'Container');
|
|
229
|
+
|
|
230
|
+
const innerComponent = document.createElement('div');
|
|
231
|
+
innerComponent.setAttribute('data-parent-component', 'Button');
|
|
232
|
+
|
|
233
|
+
const child = document.createElement('span');
|
|
234
|
+
child.setAttribute('data-parent-component', 'Button');
|
|
235
|
+
|
|
236
|
+
root.appendChild(outerComponent);
|
|
237
|
+
outerComponent.appendChild(innerComponent);
|
|
238
|
+
innerComponent.appendChild(child);
|
|
239
|
+
container.appendChild(root);
|
|
240
|
+
|
|
241
|
+
const result = findComponentInstanceRootByParent(child);
|
|
242
|
+
expect(result).toBe(root);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared element utility functions
|
|
3
|
+
* Used for finding and manipulating DOM elements in the editor
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Find an element with data-element-path attribute by traversing up the DOM tree
|
|
8
|
+
*
|
|
9
|
+
* @param element - Starting element to search from
|
|
10
|
+
* @returns Element with data-element-path attribute, or null if not found
|
|
11
|
+
*/
|
|
12
|
+
export function findElementWithPath(element: HTMLElement | null): HTMLElement | null {
|
|
13
|
+
let current: HTMLElement | null = element;
|
|
14
|
+
while (current) {
|
|
15
|
+
if (current.hasAttribute('data-element-path')) {
|
|
16
|
+
return current;
|
|
17
|
+
}
|
|
18
|
+
current = current.parentElement;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Find the component instance root element by walking up the DOM tree
|
|
25
|
+
* The component root is the topmost element (walking up) that:
|
|
26
|
+
* 1. Has data-element-path (is selectable in the tree)
|
|
27
|
+
* 2. Has data-parent-component matching the starting element's component
|
|
28
|
+
*
|
|
29
|
+
* @param element - Starting element (should have data-parent-component attribute)
|
|
30
|
+
* @returns Component instance root element, or null if not found
|
|
31
|
+
*/
|
|
32
|
+
export function findComponentInstanceRoot(element: HTMLElement): HTMLElement | null {
|
|
33
|
+
const componentName = element.getAttribute('data-parent-component');
|
|
34
|
+
if (!componentName) return null;
|
|
35
|
+
|
|
36
|
+
let current: HTMLElement | null = element;
|
|
37
|
+
let componentRoot: HTMLElement | null = null;
|
|
38
|
+
|
|
39
|
+
// Walk up the DOM tree
|
|
40
|
+
while (current) {
|
|
41
|
+
const currentComponentName = current.getAttribute('data-parent-component');
|
|
42
|
+
const hasPath = current.hasAttribute('data-element-path');
|
|
43
|
+
|
|
44
|
+
// If this element has the same component name and has a path, remember it
|
|
45
|
+
// We want the LAST (topmost) one we encounter while still in the component
|
|
46
|
+
if (currentComponentName === componentName && hasPath) {
|
|
47
|
+
componentRoot = current;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Stop if we've moved to a different component
|
|
51
|
+
if (currentComponentName && currentComponentName !== componentName) {
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// If we've exited the component (no data-parent-component) and we found a root, stop
|
|
56
|
+
if (!currentComponentName && componentRoot) {
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
current = current.parentElement;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return componentRoot;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Alternative implementation: Find component root by walking up until we find
|
|
68
|
+
* an element without data-parent-component (the component root)
|
|
69
|
+
* This is used by elementHighlighter.ts
|
|
70
|
+
*
|
|
71
|
+
* @param element - Starting element
|
|
72
|
+
* @returns Component instance root element, or null if not found
|
|
73
|
+
*/
|
|
74
|
+
export function findComponentInstanceRootByParent(element: HTMLElement): HTMLElement | null {
|
|
75
|
+
let current: HTMLElement | null = element;
|
|
76
|
+
|
|
77
|
+
// Walk up until we find an element without data-parent-component (the component root)
|
|
78
|
+
while (current) {
|
|
79
|
+
if (!current.hasAttribute('data-parent-component')) {
|
|
80
|
+
return current;
|
|
81
|
+
}
|
|
82
|
+
current = current.parentElement;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { projectPaths } from '../server/projectContext';
|
|
2
|
+
|
|
3
|
+
export interface FontConfig {
|
|
4
|
+
path: string;
|
|
5
|
+
family?: string;
|
|
6
|
+
weight?: number;
|
|
7
|
+
style?: 'normal' | 'italic';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ProjectConfig {
|
|
11
|
+
fonts?: FontConfig[];
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let cachedConfig: ProjectConfig | null = null;
|
|
16
|
+
|
|
17
|
+
export async function loadProjectConfig(): Promise<ProjectConfig> {
|
|
18
|
+
if (cachedConfig) return cachedConfig;
|
|
19
|
+
const file = Bun.file(projectPaths.config());
|
|
20
|
+
if (await file.exists()) {
|
|
21
|
+
cachedConfig = await file.json();
|
|
22
|
+
} else {
|
|
23
|
+
cachedConfig = { fonts: [] };
|
|
24
|
+
}
|
|
25
|
+
return cachedConfig;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// For synchronous access after config is loaded
|
|
29
|
+
export function getProjectConfig(): ProjectConfig {
|
|
30
|
+
return cachedConfig || { fonts: [] };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Detect font format from file extension
|
|
35
|
+
*/
|
|
36
|
+
function getFontFormat(path: string): string {
|
|
37
|
+
if (path.endsWith('.woff2')) return 'woff2';
|
|
38
|
+
if (path.endsWith('.woff')) return 'woff';
|
|
39
|
+
if (path.endsWith('.ttf')) return 'truetype';
|
|
40
|
+
if (path.endsWith('.otf')) return 'opentype';
|
|
41
|
+
return 'truetype';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Extract font family name from path if not provided
|
|
46
|
+
* Example: "/fonts/geomanist-regular.ttf" -> "Geomanist"
|
|
47
|
+
*/
|
|
48
|
+
function extractFamilyName(path: string): string {
|
|
49
|
+
const filename = path.split('/').pop() || 'Font';
|
|
50
|
+
const name = filename.replace(/\.(ttf|woff2?|otf)$/i, '');
|
|
51
|
+
// Capitalize and replace hyphens with spaces
|
|
52
|
+
return name
|
|
53
|
+
.split('-')
|
|
54
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
55
|
+
.join(' ');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function generateFontCSS(): string {
|
|
59
|
+
const config = getProjectConfig();
|
|
60
|
+
const fonts = config.fonts || [];
|
|
61
|
+
|
|
62
|
+
return fonts
|
|
63
|
+
.map((font: FontConfig) => {
|
|
64
|
+
const format = getFontFormat(font.path);
|
|
65
|
+
const family = font.family || extractFamilyName(font.path);
|
|
66
|
+
const weight = font.weight ?? 400;
|
|
67
|
+
const style = font.style ?? 'normal';
|
|
68
|
+
|
|
69
|
+
return `@font-face {
|
|
70
|
+
font-family: '${family}';
|
|
71
|
+
src: url('${font.path}') format('${format}');
|
|
72
|
+
font-weight: ${weight};
|
|
73
|
+
font-style: ${style};
|
|
74
|
+
}`;
|
|
75
|
+
})
|
|
76
|
+
.join('\n\n');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function getFontFamilies(): Record<string, number[]> {
|
|
80
|
+
const config = getProjectConfig();
|
|
81
|
+
const fonts = config.fonts || [];
|
|
82
|
+
const familiesMap: Record<string, number[]> = {};
|
|
83
|
+
|
|
84
|
+
fonts.forEach((font: FontConfig) => {
|
|
85
|
+
const family = font.family || extractFamilyName(font.path);
|
|
86
|
+
const weight = font.weight ?? 400;
|
|
87
|
+
|
|
88
|
+
if (!familiesMap[family]) {
|
|
89
|
+
familiesMap[family] = [];
|
|
90
|
+
}
|
|
91
|
+
if (!familiesMap[family].includes(weight)) {
|
|
92
|
+
familiesMap[family].push(weight);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return familiesMap;
|
|
97
|
+
}
|