@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.
Files changed (46) hide show
  1. package/a2ui/catalog.json +5622 -0
  2. package/a2ui/demo.html +287 -0
  3. package/a2ui/generate-catalog.mjs +243 -0
  4. package/a2ui/index.d.ts +4 -0
  5. package/a2ui/index.js +2 -0
  6. package/a2ui/index.ts +12 -0
  7. package/a2ui/registry.d.ts +3 -0
  8. package/a2ui/registry.js +166 -0
  9. package/a2ui/registry.ts +182 -0
  10. package/a2ui/renderer.d.ts +7 -0
  11. package/a2ui/renderer.js +108 -0
  12. package/a2ui/renderer.ts +156 -0
  13. package/a2ui/smoke-test.mjs +238 -0
  14. package/a2ui/types.d.ts +75 -0
  15. package/a2ui/types.js +1 -0
  16. package/a2ui/types.ts +80 -0
  17. package/cdn/cre8-wc.esm.js +3363 -2861
  18. package/cdn/cre8-wc.esm.js.map +1 -1
  19. package/cdn/cre8-wc.min.js +765 -262
  20. package/cdn/cre8-wc.min.js.map +1 -1
  21. package/lib/a2ui/index.d.ts +5 -0
  22. package/lib/a2ui/index.d.ts.map +1 -0
  23. package/lib/a2ui/index.js +3 -0
  24. package/lib/a2ui/index.js.map +1 -0
  25. package/lib/a2ui/registry.d.ts +4 -0
  26. package/lib/a2ui/registry.d.ts.map +1 -0
  27. package/lib/a2ui/registry.js +167 -0
  28. package/lib/a2ui/registry.js.map +1 -0
  29. package/lib/a2ui/renderer.d.ts +8 -0
  30. package/lib/a2ui/renderer.d.ts.map +1 -0
  31. package/lib/a2ui/renderer.js +109 -0
  32. package/lib/a2ui/renderer.js.map +1 -0
  33. package/lib/a2ui/types.d.ts +76 -0
  34. package/lib/a2ui/types.d.ts.map +1 -0
  35. package/lib/a2ui/types.js +2 -0
  36. package/lib/a2ui/types.js.map +1 -0
  37. package/lib/components/icon/icon.d.ts +2 -1
  38. package/lib/components/icon/icon.d.ts.map +1 -1
  39. package/lib/components/icon/icon.js +56 -55
  40. package/lib/components/icon/icon.js.map +1 -1
  41. package/lib/vite.config.cdn.js +1 -1
  42. package/lib/vite.config.cdn.js.map +1 -1
  43. package/lib/vite.config.js +1 -1
  44. package/lib/vite.config.js.map +1 -1
  45. package/mcp-manifest.json +2 -2
  46. package/package.json +15 -1
