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,58 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { RegistryManager } from './RegistryManager';
|
|
3
|
+
|
|
4
|
+
describe('RegistryManager', () => {
|
|
5
|
+
test('creates client and SSR registries', () => {
|
|
6
|
+
const manager = new RegistryManager();
|
|
7
|
+
expect(manager.getClient()).toBeDefined();
|
|
8
|
+
expect(manager.getSSR()).toBeDefined();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('registerAll adds to both registries', () => {
|
|
12
|
+
const manager = new RegistryManager();
|
|
13
|
+
const def = { component: { structure: { type: 'node', tag: 'div' } } };
|
|
14
|
+
|
|
15
|
+
manager.registerAll('TestComponent', def);
|
|
16
|
+
|
|
17
|
+
expect(manager.getClient().get('TestComponent')).toEqual(def);
|
|
18
|
+
expect(manager.getSSR().get('TestComponent')).toEqual(def);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('sync copies from client to SSR', () => {
|
|
22
|
+
const manager = new RegistryManager();
|
|
23
|
+
const def = { component: { structure: { type: 'node', tag: 'div' } } };
|
|
24
|
+
|
|
25
|
+
manager.getClient().register('TestComponent', def);
|
|
26
|
+
manager.sync('client', 'ssr');
|
|
27
|
+
|
|
28
|
+
expect(manager.getSSR().get('TestComponent')).toEqual(def);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('sync copies from SSR to client', () => {
|
|
32
|
+
const manager = new RegistryManager();
|
|
33
|
+
const def = { component: { structure: { type: 'node', tag: 'div' } } };
|
|
34
|
+
|
|
35
|
+
manager.getSSR().register('TestComponent', def);
|
|
36
|
+
manager.sync('ssr', 'client');
|
|
37
|
+
|
|
38
|
+
expect(manager.getClient().get('TestComponent')).toEqual(def);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('clearAll clears both registries', () => {
|
|
42
|
+
const manager = new RegistryManager();
|
|
43
|
+
const def = { component: { structure: { type: 'node', tag: 'div' } } };
|
|
44
|
+
|
|
45
|
+
manager.registerAll('TestComponent', def);
|
|
46
|
+
manager.clearAll();
|
|
47
|
+
|
|
48
|
+
expect(manager.getClient().get('TestComponent')).toBeUndefined();
|
|
49
|
+
expect(manager.getSSR().get('TestComponent')).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('returns same registry instances', () => {
|
|
53
|
+
const manager = new RegistryManager();
|
|
54
|
+
const client1 = manager.getClient();
|
|
55
|
+
const client2 = manager.getClient();
|
|
56
|
+
expect(client1).toBe(client2);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry Manager
|
|
3
|
+
* Manages multiple component registries (client, SSR, etc.)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ComponentDefinition } from '../types';
|
|
7
|
+
import { ClientRegistry } from './ClientRegistry';
|
|
8
|
+
import { SSRRegistry } from './SSRRegistry';
|
|
9
|
+
|
|
10
|
+
export class RegistryManager {
|
|
11
|
+
private clientRegistry: ClientRegistry;
|
|
12
|
+
private ssrRegistry: SSRRegistry;
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
this.clientRegistry = new ClientRegistry();
|
|
16
|
+
this.ssrRegistry = new SSRRegistry();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the client registry
|
|
21
|
+
*/
|
|
22
|
+
getClient(): ClientRegistry {
|
|
23
|
+
return this.clientRegistry;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get the SSR registry
|
|
28
|
+
*/
|
|
29
|
+
getSSR(): SSRRegistry {
|
|
30
|
+
return this.ssrRegistry;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Sync components from one registry to another
|
|
35
|
+
*/
|
|
36
|
+
sync(from: 'client' | 'ssr', to: 'client' | 'ssr'): void {
|
|
37
|
+
const source = from === 'client' ? this.clientRegistry : this.ssrRegistry;
|
|
38
|
+
const target = to === 'client' ? this.clientRegistry : this.ssrRegistry;
|
|
39
|
+
|
|
40
|
+
const components = source.getAll();
|
|
41
|
+
target.merge(components);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Register a component in all registries
|
|
46
|
+
*/
|
|
47
|
+
registerAll(name: string, definition: ComponentDefinition): void {
|
|
48
|
+
this.clientRegistry.register(name, definition);
|
|
49
|
+
this.ssrRegistry.register(name, definition);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Clear all registries
|
|
54
|
+
*/
|
|
55
|
+
clearAll(): void {
|
|
56
|
+
this.clientRegistry.clear();
|
|
57
|
+
this.ssrRegistry.clear();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSR Node Type Registry
|
|
3
|
+
* Registry specialized for server-side rendering (HTML strings)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ComponentNode } from '../types/nodes';
|
|
7
|
+
import { BaseNodeTypeRegistry } from './BaseNodeTypeRegistry';
|
|
8
|
+
import type { SSRRenderContext } from './NodeTypeDefinition';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Server-side node type registry
|
|
12
|
+
* Extends base registry with SSR rendering capabilities
|
|
13
|
+
*/
|
|
14
|
+
export class SSRNodeTypeRegistry extends BaseNodeTypeRegistry {
|
|
15
|
+
/**
|
|
16
|
+
* Render a node to HTML string using the registered renderer
|
|
17
|
+
* Returns empty string if no renderer is found for the node type
|
|
18
|
+
*/
|
|
19
|
+
renderNode(node: ComponentNode, context: SSRRenderContext): string {
|
|
20
|
+
const definition = this.findByNode(node);
|
|
21
|
+
if (!definition) {
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
return definition.ssrRenderer(node as any, context);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if a node can be rendered by this registry
|
|
29
|
+
*/
|
|
30
|
+
canRender(node: ComponentNode): boolean {
|
|
31
|
+
return this.findByNode(node) !== undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { SSRRegistry } from './SSRRegistry';
|
|
3
|
+
|
|
4
|
+
describe('SSRRegistry', () => {
|
|
5
|
+
test('can be instantiated', () => {
|
|
6
|
+
const registry = new SSRRegistry();
|
|
7
|
+
expect(registry).toBeDefined();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('inherits from BaseComponentRegistry', () => {
|
|
11
|
+
const registry = new SSRRegistry();
|
|
12
|
+
const def = { component: { structure: { type: 'node', tag: 'div' } } };
|
|
13
|
+
|
|
14
|
+
registry.register('Test', def);
|
|
15
|
+
expect(registry.get('Test')).toEqual(def);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('supports all base registry methods', () => {
|
|
19
|
+
const registry = new SSRRegistry();
|
|
20
|
+
expect(typeof registry.register).toBe('function');
|
|
21
|
+
expect(typeof registry.get).toBe('function');
|
|
22
|
+
expect(typeof registry.has).toBe('function');
|
|
23
|
+
expect(typeof registry.getAll).toBe('function');
|
|
24
|
+
expect(typeof registry.clear).toBe('function');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSR Component Registry
|
|
3
|
+
* Extends base registry with SSR-specific functionality
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { BaseComponentRegistry } from './ComponentRegistry';
|
|
7
|
+
import type { ComponentDefinition } from '../types';
|
|
8
|
+
|
|
9
|
+
export class SSRRegistry extends BaseComponentRegistry {
|
|
10
|
+
/**
|
|
11
|
+
* SSR-specific methods can be added here
|
|
12
|
+
* For now, it's the same as base registry
|
|
13
|
+
*/
|
|
14
|
+
}
|
|
15
|
+
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createNodeType Helper
|
|
3
|
+
* Simplified API for creating node type definitions with auto-generated type guards
|
|
4
|
+
* and inferred TypeScript types from Zod schemas
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ReactElement } from 'react';
|
|
8
|
+
import type { ZodType, z } from 'zod';
|
|
9
|
+
import type {
|
|
10
|
+
NodeTypeDefinition,
|
|
11
|
+
NodeCapabilities,
|
|
12
|
+
NodeCategory,
|
|
13
|
+
TreeIcon,
|
|
14
|
+
ClientRenderContext,
|
|
15
|
+
SSRRenderContext,
|
|
16
|
+
EditableField,
|
|
17
|
+
} from './NodeTypeDefinition';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Default capabilities for node types
|
|
21
|
+
*/
|
|
22
|
+
const DEFAULT_CAPABILITIES: NodeCapabilities = {
|
|
23
|
+
canHaveChildren: true,
|
|
24
|
+
canHaveStyle: true,
|
|
25
|
+
canHaveAttributes: true,
|
|
26
|
+
canBeNested: true,
|
|
27
|
+
requiresProps: [],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a type guard function from a type string value
|
|
32
|
+
* Automatically generates the type guard based on the node's type field
|
|
33
|
+
*/
|
|
34
|
+
function createTypeGuard<T>(typeValue: string) {
|
|
35
|
+
return (node: unknown): node is T =>
|
|
36
|
+
node !== null &&
|
|
37
|
+
typeof node === 'object' &&
|
|
38
|
+
'type' in node &&
|
|
39
|
+
(node as { type: unknown }).type === typeValue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Input configuration for createNodeType
|
|
44
|
+
* Schema is the single source of truth - TypeScript types are inferred from it
|
|
45
|
+
*/
|
|
46
|
+
export interface CreateNodeTypeInput<TSchema extends ZodType> {
|
|
47
|
+
/** The type discriminator value (e.g., 'image', 'video') */
|
|
48
|
+
type: string;
|
|
49
|
+
|
|
50
|
+
/** Human-readable display name (e.g., 'Image', 'Video') */
|
|
51
|
+
displayName: string;
|
|
52
|
+
|
|
53
|
+
/** Category for grouping in command palette */
|
|
54
|
+
category?: NodeCategory;
|
|
55
|
+
|
|
56
|
+
/** Zod schema - single source of truth for validation and type inference */
|
|
57
|
+
schema: TSchema;
|
|
58
|
+
|
|
59
|
+
/** Default values for creating new nodes (type field is auto-added) */
|
|
60
|
+
defaultValues: Omit<Partial<z.infer<TSchema>>, 'type'>;
|
|
61
|
+
|
|
62
|
+
/** Tree display configuration */
|
|
63
|
+
treeDisplay: {
|
|
64
|
+
icon: TreeIcon;
|
|
65
|
+
getLabel?: (node: z.infer<TSchema>) => string;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/** Client-side React renderer */
|
|
69
|
+
clientRenderer: (node: z.infer<TSchema>, context: ClientRenderContext) => ReactElement | null;
|
|
70
|
+
|
|
71
|
+
/** Server-side HTML string renderer */
|
|
72
|
+
ssrRenderer: (node: z.infer<TSchema>, context: SSRRenderContext) => string;
|
|
73
|
+
|
|
74
|
+
/** Optional capabilities override */
|
|
75
|
+
capabilities?: Partial<NodeCapabilities>;
|
|
76
|
+
|
|
77
|
+
/** Optional custom props editor component */
|
|
78
|
+
propsEditor?: React.ComponentType<{
|
|
79
|
+
node: z.infer<TSchema>;
|
|
80
|
+
selectedPath: string | null;
|
|
81
|
+
onPropChange?: (path: string, propName: string, newValue: unknown) => void;
|
|
82
|
+
themeColors?: Record<string, string>;
|
|
83
|
+
}>;
|
|
84
|
+
|
|
85
|
+
/** Editable fields for auto-generated props editor UI */
|
|
86
|
+
editableFields?: EditableField[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Create a complete node type definition from a simplified configuration
|
|
91
|
+
*
|
|
92
|
+
* Benefits over defineNodeType:
|
|
93
|
+
* - Type guards are auto-generated from the type string
|
|
94
|
+
* - Default factory is auto-generated from defaultValues
|
|
95
|
+
* - TypeScript types are inferred from the Zod schema
|
|
96
|
+
* - Less boilerplate, single source of truth
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* const schema = z.object({
|
|
101
|
+
* type: z.literal('image'),
|
|
102
|
+
* src: z.string(),
|
|
103
|
+
* alt: z.string().optional(),
|
|
104
|
+
* });
|
|
105
|
+
*
|
|
106
|
+
* export type ImageNode = z.infer<typeof schema>;
|
|
107
|
+
* export const ImageNodeSchema = schema;
|
|
108
|
+
*
|
|
109
|
+
* export const ImageNodeType = createNodeType({
|
|
110
|
+
* type: 'image',
|
|
111
|
+
* displayName: 'Image',
|
|
112
|
+
* schema,
|
|
113
|
+
* defaultValues: { src: '', alt: '' },
|
|
114
|
+
* treeDisplay: { icon: 'IMAGE' },
|
|
115
|
+
* clientRenderer: (node, ctx) => h('img', { src: node.src, alt: node.alt }),
|
|
116
|
+
* ssrRenderer: (node, ctx) => `<img src="${node.src}" alt="${node.alt || ''}" />`,
|
|
117
|
+
* });
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export function createNodeType<TSchema extends ZodType>(
|
|
121
|
+
input: CreateNodeTypeInput<TSchema>
|
|
122
|
+
): NodeTypeDefinition<z.infer<TSchema>> {
|
|
123
|
+
type TNode = z.infer<TSchema>;
|
|
124
|
+
|
|
125
|
+
// Validate required fields
|
|
126
|
+
if (!input.type) {
|
|
127
|
+
throw new Error('Node type definition must have a type');
|
|
128
|
+
}
|
|
129
|
+
if (!input.displayName) {
|
|
130
|
+
throw new Error('Node type definition must have a displayName');
|
|
131
|
+
}
|
|
132
|
+
if (!input.schema) {
|
|
133
|
+
throw new Error('Node type definition must have a schema');
|
|
134
|
+
}
|
|
135
|
+
if (!input.clientRenderer) {
|
|
136
|
+
throw new Error('Node type definition must have a clientRenderer');
|
|
137
|
+
}
|
|
138
|
+
if (!input.ssrRenderer) {
|
|
139
|
+
throw new Error('Node type definition must have an ssrRenderer');
|
|
140
|
+
}
|
|
141
|
+
if (!input.treeDisplay) {
|
|
142
|
+
throw new Error('Node type definition must have treeDisplay');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
type: input.type,
|
|
147
|
+
displayName: input.displayName,
|
|
148
|
+
category: input.category ?? 'core',
|
|
149
|
+
schema: input.schema as ZodType<TNode>,
|
|
150
|
+
|
|
151
|
+
// Auto-generated type guard
|
|
152
|
+
typeGuard: createTypeGuard<TNode>(input.type),
|
|
153
|
+
|
|
154
|
+
// Auto-generated default factory
|
|
155
|
+
defaultFactory: (): TNode => ({
|
|
156
|
+
type: input.type,
|
|
157
|
+
...input.defaultValues,
|
|
158
|
+
} as TNode),
|
|
159
|
+
|
|
160
|
+
treeDisplay: {
|
|
161
|
+
icon: input.treeDisplay.icon,
|
|
162
|
+
getLabel: input.treeDisplay.getLabel ?? (() => input.displayName),
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
clientRenderer: input.clientRenderer,
|
|
166
|
+
ssrRenderer: input.ssrRenderer,
|
|
167
|
+
propsEditor: input.propsEditor,
|
|
168
|
+
editableFields: input.editableFields,
|
|
169
|
+
|
|
170
|
+
capabilities: {
|
|
171
|
+
...DEFAULT_CAPABILITIES,
|
|
172
|
+
...input.capabilities,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* defineNodeType Helper
|
|
3
|
+
* Type-safe helper function to create node type definitions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ComponentNode } from '../types/nodes';
|
|
7
|
+
import type {
|
|
8
|
+
NodeTypeDefinition,
|
|
9
|
+
NodeTypeDefinitionInput,
|
|
10
|
+
NodeCapabilities,
|
|
11
|
+
} from './NodeTypeDefinition';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Default capabilities for node types
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_CAPABILITIES: NodeCapabilities = {
|
|
17
|
+
canHaveChildren: true,
|
|
18
|
+
canHaveStyle: true,
|
|
19
|
+
canHaveAttributes: true,
|
|
20
|
+
canBeNested: true,
|
|
21
|
+
requiresProps: [],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create a node type definition with sensible defaults
|
|
26
|
+
* Provides type safety and validation at definition time
|
|
27
|
+
*/
|
|
28
|
+
export function defineNodeType<TNode extends ComponentNode>(
|
|
29
|
+
input: NodeTypeDefinitionInput<TNode>
|
|
30
|
+
): NodeTypeDefinition<TNode> {
|
|
31
|
+
// Validate required fields
|
|
32
|
+
if (!input.type) {
|
|
33
|
+
throw new Error('Node type definition must have a type');
|
|
34
|
+
}
|
|
35
|
+
if (!input.displayName) {
|
|
36
|
+
throw new Error('Node type definition must have a displayName');
|
|
37
|
+
}
|
|
38
|
+
if (!input.schema) {
|
|
39
|
+
throw new Error('Node type definition must have a schema');
|
|
40
|
+
}
|
|
41
|
+
if (!input.typeGuard) {
|
|
42
|
+
throw new Error('Node type definition must have a typeGuard');
|
|
43
|
+
}
|
|
44
|
+
if (!input.clientRenderer) {
|
|
45
|
+
throw new Error('Node type definition must have a clientRenderer');
|
|
46
|
+
}
|
|
47
|
+
if (!input.ssrRenderer) {
|
|
48
|
+
throw new Error('Node type definition must have an ssrRenderer');
|
|
49
|
+
}
|
|
50
|
+
if (!input.treeDisplay) {
|
|
51
|
+
throw new Error('Node type definition must have treeDisplay');
|
|
52
|
+
}
|
|
53
|
+
if (!input.defaultFactory) {
|
|
54
|
+
throw new Error('Node type definition must have a defaultFactory');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
type: input.type,
|
|
59
|
+
displayName: input.displayName,
|
|
60
|
+
category: input.category ?? 'core',
|
|
61
|
+
schema: input.schema,
|
|
62
|
+
typeGuard: input.typeGuard,
|
|
63
|
+
clientRenderer: input.clientRenderer,
|
|
64
|
+
ssrRenderer: input.ssrRenderer,
|
|
65
|
+
treeDisplay: input.treeDisplay,
|
|
66
|
+
propsEditor: input.propsEditor,
|
|
67
|
+
defaultFactory: input.defaultFactory,
|
|
68
|
+
capabilities: {
|
|
69
|
+
...DEFAULT_CAPABILITIES,
|
|
70
|
+
...input.capabilities,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Field Type Factories for Node Types
|
|
3
|
+
*
|
|
4
|
+
* Configurable field factories that can be used for any property.
|
|
5
|
+
* Each factory creates an EditableField with the specified configuration.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* import { textField, urlField, imageField, booleanField, selectField } from '../fieldPresets';
|
|
9
|
+
*
|
|
10
|
+
* editableFields: [
|
|
11
|
+
* urlField({ name: 'src', label: 'Video Source', required: true }),
|
|
12
|
+
* imageField({ name: 'poster', label: 'Poster Image' }),
|
|
13
|
+
* selectField({ name: 'preload', label: 'Preload', options: ['auto', 'metadata', 'none'] }),
|
|
14
|
+
* booleanField({ name: 'controls', label: 'Show Controls', group: 'Playback' }),
|
|
15
|
+
* ],
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { EditableField } from './NodeTypeDefinition';
|
|
19
|
+
|
|
20
|
+
// ============================================
|
|
21
|
+
// Field Configuration Types
|
|
22
|
+
// ============================================
|
|
23
|
+
|
|
24
|
+
interface BaseFieldConfig {
|
|
25
|
+
name: string;
|
|
26
|
+
label: string;
|
|
27
|
+
required?: boolean;
|
|
28
|
+
group?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface TextFieldConfig extends BaseFieldConfig {
|
|
32
|
+
placeholder?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface SelectFieldConfig extends BaseFieldConfig {
|
|
36
|
+
options: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ============================================
|
|
40
|
+
// Generic Field Type Factories
|
|
41
|
+
// ============================================
|
|
42
|
+
|
|
43
|
+
/** Text input field */
|
|
44
|
+
export const textField = (config: TextFieldConfig): EditableField => ({
|
|
45
|
+
name: config.name,
|
|
46
|
+
label: config.label,
|
|
47
|
+
type: 'string',
|
|
48
|
+
required: config.required,
|
|
49
|
+
placeholder: config.placeholder,
|
|
50
|
+
group: config.group,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/** URL input field */
|
|
54
|
+
export const urlField = (config: TextFieldConfig): EditableField => ({
|
|
55
|
+
name: config.name,
|
|
56
|
+
label: config.label,
|
|
57
|
+
type: 'url',
|
|
58
|
+
required: config.required,
|
|
59
|
+
placeholder: config.placeholder ?? 'https://...',
|
|
60
|
+
group: config.group,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
/** Image URL field (can add picker UI later) */
|
|
64
|
+
export const imageField = (config: BaseFieldConfig): EditableField => ({
|
|
65
|
+
name: config.name,
|
|
66
|
+
label: config.label,
|
|
67
|
+
type: 'url',
|
|
68
|
+
required: config.required,
|
|
69
|
+
placeholder: 'https://example.com/image.jpg',
|
|
70
|
+
group: config.group,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
/** Number input field */
|
|
74
|
+
export const numberField = (config: TextFieldConfig): EditableField => ({
|
|
75
|
+
name: config.name,
|
|
76
|
+
label: config.label,
|
|
77
|
+
type: 'number',
|
|
78
|
+
required: config.required,
|
|
79
|
+
placeholder: config.placeholder,
|
|
80
|
+
group: config.group,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
/** Boolean toggle field */
|
|
84
|
+
export const booleanField = (config: BaseFieldConfig): EditableField => ({
|
|
85
|
+
name: config.name,
|
|
86
|
+
label: config.label,
|
|
87
|
+
type: 'boolean',
|
|
88
|
+
group: config.group,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
/** Select dropdown field */
|
|
92
|
+
export const selectField = (config: SelectFieldConfig): EditableField => ({
|
|
93
|
+
name: config.name,
|
|
94
|
+
label: config.label,
|
|
95
|
+
type: 'select',
|
|
96
|
+
options: config.options,
|
|
97
|
+
required: config.required,
|
|
98
|
+
group: config.group,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
/** Textarea field (multiline text) */
|
|
102
|
+
export const textareaField = (config: TextFieldConfig): EditableField => ({
|
|
103
|
+
name: config.name,
|
|
104
|
+
label: config.label,
|
|
105
|
+
type: 'text',
|
|
106
|
+
required: config.required,
|
|
107
|
+
placeholder: config.placeholder,
|
|
108
|
+
group: config.group,
|
|
109
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Registry System
|
|
3
|
+
* Unified registry system for client and SSR
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Component registries
|
|
7
|
+
export { BaseComponentRegistry } from './ComponentRegistry';
|
|
8
|
+
export { ClientRegistry } from './ClientRegistry';
|
|
9
|
+
export { SSRRegistry } from './SSRRegistry';
|
|
10
|
+
export { RegistryManager } from './RegistryManager';
|
|
11
|
+
|
|
12
|
+
// Node type registries
|
|
13
|
+
export { BaseNodeTypeRegistry } from './BaseNodeTypeRegistry';
|
|
14
|
+
export type { NodeLabelInfo } from './BaseNodeTypeRegistry';
|
|
15
|
+
export { ClientNodeTypeRegistry } from './ClientNodeTypeRegistry';
|
|
16
|
+
export { SSRNodeTypeRegistry } from './SSRNodeTypeRegistry';
|
|
17
|
+
export { NodeTypeManager, globalNodeTypeManager } from './NodeTypeManager';
|
|
18
|
+
export { defineNodeType } from './defineNodeType';
|
|
19
|
+
export { createNodeType } from './createNodeType';
|
|
20
|
+
export type { CreateNodeTypeInput } from './createNodeType';
|
|
21
|
+
export type {
|
|
22
|
+
NodeTypeDefinition,
|
|
23
|
+
NodeTypeDefinitionInput,
|
|
24
|
+
TreeIcon,
|
|
25
|
+
NodeCategory,
|
|
26
|
+
TreeDisplayConfig,
|
|
27
|
+
NodeCapabilities,
|
|
28
|
+
ClientRenderContext,
|
|
29
|
+
SSRRenderContext,
|
|
30
|
+
ClientNodeRenderer,
|
|
31
|
+
SSRNodeRenderer,
|
|
32
|
+
PropsEditorProps,
|
|
33
|
+
} from './NodeTypeDefinition';
|
|
34
|
+
|
|
35
|
+
// Field presets for node types
|
|
36
|
+
export * from './fieldPresets';
|
|
37
|
+
|
|
38
|
+
// Built-in node types
|
|
39
|
+
export {
|
|
40
|
+
registerBuiltInNodeTypes,
|
|
41
|
+
builtInNodeTypes,
|
|
42
|
+
HtmlNodeType,
|
|
43
|
+
ComponentInstanceNodeType,
|
|
44
|
+
SlotMarkerType,
|
|
45
|
+
EmbedNodeType,
|
|
46
|
+
ObjectLinkNodeType,
|
|
47
|
+
LocaleListNodeType,
|
|
48
|
+
TextNodeType,
|
|
49
|
+
} from './nodeTypes';
|
|
50
|
+
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component Instance Node Type Definition
|
|
3
|
+
* Instances of registered components
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { createElement as h } from 'react';
|
|
8
|
+
import { StyleValueSchema } from '../../validation/schemas';
|
|
9
|
+
import { createNodeType } from '../createNodeType';
|
|
10
|
+
|
|
11
|
+
// Forward reference for ComponentNode schema (recursive)
|
|
12
|
+
const ComponentNodeSchemaRef: z.ZodType<any> = z.lazy(() => z.any());
|
|
13
|
+
|
|
14
|
+
// Schema is the SINGLE source of truth
|
|
15
|
+
const ComponentInstanceNodeSchemaInternal = z.object({
|
|
16
|
+
type: z.literal('component'),
|
|
17
|
+
component: z.string(),
|
|
18
|
+
props: z.record(z.string(), z.any()).optional(),
|
|
19
|
+
style: StyleValueSchema.optional(),
|
|
20
|
+
attributes: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional(),
|
|
21
|
+
children: z.union([
|
|
22
|
+
z.array(z.union([ComponentNodeSchemaRef, z.string()])),
|
|
23
|
+
z.string(),
|
|
24
|
+
]).optional(),
|
|
25
|
+
}).passthrough();
|
|
26
|
+
|
|
27
|
+
// TypeScript type inferred from schema
|
|
28
|
+
export type ComponentInstanceNode = z.infer<typeof ComponentInstanceNodeSchemaInternal>;
|
|
29
|
+
|
|
30
|
+
// Export schema for validation/schemas.ts
|
|
31
|
+
export const ComponentInstanceNodeSchema = ComponentInstanceNodeSchemaInternal;
|
|
32
|
+
|
|
33
|
+
export const ComponentInstanceNodeType = createNodeType({
|
|
34
|
+
type: 'component',
|
|
35
|
+
displayName: 'Component',
|
|
36
|
+
category: 'core',
|
|
37
|
+
schema: ComponentInstanceNodeSchemaInternal,
|
|
38
|
+
|
|
39
|
+
defaultValues: {
|
|
40
|
+
component: '',
|
|
41
|
+
props: {},
|
|
42
|
+
style: { base: {} },
|
|
43
|
+
children: [],
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
treeDisplay: {
|
|
47
|
+
icon: 'COMPONENT',
|
|
48
|
+
getLabel: (node) => node.component,
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
clientRenderer: (node, context) => {
|
|
52
|
+
const definition = context.getComponentDefinition(node.component);
|
|
53
|
+
if (!definition) {
|
|
54
|
+
return h('div', { key: context.key }, `[Unknown component: ${node.component}]`);
|
|
55
|
+
}
|
|
56
|
+
return h('div', { key: context.key }, `[Component: ${node.component}]`);
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
ssrRenderer: (node, context) => {
|
|
60
|
+
const definition = context.getComponentDefinition(node.component);
|
|
61
|
+
if (!definition) {
|
|
62
|
+
return `<!-- Unknown component: ${context.escapeHtml(node.component)} -->`;
|
|
63
|
+
}
|
|
64
|
+
return `<!-- Component: ${context.escapeHtml(node.component)} -->`;
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
capabilities: {
|
|
68
|
+
canHaveChildren: true,
|
|
69
|
+
requiresProps: ['component'],
|
|
70
|
+
},
|
|
71
|
+
});
|