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,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
+ }