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