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,293 @@
1
+ import { describe, test, expect, beforeEach } from 'bun:test';
2
+ import { BaseComponentRegistry } from './ComponentRegistry';
3
+ import type { ComponentDefinition } from '../types';
4
+
5
+ // Create a concrete implementation for testing
6
+ class TestRegistry extends BaseComponentRegistry {}
7
+
8
+ describe('BaseComponentRegistry', () => {
9
+ let registry: TestRegistry;
10
+
11
+ beforeEach(() => {
12
+ registry = new TestRegistry();
13
+ });
14
+
15
+ describe('register', () => {
16
+ test('registers a component', () => {
17
+ const component: ComponentDefinition = {
18
+ component: { structure: { type: 'node' as const, tag: 'div' } }
19
+ };
20
+
21
+ registry.register('Button', component);
22
+
23
+ expect(registry.has('Button')).toBe(true);
24
+ expect(registry.get('Button')).toEqual(component);
25
+ });
26
+
27
+ test('overwrites existing component', () => {
28
+ const component1: ComponentDefinition = {
29
+ component: { structure: { type: 'node' as const, tag: 'div' } }
30
+ };
31
+ const component2: ComponentDefinition = {
32
+ component: { structure: { type: 'node' as const, tag: 'span' } }
33
+ };
34
+
35
+ registry.register('Button', component1);
36
+ registry.register('Button', component2);
37
+
38
+ expect(registry.get('Button')).toEqual(component2);
39
+ });
40
+ });
41
+
42
+ describe('get', () => {
43
+ test('returns component by name', () => {
44
+ const component: ComponentDefinition = {
45
+ component: { structure: { type: 'node' as const, tag: 'div' } }
46
+ };
47
+
48
+ registry.register('Button', component);
49
+
50
+ expect(registry.get('Button')).toEqual(component);
51
+ });
52
+
53
+ test('returns undefined for non-existent component', () => {
54
+ expect(registry.get('NonExistent')).toBeUndefined();
55
+ });
56
+ });
57
+
58
+ describe('has', () => {
59
+ test('returns true for registered component', () => {
60
+ const component: ComponentDefinition = {
61
+ component: { structure: { type: 'node' as const, tag: 'div' } }
62
+ };
63
+
64
+ registry.register('Button', component);
65
+
66
+ expect(registry.has('Button')).toBe(true);
67
+ });
68
+
69
+ test('returns false for non-existent component', () => {
70
+ expect(registry.has('NonExistent')).toBe(false);
71
+ });
72
+ });
73
+
74
+ describe('clear', () => {
75
+ test('removes all components', () => {
76
+ const component1: ComponentDefinition = {
77
+ component: { structure: { type: 'node' as const, tag: 'div' } }
78
+ };
79
+ const component2: ComponentDefinition = {
80
+ component: { structure: { type: 'node' as const, tag: 'span' } }
81
+ };
82
+
83
+ registry.register('Button', component1);
84
+ registry.register('Card', component2);
85
+
86
+ registry.clear();
87
+
88
+ expect(registry.has('Button')).toBe(false);
89
+ expect(registry.has('Card')).toBe(false);
90
+ expect(registry.getNames()).toEqual([]);
91
+ });
92
+ });
93
+
94
+ describe('merge', () => {
95
+ test('merges multiple components', () => {
96
+ const components: Record<string, ComponentDefinition> = {
97
+ 'Button': { component: { structure: { type: 'node' as const, tag: 'button' } } },
98
+ 'Card': { component: { structure: { type: 'node' as const, tag: 'div' } } }
99
+ };
100
+
101
+ registry.merge(components);
102
+
103
+ expect(registry.has('Button')).toBe(true);
104
+ expect(registry.has('Card')).toBe(true);
105
+ });
106
+
107
+ test('overwrites existing components', () => {
108
+ const component1: ComponentDefinition = {
109
+ component: { structure: { type: 'node' as const, tag: 'div' } }
110
+ };
111
+ const component2: ComponentDefinition = {
112
+ component: { structure: { type: 'node' as const, tag: 'button' } }
113
+ };
114
+
115
+ registry.register('Button', component1);
116
+ registry.merge({ 'Button': component2 });
117
+
118
+ expect(registry.get('Button')).toEqual(component2);
119
+ });
120
+ });
121
+
122
+ describe('getAll', () => {
123
+ test('returns all registered components', () => {
124
+ const component1: ComponentDefinition = {
125
+ component: { structure: { type: 'node' as const, tag: 'button' } }
126
+ };
127
+ const component2: ComponentDefinition = {
128
+ component: { structure: { type: 'node' as const, tag: 'div' } }
129
+ };
130
+
131
+ registry.register('Button', component1);
132
+ registry.register('Card', component2);
133
+
134
+ const all = registry.getAll();
135
+
136
+ expect(all['Button']).toEqual(component1);
137
+ expect(all['Card']).toEqual(component2);
138
+ expect(Object.keys(all).length).toBe(2);
139
+ });
140
+
141
+ test('returns copy of registry', () => {
142
+ const component: ComponentDefinition = {
143
+ component: { structure: { type: 'node' as const, tag: 'button' } }
144
+ };
145
+
146
+ registry.register('Button', component);
147
+
148
+ const all = registry.getAll();
149
+ delete all['Button'];
150
+
151
+ expect(registry.has('Button')).toBe(true);
152
+ });
153
+
154
+ test('returns empty object when no components', () => {
155
+ expect(registry.getAll()).toEqual({});
156
+ });
157
+ });
158
+
159
+ describe('getNames', () => {
160
+ test('returns list of component names', () => {
161
+ const component1: ComponentDefinition = {
162
+ component: { structure: { type: 'node' as const, tag: 'button' } }
163
+ };
164
+ const component2: ComponentDefinition = {
165
+ component: { structure: { type: 'node' as const, tag: 'div' } }
166
+ };
167
+
168
+ registry.register('Button', component1);
169
+ registry.register('Card', component2);
170
+
171
+ const names = registry.getNames();
172
+
173
+ expect(names).toContain('Button');
174
+ expect(names).toContain('Card');
175
+ expect(names.length).toBe(2);
176
+ });
177
+
178
+ test('returns empty array when no components', () => {
179
+ expect(registry.getNames()).toEqual([]);
180
+ });
181
+ });
182
+
183
+ describe('remove', () => {
184
+ test('removes component and returns true', () => {
185
+ const component: ComponentDefinition = {
186
+ component: { structure: { type: 'node' as const, tag: 'button' } }
187
+ };
188
+
189
+ registry.register('Button', component);
190
+
191
+ const result = registry.remove('Button');
192
+
193
+ expect(result).toBe(true);
194
+ expect(registry.has('Button')).toBe(false);
195
+ });
196
+
197
+ test('returns false when component does not exist', () => {
198
+ const result = registry.remove('NonExistent');
199
+
200
+ expect(result).toBe(false);
201
+ });
202
+ });
203
+
204
+ describe('subscribe', () => {
205
+ test('notifies listeners on register', () => {
206
+ let called = false;
207
+ const listener = () => { called = true; };
208
+
209
+ registry.subscribe(listener);
210
+
211
+ const component: ComponentDefinition = {
212
+ component: { structure: { type: 'node' as const, tag: 'button' } }
213
+ };
214
+ registry.register('Button', component);
215
+
216
+ expect(called).toBe(true);
217
+ });
218
+
219
+ test('notifies listeners on clear', () => {
220
+ let called = false;
221
+ const listener = () => { called = true; };
222
+
223
+ registry.subscribe(listener);
224
+
225
+ registry.clear();
226
+
227
+ expect(called).toBe(true);
228
+ });
229
+
230
+ test('notifies listeners on merge', () => {
231
+ let called = false;
232
+ const listener = () => { called = true; };
233
+
234
+ registry.subscribe(listener);
235
+
236
+ registry.merge({});
237
+
238
+ expect(called).toBe(true);
239
+ });
240
+
241
+ test('notifies listeners on remove', () => {
242
+ let called = false;
243
+ const component: ComponentDefinition = {
244
+ component: { structure: { type: 'node' as const, tag: 'button' } }
245
+ };
246
+
247
+ registry.register('Button', component);
248
+
249
+ const listener = () => { called = true; };
250
+ registry.subscribe(listener);
251
+
252
+ registry.remove('Button');
253
+
254
+ expect(called).toBe(true);
255
+ });
256
+
257
+ test('returns unsubscribe function', () => {
258
+ let callCount = 0;
259
+ const listener = () => { callCount++; };
260
+
261
+ const unsubscribe = registry.subscribe(listener);
262
+
263
+ const component: ComponentDefinition = {
264
+ component: { structure: { type: 'node' as const, tag: 'button' } }
265
+ };
266
+ registry.register('Button', component);
267
+
268
+ unsubscribe();
269
+
270
+ registry.register('Card', component);
271
+
272
+ expect(callCount).toBe(1);
273
+ });
274
+
275
+ test('handles multiple listeners', () => {
276
+ let count1 = 0;
277
+ let count2 = 0;
278
+ const listener1 = () => { count1++; };
279
+ const listener2 = () => { count2++; };
280
+
281
+ registry.subscribe(listener1);
282
+ registry.subscribe(listener2);
283
+
284
+ const component: ComponentDefinition = {
285
+ component: { structure: { type: 'node' as const, tag: 'button' } }
286
+ };
287
+ registry.register('Button', component);
288
+
289
+ expect(count1).toBe(1);
290
+ expect(count2).toBe(1);
291
+ });
292
+ });
293
+ });
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Base Component Registry
3
+ * Abstract base class for component registries
4
+ * Provides common functionality for client and SSR registries
5
+ */
6
+
7
+ import type { ComponentDefinition } from '../types';
8
+
9
+ /**
10
+ * Base registry class with common functionality
11
+ */
12
+ export abstract class BaseComponentRegistry {
13
+ protected registry: Record<string, ComponentDefinition> = {};
14
+ private listeners: Set<() => void> = new Set();
15
+
16
+ /**
17
+ * Register a component with the given name
18
+ */
19
+ register(name: string, definition: ComponentDefinition): void {
20
+ this.registry[name] = definition;
21
+ this.notify();
22
+ }
23
+
24
+ /**
25
+ * Get a component by name
26
+ */
27
+ get(name: string): ComponentDefinition | undefined {
28
+ return this.registry[name];
29
+ }
30
+
31
+ /**
32
+ * Check if a component exists
33
+ */
34
+ has(name: string): boolean {
35
+ return name in this.registry;
36
+ }
37
+
38
+ /**
39
+ * Clear all registered components
40
+ */
41
+ clear(): void {
42
+ this.registry = {};
43
+ this.notify();
44
+ }
45
+
46
+ /**
47
+ * Merge components into the registry
48
+ * Existing components with the same name will be overwritten
49
+ */
50
+ merge(components: Record<string, ComponentDefinition>): void {
51
+ Object.assign(this.registry, components);
52
+ this.notify();
53
+ }
54
+
55
+ /**
56
+ * Get all registered components
57
+ */
58
+ getAll(): Record<string, ComponentDefinition> {
59
+ return { ...this.registry };
60
+ }
61
+
62
+ /**
63
+ * Get list of registered component names
64
+ */
65
+ getNames(): string[] {
66
+ return Object.keys(this.registry);
67
+ }
68
+
69
+ /**
70
+ * Remove a component by name
71
+ */
72
+ remove(name: string): boolean {
73
+ if (this.has(name)) {
74
+ delete this.registry[name];
75
+ this.notify();
76
+ return true;
77
+ }
78
+ return false;
79
+ }
80
+
81
+ /**
82
+ * Subscribe to registry changes. Returns an unsubscribe function.
83
+ */
84
+ subscribe(listener: () => void): () => void {
85
+ this.listeners.add(listener);
86
+ return () => {
87
+ this.listeners.delete(listener);
88
+ };
89
+ }
90
+
91
+ protected notify(): void {
92
+ for (const listener of this.listeners) {
93
+ try {
94
+ listener();
95
+ } catch (err) {
96
+ }
97
+ }
98
+ }
99
+ }
100
+
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Node Type Definition
3
+ * TypeScript interfaces for the node type registry system
4
+ */
5
+
6
+ import type { ReactElement } from 'react';
7
+ import type { ZodType } from 'zod';
8
+ import type { ComponentNode } from '../types/nodes';
9
+ import type { StyleValue } from '../types/styles';
10
+
11
+ /**
12
+ * Tree icon types for node display
13
+ */
14
+ export type TreeIcon = 'TEXT' | 'COMPONENT' | 'HTML_ELEMENT' | 'IMAGE' | 'OBJECT' | 'SLOT_MARKER' | 'ARRAY' | 'UNKNOWN' | 'FORM';
15
+
16
+ /**
17
+ * Node category for grouping in command palette
18
+ */
19
+ export type NodeCategory = 'core' | 'layout' | 'content' | 'special';
20
+
21
+ /**
22
+ * Field types for editable fields in the props panel
23
+ */
24
+ export type EditableFieldType = 'string' | 'url' | 'boolean' | 'select' | 'number' | 'text';
25
+
26
+ /**
27
+ * Definition for an editable field in the props panel
28
+ * Used to auto-generate editor UI for node types
29
+ */
30
+ export interface EditableField {
31
+ /** Property name on the node object */
32
+ name: string;
33
+ /** Display label in the UI */
34
+ label: string;
35
+ /** Input type to render */
36
+ type: EditableFieldType;
37
+ /** Show as required field */
38
+ required?: boolean;
39
+ /** Placeholder text for input */
40
+ placeholder?: string;
41
+ /** Options for 'select' type */
42
+ options?: string[];
43
+ /** Group related fields together (e.g., "Dimensions", "Playback") */
44
+ group?: string;
45
+ }
46
+
47
+ /**
48
+ * Tree display configuration
49
+ */
50
+ export interface TreeDisplayConfig<TNode extends ComponentNode = ComponentNode> {
51
+ icon: TreeIcon;
52
+ getLabel: (node: TNode) => string;
53
+ }
54
+
55
+ /**
56
+ * Node capabilities - what the node supports
57
+ */
58
+ export interface NodeCapabilities {
59
+ canHaveChildren: boolean;
60
+ canHaveStyle: boolean;
61
+ canHaveAttributes: boolean;
62
+ canBeNested: boolean;
63
+ requiresProps: string[];
64
+ }
65
+
66
+ /**
67
+ * Client-side render context
68
+ * Passed to client renderers with utilities and state
69
+ */
70
+ export interface ClientRenderContext {
71
+ key: number;
72
+ elementPath: (string | number)[];
73
+ customProps?: Record<string, unknown>;
74
+ parentComponentName?: string | null;
75
+ viewportWidth: number;
76
+ componentContext?: string | null;
77
+ locale?: string;
78
+ cmsContext?: Record<string, unknown> | null;
79
+ cmsLocale?: string | null;
80
+
81
+ // Utilities provided by ComponentBuilder
82
+ buildChildren: (
83
+ children: Array<ComponentNode | string> | string | undefined,
84
+ elementPath: (string | number)[],
85
+ parentComponentName?: string | null,
86
+ componentContext?: string | null
87
+ ) => ReactElement | ReactElement[] | string | number | null;
88
+ registerElement: (
89
+ path: (string | number)[],
90
+ element: HTMLElement | null,
91
+ parentComponentName?: string | null,
92
+ isComponentRoot?: boolean
93
+ ) => void;
94
+ getComponentDefinition: (name: string) => unknown | undefined;
95
+ processI18n: (value: unknown) => string;
96
+ }
97
+
98
+ /**
99
+ * SSR-side render context
100
+ * Passed to SSR renderers with utilities and state
101
+ */
102
+ export interface SSRRenderContext {
103
+ locale?: string;
104
+ slugMappings?: Array<{ slug: string; locale: string; pagePath: string }>;
105
+ pagePath?: string;
106
+ breakpoints?: { tablet: number; mobile: number };
107
+ viewportWidth?: number;
108
+ cmsContext?: Record<string, unknown> | null;
109
+
110
+ // Utilities provided by ssrRenderer
111
+ renderNode: (node: ComponentNode | string, context: SSRRenderContext) => string;
112
+ renderChildren: (children: Array<ComponentNode | string> | string | undefined, context: SSRRenderContext) => string;
113
+ escapeHtml: (str: string) => string;
114
+ buildAttributes: (attrs: Record<string, unknown>, exclude?: string[]) => string;
115
+ processI18n: (value: unknown) => string;
116
+ getComponentDefinition: (name: string) => unknown | undefined;
117
+ }
118
+
119
+ /**
120
+ * Client-side renderer function type
121
+ */
122
+ export type ClientNodeRenderer<TNode extends ComponentNode> = (
123
+ node: TNode,
124
+ context: ClientRenderContext
125
+ ) => ReactElement | null;
126
+
127
+ /**
128
+ * SSR renderer function type
129
+ */
130
+ export type SSRNodeRenderer<TNode extends ComponentNode> = (
131
+ node: TNode,
132
+ context: SSRRenderContext
133
+ ) => string;
134
+
135
+ /**
136
+ * Props editor component props
137
+ */
138
+ export interface PropsEditorProps<TNode extends ComponentNode = ComponentNode> {
139
+ node: TNode;
140
+ selectedPath: string | null;
141
+ onPropChange?: (path: string, propName: string, newValue: unknown) => void;
142
+ themeColors?: Record<string, string>;
143
+ }
144
+
145
+ /**
146
+ * Node Type Definition
147
+ * Complete definition for a node type including all behavior
148
+ */
149
+ export interface NodeTypeDefinition<TNode extends ComponentNode = ComponentNode> {
150
+ // Identity
151
+ type: string;
152
+ displayName: string;
153
+ category: NodeCategory;
154
+
155
+ // Validation
156
+ schema: ZodType<TNode>;
157
+ typeGuard: (node: unknown) => node is TNode;
158
+
159
+ // Rendering
160
+ clientRenderer: ClientNodeRenderer<TNode>;
161
+ ssrRenderer: SSRNodeRenderer<TNode>;
162
+
163
+ // Editor UI
164
+ treeDisplay: TreeDisplayConfig<TNode>;
165
+ propsEditor?: React.ComponentType<PropsEditorProps<TNode>>;
166
+ /** Editable fields for auto-generated props editor UI */
167
+ editableFields?: EditableField[];
168
+
169
+ // Creation
170
+ defaultFactory: () => TNode;
171
+
172
+ // Capabilities
173
+ capabilities: NodeCapabilities;
174
+ }
175
+
176
+ /**
177
+ * Partial node type definition for defineNodeType helper
178
+ * Makes some fields optional with sensible defaults
179
+ */
180
+ export interface NodeTypeDefinitionInput<TNode extends ComponentNode = ComponentNode> {
181
+ type: string;
182
+ displayName: string;
183
+ category?: NodeCategory;
184
+
185
+ schema: ZodType<TNode>;
186
+ typeGuard: (node: unknown) => node is TNode;
187
+
188
+ clientRenderer: ClientNodeRenderer<TNode>;
189
+ ssrRenderer: SSRNodeRenderer<TNode>;
190
+
191
+ treeDisplay: TreeDisplayConfig<TNode>;
192
+ propsEditor?: React.ComponentType<PropsEditorProps<TNode>>;
193
+ editableFields?: EditableField[];
194
+
195
+ defaultFactory: () => TNode;
196
+
197
+ capabilities?: Partial<NodeCapabilities>;
198
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Node Type Manager
3
+ * Global singleton managing both client and SSR registries
4
+ */
5
+
6
+ import { ClientNodeTypeRegistry } from './ClientNodeTypeRegistry';
7
+ import { SSRNodeTypeRegistry } from './SSRNodeTypeRegistry';
8
+ import type { NodeTypeDefinition } from './NodeTypeDefinition';
9
+
10
+ /**
11
+ * Manages both client and SSR node type registries
12
+ * Ensures both registries stay in sync
13
+ */
14
+ export class NodeTypeManager {
15
+ private clientRegistry: ClientNodeTypeRegistry;
16
+ private ssrRegistry: SSRNodeTypeRegistry;
17
+
18
+ constructor() {
19
+ this.clientRegistry = new ClientNodeTypeRegistry();
20
+ this.ssrRegistry = new SSRNodeTypeRegistry();
21
+ }
22
+
23
+ /**
24
+ * Get the client-side registry
25
+ */
26
+ getClient(): ClientNodeTypeRegistry {
27
+ return this.clientRegistry;
28
+ }
29
+
30
+ /**
31
+ * Get the SSR registry
32
+ */
33
+ getSSR(): SSRNodeTypeRegistry {
34
+ return this.ssrRegistry;
35
+ }
36
+
37
+ /**
38
+ * Register a node type in both registries
39
+ */
40
+ register(definition: NodeTypeDefinition): void {
41
+ this.clientRegistry.register(definition);
42
+ this.ssrRegistry.register(definition);
43
+ }
44
+
45
+ /**
46
+ * Register multiple node types in both registries
47
+ */
48
+ registerAll(definitions: NodeTypeDefinition[]): void {
49
+ this.clientRegistry.registerAll(definitions);
50
+ this.ssrRegistry.registerAll(definitions);
51
+ }
52
+
53
+ /**
54
+ * Check if a node type is registered
55
+ */
56
+ has(type: string): boolean {
57
+ return this.clientRegistry.has(type);
58
+ }
59
+
60
+ /**
61
+ * Get a node type definition
62
+ */
63
+ get(type: string): NodeTypeDefinition | undefined {
64
+ return this.clientRegistry.get(type);
65
+ }
66
+
67
+ /**
68
+ * Get all registered node types
69
+ */
70
+ getAll(): NodeTypeDefinition[] {
71
+ return this.clientRegistry.getAll();
72
+ }
73
+
74
+ /**
75
+ * Get list of registered type names
76
+ */
77
+ getNames(): string[] {
78
+ return this.clientRegistry.getNames();
79
+ }
80
+
81
+ /**
82
+ * Clear both registries
83
+ */
84
+ clear(): void {
85
+ this.clientRegistry.clear();
86
+ this.ssrRegistry.clear();
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Global singleton node type manager
92
+ * Use this instance throughout the application
93
+ */
94
+ export const globalNodeTypeManager = new NodeTypeManager();