view-contracts 0.1.0 → 0.2.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/dist/view-contracts.bundle.mjs +148 -153
- package/dist/view-contracts.bundle.mjs.map +4 -4
- package/package.json +10 -4
- package/renderers/react/README.md +19 -3
- package/renderers/react/element-config.meta.json +23 -0
- package/renderers/react/elementMap.ts +55 -0
- package/renderers/react/elements.yaml +87 -0
- package/renderers/react/index.ts +2 -0
- package/renderers/react/lowering/lowerToJsx.ts +397 -0
- package/renderers/react/paths.ts +10 -0
- package/renderers/react/renderComponents.ts +65 -0
- package/renderers/react/renderRuntime.ts +134 -0
- package/renderers/react/routes.yaml +3 -2
- package/{src/renderer → renderers}/react/runtime/actionRuntime.ts +3 -3
- package/renderers/react/runtime/dispatch.ts +21 -0
- package/renderers/react/runtime/index.ts +5 -0
- package/{src/renderer → renderers}/react/runtime/style.ts +35 -8
- package/renderers/react/runtime/types.ts +63 -0
- package/renderers/react/templates/components-index.ts.hbs +5 -0
- package/renderers/react/templates/dispatch.ts.hbs +22 -0
- package/renderers/react/templates/handlers.ts.hbs +4 -0
- package/renderers/react/templates/runtime-types.ts.hbs +5 -0
- package/renderers/react/templates/style-helpers.ts.hbs +2 -0
- package/renderers/react/templates/theme.css.hbs +2 -0
- package/src/generated/schema/lowering-style-properties.ts +36 -0
- package/src/generated/schema/react-renderer-element-config.ts +26 -0
- package/src/renderer/react/runtime/components.tsx +0 -216
- package/src/renderer/react/runtime/index.ts +0 -4
- package/src/renderer/react/runtime/shared.tsx +0 -53
- package/src/renderer/react/runtime/types.ts +0 -46
- /package/{src/renderer → renderers}/react/runtime/theme.css +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { ViewIrDocument } from '../../src/ir/types.js';
|
|
2
|
+
import { loadReactElementMap } from './elementMap.js';
|
|
3
|
+
import { collectExtensionComponents, createNodeRenderer } from './lowering/lowerToJsx.js';
|
|
4
|
+
|
|
5
|
+
export interface RenderReactOptions {
|
|
6
|
+
componentsImportFrom: string;
|
|
7
|
+
allowlistExtra?: string[];
|
|
8
|
+
elementMapPath?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function renderReactComponent(
|
|
12
|
+
doc: ViewIrDocument,
|
|
13
|
+
options: RenderReactOptions,
|
|
14
|
+
): Promise<string> {
|
|
15
|
+
const map = await loadReactElementMap(options.elementMapPath);
|
|
16
|
+
const allowlistExtra = options.allowlistExtra ?? [];
|
|
17
|
+
const baseName = doc.exportName.replace(/View$/, '') || doc.exportName;
|
|
18
|
+
const componentName = baseName.charAt(0).toUpperCase() + baseName.slice(1);
|
|
19
|
+
const root = doc.root.kind === 'component' ? doc.root : null;
|
|
20
|
+
if (!root) {
|
|
21
|
+
throw new Error(`View IR root must be a component node (${doc.source})`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const renderNode = createNodeRenderer(map, allowlistExtra);
|
|
25
|
+
const body = renderNode(root, 2);
|
|
26
|
+
|
|
27
|
+
const extensions = new Set<string>();
|
|
28
|
+
collectExtensionComponents(doc.root, map, allowlistExtra, extensions);
|
|
29
|
+
const extensionImports = [...extensions].sort();
|
|
30
|
+
|
|
31
|
+
const imports: string[] = [
|
|
32
|
+
`import type { ReactElement } from 'react';`,
|
|
33
|
+
`import { dispatchAction } from '../runtime/dispatch.js';`,
|
|
34
|
+
`import { commonStyle, mapAlign, mapJustify } from '../components/style-helpers.js';`,
|
|
35
|
+
`import type { ${doc.viewModel}, ActionDescriptor } from '../contracts';`,
|
|
36
|
+
];
|
|
37
|
+
if (extensionImports.length > 0) {
|
|
38
|
+
imports.push(`import { ${extensionImports.join(', ')} } from '${options.componentsImportFrom}';`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return `/**
|
|
42
|
+
* Auto-generated React component from ${doc.source}
|
|
43
|
+
* DO NOT EDIT — regenerate with: view-contracts render react
|
|
44
|
+
*/
|
|
45
|
+
${imports.join('\n')}
|
|
46
|
+
|
|
47
|
+
export function ${componentName}({ vm }: { vm: ${doc.viewModel} }): ReactElement {
|
|
48
|
+
return (
|
|
49
|
+
${body}
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function renderReactFromIr(
|
|
56
|
+
doc: ViewIrDocument,
|
|
57
|
+
outputPath: string,
|
|
58
|
+
options: RenderReactOptions,
|
|
59
|
+
): Promise<void> {
|
|
60
|
+
const { writeFile, mkdir } = await import('node:fs/promises');
|
|
61
|
+
const { dirname } = await import('node:path');
|
|
62
|
+
const content = await renderReactComponent(doc, options);
|
|
63
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
64
|
+
await writeFile(outputPath, content, 'utf-8');
|
|
65
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir, stat, cp, unlink } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import type { ViewContractsConfig } from '../../src/config.js';
|
|
4
|
+
import { projectRoot } from '../../src/config.js';
|
|
5
|
+
import { loadThemeCss } from '../../src/catalog/loadThemeCss.js';
|
|
6
|
+
import { packageRoot } from '../../src/lib/packageRoot.js';
|
|
7
|
+
import { renderTemplate } from '../../src/renderer/template.js';
|
|
8
|
+
import { reactRuntimeDir, reactTemplatesDir } from './paths.js';
|
|
9
|
+
|
|
10
|
+
const toolchainRoot = packageRoot();
|
|
11
|
+
|
|
12
|
+
function toolchainSchemaDir(): string {
|
|
13
|
+
return resolve(toolchainRoot, 'src/generated/schema');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function pathExists(target: string): Promise<boolean> {
|
|
17
|
+
try {
|
|
18
|
+
await stat(target);
|
|
19
|
+
return true;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function rewriteSchemaImports(content: string): string {
|
|
26
|
+
return content.split('../../../src/generated/schema/').join('./schema/');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function copySchemaBundle(componentsDir: string): Promise<void> {
|
|
30
|
+
const schemaSrc = toolchainSchemaDir();
|
|
31
|
+
if (!(await pathExists(schemaSrc))) return;
|
|
32
|
+
|
|
33
|
+
const schemaDest = resolve(componentsDir, 'schema');
|
|
34
|
+
await cp(schemaSrc, schemaDest, { recursive: true });
|
|
35
|
+
for (const name of ['dsl-properties.ts', 'tokens.ts', 'schema-document.ts', 'index.ts'] as const) {
|
|
36
|
+
const file = resolve(schemaDest, name);
|
|
37
|
+
if (await pathExists(file)) {
|
|
38
|
+
await writeFile(file, rewriteSchemaImports(await readFile(file, 'utf-8')), 'utf-8');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function copyStyleBundle(componentsDir: string): Promise<void> {
|
|
44
|
+
const runtimeSrc = reactRuntimeDir();
|
|
45
|
+
const style = rewriteSchemaImports(await readFile(resolve(runtimeSrc, 'style.ts'), 'utf-8'));
|
|
46
|
+
await writeFile(resolve(componentsDir, 'style.ts'), style, 'utf-8');
|
|
47
|
+
|
|
48
|
+
const types = rewriteSchemaImports(await readFile(resolve(runtimeSrc, 'types.ts'), 'utf-8'));
|
|
49
|
+
await writeFile(resolve(componentsDir, 'types.ts'), types, 'utf-8');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function writeThemeCss(root: string, themeOut: string, templatesDir?: string): Promise<void> {
|
|
53
|
+
const runtimeTheme = resolve(reactRuntimeDir(), 'theme.css');
|
|
54
|
+
let css = await loadThemeCss(runtimeTheme);
|
|
55
|
+
const extensionsCss = resolve(root, 'src/theme.extensions.css');
|
|
56
|
+
if (await pathExists(extensionsCss)) {
|
|
57
|
+
css += `\n\n${await readFile(extensionsCss, 'utf-8')}`;
|
|
58
|
+
}
|
|
59
|
+
const content = await renderTemplate(reactTemplatesDir(templatesDir), 'theme.css.hbs', { themeCss: css });
|
|
60
|
+
await writeFile(themeOut, content, 'utf-8');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function removeStaleGeneratedFiles(componentsDir: string): Promise<void> {
|
|
64
|
+
for (const name of ['ui-components.tsx', 'core.ts', 'actionRuntime.ts', 'shared.tsx', 'shared.ts'] as const) {
|
|
65
|
+
const target = resolve(componentsDir, name);
|
|
66
|
+
if (await pathExists(target)) {
|
|
67
|
+
await unlink(target);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Emit standalone generated runtime (no preview vocabulary copy; no @view-contracts imports). */
|
|
73
|
+
export async function renderReactRuntime(configPath: string, config: ViewContractsConfig): Promise<void> {
|
|
74
|
+
const root = projectRoot(configPath);
|
|
75
|
+
const templatesDir = config.rendererTemplatesDir
|
|
76
|
+
? resolve(root, config.rendererTemplatesDir)
|
|
77
|
+
: undefined;
|
|
78
|
+
const templates = reactTemplatesDir(templatesDir);
|
|
79
|
+
const componentsDir = resolve(config.generated.reactDir, '../components');
|
|
80
|
+
const runtimeDir = resolve(config.generated.reactDir, '../runtime');
|
|
81
|
+
const srcComponentsDir = resolve(root, 'src/components');
|
|
82
|
+
|
|
83
|
+
await mkdir(componentsDir, { recursive: true });
|
|
84
|
+
await mkdir(runtimeDir, { recursive: true });
|
|
85
|
+
|
|
86
|
+
await removeStaleGeneratedFiles(componentsDir);
|
|
87
|
+
await copySchemaBundle(componentsDir);
|
|
88
|
+
await copyStyleBundle(componentsDir);
|
|
89
|
+
await writeFile(
|
|
90
|
+
resolve(componentsDir, 'style-helpers.ts'),
|
|
91
|
+
await renderTemplate(templates, 'style-helpers.ts.hbs', {}),
|
|
92
|
+
'utf-8',
|
|
93
|
+
);
|
|
94
|
+
await writeFile(
|
|
95
|
+
resolve(componentsDir, 'index.ts'),
|
|
96
|
+
await renderTemplate(templates, 'components-index.ts.hbs', {}),
|
|
97
|
+
'utf-8',
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
for (const name of ['extensions.tsx', 'sample-types.ts'] as const) {
|
|
101
|
+
const src = resolve(srcComponentsDir, name);
|
|
102
|
+
let body = await readFile(src, 'utf-8');
|
|
103
|
+
if (name === 'sample-types.ts') {
|
|
104
|
+
body = body.replace("from './core.js'", "from './types.js'");
|
|
105
|
+
}
|
|
106
|
+
await writeFile(resolve(componentsDir, name), body, 'utf-8');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const handlersSrc = resolve(root, 'src/runtime/handlers.ts');
|
|
110
|
+
const handlersBody = (await readFile(handlersSrc, 'utf-8')).replace(
|
|
111
|
+
/export async function handleAppAction\(action:\s*\{[\s\S]*?\}\)/,
|
|
112
|
+
'export async function handleAppAction(action: DispatchableAction)',
|
|
113
|
+
);
|
|
114
|
+
await writeFile(
|
|
115
|
+
resolve(runtimeDir, 'handlers.ts'),
|
|
116
|
+
await renderTemplate(templates, 'handlers.ts.hbs', { handlersBody }),
|
|
117
|
+
'utf-8',
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
await writeThemeCss(root, resolve(config.generated.reactDir, '../theme.css'), templatesDir);
|
|
121
|
+
await writeFile(
|
|
122
|
+
resolve(runtimeDir, 'types.ts'),
|
|
123
|
+
await renderTemplate(templates, 'runtime-types.ts.hbs', {}),
|
|
124
|
+
'utf-8',
|
|
125
|
+
);
|
|
126
|
+
await writeFile(
|
|
127
|
+
resolve(runtimeDir, 'dispatch.ts'),
|
|
128
|
+
await renderTemplate(templates, 'dispatch.ts.hbs', {}),
|
|
129
|
+
'utf-8',
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
console.log(` components → ${componentsDir}`);
|
|
133
|
+
console.log(` theme → ${resolve(config.generated.reactDir, '../theme.css')}`);
|
|
134
|
+
}
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
# Vocabulary: spec/ui-dsl.schema.json. Per-target maps: renderers/{target}/elements.yaml
|
|
3
3
|
|
|
4
4
|
react:
|
|
5
|
-
description: Production
|
|
6
|
-
importFrom: '
|
|
5
|
+
description: Production React via compile-time IR lowering + template-emitted runtime
|
|
6
|
+
importFrom: '../components/index.js'
|
|
7
7
|
themeCss: theme.css
|
|
8
|
+
templates: renderers/react/templates
|
|
8
9
|
outputs:
|
|
9
10
|
screen:
|
|
10
11
|
pattern: '{{projectRoot}}/generated/react/{{screenName}}.generated.tsx'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { dispatchAction } from '
|
|
1
|
+
import type { DispatchableAction } from './types.js';
|
|
2
|
+
import { dispatchAction } from './dispatch.js';
|
|
3
3
|
|
|
4
4
|
const globalHandlers: Record<string, (params?: unknown) => void | Promise<void>> = {};
|
|
5
5
|
|
|
@@ -15,7 +15,7 @@ export function clearPreviewActions(): void {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export async function dispatchPreviewAction(action?:
|
|
18
|
+
export async function dispatchPreviewAction(action?: DispatchableAction): Promise<void> {
|
|
19
19
|
if (!action) return;
|
|
20
20
|
const handler = globalHandlers[action.name];
|
|
21
21
|
if (handler) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DispatchableAction } from './types.js';
|
|
2
|
+
|
|
3
|
+
type ActionHandler = (action: DispatchableAction) => void | Promise<void>;
|
|
4
|
+
|
|
5
|
+
let handler: ActionHandler | undefined;
|
|
6
|
+
|
|
7
|
+
export function registerActionHandler(nextHandler: ActionHandler): void {
|
|
8
|
+
handler = nextHandler;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function dispatchAction(action: DispatchableAction): Promise<void> {
|
|
12
|
+
if (handler) {
|
|
13
|
+
await handler(action);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (action.name === 'navigate' && typeof action.params?.to === 'string') {
|
|
17
|
+
window.location.href = action.params.to;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
window.dispatchEvent(new CustomEvent('view-contracts:action', { detail: action }));
|
|
21
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Preview/toolchain React runtime primitives — not copied to generated/. */
|
|
2
|
+
export * from './types.js';
|
|
3
|
+
export { commonStyle, isHidden, toneClass, mapAlign, mapJustify } from './style.js';
|
|
4
|
+
export { registerPreviewActions, clearPreviewActions, dispatchPreviewAction } from './actionRuntime.js';
|
|
5
|
+
export { registerActionHandler, dispatchAction } from './dispatch.js';
|
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import type { CSSProperties } from 'react';
|
|
2
|
-
import type { Clip, ContentAlign, Shadow } from '../../../generated/schema/tokens.js';
|
|
3
|
-
import type {
|
|
2
|
+
import type { Align, Clip, ContentAlign, FontWeight, Justify, Shadow } from '../../../src/generated/schema/tokens.js';
|
|
3
|
+
import type { CommonProps } from './types.js';
|
|
4
|
+
|
|
5
|
+
export function mapAlign(value?: Align): string | undefined {
|
|
6
|
+
if (!value) return undefined;
|
|
7
|
+
if (value === 'start') return 'flex-start';
|
|
8
|
+
if (value === 'end') return 'flex-end';
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function mapJustify(value?: Justify | string): string | undefined {
|
|
13
|
+
if (!value) return undefined;
|
|
14
|
+
if (value === 'start') return 'flex-start';
|
|
15
|
+
if (value === 'end') return 'flex-end';
|
|
16
|
+
if (value === 'between' || value === 'spaceBetween') return 'space-between';
|
|
17
|
+
if (value === 'around' || value === 'spaceAround') return 'space-around';
|
|
18
|
+
if (value === 'spaceEvenly') return 'space-evenly';
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
4
21
|
|
|
5
22
|
function len(value: number): string {
|
|
6
23
|
return `${value}px`;
|
|
@@ -24,12 +41,18 @@ const CONTENT_ALIGN: Record<ContentAlign, string> = {
|
|
|
24
41
|
bottomEnd: 'end end',
|
|
25
42
|
};
|
|
26
43
|
|
|
27
|
-
function shadowStyle(shadow: Shadow | undefined): string | undefined {
|
|
44
|
+
function shadowStyle(shadow: Shadow | boolean | undefined): string | undefined {
|
|
45
|
+
if (shadow === true) return SHADOW.md;
|
|
28
46
|
if (!shadow || shadow === 'none') return undefined;
|
|
29
47
|
return SHADOW[shadow];
|
|
30
48
|
}
|
|
31
49
|
|
|
32
|
-
|
|
50
|
+
function fontSizeStyle(size: number | string | undefined): string | undefined {
|
|
51
|
+
if (size === undefined) return undefined;
|
|
52
|
+
return typeof size === 'number' ? len(size) : size;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function commonStyle(props: CommonProps): CSSProperties {
|
|
33
56
|
const style: CSSProperties = {};
|
|
34
57
|
if (typeof props.width === 'number') style.width = len(props.width);
|
|
35
58
|
if (typeof props.minWidth === 'number') style.minWidth = len(props.minWidth);
|
|
@@ -38,9 +61,9 @@ export function commonStyle(props: DslProperties): CSSProperties {
|
|
|
38
61
|
if (typeof props.minHeight === 'number') style.minHeight = len(props.minHeight);
|
|
39
62
|
if (typeof props.maxHeight === 'number') style.maxHeight = len(props.maxHeight);
|
|
40
63
|
if (props.aspectRatio !== undefined) style.aspectRatio = String(props.aspectRatio);
|
|
41
|
-
if (props.weight
|
|
64
|
+
if (typeof props.weight === 'number') style.flexGrow = props.weight;
|
|
42
65
|
if (typeof props.padding === 'number') style.padding = len(props.padding);
|
|
43
|
-
if (typeof props.margin === 'number') style.margin = len(props.margin);
|
|
66
|
+
if (typeof (props as { margin?: number }).margin === 'number') style.margin = len((props as { margin: number }).margin);
|
|
44
67
|
if (typeof props.gap === 'number') style.gap = len(props.gap);
|
|
45
68
|
if (typeof props.radius === 'number') style.borderRadius = len(props.radius);
|
|
46
69
|
if (props.border) style.border = '1px solid var(--ui-border)';
|
|
@@ -52,12 +75,16 @@ export function commonStyle(props: DslProperties): CSSProperties {
|
|
|
52
75
|
if (props.foreground) style.color = `var(--ui-${props.foreground}, ${props.foreground})`;
|
|
53
76
|
if (props.textAlign) style.textAlign = props.textAlign === 'start' ? 'left' : props.textAlign === 'end' ? 'right' : 'center';
|
|
54
77
|
if (props.contentAlign) style.placeItems = CONTENT_ALIGN[props.contentAlign];
|
|
55
|
-
if (props.overflow) style.overflow = props.overflow;
|
|
78
|
+
if ((props as { overflow?: string }).overflow) style.overflow = (props as { overflow: string }).overflow;
|
|
56
79
|
if (props.selfAlign) style.alignSelf = props.selfAlign === 'start' ? 'flex-start' : props.selfAlign === 'end' ? 'flex-end' : props.selfAlign;
|
|
80
|
+
const fontSize = fontSizeStyle(props.size);
|
|
81
|
+
if (fontSize) style.fontSize = fontSize;
|
|
82
|
+
const fontWeight = typeof props.weight === 'string' ? props.weight : props.fontWeight;
|
|
83
|
+
if (fontWeight) style.fontWeight = fontWeight as FontWeight;
|
|
57
84
|
return style;
|
|
58
85
|
}
|
|
59
86
|
|
|
60
|
-
export function isHidden(props:
|
|
87
|
+
export function isHidden(props: CommonProps): boolean {
|
|
61
88
|
return props.visible === false;
|
|
62
89
|
}
|
|
63
90
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { DslProperties } from '../../../src/generated/schema/dsl-properties.js';
|
|
3
|
+
import type { FontWeight, Justify, Shadow, WidthSize } from '../../../src/generated/schema/tokens.js';
|
|
4
|
+
|
|
5
|
+
export type {
|
|
6
|
+
Align,
|
|
7
|
+
Axis,
|
|
8
|
+
Border,
|
|
9
|
+
Clip,
|
|
10
|
+
ContentAlign,
|
|
11
|
+
Direction,
|
|
12
|
+
FontWeight,
|
|
13
|
+
HeightSize,
|
|
14
|
+
ImageFit,
|
|
15
|
+
Justify,
|
|
16
|
+
KeyboardType,
|
|
17
|
+
LineLimit,
|
|
18
|
+
TextAlign,
|
|
19
|
+
TextOverflow,
|
|
20
|
+
WidthSize,
|
|
21
|
+
} from '../../../src/generated/schema/tokens.js';
|
|
22
|
+
|
|
23
|
+
export type { DslProperties };
|
|
24
|
+
|
|
25
|
+
export interface ViewAction {
|
|
26
|
+
name: string;
|
|
27
|
+
params?: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type DispatchableAction = ViewAction;
|
|
31
|
+
|
|
32
|
+
/** view-contracts view TSX action binding (schema property: onClick / onChange). */
|
|
33
|
+
export type Action = ViewAction;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Preview + sample extension props layered on the DSL catalog.
|
|
37
|
+
* Widens a few catalog types so sample views can use pragmatic literals (e.g. radius={16}, justify="between").
|
|
38
|
+
*/
|
|
39
|
+
export interface CommonProps extends Omit<DslProperties, 'justify' | 'radius' | 'shadow' | 'width' | 'weight' | 'fontWeight'> {
|
|
40
|
+
children?: ReactNode;
|
|
41
|
+
/** View-layer alias for onClick in interactive components. */
|
|
42
|
+
action?: ViewAction;
|
|
43
|
+
justify?: Justify | 'between' | 'around';
|
|
44
|
+
radius?: string | number;
|
|
45
|
+
shadow?: Shadow | boolean;
|
|
46
|
+
width?: WidthSize | number;
|
|
47
|
+
/** Flex grow (number) or sample font weight alias (string) on Text. */
|
|
48
|
+
weight?: number | string;
|
|
49
|
+
fontWeight?: FontWeight | string;
|
|
50
|
+
/** Sample/preview extension props (not in ui-dsl.schema.json). */
|
|
51
|
+
tone?: string;
|
|
52
|
+
size?: number | string;
|
|
53
|
+
ariaLabel?: string;
|
|
54
|
+
testId?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface Option {
|
|
58
|
+
label: string;
|
|
59
|
+
value: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Preview-only field density (not in ui-dsl.schema.json). */
|
|
63
|
+
export type FieldSize = 'sm' | 'md' | 'lg';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Auto-generated component barrel — DO NOT EDIT */
|
|
2
|
+
export * from './extensions.js';
|
|
3
|
+
export type { TableColumn } from './extensions.js';
|
|
4
|
+
export { commonStyle, mapAlign, mapJustify, isHidden, toneClass } from './style.js';
|
|
5
|
+
export type { Action, CommonProps, Option } from './types.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Auto-generated runtime dispatcher — DO NOT EDIT */
|
|
2
|
+
import type { DispatchableAction } from './types.js';
|
|
3
|
+
|
|
4
|
+
type ActionHandler = (action: DispatchableAction) => void | Promise<void>;
|
|
5
|
+
|
|
6
|
+
let handler: ActionHandler | undefined;
|
|
7
|
+
|
|
8
|
+
export function registerActionHandler(nextHandler: ActionHandler): void {
|
|
9
|
+
handler = nextHandler;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function dispatchAction(action: DispatchableAction): Promise<void> {
|
|
13
|
+
if (handler) {
|
|
14
|
+
await handler(action);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (action.name === 'navigate' && typeof action.params?.to === 'string') {
|
|
18
|
+
window.location.href = action.params.to;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
window.dispatchEvent(new CustomEvent('view-contracts:action', { detail: action }));
|
|
22
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Auto-generated from spec/ui-dsl.schema.json — view-contracts v0.1.0 — DO NOT EDIT */
|
|
2
|
+
/** Property names passed to commonStyle() during React HTML lowering (x-category from DSL + sample extensions). */
|
|
3
|
+
export const LOWERING_STYLE_PROPERTIES = new Set<string>([
|
|
4
|
+
"align",
|
|
5
|
+
"aspectRatio",
|
|
6
|
+
"axis",
|
|
7
|
+
"background",
|
|
8
|
+
"border",
|
|
9
|
+
"clip",
|
|
10
|
+
"contentAlign",
|
|
11
|
+
"direction",
|
|
12
|
+
"fontWeight",
|
|
13
|
+
"foreground",
|
|
14
|
+
"gap",
|
|
15
|
+
"headingLevel",
|
|
16
|
+
"height",
|
|
17
|
+
"justify",
|
|
18
|
+
"lineLimit",
|
|
19
|
+
"maxHeight",
|
|
20
|
+
"maxWidth",
|
|
21
|
+
"minHeight",
|
|
22
|
+
"minWidth",
|
|
23
|
+
"opacity",
|
|
24
|
+
"padding",
|
|
25
|
+
"radius",
|
|
26
|
+
"scroll",
|
|
27
|
+
"selfAlign",
|
|
28
|
+
"shadow",
|
|
29
|
+
"textAlign",
|
|
30
|
+
"textOverflow",
|
|
31
|
+
"textStyle",
|
|
32
|
+
"variant",
|
|
33
|
+
"weight",
|
|
34
|
+
"width",
|
|
35
|
+
"size",
|
|
36
|
+
]);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Auto-generated from renderers/react/element-config.meta.json — view-contracts v0.1.0 — DO NOT EDIT */
|
|
2
|
+
/** Shape of entries in renderers/react/elements.yaml (target-specific HTML map, not DSL vocabulary). */
|
|
3
|
+
export interface ReactRendererElementConfig {
|
|
4
|
+
tag: string;
|
|
5
|
+
className?: string;
|
|
6
|
+
ariaLabel?: boolean;
|
|
7
|
+
ariaHidden?: boolean;
|
|
8
|
+
flex?: "row" | "column";
|
|
9
|
+
wrapProp?: string;
|
|
10
|
+
flexGrowProp?: string;
|
|
11
|
+
void?: boolean;
|
|
12
|
+
levelProp?: string;
|
|
13
|
+
defaultLevel?: number;
|
|
14
|
+
actionEvent?: "onClick" | "onSubmit";
|
|
15
|
+
variantClass?: boolean;
|
|
16
|
+
fieldWrap?: boolean;
|
|
17
|
+
inputType?: string;
|
|
18
|
+
sizeClass?: string;
|
|
19
|
+
optionsProp?: string;
|
|
20
|
+
checkbox?: boolean;
|
|
21
|
+
defaultRole?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ReactElementMapFile {
|
|
25
|
+
elements: Record<string, ReactRendererElementConfig>;
|
|
26
|
+
}
|