@tmorrow/cre8-wc 2.0.2 → 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/cdn/cre8-wc.esm.js +3363 -2861
- package/cdn/cre8-wc.esm.js.map +1 -1
- package/cdn/cre8-wc.min.js +765 -262
- package/cdn/cre8-wc.min.js.map +1 -1
- 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/lib/components/icon/icon.d.ts +2 -1
- package/lib/components/icon/icon.d.ts.map +1 -1
- package/lib/components/icon/icon.js +56 -55
- package/lib/components/icon/icon.js.map +1 -1
- package/lib/vite.config.cdn.js +1 -1
- package/lib/vite.config.cdn.js.map +1 -1
- package/lib/vite.config.js +1 -1
- package/lib/vite.config.js.map +1 -1
- package/mcp-manifest.json +2 -2
- package/package.json +15 -1
package/a2ui/demo.html
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>cre8-wc A2UI renderer demo</title>
|
|
7
|
+
<link
|
|
8
|
+
rel="stylesheet"
|
|
9
|
+
href="../design-tokens/brands/cre8-a2ui/css/tokens_cre8-a2ui.css"
|
|
10
|
+
/>
|
|
11
|
+
<style>
|
|
12
|
+
html,
|
|
13
|
+
body {
|
|
14
|
+
margin: 0;
|
|
15
|
+
font-family:
|
|
16
|
+
system-ui,
|
|
17
|
+
-apple-system,
|
|
18
|
+
sans-serif;
|
|
19
|
+
height: 100vh;
|
|
20
|
+
}
|
|
21
|
+
body {
|
|
22
|
+
display: grid;
|
|
23
|
+
grid-template-rows: auto 1fr;
|
|
24
|
+
grid-template-columns: 1fr;
|
|
25
|
+
}
|
|
26
|
+
header {
|
|
27
|
+
padding: 12px 20px;
|
|
28
|
+
border-bottom: 1px solid #e1e4e8;
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: baseline;
|
|
31
|
+
gap: 16px;
|
|
32
|
+
}
|
|
33
|
+
header h1 {
|
|
34
|
+
font-size: 16px;
|
|
35
|
+
margin: 0;
|
|
36
|
+
}
|
|
37
|
+
header .status {
|
|
38
|
+
font-size: 12px;
|
|
39
|
+
color: #6a737d;
|
|
40
|
+
}
|
|
41
|
+
main {
|
|
42
|
+
display: grid;
|
|
43
|
+
grid-template-columns: 1fr 1fr 1fr;
|
|
44
|
+
overflow: hidden;
|
|
45
|
+
}
|
|
46
|
+
.panel {
|
|
47
|
+
display: flex;
|
|
48
|
+
flex-direction: column;
|
|
49
|
+
border-right: 1px solid #e1e4e8;
|
|
50
|
+
overflow: hidden;
|
|
51
|
+
}
|
|
52
|
+
.panel:last-child {
|
|
53
|
+
border-right: none;
|
|
54
|
+
}
|
|
55
|
+
.panel h2 {
|
|
56
|
+
margin: 0;
|
|
57
|
+
padding: 10px 16px;
|
|
58
|
+
font-size: 12px;
|
|
59
|
+
font-weight: 600;
|
|
60
|
+
text-transform: uppercase;
|
|
61
|
+
letter-spacing: 0.5px;
|
|
62
|
+
color: #586069;
|
|
63
|
+
background: #f6f8fa;
|
|
64
|
+
border-bottom: 1px solid #e1e4e8;
|
|
65
|
+
}
|
|
66
|
+
.panel-body {
|
|
67
|
+
flex: 1;
|
|
68
|
+
padding: 16px;
|
|
69
|
+
overflow: auto;
|
|
70
|
+
}
|
|
71
|
+
textarea {
|
|
72
|
+
width: 100%;
|
|
73
|
+
height: 100%;
|
|
74
|
+
border: 1px solid #d1d5da;
|
|
75
|
+
border-radius: 4px;
|
|
76
|
+
padding: 10px;
|
|
77
|
+
font-family: ui-monospace, 'SF Mono', monospace;
|
|
78
|
+
font-size: 12px;
|
|
79
|
+
line-height: 1.5;
|
|
80
|
+
resize: none;
|
|
81
|
+
box-sizing: border-box;
|
|
82
|
+
}
|
|
83
|
+
button {
|
|
84
|
+
padding: 6px 14px;
|
|
85
|
+
font-size: 13px;
|
|
86
|
+
border: 1px solid #d1d5da;
|
|
87
|
+
background: #fafbfc;
|
|
88
|
+
border-radius: 4px;
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
}
|
|
91
|
+
button:hover {
|
|
92
|
+
background: #f3f4f6;
|
|
93
|
+
}
|
|
94
|
+
.toolbar {
|
|
95
|
+
padding: 8px 16px;
|
|
96
|
+
display: flex;
|
|
97
|
+
gap: 8px;
|
|
98
|
+
border-bottom: 1px solid #e1e4e8;
|
|
99
|
+
}
|
|
100
|
+
.error {
|
|
101
|
+
color: #d73a49;
|
|
102
|
+
font-size: 12px;
|
|
103
|
+
padding: 8px 16px;
|
|
104
|
+
background: #ffeef0;
|
|
105
|
+
border-bottom: 1px solid #f97583;
|
|
106
|
+
white-space: pre-wrap;
|
|
107
|
+
font-family: ui-monospace, monospace;
|
|
108
|
+
display: none;
|
|
109
|
+
}
|
|
110
|
+
.error.visible {
|
|
111
|
+
display: block;
|
|
112
|
+
}
|
|
113
|
+
#event-log {
|
|
114
|
+
font-family: ui-monospace, monospace;
|
|
115
|
+
font-size: 12px;
|
|
116
|
+
line-height: 1.5;
|
|
117
|
+
}
|
|
118
|
+
.event-entry {
|
|
119
|
+
padding: 6px 0;
|
|
120
|
+
border-bottom: 1px dashed #e1e4e8;
|
|
121
|
+
}
|
|
122
|
+
.event-entry .evt-name {
|
|
123
|
+
color: #0366d6;
|
|
124
|
+
font-weight: 600;
|
|
125
|
+
}
|
|
126
|
+
.event-entry .evt-handler {
|
|
127
|
+
color: #22863a;
|
|
128
|
+
}
|
|
129
|
+
.event-entry .evt-path {
|
|
130
|
+
color: #6a737d;
|
|
131
|
+
}
|
|
132
|
+
.event-entry .evt-detail {
|
|
133
|
+
color: #24292e;
|
|
134
|
+
}
|
|
135
|
+
#output {
|
|
136
|
+
display: flex;
|
|
137
|
+
flex-direction: column;
|
|
138
|
+
gap: 16px;
|
|
139
|
+
}
|
|
140
|
+
</style>
|
|
141
|
+
</head>
|
|
142
|
+
<body>
|
|
143
|
+
<header>
|
|
144
|
+
<h1>cre8-wc A2UI renderer demo</h1>
|
|
145
|
+
<span class="status" id="status">loading…</span>
|
|
146
|
+
</header>
|
|
147
|
+
<main>
|
|
148
|
+
<section class="panel">
|
|
149
|
+
<h2>Spec (JSON)</h2>
|
|
150
|
+
<div class="toolbar">
|
|
151
|
+
<button id="render-btn">Render →</button>
|
|
152
|
+
<button id="reset-btn">Reset</button>
|
|
153
|
+
</div>
|
|
154
|
+
<div class="error" id="error"></div>
|
|
155
|
+
<div class="panel-body">
|
|
156
|
+
<textarea id="spec" spellcheck="false"></textarea>
|
|
157
|
+
</div>
|
|
158
|
+
</section>
|
|
159
|
+
<section class="panel">
|
|
160
|
+
<h2>Rendered output</h2>
|
|
161
|
+
<div class="panel-body" id="output"></div>
|
|
162
|
+
</section>
|
|
163
|
+
<section class="panel">
|
|
164
|
+
<h2>Events (onEvent callback)</h2>
|
|
165
|
+
<div class="panel-body" id="event-log">
|
|
166
|
+
<em style="color: #6a737d; font-size: 12px">
|
|
167
|
+
Interact with the rendered components to emit events.
|
|
168
|
+
</em>
|
|
169
|
+
</div>
|
|
170
|
+
</section>
|
|
171
|
+
</main>
|
|
172
|
+
|
|
173
|
+
<script type="module" src="../cdn/cre8-wc.esm.js"></script>
|
|
174
|
+
|
|
175
|
+
<script type="module">
|
|
176
|
+
import { registerCatalog, render } from './index.js';
|
|
177
|
+
|
|
178
|
+
const DEFAULT_SPEC = {
|
|
179
|
+
component: 'cre8-card',
|
|
180
|
+
slots: {
|
|
181
|
+
header: [
|
|
182
|
+
{
|
|
183
|
+
component: 'cre8-heading',
|
|
184
|
+
props: { type: 'headline-default', tagVariant: 'h2' },
|
|
185
|
+
children: ['Card heading'],
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
default: [
|
|
189
|
+
{
|
|
190
|
+
component: 'cre8-alert',
|
|
191
|
+
props: { status: 'info', headerText: 'Heads up', ctaBody: 'Review your settings before saving.' },
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
component: 'cre8-button-group',
|
|
195
|
+
children: [
|
|
196
|
+
{
|
|
197
|
+
component: 'cre8-button',
|
|
198
|
+
props: { text: 'Save', variant: 'primary' },
|
|
199
|
+
events: { click: 'save' },
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
component: 'cre8-button',
|
|
203
|
+
props: { text: 'Cancel', variant: 'tertiary' },
|
|
204
|
+
events: { click: 'cancel' },
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const $status = document.getElementById('status');
|
|
213
|
+
const $spec = document.getElementById('spec');
|
|
214
|
+
const $output = document.getElementById('output');
|
|
215
|
+
const $log = document.getElementById('event-log');
|
|
216
|
+
const $error = document.getElementById('error');
|
|
217
|
+
const $renderBtn = document.getElementById('render-btn');
|
|
218
|
+
const $resetBtn = document.getElementById('reset-btn');
|
|
219
|
+
|
|
220
|
+
$spec.value = JSON.stringify(DEFAULT_SPEC, null, 2);
|
|
221
|
+
|
|
222
|
+
let catalog;
|
|
223
|
+
try {
|
|
224
|
+
const schema = await fetch('./catalog.json').then((r) => r.json());
|
|
225
|
+
catalog = registerCatalog(schema);
|
|
226
|
+
$status.textContent = `catalog "${catalog.id}" · ${catalog.components.size} components`;
|
|
227
|
+
} catch (e) {
|
|
228
|
+
$status.textContent = 'failed to load catalog';
|
|
229
|
+
showError(e.message);
|
|
230
|
+
throw e;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function showError(msg) {
|
|
234
|
+
$error.textContent = msg;
|
|
235
|
+
$error.classList.add('visible');
|
|
236
|
+
}
|
|
237
|
+
function clearError() {
|
|
238
|
+
$error.textContent = '';
|
|
239
|
+
$error.classList.remove('visible');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function logEvent(e) {
|
|
243
|
+
const placeholder = $log.querySelector('em');
|
|
244
|
+
if (placeholder) placeholder.remove();
|
|
245
|
+
const entry = document.createElement('div');
|
|
246
|
+
entry.className = 'event-entry';
|
|
247
|
+
entry.innerHTML = `
|
|
248
|
+
<div><span class="evt-name">${escape(e.event)}</span> → <span class="evt-handler">${escape(e.handler)}</span></div>
|
|
249
|
+
<div class="evt-path">${escape(e.path)} <em>(${escape(e.component)})</em></div>
|
|
250
|
+
${e.detail ? `<div class="evt-detail">detail: ${escape(JSON.stringify(e.detail))}</div>` : ''}
|
|
251
|
+
`;
|
|
252
|
+
$log.prepend(entry);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function escape(s) {
|
|
256
|
+
return String(s).replace(
|
|
257
|
+
/[&<>"']/g,
|
|
258
|
+
(c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function doRender() {
|
|
263
|
+
clearError();
|
|
264
|
+
let spec;
|
|
265
|
+
try {
|
|
266
|
+
spec = JSON.parse($spec.value);
|
|
267
|
+
} catch (e) {
|
|
268
|
+
showError('JSON parse error: ' + e.message);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
render(spec, catalog, { root: $output, onEvent: logEvent });
|
|
273
|
+
} catch (e) {
|
|
274
|
+
showError(e.message);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
$renderBtn.addEventListener('click', doRender);
|
|
279
|
+
$resetBtn.addEventListener('click', () => {
|
|
280
|
+
$spec.value = JSON.stringify(DEFAULT_SPEC, null, 2);
|
|
281
|
+
doRender();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
doRender();
|
|
285
|
+
</script>
|
|
286
|
+
</body>
|
|
287
|
+
</html>
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { dirname, resolve } from 'node:path';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const manifestPath = resolve(__dirname, '..', 'mcp-manifest.json');
|
|
8
|
+
const outPath = resolve(__dirname, 'catalog.json');
|
|
9
|
+
|
|
10
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
11
|
+
|
|
12
|
+
const QUOTED_LITERAL = /^"([^"]*)"$/;
|
|
13
|
+
|
|
14
|
+
const SELECT_OPTION_SCHEMA = {
|
|
15
|
+
type: 'object',
|
|
16
|
+
required: ['label', 'value'],
|
|
17
|
+
additionalProperties: false,
|
|
18
|
+
properties: {
|
|
19
|
+
label: { type: 'string' },
|
|
20
|
+
value: { type: ['string', 'number'] },
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const SELECT_OPTION_GROUP_SCHEMA = {
|
|
25
|
+
type: 'object',
|
|
26
|
+
required: ['optGroupLabel', 'options'],
|
|
27
|
+
additionalProperties: false,
|
|
28
|
+
properties: {
|
|
29
|
+
optGroupLabel: { type: 'string' },
|
|
30
|
+
options: { type: 'array', items: SELECT_OPTION_SCHEMA },
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const TS_TYPE_RESOLVERS = {
|
|
35
|
+
Cre8ChartType: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
enum: ['line', 'bar', 'pie', 'doughnut', 'radar', 'polarArea', 'bubble', 'scatter'],
|
|
38
|
+
},
|
|
39
|
+
Color: { type: 'string', enum: ['neutral', 'branded', 'neutral-hybrid'] },
|
|
40
|
+
Shape: { type: 'string', enum: ['round', 'square'] },
|
|
41
|
+
status: { type: 'string', enum: ['error', 'warning', 'success'] },
|
|
42
|
+
'(Cre8SelectOption | Cre8SelectOptionGroup)[]': {
|
|
43
|
+
type: 'array',
|
|
44
|
+
items: { oneOf: [SELECT_OPTION_SCHEMA, SELECT_OPTION_GROUP_SCHEMA] },
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const PROP_OVERRIDES = {
|
|
49
|
+
'cre8-heading.type': {
|
|
50
|
+
type: 'string',
|
|
51
|
+
enum: [
|
|
52
|
+
'display-default',
|
|
53
|
+
'display-small',
|
|
54
|
+
'headline-large',
|
|
55
|
+
'headline-default',
|
|
56
|
+
'headline-small',
|
|
57
|
+
'title-xlarge',
|
|
58
|
+
'title-large',
|
|
59
|
+
'title-default',
|
|
60
|
+
'title-small',
|
|
61
|
+
'label-large',
|
|
62
|
+
'label',
|
|
63
|
+
'label-small',
|
|
64
|
+
'meta-large',
|
|
65
|
+
'meta-default',
|
|
66
|
+
'meta-small',
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
function mapAttr(attr, componentName, propName, kind) {
|
|
72
|
+
const overrideKey = `${componentName}.${propName}`;
|
|
73
|
+
let out;
|
|
74
|
+
if (PROP_OVERRIDES[overrideKey]) {
|
|
75
|
+
out = { ...PROP_OVERRIDES[overrideKey] };
|
|
76
|
+
if (attr.description) out.description = attr.description.trim();
|
|
77
|
+
if (attr.default !== undefined) out.default = attr.default;
|
|
78
|
+
} else if (typeof attr.type === 'string' && TS_TYPE_RESOLVERS[attr.type]) {
|
|
79
|
+
out = { ...TS_TYPE_RESOLVERS[attr.type] };
|
|
80
|
+
if (attr.description) out.description = attr.description.trim();
|
|
81
|
+
if (attr.default !== undefined) out.default = attr.default;
|
|
82
|
+
} else {
|
|
83
|
+
out = mapAttrPlain(attr);
|
|
84
|
+
}
|
|
85
|
+
out['x-kind'] = kind;
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function mapAttrPlain(attr) {
|
|
90
|
+
const schema = {};
|
|
91
|
+
if (attr.description) schema.description = attr.description.trim();
|
|
92
|
+
if (attr.default !== undefined) schema.default = attr.default;
|
|
93
|
+
|
|
94
|
+
if (Array.isArray(attr.values) && attr.values.length) {
|
|
95
|
+
schema.type = 'string';
|
|
96
|
+
schema.enum = attr.values;
|
|
97
|
+
return schema;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const t = attr.type;
|
|
101
|
+
if (t === 'boolean') { schema.type = 'boolean'; return schema; }
|
|
102
|
+
if (t === 'number') { schema.type = 'number'; return schema; }
|
|
103
|
+
if (t === 'string') { schema.type = 'string'; return schema; }
|
|
104
|
+
if (t === 'string[]') { schema.type = 'array'; schema.items = { type: 'string' }; return schema; }
|
|
105
|
+
if (t === 'string | number') { schema.type = ['string', 'number']; return schema; }
|
|
106
|
+
|
|
107
|
+
const lit = typeof t === 'string' ? t.match(QUOTED_LITERAL) : null;
|
|
108
|
+
if (lit) { schema.type = 'string'; schema.const = lit[1]; return schema; }
|
|
109
|
+
|
|
110
|
+
if (typeof t === 'string' && t.includes('|')) {
|
|
111
|
+
const parts = t.split('|').map((s) => s.trim());
|
|
112
|
+
const literals = parts.map((p) => p.match(QUOTED_LITERAL)?.[1]).filter(Boolean);
|
|
113
|
+
if (literals.length === parts.length) {
|
|
114
|
+
schema.type = 'string';
|
|
115
|
+
schema.enum = literals;
|
|
116
|
+
return schema;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
schema.type = 'string';
|
|
121
|
+
schema['x-tsType'] = t;
|
|
122
|
+
return schema;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const SLOT_OVERRIDES = {
|
|
126
|
+
'cre8-card': { body: 'default' },
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
function normalizeSlotName(name, componentName) {
|
|
130
|
+
const unquoted = typeof name === 'string' ? name.replace(/^"|"$/g, '') : name;
|
|
131
|
+
const base = unquoted === '' ? 'default' : unquoted;
|
|
132
|
+
const override = componentName && SLOT_OVERRIDES[componentName]?.[base];
|
|
133
|
+
return override ?? base;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function buildComponent(c) {
|
|
137
|
+
const attrs = c.attributes || {};
|
|
138
|
+
const propsSchema = {
|
|
139
|
+
type: 'object',
|
|
140
|
+
additionalProperties: false,
|
|
141
|
+
properties: {},
|
|
142
|
+
};
|
|
143
|
+
for (const [name, attr] of Object.entries(attrs)) {
|
|
144
|
+
propsSchema.properties[name] = mapAttr(attr, c.name, name, 'attribute');
|
|
145
|
+
}
|
|
146
|
+
for (const [name, attr] of Object.entries(c.properties || {})) {
|
|
147
|
+
if (propsSchema.properties[name]) continue;
|
|
148
|
+
propsSchema.properties[name] = mapAttr(attr, c.name, name, 'property');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const rawSlots = c.slots || {};
|
|
152
|
+
const slotNames = Object.keys(rawSlots).map((n) => normalizeSlotName(n, c.name));
|
|
153
|
+
const hasSlots = slotNames.length > 0;
|
|
154
|
+
const onlyDefault = slotNames.length === 1 && slotNames[0] === 'default';
|
|
155
|
+
|
|
156
|
+
const events = {};
|
|
157
|
+
for (const [name, evt] of Object.entries(c.events || {})) {
|
|
158
|
+
events[name] = { detail: evt.detail || {} };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const def = {
|
|
162
|
+
type: 'object',
|
|
163
|
+
title: c.name,
|
|
164
|
+
'x-category': c.category,
|
|
165
|
+
description: (c.description || '').trim(),
|
|
166
|
+
required: ['component'],
|
|
167
|
+
properties: {
|
|
168
|
+
component: { const: c.name, description: 'Component tag name' },
|
|
169
|
+
props: propsSchema,
|
|
170
|
+
},
|
|
171
|
+
additionalProperties: false,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
if (onlyDefault) {
|
|
175
|
+
def.properties.children = {
|
|
176
|
+
type: 'array',
|
|
177
|
+
description: (rawSlots.default?.description || '').trim() || 'Child instances rendered into the default slot.',
|
|
178
|
+
items: { $ref: '#/$defs/Component' },
|
|
179
|
+
};
|
|
180
|
+
} else if (hasSlots) {
|
|
181
|
+
const slotProps = {};
|
|
182
|
+
const slotDescriptions = {};
|
|
183
|
+
for (const [rawName, slot] of Object.entries(rawSlots)) {
|
|
184
|
+
const name = normalizeSlotName(rawName, c.name);
|
|
185
|
+
slotProps[name] = {
|
|
186
|
+
type: 'array',
|
|
187
|
+
description: (slot.description || '').trim(),
|
|
188
|
+
items: { $ref: '#/$defs/Component' },
|
|
189
|
+
};
|
|
190
|
+
slotDescriptions[name] = (slot.description || '').trim();
|
|
191
|
+
}
|
|
192
|
+
def.properties.slots = {
|
|
193
|
+
type: 'object',
|
|
194
|
+
description: 'Named slot content. Each key is a slot name; value is an array of component instances rendered into that slot.',
|
|
195
|
+
additionalProperties: false,
|
|
196
|
+
properties: slotProps,
|
|
197
|
+
};
|
|
198
|
+
def['x-slot-descriptions'] = slotDescriptions;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (Object.keys(events).length) def['x-events'] = events;
|
|
202
|
+
|
|
203
|
+
return def;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const components = {};
|
|
207
|
+
const componentRefs = [];
|
|
208
|
+
|
|
209
|
+
for (const c of manifest.components) {
|
|
210
|
+
components[c.name] = buildComponent(c);
|
|
211
|
+
componentRefs.push({ $ref: `#/$defs/components/${c.name}` });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const catalog = {
|
|
215
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
216
|
+
$id: `https://cre8.dev/a2ui/catalogs/cre8-wc/${manifest.version}`,
|
|
217
|
+
title: 'cre8-wc A2UI Catalog',
|
|
218
|
+
description: manifest.description,
|
|
219
|
+
'x-a2ui': {
|
|
220
|
+
catalogId: 'cre8-wc',
|
|
221
|
+
library: manifest.library,
|
|
222
|
+
libraryVersion: manifest.version,
|
|
223
|
+
tagPrefix: manifest.tagPrefix,
|
|
224
|
+
framework: manifest.framework,
|
|
225
|
+
},
|
|
226
|
+
type: 'object',
|
|
227
|
+
required: ['root'],
|
|
228
|
+
properties: {
|
|
229
|
+
root: { $ref: '#/$defs/Component' },
|
|
230
|
+
},
|
|
231
|
+
$defs: {
|
|
232
|
+
Component: {
|
|
233
|
+
description: 'A component instance in the cre8-wc catalog.',
|
|
234
|
+
oneOf: componentRefs,
|
|
235
|
+
},
|
|
236
|
+
components,
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
writeFileSync(outPath, JSON.stringify(catalog, null, 2) + '\n');
|
|
241
|
+
console.log(
|
|
242
|
+
`Wrote ${outPath} (${manifest.components.length} components, ${(JSON.stringify(catalog).length / 1024).toFixed(1)} KB)`
|
|
243
|
+
);
|
package/a2ui/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
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';
|
package/a2ui/index.js
ADDED
package/a2ui/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
CatalogComponentDef,
|
|
3
|
+
CatalogSchema,
|
|
4
|
+
ComponentSpec,
|
|
5
|
+
EmittedEvent,
|
|
6
|
+
EventBinding,
|
|
7
|
+
PropSchema,
|
|
8
|
+
RegisteredCatalog,
|
|
9
|
+
} from './types.js';
|
|
10
|
+
export { registerCatalog, validateSpec } from './registry.js';
|
|
11
|
+
export { render } from './renderer.js';
|
|
12
|
+
export type { RenderOptions } from './renderer.js';
|
|
@@ -0,0 +1,3 @@
|
|
|
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;
|