@tmorrow/cre8-wc 2.0.3 → 2.0.4
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/a2ui/catalog.json +5622 -0
- package/a2ui/demo.html +287 -0
- package/a2ui/generate-catalog.mjs +243 -0
- package/a2ui/index.d.ts +4 -0
- package/a2ui/index.js +2 -0
- package/a2ui/index.ts +12 -0
- package/a2ui/registry.d.ts +3 -0
- package/a2ui/registry.js +166 -0
- package/a2ui/registry.ts +182 -0
- package/a2ui/renderer.d.ts +7 -0
- package/a2ui/renderer.js +108 -0
- package/a2ui/renderer.ts +156 -0
- package/a2ui/smoke-test.mjs +238 -0
- package/a2ui/types.d.ts +75 -0
- package/a2ui/types.js +1 -0
- package/a2ui/types.ts +80 -0
- package/lib/a2ui/index.d.ts +5 -0
- package/lib/a2ui/index.d.ts.map +1 -0
- package/lib/a2ui/index.js +3 -0
- package/lib/a2ui/index.js.map +1 -0
- package/lib/a2ui/registry.d.ts +4 -0
- package/lib/a2ui/registry.d.ts.map +1 -0
- package/lib/a2ui/registry.js +167 -0
- package/lib/a2ui/registry.js.map +1 -0
- package/lib/a2ui/renderer.d.ts +8 -0
- package/lib/a2ui/renderer.d.ts.map +1 -0
- package/lib/a2ui/renderer.js +109 -0
- package/lib/a2ui/renderer.js.map +1 -0
- package/lib/a2ui/types.d.ts +76 -0
- package/lib/a2ui/types.d.ts.map +1 -0
- package/lib/a2ui/types.js +2 -0
- package/lib/a2ui/types.js.map +1 -0
- package/mcp-manifest.json +1 -1
- package/package.json +15 -1
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { JSDOM } from 'jsdom';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const catalog = JSON.parse(readFileSync(resolve(__dirname, 'catalog.json'), 'utf8'));
|
|
8
|
+
|
|
9
|
+
const dom = new JSDOM('<!doctype html><html><body></body></html>');
|
|
10
|
+
globalThis.document = dom.window.document;
|
|
11
|
+
globalThis.HTMLElement = dom.window.HTMLElement;
|
|
12
|
+
|
|
13
|
+
const { registerCatalog, render, validateSpec } = await import('./index.ts');
|
|
14
|
+
|
|
15
|
+
const cat = registerCatalog(catalog);
|
|
16
|
+
console.log(`loaded catalog "${cat.id}" (${cat.components.size} components)`);
|
|
17
|
+
|
|
18
|
+
const spec = {
|
|
19
|
+
component: 'cre8-card',
|
|
20
|
+
slots: {
|
|
21
|
+
header: [{ component: 'cre8-button', props: { text: 'Close', variant: 'tertiary' } }],
|
|
22
|
+
body: [
|
|
23
|
+
{ component: 'cre8-alert', props: { status: 'info' } },
|
|
24
|
+
{
|
|
25
|
+
component: 'cre8-button-group',
|
|
26
|
+
children: [
|
|
27
|
+
{ component: 'cre8-button', props: { text: 'Save', variant: 'primary' } },
|
|
28
|
+
{ component: 'cre8-button', props: { text: 'Cancel', variant: 'secondary', neutral: true } },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
validateSpec(spec, cat);
|
|
37
|
+
console.log('validation: OK');
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.error('validation failed:', e.message);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const el = render(spec, cat);
|
|
44
|
+
console.log('rendered:\n' + el.outerHTML);
|
|
45
|
+
|
|
46
|
+
const bad = { component: 'cre8-bogus' };
|
|
47
|
+
try {
|
|
48
|
+
validateSpec(bad, cat);
|
|
49
|
+
console.error('FAIL: expected rejection of cre8-bogus');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.log('allowlist reject:', e.message);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const badProp = { component: 'cre8-button', props: { notARealProp: 'x' } };
|
|
56
|
+
try {
|
|
57
|
+
validateSpec(badProp, cat);
|
|
58
|
+
console.error('FAIL: expected rejection of bad prop');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.log('prop reject:', e.message);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const badSlot = {
|
|
65
|
+
component: 'cre8-card',
|
|
66
|
+
slots: { footer: [], notASlot: [] },
|
|
67
|
+
};
|
|
68
|
+
try {
|
|
69
|
+
validateSpec(badSlot, cat);
|
|
70
|
+
console.error('FAIL: expected rejection of unknown slot');
|
|
71
|
+
process.exit(1);
|
|
72
|
+
} catch (e) {
|
|
73
|
+
console.log('slot reject:', e.message);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const eventSpec = {
|
|
77
|
+
component: 'cre8-button',
|
|
78
|
+
props: { text: 'Save', variant: 'primary' },
|
|
79
|
+
events: {
|
|
80
|
+
click: { handler: 'save-record', stopPropagation: true },
|
|
81
|
+
'text-click': 'emit-telemetry',
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const emitted = [];
|
|
86
|
+
const eventEl = render(eventSpec, cat, { onEvent: (e) => emitted.push(e) });
|
|
87
|
+
|
|
88
|
+
eventEl.dispatchEvent(new dom.window.Event('click', { bubbles: true }));
|
|
89
|
+
eventEl.dispatchEvent(new dom.window.CustomEvent('text-click', { detail: { source: 'kbd' } }));
|
|
90
|
+
|
|
91
|
+
if (emitted.length !== 2) {
|
|
92
|
+
console.error('FAIL: expected 2 emitted events, got', emitted.length);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
if (emitted[0].handler !== 'save-record' || emitted[0].event !== 'click') {
|
|
96
|
+
console.error('FAIL: click binding wrong:', emitted[0]);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
if (emitted[1].handler !== 'emit-telemetry' || emitted[1].detail?.source !== 'kbd') {
|
|
100
|
+
console.error('FAIL: custom event binding wrong:', emitted[1]);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
console.log('event binding:', emitted.map((e) => `${e.event}→${e.handler}`).join(', '));
|
|
104
|
+
|
|
105
|
+
const badEventSpec = { component: 'cre8-button', events: { click: { foo: 'bar' } } };
|
|
106
|
+
try {
|
|
107
|
+
validateSpec(badEventSpec, cat);
|
|
108
|
+
console.error('FAIL: expected rejection of missing handler');
|
|
109
|
+
process.exit(1);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
console.log('event reject:', e.message);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const badEnum = { component: 'cre8-button', props: { variant: 'bogus' } };
|
|
115
|
+
try {
|
|
116
|
+
validateSpec(badEnum, cat);
|
|
117
|
+
console.error('FAIL: expected enum rejection');
|
|
118
|
+
process.exit(1);
|
|
119
|
+
} catch (e) {
|
|
120
|
+
console.log('enum reject:', e.message);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const badType = { component: 'cre8-button', props: { disabled: 'yes' } };
|
|
124
|
+
try {
|
|
125
|
+
validateSpec(badType, cat);
|
|
126
|
+
console.error('FAIL: expected type rejection');
|
|
127
|
+
process.exit(1);
|
|
128
|
+
} catch (e) {
|
|
129
|
+
console.log('type reject:', e.message);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const badConst = {
|
|
133
|
+
component: 'cre8-button',
|
|
134
|
+
props: { variant: 'primary' },
|
|
135
|
+
// hand-crafted invalid: component field must match const
|
|
136
|
+
};
|
|
137
|
+
// component const is enforced via the registered component map, but verify
|
|
138
|
+
// explicit const checking works by abusing a spec path we can control.
|
|
139
|
+
// Instead, exercise the union number|string prop (cre8-field.max) with a bad type.
|
|
140
|
+
const badUnion = { component: 'cre8-field', props: { max: true } };
|
|
141
|
+
try {
|
|
142
|
+
validateSpec(badUnion, cat);
|
|
143
|
+
console.error('FAIL: expected union type rejection');
|
|
144
|
+
process.exit(1);
|
|
145
|
+
} catch (e) {
|
|
146
|
+
console.log('union reject:', e.message);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const goodUnionNum = { component: 'cre8-field', props: { max: 10 } };
|
|
150
|
+
const goodUnionStr = { component: 'cre8-field', props: { max: '10' } };
|
|
151
|
+
validateSpec(goodUnionNum, cat);
|
|
152
|
+
validateSpec(goodUnionStr, cat);
|
|
153
|
+
console.log('union accepts string|number: OK');
|
|
154
|
+
|
|
155
|
+
// x-tsType resolver coverage
|
|
156
|
+
validateSpec({ component: 'cre8-chart', props: { type: 'doughnut' } }, cat);
|
|
157
|
+
try {
|
|
158
|
+
validateSpec({ component: 'cre8-chart', props: { type: 'donut' } }, cat);
|
|
159
|
+
console.error('FAIL: expected Cre8ChartType rejection');
|
|
160
|
+
process.exit(1);
|
|
161
|
+
} catch (e) {
|
|
162
|
+
console.log('Cre8ChartType reject:', e.message);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
validateSpec({ component: 'cre8-heading', props: { type: 'meta-small' } }, cat);
|
|
166
|
+
try {
|
|
167
|
+
validateSpec({ component: 'cre8-heading', props: { type: 'title-xxlarge' } }, cat);
|
|
168
|
+
console.error('FAIL: expected heading union rejection');
|
|
169
|
+
process.exit(1);
|
|
170
|
+
} catch (e) {
|
|
171
|
+
console.log('heading-type reject:', e.message);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
validateSpec(
|
|
175
|
+
{
|
|
176
|
+
component: 'cre8-select',
|
|
177
|
+
props: {
|
|
178
|
+
items: [
|
|
179
|
+
{ label: 'One', value: 1 },
|
|
180
|
+
{ optGroupLabel: 'Group', options: [{ label: 'Sub', value: 'sub' }] },
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
cat
|
|
185
|
+
);
|
|
186
|
+
console.log('Cre8SelectOption[] accepts mixed union: OK');
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
validateSpec(
|
|
190
|
+
{
|
|
191
|
+
component: 'cre8-select',
|
|
192
|
+
props: { items: [{ label: 'Missing value' }] },
|
|
193
|
+
},
|
|
194
|
+
cat
|
|
195
|
+
);
|
|
196
|
+
console.error('FAIL: expected required-value rejection');
|
|
197
|
+
process.exit(1);
|
|
198
|
+
} catch (e) {
|
|
199
|
+
console.log('select-items required reject:', e.message);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
validateSpec(
|
|
204
|
+
{
|
|
205
|
+
component: 'cre8-select',
|
|
206
|
+
props: { items: [{ label: 'A', value: 1, extra: 'nope' }] },
|
|
207
|
+
},
|
|
208
|
+
cat
|
|
209
|
+
);
|
|
210
|
+
console.error('FAIL: expected additionalProperties rejection');
|
|
211
|
+
process.exit(1);
|
|
212
|
+
} catch (e) {
|
|
213
|
+
console.log('select-items additionalProperties reject:', e.message);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// text-children + x-kind routing
|
|
217
|
+
const textSpec = {
|
|
218
|
+
component: 'cre8-heading',
|
|
219
|
+
props: { type: 'headline-default', tagVariant: 'h2' },
|
|
220
|
+
children: ['Hello world'],
|
|
221
|
+
};
|
|
222
|
+
validateSpec(textSpec, cat);
|
|
223
|
+
const hEl = render(textSpec, cat);
|
|
224
|
+
if (hEl.textContent !== 'Hello world') {
|
|
225
|
+
console.error('FAIL: text child not rendered; got:', hEl.textContent);
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
if (hEl.getAttribute('tagVariant') !== null) {
|
|
229
|
+
console.error('FAIL: tagVariant should be set as property, not attribute');
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
if (hEl.tagVariant !== 'h2') {
|
|
233
|
+
console.error('FAIL: tagVariant property not set');
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
console.log('text child + x-kind=property routing: OK');
|
|
237
|
+
|
|
238
|
+
console.log('all checks passed');
|
package/a2ui/types.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export type SpecChild = ComponentSpec | string;
|
|
2
|
+
export interface ComponentSpec {
|
|
3
|
+
component: string;
|
|
4
|
+
props?: Record<string, unknown>;
|
|
5
|
+
children?: SpecChild[];
|
|
6
|
+
slots?: Record<string, SpecChild[]>;
|
|
7
|
+
events?: Record<string, EventBinding>;
|
|
8
|
+
}
|
|
9
|
+
export type EventBinding = string | {
|
|
10
|
+
handler: string;
|
|
11
|
+
stopPropagation?: boolean;
|
|
12
|
+
preventDefault?: boolean;
|
|
13
|
+
};
|
|
14
|
+
export interface EmittedEvent {
|
|
15
|
+
component: string;
|
|
16
|
+
path: string;
|
|
17
|
+
event: string;
|
|
18
|
+
handler: string;
|
|
19
|
+
detail: unknown;
|
|
20
|
+
nativeEvent: Event;
|
|
21
|
+
}
|
|
22
|
+
export interface CatalogSchema {
|
|
23
|
+
$id?: string;
|
|
24
|
+
$defs?: {
|
|
25
|
+
components?: Record<string, CatalogComponentDef>;
|
|
26
|
+
};
|
|
27
|
+
'x-a2ui'?: {
|
|
28
|
+
catalogId?: string;
|
|
29
|
+
library?: string;
|
|
30
|
+
libraryVersion?: string;
|
|
31
|
+
tagPrefix?: string;
|
|
32
|
+
framework?: string;
|
|
33
|
+
};
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
}
|
|
36
|
+
export interface CatalogComponentDef {
|
|
37
|
+
title?: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
'x-category'?: string;
|
|
40
|
+
'x-slot-descriptions'?: Record<string, string>;
|
|
41
|
+
'x-events'?: Record<string, {
|
|
42
|
+
detail?: unknown;
|
|
43
|
+
}>;
|
|
44
|
+
properties?: {
|
|
45
|
+
component?: {
|
|
46
|
+
const?: string;
|
|
47
|
+
};
|
|
48
|
+
props?: {
|
|
49
|
+
properties?: Record<string, PropSchema>;
|
|
50
|
+
};
|
|
51
|
+
children?: unknown;
|
|
52
|
+
slots?: {
|
|
53
|
+
properties?: Record<string, unknown>;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export interface PropSchema {
|
|
58
|
+
type?: string | string[];
|
|
59
|
+
enum?: string[];
|
|
60
|
+
const?: unknown;
|
|
61
|
+
default?: unknown;
|
|
62
|
+
description?: string;
|
|
63
|
+
items?: PropSchema;
|
|
64
|
+
properties?: Record<string, PropSchema>;
|
|
65
|
+
required?: string[];
|
|
66
|
+
additionalProperties?: boolean;
|
|
67
|
+
oneOf?: PropSchema[];
|
|
68
|
+
'x-tsType'?: string;
|
|
69
|
+
'x-kind'?: 'attribute' | 'property';
|
|
70
|
+
}
|
|
71
|
+
export interface RegisteredCatalog {
|
|
72
|
+
id: string;
|
|
73
|
+
schema: CatalogSchema;
|
|
74
|
+
components: Map<string, CatalogComponentDef>;
|
|
75
|
+
}
|
package/a2ui/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/a2ui/types.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export type SpecChild = ComponentSpec | string;
|
|
2
|
+
|
|
3
|
+
export interface ComponentSpec {
|
|
4
|
+
component: string;
|
|
5
|
+
props?: Record<string, unknown>;
|
|
6
|
+
children?: SpecChild[];
|
|
7
|
+
slots?: Record<string, SpecChild[]>;
|
|
8
|
+
events?: Record<string, EventBinding>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type EventBinding =
|
|
12
|
+
| string
|
|
13
|
+
| {
|
|
14
|
+
handler: string;
|
|
15
|
+
stopPropagation?: boolean;
|
|
16
|
+
preventDefault?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export interface EmittedEvent {
|
|
20
|
+
component: string;
|
|
21
|
+
path: string;
|
|
22
|
+
event: string;
|
|
23
|
+
handler: string;
|
|
24
|
+
detail: unknown;
|
|
25
|
+
nativeEvent: Event;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface CatalogSchema {
|
|
29
|
+
$id?: string;
|
|
30
|
+
$defs?: {
|
|
31
|
+
components?: Record<string, CatalogComponentDef>;
|
|
32
|
+
};
|
|
33
|
+
'x-a2ui'?: {
|
|
34
|
+
catalogId?: string;
|
|
35
|
+
library?: string;
|
|
36
|
+
libraryVersion?: string;
|
|
37
|
+
tagPrefix?: string;
|
|
38
|
+
framework?: string;
|
|
39
|
+
};
|
|
40
|
+
[key: string]: unknown;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface CatalogComponentDef {
|
|
44
|
+
title?: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
'x-category'?: string;
|
|
47
|
+
'x-slot-descriptions'?: Record<string, string>;
|
|
48
|
+
'x-events'?: Record<string, { detail?: unknown }>;
|
|
49
|
+
properties?: {
|
|
50
|
+
component?: { const?: string };
|
|
51
|
+
props?: {
|
|
52
|
+
properties?: Record<string, PropSchema>;
|
|
53
|
+
};
|
|
54
|
+
children?: unknown;
|
|
55
|
+
slots?: {
|
|
56
|
+
properties?: Record<string, unknown>;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface PropSchema {
|
|
62
|
+
type?: string | string[];
|
|
63
|
+
enum?: string[];
|
|
64
|
+
const?: unknown;
|
|
65
|
+
default?: unknown;
|
|
66
|
+
description?: string;
|
|
67
|
+
items?: PropSchema;
|
|
68
|
+
properties?: Record<string, PropSchema>;
|
|
69
|
+
required?: string[];
|
|
70
|
+
additionalProperties?: boolean;
|
|
71
|
+
oneOf?: PropSchema[];
|
|
72
|
+
'x-tsType'?: string;
|
|
73
|
+
'x-kind'?: 'attribute' | 'property';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface RegisteredCatalog {
|
|
77
|
+
id: string;
|
|
78
|
+
schema: CatalogSchema;
|
|
79
|
+
components: Map<string, CatalogComponentDef>;
|
|
80
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { CatalogComponentDef, CatalogSchema, ComponentSpec, EmittedEvent, EventBinding, PropSchema, RegisteredCatalog, } from './types.js';
|
|
2
|
+
export { registerCatalog, validateSpec } from './registry.js';
|
|
3
|
+
export { render } from './renderer.js';
|
|
4
|
+
export type { RenderOptions } from './renderer.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../a2ui/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../a2ui/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC","sourcesContent":["export type {\n CatalogComponentDef,\n CatalogSchema,\n ComponentSpec,\n EmittedEvent,\n EventBinding,\n PropSchema,\n RegisteredCatalog,\n} from './types.js';\nexport { registerCatalog, validateSpec } from './registry.js';\nexport { render } from './renderer.js';\nexport type { RenderOptions } from './renderer.js';\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CatalogSchema, ComponentSpec, RegisteredCatalog } from './types.js';
|
|
2
|
+
export declare function registerCatalog(schema: CatalogSchema): RegisteredCatalog;
|
|
3
|
+
export declare function validateSpec(spec: unknown, catalog: RegisteredCatalog, path?: string): asserts spec is ComponentSpec;
|
|
4
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../a2ui/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAc,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE9F,wBAAgB,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,iBAAiB,CAKxE;AA+FD,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,IAAI,SAAM,GAAG,OAAO,CAAC,IAAI,IAAI,aAAa,CA+EjH"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
export function registerCatalog(schema) {
|
|
2
|
+
const defs = schema.$defs?.components ?? {};
|
|
3
|
+
const components = new Map(Object.entries(defs));
|
|
4
|
+
const id = schema['x-a2ui']?.catalogId ?? schema.$id ?? 'unknown';
|
|
5
|
+
return { id, schema, components };
|
|
6
|
+
}
|
|
7
|
+
function validatePropValue(value, schema, path) {
|
|
8
|
+
if (value === undefined || value === null)
|
|
9
|
+
return;
|
|
10
|
+
if (!schema)
|
|
11
|
+
return;
|
|
12
|
+
if (schema.const !== undefined && value !== schema.const) {
|
|
13
|
+
throw new Error(`${path}: expected const ${JSON.stringify(schema.const)}, got ${JSON.stringify(value)}`);
|
|
14
|
+
}
|
|
15
|
+
if (schema.enum && !schema.enum.includes(value)) {
|
|
16
|
+
const allowed = schema.enum.map((v) => JSON.stringify(v)).join(', ');
|
|
17
|
+
throw new Error(`${path}: value ${JSON.stringify(value)} not in enum [${allowed}]`);
|
|
18
|
+
}
|
|
19
|
+
if (schema.oneOf && schema.oneOf.length) {
|
|
20
|
+
const errors = [];
|
|
21
|
+
for (const branch of schema.oneOf) {
|
|
22
|
+
try {
|
|
23
|
+
validatePropValue(value, branch, path);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
errors.push(e.message);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
throw new Error(`${path}: value ${JSON.stringify(value)} matched none of oneOf branches: ${errors.join(' | ')}`);
|
|
31
|
+
}
|
|
32
|
+
const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
|
|
33
|
+
if (types.length && !types.some((t) => matchesType(t, value))) {
|
|
34
|
+
throw new Error(`${path}: expected type ${types.join('|')}, got ${describeType(value)}`);
|
|
35
|
+
}
|
|
36
|
+
if (Array.isArray(value) && schema.items) {
|
|
37
|
+
value.forEach((item, i) => validatePropValue(item, schema.items, `${path}[${i}]`));
|
|
38
|
+
}
|
|
39
|
+
if (typeof value === 'object' &&
|
|
40
|
+
value !== null &&
|
|
41
|
+
!Array.isArray(value) &&
|
|
42
|
+
schema.properties) {
|
|
43
|
+
const obj = value;
|
|
44
|
+
if (schema.required) {
|
|
45
|
+
for (const req of schema.required) {
|
|
46
|
+
if (!(req in obj)) {
|
|
47
|
+
throw new Error(`${path}.${req}: required property missing`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
for (const [key, childVal] of Object.entries(obj)) {
|
|
52
|
+
const childSchema = schema.properties[key];
|
|
53
|
+
if (!childSchema) {
|
|
54
|
+
if (schema.additionalProperties === false) {
|
|
55
|
+
throw new Error(`${path}.${key}: unexpected property`);
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
validatePropValue(childVal, childSchema, `${path}.${key}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function matchesType(t, v) {
|
|
64
|
+
switch (t) {
|
|
65
|
+
case 'string':
|
|
66
|
+
return typeof v === 'string';
|
|
67
|
+
case 'number':
|
|
68
|
+
return typeof v === 'number' && Number.isFinite(v);
|
|
69
|
+
case 'integer':
|
|
70
|
+
return typeof v === 'number' && Number.isInteger(v);
|
|
71
|
+
case 'boolean':
|
|
72
|
+
return typeof v === 'boolean';
|
|
73
|
+
case 'array':
|
|
74
|
+
return Array.isArray(v);
|
|
75
|
+
case 'object':
|
|
76
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
77
|
+
case 'null':
|
|
78
|
+
return v === null;
|
|
79
|
+
default:
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function describeType(v) {
|
|
84
|
+
if (v === null)
|
|
85
|
+
return 'null';
|
|
86
|
+
if (Array.isArray(v))
|
|
87
|
+
return 'array';
|
|
88
|
+
return typeof v;
|
|
89
|
+
}
|
|
90
|
+
export function validateSpec(spec, catalog, path = '$') {
|
|
91
|
+
if (!spec || typeof spec !== 'object') {
|
|
92
|
+
throw new Error(`${path}: spec must be an object`);
|
|
93
|
+
}
|
|
94
|
+
const s = spec;
|
|
95
|
+
if (typeof s.component !== 'string') {
|
|
96
|
+
throw new Error(`${path}: spec.component must be a string`);
|
|
97
|
+
}
|
|
98
|
+
if (!catalog.components.has(s.component)) {
|
|
99
|
+
throw new Error(`${path}: component "${s.component}" is not registered in catalog "${catalog.id}"`);
|
|
100
|
+
}
|
|
101
|
+
const def = catalog.components.get(s.component);
|
|
102
|
+
const allowedProps = new Set(Object.keys(def.properties?.props?.properties ?? {}));
|
|
103
|
+
const hasChildren = def.properties?.children !== undefined;
|
|
104
|
+
const allowedSlots = def.properties?.slots
|
|
105
|
+
? new Set(Object.keys(def.properties.slots.properties ?? {}))
|
|
106
|
+
: null;
|
|
107
|
+
if (s.props) {
|
|
108
|
+
if (typeof s.props !== 'object')
|
|
109
|
+
throw new Error(`${path}.props: must be an object`);
|
|
110
|
+
const propDefs = def.properties?.props?.properties ?? {};
|
|
111
|
+
for (const [prop, value] of Object.entries(s.props)) {
|
|
112
|
+
if (!allowedProps.has(prop)) {
|
|
113
|
+
throw new Error(`${path}.props.${prop}: not a declared prop on ${s.component}`);
|
|
114
|
+
}
|
|
115
|
+
validatePropValue(value, propDefs[prop], `${path}.props.${prop}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (s.children !== undefined) {
|
|
119
|
+
if (!hasChildren) {
|
|
120
|
+
throw new Error(`${path}.children: ${s.component} does not accept default children`);
|
|
121
|
+
}
|
|
122
|
+
if (!Array.isArray(s.children))
|
|
123
|
+
throw new Error(`${path}.children: must be an array`);
|
|
124
|
+
s.children.forEach((c, i) => {
|
|
125
|
+
if (typeof c === 'string')
|
|
126
|
+
return;
|
|
127
|
+
validateSpec(c, catalog, `${path}.children[${i}]`);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (s.events !== undefined) {
|
|
131
|
+
if (!s.events || typeof s.events !== 'object' || Array.isArray(s.events)) {
|
|
132
|
+
throw new Error(`${path}.events: must be an object`);
|
|
133
|
+
}
|
|
134
|
+
for (const [evtName, binding] of Object.entries(s.events)) {
|
|
135
|
+
if (typeof binding === 'string')
|
|
136
|
+
continue;
|
|
137
|
+
if (!binding || typeof binding !== 'object') {
|
|
138
|
+
throw new Error(`${path}.events.${evtName}: must be a string or { handler } object`);
|
|
139
|
+
}
|
|
140
|
+
const b = binding;
|
|
141
|
+
if (typeof b.handler !== 'string' || b.handler.length === 0) {
|
|
142
|
+
throw new Error(`${path}.events.${evtName}.handler: must be a non-empty string`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (s.slots !== undefined) {
|
|
147
|
+
if (!allowedSlots) {
|
|
148
|
+
throw new Error(`${path}.slots: ${s.component} does not accept named slots`);
|
|
149
|
+
}
|
|
150
|
+
if (typeof s.slots !== 'object')
|
|
151
|
+
throw new Error(`${path}.slots: must be an object`);
|
|
152
|
+
for (const [slotName, arr] of Object.entries(s.slots)) {
|
|
153
|
+
if (!allowedSlots.has(slotName)) {
|
|
154
|
+
throw new Error(`${path}.slots.${slotName}: not a declared slot on ${s.component}`);
|
|
155
|
+
}
|
|
156
|
+
if (!Array.isArray(arr)) {
|
|
157
|
+
throw new Error(`${path}.slots.${slotName}: must be an array`);
|
|
158
|
+
}
|
|
159
|
+
arr.forEach((c, i) => {
|
|
160
|
+
if (typeof c === 'string')
|
|
161
|
+
return;
|
|
162
|
+
validateSpec(c, catalog, `${path}.slots.${slotName}[${i}]`);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../a2ui/registry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CatalogSchema, ComponentSpec, PropSchema, RegisteredCatalog } from './types.js';\n\nexport function registerCatalog(schema: CatalogSchema): RegisteredCatalog {\n const defs = schema.$defs?.components ?? {};\n const components = new Map(Object.entries(defs));\n const id = schema['x-a2ui']?.catalogId ?? schema.$id ?? 'unknown';\n return { id, schema, components };\n}\n\nfunction validatePropValue(value: unknown, schema: PropSchema | undefined, path: string): void {\n if (value === undefined || value === null) return;\n if (!schema) return;\n\n if (schema.const !== undefined && value !== schema.const) {\n throw new Error(\n `${path}: expected const ${JSON.stringify(schema.const)}, got ${JSON.stringify(value)}`\n );\n }\n\n if (schema.enum && !schema.enum.includes(value as string)) {\n const allowed = schema.enum.map((v) => JSON.stringify(v)).join(', ');\n throw new Error(`${path}: value ${JSON.stringify(value)} not in enum [${allowed}]`);\n }\n\n if (schema.oneOf && schema.oneOf.length) {\n const errors: string[] = [];\n for (const branch of schema.oneOf) {\n try {\n validatePropValue(value, branch, path);\n return;\n } catch (e) {\n errors.push((e as Error).message);\n }\n }\n throw new Error(\n `${path}: value ${JSON.stringify(value)} matched none of oneOf branches: ${errors.join(' | ')}`\n );\n }\n\n const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];\n if (types.length && !types.some((t) => matchesType(t, value))) {\n throw new Error(`${path}: expected type ${types.join('|')}, got ${describeType(value)}`);\n }\n\n if (Array.isArray(value) && schema.items) {\n value.forEach((item, i) => validatePropValue(item, schema.items, `${path}[${i}]`));\n }\n\n if (\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value) &&\n schema.properties\n ) {\n const obj = value as Record<string, unknown>;\n if (schema.required) {\n for (const req of schema.required) {\n if (!(req in obj)) {\n throw new Error(`${path}.${req}: required property missing`);\n }\n }\n }\n for (const [key, childVal] of Object.entries(obj)) {\n const childSchema = schema.properties[key];\n if (!childSchema) {\n if (schema.additionalProperties === false) {\n throw new Error(`${path}.${key}: unexpected property`);\n }\n continue;\n }\n validatePropValue(childVal, childSchema, `${path}.${key}`);\n }\n }\n}\n\nfunction matchesType(t: string, v: unknown): boolean {\n switch (t) {\n case 'string':\n return typeof v === 'string';\n case 'number':\n return typeof v === 'number' && Number.isFinite(v);\n case 'integer':\n return typeof v === 'number' && Number.isInteger(v);\n case 'boolean':\n return typeof v === 'boolean';\n case 'array':\n return Array.isArray(v);\n case 'object':\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n case 'null':\n return v === null;\n default:\n return true;\n }\n}\n\nfunction describeType(v: unknown): string {\n if (v === null) return 'null';\n if (Array.isArray(v)) return 'array';\n return typeof v;\n}\n\nexport function validateSpec(spec: unknown, catalog: RegisteredCatalog, path = '$'): asserts spec is ComponentSpec {\n if (!spec || typeof spec !== 'object') {\n throw new Error(`${path}: spec must be an object`);\n }\n const s = spec as Record<string, unknown>;\n if (typeof s.component !== 'string') {\n throw new Error(`${path}: spec.component must be a string`);\n }\n if (!catalog.components.has(s.component)) {\n throw new Error(\n `${path}: component \"${s.component}\" is not registered in catalog \"${catalog.id}\"`\n );\n }\n\n const def = catalog.components.get(s.component)!;\n const allowedProps = new Set(Object.keys(def.properties?.props?.properties ?? {}));\n const hasChildren = def.properties?.children !== undefined;\n const allowedSlots = def.properties?.slots\n ? new Set(Object.keys(def.properties.slots.properties ?? {}))\n : null;\n\n if (s.props) {\n if (typeof s.props !== 'object') throw new Error(`${path}.props: must be an object`);\n const propDefs = def.properties?.props?.properties ?? {};\n for (const [prop, value] of Object.entries(s.props as Record<string, unknown>)) {\n if (!allowedProps.has(prop)) {\n throw new Error(`${path}.props.${prop}: not a declared prop on ${s.component}`);\n }\n validatePropValue(value, propDefs[prop], `${path}.props.${prop}`);\n }\n }\n\n if (s.children !== undefined) {\n if (!hasChildren) {\n throw new Error(`${path}.children: ${s.component} does not accept default children`);\n }\n if (!Array.isArray(s.children)) throw new Error(`${path}.children: must be an array`);\n s.children.forEach((c, i) => {\n if (typeof c === 'string') return;\n validateSpec(c, catalog, `${path}.children[${i}]`);\n });\n }\n\n if (s.events !== undefined) {\n if (!s.events || typeof s.events !== 'object' || Array.isArray(s.events)) {\n throw new Error(`${path}.events: must be an object`);\n }\n for (const [evtName, binding] of Object.entries(s.events as Record<string, unknown>)) {\n if (typeof binding === 'string') continue;\n if (!binding || typeof binding !== 'object') {\n throw new Error(`${path}.events.${evtName}: must be a string or { handler } object`);\n }\n const b = binding as Record<string, unknown>;\n if (typeof b.handler !== 'string' || b.handler.length === 0) {\n throw new Error(`${path}.events.${evtName}.handler: must be a non-empty string`);\n }\n }\n }\n\n if (s.slots !== undefined) {\n if (!allowedSlots) {\n throw new Error(`${path}.slots: ${s.component} does not accept named slots`);\n }\n if (typeof s.slots !== 'object') throw new Error(`${path}.slots: must be an object`);\n for (const [slotName, arr] of Object.entries(s.slots as Record<string, unknown>)) {\n if (!allowedSlots.has(slotName)) {\n throw new Error(\n `${path}.slots.${slotName}: not a declared slot on ${s.component}`\n );\n }\n if (!Array.isArray(arr)) {\n throw new Error(`${path}.slots.${slotName}: must be an array`);\n }\n arr.forEach((c, i) => {\n if (typeof c === 'string') return;\n validateSpec(c, catalog, `${path}.slots.${slotName}[${i}]`);\n });\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ComponentSpec, EmittedEvent, RegisteredCatalog } from './types.js';
|
|
2
|
+
export interface RenderOptions {
|
|
3
|
+
root?: HTMLElement;
|
|
4
|
+
doc?: Document;
|
|
5
|
+
onEvent?: (evt: EmittedEvent) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function render(spec: ComponentSpec, catalog: RegisteredCatalog, options?: RenderOptions): HTMLElement;
|
|
8
|
+
//# sourceMappingURL=renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../a2ui/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EAGZ,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,GAAG,CAAC,EAAE,QAAQ,CAAC;IACf,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,CAAC;CACvC;AAED,wBAAgB,MAAM,CACpB,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,iBAAiB,EAC1B,OAAO,GAAE,aAAkB,GAC1B,WAAW,CAMb"}
|