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,40 @@
1
+ /**
2
+ * Shared hook for property autocomplete functionality
3
+ * Handles property input, suggestions, and keyboard navigation
4
+ */
5
+
6
+ import { useState, useRef, useEffect } from 'react';
7
+ import { filterCSSProperties } from '../../shared/cssProperties';
8
+
9
+ export function usePropertyAutocomplete(initialProperty: string = '') {
10
+ const [property, setProperty] = useState(initialProperty);
11
+ const [showSuggestions, setShowSuggestions] = useState(false);
12
+ const [selectedIndex, setSelectedIndex] = useState(-1);
13
+ const inputRef = useRef<HTMLInputElement>(null);
14
+
15
+ const suggestions = filterCSSProperties(property);
16
+
17
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
18
+ if (e.key === 'ArrowDown') {
19
+ e.preventDefault();
20
+ setSelectedIndex(prev =>
21
+ prev < suggestions.length - 1 ? prev + 1 : prev
22
+ );
23
+ } else if (e.key === 'ArrowUp') {
24
+ e.preventDefault();
25
+ setSelectedIndex(prev => prev > 0 ? prev - 1 : -1);
26
+ }
27
+ };
28
+
29
+ return {
30
+ property,
31
+ setProperty,
32
+ showSuggestions,
33
+ setShowSuggestions,
34
+ selectedIndex,
35
+ setSelectedIndex,
36
+ suggestions,
37
+ inputRef,
38
+ handleKeyDown
39
+ };
40
+ }
@@ -0,0 +1,154 @@
1
+ import { test, expect, describe, beforeEach, afterEach, mock } from "bun:test";
2
+ import { detectSSRContent, getInitialLoadingState } from "./HydrationUtils";
3
+
4
+ describe("HydrationUtils", () => {
5
+ let originalDocument: Document | undefined;
6
+ let originalWindow: Window | undefined;
7
+ let mockGetElementById: ReturnType<typeof mock>;
8
+
9
+ beforeEach(() => {
10
+ // Store original globals
11
+ originalDocument = globalThis.document;
12
+ originalWindow = globalThis.window;
13
+ });
14
+
15
+ afterEach(() => {
16
+ // Restore original globals
17
+ if (originalDocument) {
18
+ globalThis.document = originalDocument;
19
+ }
20
+ if (originalWindow) {
21
+ globalThis.window = originalWindow;
22
+ }
23
+ });
24
+
25
+ describe("detectSSRContent", () => {
26
+ test("should return true when root element exists with children", () => {
27
+ // Create a mock root element with children
28
+ const mockRoot = {
29
+ id: "root",
30
+ children: {
31
+ length: 1,
32
+ },
33
+ } as HTMLElement;
34
+
35
+ mockGetElementById = mock(() => mockRoot);
36
+ global.window = {} as any;
37
+ global.document = {
38
+ getElementById: mockGetElementById as any,
39
+ } as any;
40
+
41
+ const result = detectSSRContent();
42
+ expect(result).toBe(true);
43
+ expect(mockGetElementById).toHaveBeenCalledWith("root");
44
+ });
45
+
46
+ test("should return false when root element exists without children", () => {
47
+ // Create a mock root element without children
48
+ const mockRoot = {
49
+ id: "root",
50
+ children: {
51
+ length: 0,
52
+ },
53
+ } as HTMLElement;
54
+
55
+ mockGetElementById = mock(() => mockRoot);
56
+ global.window = {} as any;
57
+ global.document = {
58
+ getElementById: mockGetElementById as any,
59
+ } as any;
60
+
61
+ const result = detectSSRContent();
62
+ expect(result).toBe(false);
63
+ expect(mockGetElementById).toHaveBeenCalledWith("root");
64
+ });
65
+
66
+ test("should return false when root element does not exist", () => {
67
+ // Mock getElementById to return null
68
+ mockGetElementById = mock(() => null);
69
+ global.window = {} as any;
70
+ global.document = {
71
+ getElementById: mockGetElementById as any,
72
+ } as any;
73
+
74
+ const result = detectSSRContent();
75
+ expect(result).toBe(false);
76
+ expect(mockGetElementById).toHaveBeenCalledWith("root");
77
+ });
78
+
79
+ test("should return false when window is undefined (SSR environment)", () => {
80
+ // Mock SSR environment
81
+ const originalWindow = globalThis.window;
82
+ const originalDocument = globalThis.document;
83
+
84
+ // @ts-expect-error - intentionally removing window for SSR test
85
+ delete globalThis.window;
86
+ // @ts-expect-error - intentionally removing document for SSR test
87
+ delete globalThis.document;
88
+
89
+ const result = detectSSRContent();
90
+ expect(result).toBe(false);
91
+
92
+ // Restore
93
+ globalThis.window = originalWindow;
94
+ globalThis.document = originalDocument;
95
+ });
96
+
97
+ test("should return false when document is undefined", () => {
98
+ // Mock environment without document
99
+ const originalDocument = globalThis.document;
100
+
101
+ // @ts-expect-error - intentionally removing document for test
102
+ delete globalThis.document;
103
+
104
+ const result = detectSSRContent();
105
+ expect(result).toBe(false);
106
+
107
+ // Restore
108
+ globalThis.document = originalDocument;
109
+ });
110
+
111
+ test("should handle root element with multiple children", () => {
112
+ // Create a mock root element with multiple children
113
+ const mockRoot = {
114
+ id: "root",
115
+ children: {
116
+ length: 3,
117
+ },
118
+ } as HTMLElement;
119
+
120
+ mockGetElementById = mock(() => mockRoot);
121
+ global.window = {} as any;
122
+ global.document = {
123
+ getElementById: mockGetElementById as any,
124
+ } as any;
125
+
126
+ const result = detectSSRContent();
127
+ expect(result).toBe(true);
128
+ expect(mockGetElementById).toHaveBeenCalledWith("root");
129
+ });
130
+ });
131
+
132
+ describe("getInitialLoadingState", () => {
133
+ test("should return false when SSR content exists (smooth hydration)", () => {
134
+ const result = getInitialLoadingState(true);
135
+ expect(result).toBe(false);
136
+ });
137
+
138
+ test("should return true when no SSR content exists (show loading)", () => {
139
+ const result = getInitialLoadingState(false);
140
+ expect(result).toBe(true);
141
+ });
142
+
143
+ test("should handle edge case with explicit false", () => {
144
+ const result = getInitialLoadingState(false);
145
+ expect(result).toBe(true);
146
+ });
147
+
148
+ test("should handle edge case with explicit true", () => {
149
+ const result = getInitialLoadingState(true);
150
+ expect(result).toBe(false);
151
+ });
152
+ });
153
+ });
154
+
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Hydration Utilities
3
+ * Provides utilities for detecting SSR content and managing hydration state.
4
+ */
5
+
6
+ /**
7
+ * Detects if server-side rendered content exists in the DOM.
8
+ * Checks if the root element has children, which indicates SSR rendered content.
9
+ *
10
+ * @returns true if SSR content is detected, false otherwise
11
+ */
12
+ export function detectSSRContent(): boolean {
13
+ // Check if we're in a browser environment
14
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
15
+ return false;
16
+ }
17
+
18
+ const rootElement = document.getElementById('root');
19
+
20
+ // SSR content exists if root element has children
21
+ return rootElement !== null && rootElement.children.length > 0;
22
+ }
23
+
24
+ /**
25
+ * Determines the initial loading state based on SSR content presence.
26
+ *
27
+ * @param hasSSRContent - Whether SSR content was detected
28
+ * @returns false if SSR content exists (for smooth hydration), true otherwise
29
+ */
30
+ export function getInitialLoadingState(hasSSRContent: boolean): boolean {
31
+ // If SSR content exists, start with loading=false for smooth hydration
32
+ // If no SSR content, start with loading=true to show loading state
33
+ return !hasSSRContent;
34
+ }
35
+
@@ -0,0 +1,74 @@
1
+ import { describe, test, expect, beforeEach } from 'bun:test';
2
+ import {
3
+ getI18nConfig,
4
+ isI18nConfigLoaded,
5
+ invalidateI18nConfig,
6
+ setI18nConfig,
7
+ } from './i18nConfigService';
8
+ import { DEFAULT_I18N_CONFIG } from '../shared/i18n';
9
+
10
+ describe('i18nConfigService', () => {
11
+ beforeEach(() => {
12
+ invalidateI18nConfig();
13
+ });
14
+
15
+ describe('getI18nConfig', () => {
16
+ test('returns default config initially', () => {
17
+ const config = getI18nConfig();
18
+ expect(config.enabled).toBe(DEFAULT_I18N_CONFIG.enabled);
19
+ expect(config.defaultLocale).toBe(DEFAULT_I18N_CONFIG.defaultLocale);
20
+ });
21
+
22
+ test('returns cached config after set', () => {
23
+ const customConfig = { enabled: true, defaultLocale: 'fr', locales: [] };
24
+ setI18nConfig(customConfig);
25
+ const config = getI18nConfig();
26
+ expect(config.defaultLocale).toBe('fr');
27
+ });
28
+ });
29
+
30
+ describe('isI18nConfigLoaded', () => {
31
+ test('returns false initially', () => {
32
+ expect(isI18nConfigLoaded()).toBe(false);
33
+ });
34
+
35
+ test('returns true after config is set', () => {
36
+ const customConfig = { enabled: true, defaultLocale: 'en', locales: [] };
37
+ setI18nConfig(customConfig);
38
+ expect(isI18nConfigLoaded()).toBe(true);
39
+ });
40
+
41
+ test('returns false after invalidation', () => {
42
+ const customConfig = { enabled: true, defaultLocale: 'en', locales: [] };
43
+ setI18nConfig(customConfig);
44
+ expect(isI18nConfigLoaded()).toBe(true);
45
+ invalidateI18nConfig();
46
+ expect(isI18nConfigLoaded()).toBe(false);
47
+ });
48
+ });
49
+
50
+ describe('setI18nConfig', () => {
51
+ test('sets config that can be retrieved', () => {
52
+ const customConfig = {
53
+ enabled: true,
54
+ defaultLocale: 'de',
55
+ locales: [{ code: 'de', label: 'German' }]
56
+ };
57
+ setI18nConfig(customConfig);
58
+ const config = getI18nConfig();
59
+ expect(config).toEqual(customConfig);
60
+ });
61
+ });
62
+
63
+ describe('invalidateI18nConfig', () => {
64
+ test('clears cached config', () => {
65
+ const customConfig = { enabled: true, defaultLocale: 'es', locales: [] };
66
+ setI18nConfig(customConfig);
67
+ expect(isI18nConfigLoaded()).toBe(true);
68
+
69
+ invalidateI18nConfig();
70
+ expect(isI18nConfigLoaded()).toBe(false);
71
+ expect(getI18nConfig().defaultLocale).toBe(DEFAULT_I18N_CONFIG.defaultLocale);
72
+ });
73
+ });
74
+ });
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Client-side i18n Config Service
3
+ * Provides cached, deduplicated access to i18n configuration
4
+ */
5
+
6
+ import type { I18nConfig } from '../shared/types';
7
+ import { DEFAULT_I18N_CONFIG, migrateI18nConfig } from '../shared/i18n';
8
+
9
+ let cachedConfig: I18nConfig | null = null;
10
+ let configPromise: Promise<I18nConfig> | null = null;
11
+
12
+ /**
13
+ * Fetch i18n config from API with request deduplication and caching
14
+ * Multiple simultaneous calls will share the same request
15
+ */
16
+ export async function fetchI18nConfig(): Promise<I18nConfig> {
17
+ // Return cached config if available
18
+ if (cachedConfig) {
19
+ return cachedConfig;
20
+ }
21
+
22
+ // Return existing promise if request is in flight
23
+ if (configPromise) {
24
+ return configPromise;
25
+ }
26
+
27
+ // Create new request
28
+ configPromise = fetch('/api/config')
29
+ .then(response => {
30
+ if (!response.ok) {
31
+ throw new Error('Failed to fetch config');
32
+ }
33
+ return response.json();
34
+ })
35
+ .then(config => {
36
+ cachedConfig = migrateI18nConfig(config.i18n);
37
+ return cachedConfig;
38
+ })
39
+ .catch(() => {
40
+ cachedConfig = DEFAULT_I18N_CONFIG;
41
+ return cachedConfig;
42
+ })
43
+ .finally(() => {
44
+ configPromise = null;
45
+ });
46
+
47
+ return configPromise;
48
+ }
49
+
50
+ /**
51
+ * Get cached i18n config synchronously
52
+ * Returns default config if not yet loaded
53
+ */
54
+ export function getI18nConfig(): I18nConfig {
55
+ return cachedConfig || DEFAULT_I18N_CONFIG;
56
+ }
57
+
58
+ /**
59
+ * Check if i18n config has been loaded
60
+ */
61
+ export function isI18nConfigLoaded(): boolean {
62
+ return cachedConfig !== null;
63
+ }
64
+
65
+ /**
66
+ * Invalidate cached config (useful for HMR or config changes)
67
+ */
68
+ export function invalidateI18nConfig(): void {
69
+ cachedConfig = null;
70
+ configPromise = null;
71
+ }
72
+
73
+ /**
74
+ * Set config directly (useful for SSR hydration)
75
+ */
76
+ export function setI18nConfig(config: I18nConfig): void {
77
+ cachedConfig = config;
78
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @meno/core/client exports
3
+ * Core client-side utilities for page rendering and routing
4
+ * Does NOT include editor-specific code (that's in @meno/studio)
5
+ */
6
+
7
+ // Core rendering
8
+ export * from './core/ComponentBuilder';
9
+ export * from './core/ComponentRenderer';
10
+ export * from './core/cmsTemplateProcessor';
11
+
12
+ // Routing
13
+ export * from './routing/Router';
14
+ export * from './routing/RouteLoader';
15
+
16
+ // HMR
17
+ export * from './hmr';
18
+ export { HMRWebSocket } from './hmrWebSocket';
19
+
20
+ // Hydration
21
+ export * from './hydration/HydrationUtils';
22
+
23
+ // Theme
24
+ export * from './theme';
25
+ export { ThemeProvider, useTheme } from './contexts/ThemeContext';
26
+
27
+ // Style processing
28
+ export * from './styleProcessor';
29
+ export * from './responsiveStyleResolver';
30
+
31
+ // Scripts and styles injection
32
+ export { StyleInjector } from './styles/StyleInjector';
33
+ export { ScriptExecutor } from './scripts/ScriptExecutor';
34
+
35
+ // i18n
36
+ export * from './i18nConfigService';
37
+
38
+ // Registries
39
+ export { globalComponentRegistry, ComponentRegistry } from './componentRegistry';
40
+ export { ElementRegistry, elementRegistry } from './elementRegistry';
41
+
42
+ // Error handling
43
+ export { ErrorBoundary } from './ErrorBoundary';
44
+
45
+ // Navigation
46
+ export * from './navigation';
47
+
48
+ // Hooks
49
+ export * from './hooks/useColorVariables';
50
+ export * from './hooks/usePropertyAutocomplete';
51
+
52
+ // Template engine
53
+ export * from './templateEngine';
54
+
55
+ // Note: UI components, elementRegistry, treeItemRegistry, treeNodeUtils, and elementHighlighter
56
+ // are in @meno/studio as they're editor-specific