@@ -0,0 +1,166 @@
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
+ }
@@ -0,0 +1,182 @@
1
+ import type { CatalogSchema, ComponentSpec, PropSchema, RegisteredCatalog } from './types.js';
2
+
3
+ export function registerCatalog(schema: CatalogSchema): RegisteredCatalog {
4
+ const defs = schema.$defs?.components ?? {};
5
+ const components = new Map(Object.entries(defs));
6
+ const id = schema['x-a2ui']?.catalogId ?? schema.$id ?? 'unknown';
7
+ return { id, schema, components };
8
+ }
9
+
10
+ function validatePropValue(value: unknown, schema: PropSchema | undefined, path: string): void {
11
+ if (value === undefined || value === null) return;
12
+ if (!schema) return;
13
+
14
+ if (schema.const !== undefined && value !== schema.const) {
15
+ throw new Error(
16
+ `${path}: expected const ${JSON.stringify(schema.const)}, got ${JSON.stringify(value)}`
17
+ );
18
+ }
19
+
20
+ if (schema.enum && !schema.enum.includes(value as string)) {
21
+ const allowed = schema.enum.map((v) => JSON.stringify(v)).join(', ');
22
+ throw new Error(`${path}: value ${JSON.stringify(value)} not in enum [${allowed}]`);
23
+ }
24
+
25
+ if (schema.oneOf && schema.oneOf.length) {
26
+ const errors: string[] = [];
27
+ for (const branch of schema.oneOf) {
28
+ try {
29
+ validatePropValue(value, branch, path);
30
+ return;
31
+ } catch (e) {
32
+ errors.push((e as Error).message);
33
+ }
34
+ }
35
+ throw new Error(
36
+ `${path}: value ${JSON.stringify(value)} matched none of oneOf branches: ${errors.join(' | ')}`
37
+ );
38
+ }
39
+
40
+ const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
41
+ if (types.length && !types.some((t) => matchesType(t, value))) {
42
+ throw new Error(`${path}: expected type ${types.join('|')}, got ${describeType(value)}`);
43
+ }
44
+
45
+ if (Array.isArray(value) && schema.items) {
46
+ value.forEach((item, i) => validatePropValue(item, schema.items, `${path}[${i}]`));
47
+ }
48
+
49
+ if (
50
+ typeof value === 'object' &&
51
+ value !== null &&
52
+ !Array.isArray(value) &&
53
+ schema.properties
54
+ ) {
55
+ const obj = value as Record<string, unknown>;
56
+ if (schema.required) {
57
+ for (const req of schema.required) {
58
+ if (!(req in obj)) {
59
+ throw new Error(`${path}.${req}: required property missing`);
60
+ }
61
+ }
62
+ }
63
+ for (const [key, childVal] of Object.entries(obj)) {
64
+ const childSchema = schema.properties[key];
65
+ if (!childSchema) {
66
+ if (schema.additionalProperties === false) {
67
+ throw new Error(`${path}.${key}: unexpected property`);
68
+ }
69
+ continue;
70
+ }
71
+ validatePropValue(childVal, childSchema, `${path}.${key}`);
72
+ }
73
+ }
74
+ }
75
+
76
+ function matchesType(t: string, v: unknown): boolean {
77
+ switch (t) {
78
+ case 'string':
79
+ return typeof v === 'string';
80
+ case 'number':
81
+ return typeof v === 'number' && Number.isFinite(v);
82
+ case 'integer':
83
+ return typeof v === 'number' && Number.isInteger(v);
84
+ case 'boolean':
85
+ return typeof v === 'boolean';
86
+ case 'array':
87
+ return Array.isArray(v);
88
+ case 'object':
89
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
90
+ case 'null':
91
+ return v === null;
92
+ default:
93
+ return true;
94
+ }
95
+ }
96
+
97
+ function describeType(v: unknown): string {
98
+ if (v === null) return 'null';
99
+ if (Array.isArray(v)) return 'array';
100
+ return typeof v;
101
+ }
102
+
103
+ export function validateSpec(spec: unknown, catalog: RegisteredCatalog, path = '$'): asserts spec is ComponentSpec {
104
+ if (!spec || typeof spec !== 'object') {
105
+ throw new Error(`${path}: spec must be an object`);
106
+ }
107
+ const s = spec as Record<string, unknown>;
108
+ if (typeof s.component !== 'string') {
109
+ throw new Error(`${path}: spec.component must be a string`);
110
+ }
111
+ if (!catalog.components.has(s.component)) {
112
+ throw new Error(
113
+ `${path}: component "${s.component}" is not registered in catalog "${catalog.id}"`
114
+ );
115
+ }
116
+
117
+ const def = catalog.components.get(s.component)!;
118
+ const allowedProps = new Set(Object.keys(def.properties?.props?.properties ?? {}));
119
+ const hasChildren = def.properties?.children !== undefined;
120
+ const allowedSlots = def.properties?.slots
121
+ ? new Set(Object.keys(def.properties.slots.properties ?? {}))
122
+ : null;
123
+
124
+ if (s.props) {
125
+ if (typeof s.props !== 'object') throw new Error(`${path}.props: must be an object`);
126
+ const propDefs = def.properties?.props?.properties ?? {};
127
+ for (const [prop, value] of Object.entries(s.props as Record<string, unknown>)) {
128
+ if (!allowedProps.has(prop)) {
129
+ throw new Error(`${path}.props.${prop}: not a declared prop on ${s.component}`);
130
+ }
131
+ validatePropValue(value, propDefs[prop], `${path}.props.${prop}`);
132
+ }
133
+ }
134
+
135
+ if (s.children !== undefined) {
136
+ if (!hasChildren) {
137
+ throw new Error(`${path}.children: ${s.component} does not accept default children`);
138
+ }
139
+ if (!Array.isArray(s.children)) throw new Error(`${path}.children: must be an array`);
140
+ s.children.forEach((c, i) => {
141
+ if (typeof c === 'string') return;
142
+ validateSpec(c, catalog, `${path}.children[${i}]`);
143
+ });
144
+ }
145
+
146
+ if (s.events !== undefined) {
147
+ if (!s.events || typeof s.events !== 'object' || Array.isArray(s.events)) {
148
+ throw new Error(`${path}.events: must be an object`);
149
+ }
150
+ for (const [evtName, binding] of Object.entries(s.events as Record<string, unknown>)) {
151
+ if (typeof binding === 'string') continue;
152
+ if (!binding || typeof binding !== 'object') {
153
+ throw new Error(`${path}.events.${evtName}: must be a string or { handler } object`);
154
+ }
155
+ const b = binding as Record<string, unknown>;
156
+ if (typeof b.handler !== 'string' || b.handler.length === 0) {
157
+ throw new Error(`${path}.events.${evtName}.handler: must be a non-empty string`);
158
+ }
159
+ }
160
+ }
161
+
162
+ if (s.slots !== undefined) {
163
+ if (!allowedSlots) {
164
+ throw new Error(`${path}.slots: ${s.component} does not accept named slots`);
165
+ }
166
+ if (typeof s.slots !== 'object') throw new Error(`${path}.slots: must be an object`);
167
+ for (const [slotName, arr] of Object.entries(s.slots as Record<string, unknown>)) {
168
+ if (!allowedSlots.has(slotName)) {
169
+ throw new Error(
170
+ `${path}.slots.${slotName}: not a declared slot on ${s.component}`
171
+ );
172
+ }
173
+ if (!Array.isArray(arr)) {
174
+ throw new Error(`${path}.slots.${slotName}: must be an array`);
175
+ }
176
+ arr.forEach((c, i) => {
177
+ if (typeof c === 'string') return;
178
+ validateSpec(c, catalog, `${path}.slots.${slotName}[${i}]`);
179
+ });
180
+ }
181
+ }
182
+ }
@@ -0,0 +1,7 @@
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;
@@ -0,0 +1,108 @@
1
+ import { validateSpec } from './registry.js';
2
+ export function render(spec, catalog, options = {}) {
3
+ validateSpec(spec, catalog);
4
+ const doc = options.doc ?? document;
5
+ const el = buildElement(spec, catalog, doc, '$', options.onEvent);
6
+ if (options.root)
7
+ options.root.replaceChildren(el);
8
+ return el;
9
+ }
10
+ function buildElement(spec, catalog, doc, path, onEvent) {
11
+ const def = catalog.components.get(spec.component);
12
+ const propDefs = def.properties?.props?.properties ?? {};
13
+ const el = doc.createElement(spec.component);
14
+ if (spec.props) {
15
+ for (const [key, value] of Object.entries(spec.props)) {
16
+ applyProp(el, key, value, propDefs[key]);
17
+ }
18
+ }
19
+ if (spec.events && onEvent) {
20
+ for (const [evtName, binding] of Object.entries(spec.events)) {
21
+ attachEvent(el, spec.component, path, evtName, binding, onEvent);
22
+ }
23
+ }
24
+ if (spec.children) {
25
+ spec.children.forEach((child, i) => {
26
+ if (typeof child === 'string') {
27
+ el.appendChild(doc.createTextNode(child));
28
+ return;
29
+ }
30
+ el.appendChild(buildElement(child, catalog, doc, `${path}.children[${i}]`, onEvent));
31
+ });
32
+ }
33
+ if (spec.slots) {
34
+ for (const [slotName, children] of Object.entries(spec.slots)) {
35
+ children.forEach((child, i) => {
36
+ if (typeof child === 'string') {
37
+ if (slotName === 'default') {
38
+ el.appendChild(doc.createTextNode(child));
39
+ }
40
+ else {
41
+ const wrap = doc.createElement('span');
42
+ wrap.setAttribute('slot', slotName);
43
+ wrap.textContent = child;
44
+ el.appendChild(wrap);
45
+ }
46
+ return;
47
+ }
48
+ const childEl = buildElement(child, catalog, doc, `${path}.slots.${slotName}[${i}]`, onEvent);
49
+ if (slotName !== 'default')
50
+ childEl.setAttribute('slot', slotName);
51
+ el.appendChild(childEl);
52
+ });
53
+ }
54
+ }
55
+ return el;
56
+ }
57
+ function attachEvent(el, component, path, eventName, binding, onEvent) {
58
+ const handler = typeof binding === 'string' ? binding : binding.handler;
59
+ const stop = typeof binding === 'object' && binding.stopPropagation === true;
60
+ const prevent = typeof binding === 'object' && binding.preventDefault === true;
61
+ el.addEventListener(eventName, (nativeEvent) => {
62
+ if (stop)
63
+ nativeEvent.stopPropagation();
64
+ if (prevent)
65
+ nativeEvent.preventDefault();
66
+ const detail = 'detail' in nativeEvent
67
+ ? nativeEvent.detail
68
+ : undefined;
69
+ onEvent({
70
+ component,
71
+ path,
72
+ event: eventName,
73
+ handler,
74
+ detail,
75
+ nativeEvent,
76
+ });
77
+ });
78
+ }
79
+ function applyProp(el, key, value, schema) {
80
+ if (value === undefined || value === null)
81
+ return;
82
+ if (schema?.['x-kind'] === 'property') {
83
+ el[key] = value;
84
+ return;
85
+ }
86
+ if (typeof value === 'boolean') {
87
+ if (value)
88
+ el.setAttribute(key, '');
89
+ else
90
+ el.removeAttribute(key);
91
+ return;
92
+ }
93
+ if (Array.isArray(value) || (typeof value === 'object' && value !== null)) {
94
+ el[key] = value;
95
+ return;
96
+ }
97
+ if (schema && isComplexSchema(schema)) {
98
+ el[key] = value;
99
+ return;
100
+ }
101
+ el.setAttribute(key, String(value));
102
+ }
103
+ function isComplexSchema(schema) {
104
+ const t = schema.type;
105
+ if (Array.isArray(t))
106
+ return t.some((x) => x === 'object' || x === 'array');
107
+ return t === 'object' || t === 'array';
108
+ }
@@ -0,0 +1,156 @@
1
+ import type {
2
+ ComponentSpec,
3
+ EmittedEvent,
4
+ EventBinding,
5
+ PropSchema,
6
+ RegisteredCatalog,
7
+ } from './types.js';
8
+ import { validateSpec } from './registry.js';
9
+
10
+ export interface RenderOptions {
11
+ root?: HTMLElement;
12
+ doc?: Document;
13
+ onEvent?: (evt: EmittedEvent) => void;
14
+ }
15
+
16
+ export function render(
17
+ spec: ComponentSpec,
18
+ catalog: RegisteredCatalog,
19
+ options: RenderOptions = {}
20
+ ): HTMLElement {
21
+ validateSpec(spec, catalog);
22
+ const doc = options.doc ?? document;
23
+ const el = buildElement(spec, catalog, doc, '$', options.onEvent);
24
+ if (options.root) options.root.replaceChildren(el);
25
+ return el;
26
+ }
27
+
28
+ function buildElement(
29
+ spec: ComponentSpec,
30
+ catalog: RegisteredCatalog,
31
+ doc: Document,
32
+ path: string,
33
+ onEvent?: (evt: EmittedEvent) => void
34
+ ): HTMLElement {
35
+ const def = catalog.components.get(spec.component)!;
36
+ const propDefs = def.properties?.props?.properties ?? {};
37
+ const el = doc.createElement(spec.component);
38
+
39
+ if (spec.props) {
40
+ for (const [key, value] of Object.entries(spec.props)) {
41
+ applyProp(el, key, value, propDefs[key]);
42
+ }
43
+ }
44
+
45
+ if (spec.events && onEvent) {
46
+ for (const [evtName, binding] of Object.entries(spec.events)) {
47
+ attachEvent(el, spec.component, path, evtName, binding, onEvent);
48
+ }
49
+ }
50
+
51
+ if (spec.children) {
52
+ spec.children.forEach((child, i) => {
53
+ if (typeof child === 'string') {
54
+ el.appendChild(doc.createTextNode(child));
55
+ return;
56
+ }
57
+ el.appendChild(buildElement(child, catalog, doc, `${path}.children[${i}]`, onEvent));
58
+ });
59
+ }
60
+
61
+ if (spec.slots) {
62
+ for (const [slotName, children] of Object.entries(spec.slots)) {
63
+ children.forEach((child, i) => {
64
+ if (typeof child === 'string') {
65
+ if (slotName === 'default') {
66
+ el.appendChild(doc.createTextNode(child));
67
+ } else {
68
+ const wrap = doc.createElement('span');
69
+ wrap.setAttribute('slot', slotName);
70
+ wrap.textContent = child;
71
+ el.appendChild(wrap);
72
+ }
73
+ return;
74
+ }
75
+ const childEl = buildElement(
76
+ child,
77
+ catalog,
78
+ doc,
79
+ `${path}.slots.${slotName}[${i}]`,
80
+ onEvent
81
+ );
82
+ if (slotName !== 'default') childEl.setAttribute('slot', slotName);
83
+ el.appendChild(childEl);
84
+ });
85
+ }
86
+ }
87
+
88
+ return el;
89
+ }
90
+
91
+ function attachEvent(
92
+ el: HTMLElement,
93
+ component: string,
94
+ path: string,
95
+ eventName: string,
96
+ binding: EventBinding,
97
+ onEvent: (evt: EmittedEvent) => void
98
+ ): void {
99
+ const handler = typeof binding === 'string' ? binding : binding.handler;
100
+ const stop = typeof binding === 'object' && binding.stopPropagation === true;
101
+ const prevent = typeof binding === 'object' && binding.preventDefault === true;
102
+
103
+ el.addEventListener(eventName, (nativeEvent) => {
104
+ if (stop) nativeEvent.stopPropagation();
105
+ if (prevent) nativeEvent.preventDefault();
106
+ const detail = 'detail' in nativeEvent
107
+ ? (nativeEvent as CustomEvent).detail
108
+ : undefined;
109
+ onEvent({
110
+ component,
111
+ path,
112
+ event: eventName,
113
+ handler,
114
+ detail,
115
+ nativeEvent,
116
+ });
117
+ });
118
+ }
119
+
120
+ function applyProp(
121
+ el: HTMLElement,
122
+ key: string,
123
+ value: unknown,
124
+ schema: PropSchema | undefined
125
+ ): void {
126
+ if (value === undefined || value === null) return;
127
+
128
+ if (schema?.['x-kind'] === 'property') {
129
+ (el as unknown as Record<string, unknown>)[key] = value;
130
+ return;
131
+ }
132
+
133
+ if (typeof value === 'boolean') {
134
+ if (value) el.setAttribute(key, '');
135
+ else el.removeAttribute(key);
136
+ return;
137
+ }
138
+
139
+ if (Array.isArray(value) || (typeof value === 'object' && value !== null)) {
140
+ (el as unknown as Record<string, unknown>)[key] = value;
141
+ return;
142
+ }
143
+
144
+ if (schema && isComplexSchema(schema)) {
145
+ (el as unknown as Record<string, unknown>)[key] = value;
146
+ return;
147
+ }
148
+
149
+ el.setAttribute(key, String(value));
150
+ }
151
+
152
+ function isComplexSchema(schema: PropSchema): boolean {
153
+ const t = schema.type;
154
+ if (Array.isArray(t)) return t.some((x) => x === 'object' || x === 'array');
155
+ return t === 'object' || t === 'array';
156
+ }