@openwebf/webf 0.23.0 → 0.23.7

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/src/react.ts CHANGED
@@ -2,24 +2,63 @@ import _ from "lodash";
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
  import {ParameterType} from "./analyzer";
5
- import {ClassObject, FunctionArgumentType, FunctionDeclaration, TypeAliasObject} from "./declaration";
5
+ import {ClassObject, FunctionArgumentType, FunctionDeclaration, TypeAliasObject, ConstObject, EnumObject} from "./declaration";
6
6
  import {IDLBlob} from "./IDLBlob";
7
- import {getPointerType, isPointerType, isUnionType} from "./utils";
7
+ import {getPointerType, isPointerType, isUnionType, trimNullTypeFromType} from "./utils";
8
+ import { debug } from './logger';
8
9
 
9
10
  function readTemplate(name: string) {
10
11
  return fs.readFileSync(path.join(__dirname, '../templates/' + name + '.tpl'), {encoding: 'utf-8'});
11
12
  }
12
13
 
13
- function generateReturnType(type: ParameterType) {
14
+ function generateReturnType(type: ParameterType): string {
14
15
  if (isUnionType(type)) {
15
- return (type.value as ParameterType[]).map(v => `'${v.value}'`).join(' | ');
16
+ const values = type.value as ParameterType[];
17
+ return values.map(v => {
18
+ if (v.value === FunctionArgumentType.null) {
19
+ return 'null';
20
+ }
21
+ // String literal unions: 'left' | 'center' | 'right'
22
+ if (typeof v.value === 'string') {
23
+ return `'${v.value}'`;
24
+ }
25
+ return 'any';
26
+ }).join(' | ');
27
+ }
28
+
29
+ // Handle non-literal unions such as boolean | null, number | null, CustomType | null
30
+ if (Array.isArray(type.value)) {
31
+ const values = type.value as ParameterType[];
32
+ const hasNull = values.some(v => v.value === FunctionArgumentType.null);
33
+ if (hasNull) {
34
+ const nonNulls = values.filter(v => v.value !== FunctionArgumentType.null);
35
+ if (nonNulls.length === 0) {
36
+ return 'null';
37
+ }
38
+ const parts: string[] = nonNulls.map(v => generateReturnType(v));
39
+ // Deduplicate and append null
40
+ const unique: string[] = Array.from(new Set(parts));
41
+ unique.push('null');
42
+ return unique.join(' | ');
43
+ }
44
+ // Complex non-null unions are rare for React typings; fall back to any
45
+ return 'any';
16
46
  }
47
+
17
48
  if (isPointerType(type)) {
18
49
  const pointerType = getPointerType(type);
50
+ // Map Dart's `Type` (from TS typeof) to TS `any`
51
+ if (pointerType === 'Type') return 'any';
19
52
  return pointerType;
20
53
  }
21
54
  if (type.isArray && typeof type.value === 'object' && !Array.isArray(type.value)) {
22
- return `${getPointerType(type.value)}[]`;
55
+ const elemType = getPointerType(type.value);
56
+ // Map arrays of Dart `Type` to `any[]` in TS; parenthesize typeof
57
+ if (elemType === 'Type') return 'any[]';
58
+ if (typeof elemType === 'string' && elemType.startsWith('typeof ')) {
59
+ return `(${elemType})[]`;
60
+ }
61
+ return `${elemType}[]`;
23
62
  }
24
63
  switch (type.value) {
25
64
  case FunctionArgumentType.int:
@@ -125,6 +164,8 @@ export function toWebFTagName(className: string): string {
125
164
  export function generateReactComponent(blob: IDLBlob, packageName?: string, relativeDir?: string) {
126
165
  const classObjects = blob.objects.filter(obj => obj instanceof ClassObject) as ClassObject[];
127
166
  const typeAliases = blob.objects.filter(obj => obj instanceof TypeAliasObject) as TypeAliasObject[];
167
+ const constObjects = blob.objects.filter(obj => obj instanceof ConstObject) as ConstObject[];
168
+ const enumObjects = blob.objects.filter(obj => obj instanceof EnumObject) as EnumObject[];
128
169
 
129
170
  const classObjectDictionary = Object.fromEntries(
130
171
  classObjects.map(object => {
@@ -153,36 +194,87 @@ export function generateReactComponent(blob: IDLBlob, packageName?: string, rela
153
194
  return `type ${typeAlias.name} = ${typeAlias.type};`;
154
195
  }).join('\n');
155
196
 
197
+ // Include declare const values as ambient exports for type usage (e.g., unique symbol branding)
198
+ const constDeclarations = constObjects.map(c => `export declare const ${c.name}: ${c.type};`).join('\n');
199
+
200
+ // Include enums as concrete exports (no declare) so they are usable as values
201
+ const enumDeclarations = enumObjects.map(e => {
202
+ const members = e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ');
203
+ return `export enum ${e.name} { ${members} }`;
204
+ }).join('\n');
205
+
206
+ // Names declared within this blob (so we shouldn't prefix them with __webfTypes)
207
+ const localTypeNames = new Set<string>([
208
+ ...others.map(o => o.name),
209
+ ...typeAliases.map(t => t.name),
210
+ ...constObjects.map(c => c.name),
211
+ ...enumObjects.map(e => e.name),
212
+ ]);
213
+
156
214
  const dependencies = [
157
215
  typeAliasDeclarations,
216
+ constDeclarations,
217
+ enumDeclarations,
158
218
  // Include Methods interfaces as dependencies
159
219
  methods.map(object => {
160
220
  const methodDeclarations = object.methods.map(method => {
161
221
  return generateMethodDeclarationWithDocs(method, ' ');
162
222
  }).join('\n');
163
223
 
164
- let interfaceDoc = '';
165
- if (object.documentation) {
166
- interfaceDoc = `/**\n${object.documentation.split('\n').map(line => ` * ${line}`).join('\n')}\n */\n`;
224
+ const lines: string[] = [];
225
+ if (object.documentation && object.documentation.trim().length > 0) {
226
+ lines.push('/**');
227
+ object.documentation.split('\n').forEach(line => {
228
+ lines.push(` * ${line}`);
229
+ });
230
+ lines.push(' */');
167
231
  }
168
232
 
169
- return `${interfaceDoc}interface ${object.name} {
170
- ${methodDeclarations}
171
- }`;
233
+ lines.push(`interface ${object.name} {`);
234
+ lines.push(methodDeclarations);
235
+ lines.push('}');
236
+
237
+ return lines.join('\n');
172
238
  }).join('\n\n'),
173
239
  others.map(object => {
174
- const props = object.props.map(prop => {
175
- if (prop.optional) {
176
- return `${prop.name}?: ${generateReturnType(prop.type)};`;
240
+ if (!object || !object.props || object.props.length === 0) {
241
+ return '';
242
+ }
243
+
244
+ const interfaceLines: string[] = [];
245
+
246
+ if (object.documentation && object.documentation.trim().length > 0) {
247
+ interfaceLines.push('/**');
248
+ object.documentation.split('\n').forEach(line => {
249
+ interfaceLines.push(` * ${line}`);
250
+ });
251
+ interfaceLines.push(' */');
252
+ }
253
+
254
+ interfaceLines.push(`interface ${object.name} {`);
255
+
256
+ const propLines = object.props.map(prop => {
257
+ const lines: string[] = [];
258
+
259
+ if (prop.documentation && prop.documentation.trim().length > 0) {
260
+ lines.push(' /**');
261
+ prop.documentation.split('\n').forEach(line => {
262
+ lines.push(` * ${line}`);
263
+ });
264
+ lines.push(' */');
177
265
  }
178
- return `${prop.name}: ${generateReturnType(prop.type)};`;
179
- }).join('\n ');
180
-
181
- return `
182
- interface ${object.name} {
183
- ${props}
184
- }`;
185
- }).join('\n\n')
266
+
267
+ const optionalToken = prop.optional ? '?' : '';
268
+ lines.push(` ${prop.name}${optionalToken}: ${generateReturnType(prop.type)};`);
269
+
270
+ return lines.join('\n');
271
+ });
272
+
273
+ interfaceLines.push(propLines.join('\n'));
274
+ interfaceLines.push('}');
275
+
276
+ return interfaceLines.join('\n');
277
+ }).filter(Boolean).join('\n\n')
186
278
  ].filter(Boolean).join('\n\n');
187
279
 
188
280
  // Generate all components from this file
@@ -249,17 +341,115 @@ interface ${object.name} {
249
341
  createWebFComponentImport
250
342
  );
251
343
 
252
- const content = _.template(templateContent)({
344
+ // Generate return type mapping; always use __webfTypes namespace for typeof
345
+ const genRT = (type: ParameterType): string => {
346
+ if (isUnionType(type)) {
347
+ const values = type.value as ParameterType[];
348
+ return values.map(v => {
349
+ if (v.value === FunctionArgumentType.null) {
350
+ return 'null';
351
+ }
352
+ if (typeof v.value === 'string') {
353
+ return `'${v.value}'`;
354
+ }
355
+ return 'any';
356
+ }).join(' | ');
357
+ }
358
+ if (Array.isArray(type.value)) {
359
+ const values = type.value as ParameterType[];
360
+ const hasNull = values.some(v => v.value === FunctionArgumentType.null);
361
+ if (hasNull) {
362
+ const nonNulls = values.filter(v => v.value !== FunctionArgumentType.null);
363
+ if (nonNulls.length === 0) {
364
+ return 'null';
365
+ }
366
+ const parts: string[] = nonNulls.map(v => genRT(v));
367
+ const unique: string[] = Array.from(new Set(parts));
368
+ unique.push('null');
369
+ return unique.join(' | ');
370
+ }
371
+ return 'any';
372
+ }
373
+ if (isPointerType(type)) {
374
+ const pointerType = getPointerType(type);
375
+ if (pointerType === 'Type') return 'any';
376
+ if (typeof pointerType === 'string' && pointerType.startsWith('typeof ')) {
377
+ const ident = pointerType.substring('typeof '.length).trim();
378
+ return `typeof __webfTypes.${ident}`;
379
+ }
380
+ // Prefix external pointer types with __webfTypes unless locally declared
381
+ if (typeof pointerType === 'string' && /^(?:[A-Za-z_][A-Za-z0-9_]*)(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(pointerType)) {
382
+ const base = pointerType.split('.')[0];
383
+ if (!localTypeNames.has(base)) {
384
+ return `__webfTypes.${pointerType}`;
385
+ }
386
+ }
387
+ return pointerType;
388
+ }
389
+ if (type.isArray && typeof type.value === 'object' && !Array.isArray(type.value)) {
390
+ const elemType = getPointerType(type.value);
391
+ if (elemType === 'Type') return 'any[]';
392
+ if (typeof elemType === 'string' && elemType.startsWith('typeof ')) {
393
+ const ident = elemType.substring('typeof '.length).trim();
394
+ return `(typeof __webfTypes.${ident})[]`;
395
+ }
396
+ if (typeof elemType === 'string' && /^(?:[A-Za-z_][A-Za-z0-9_]*)(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(elemType)) {
397
+ const base = elemType.split('.')[0];
398
+ if (!localTypeNames.has(base)) {
399
+ return `__webfTypes.${elemType}[]`;
400
+ }
401
+ }
402
+ return `${elemType}[]`;
403
+ }
404
+ switch (type.value) {
405
+ case FunctionArgumentType.int:
406
+ case FunctionArgumentType.double:
407
+ return 'number';
408
+ case FunctionArgumentType.any:
409
+ return 'any';
410
+ case FunctionArgumentType.boolean:
411
+ return 'boolean';
412
+ case FunctionArgumentType.dom_string:
413
+ return 'string';
414
+ case FunctionArgumentType.void:
415
+ default:
416
+ return 'void';
417
+ }
418
+ };
419
+
420
+ // Compute relative import path to src/types
421
+ const depth = (relativeDir || '').split('/').filter(p => p).length;
422
+ const upPath = '../'.repeat(depth);
423
+ // Always import the types namespace for typeof references
424
+ const typesImport = `import * as __webfTypes from "${upPath}types";\n\n`;
425
+
426
+ // Debug: collect typeof references from props for this component
427
+ const typeofRefs = new Set<string>();
428
+ if (component.properties) {
429
+ component.properties.props.forEach(p => {
430
+ const t = p.type;
431
+ if (!t) return;
432
+ if (!t.isArray && typeof (t.value as any) === 'string' && String((t.value as any)).startsWith('typeof ')) {
433
+ const ident = String((t.value as any)).substring('typeof '.length).trim();
434
+ typeofRefs.add(ident);
435
+ }
436
+ });
437
+ }
438
+ debug(`[react] Generating ${className} (${blob.relativeDir}/${blob.filename}.tsx) typeof refs: ${Array.from(typeofRefs).join(', ') || '(none)'}; types import: ${upPath}types`);
439
+
440
+ const dependenciesWithImports = `${typesImport}${dependencies}`;
441
+
442
+ let content = _.template(templateContent)({
253
443
  className: className,
254
444
  properties: component.properties,
255
445
  events: component.events,
256
446
  methods: component.methods,
257
447
  classObjectDictionary,
258
- dependencies,
448
+ dependencies: dependenciesWithImports,
259
449
  blob,
260
450
  toReactEventName,
261
451
  toWebFTagName,
262
- generateReturnType,
452
+ generateReturnType: genRT,
263
453
  generateMethodDeclaration,
264
454
  generateMethodDeclarationWithDocs,
265
455
  generateEventHandlerType,
@@ -289,6 +479,80 @@ interface ${object.name} {
289
479
  }
290
480
 
291
481
  componentEntries.forEach(([className, component]) => {
482
+ const genRT = (type: ParameterType): string => {
483
+ if (isUnionType(type)) {
484
+ const values = type.value as ParameterType[];
485
+ return values.map(v => {
486
+ if (v.value === FunctionArgumentType.null) {
487
+ return 'null';
488
+ }
489
+ if (typeof v.value === 'string') {
490
+ return `'${v.value}'`;
491
+ }
492
+ return 'any';
493
+ }).join(' | ');
494
+ }
495
+ if (Array.isArray(type.value)) {
496
+ const values = type.value as ParameterType[];
497
+ const hasNull = values.some(v => v.value === FunctionArgumentType.null);
498
+ if (hasNull) {
499
+ const nonNulls = values.filter(v => v.value !== FunctionArgumentType.null);
500
+ if (nonNulls.length === 0) {
501
+ return 'null';
502
+ }
503
+ const parts: string[] = nonNulls.map(v => genRT(v));
504
+ const unique: string[] = Array.from(new Set(parts));
505
+ unique.push('null');
506
+ return unique.join(' | ');
507
+ }
508
+ return 'any';
509
+ }
510
+ if (isPointerType(type)) {
511
+ const pointerType = getPointerType(type);
512
+ if (pointerType === 'Type') return 'any';
513
+ if (typeof pointerType === 'string' && pointerType.startsWith('typeof ')) {
514
+ const ident = pointerType.substring('typeof '.length).trim();
515
+ return `typeof __webfTypes.${ident}`;
516
+ }
517
+ if (typeof pointerType === 'string' && /^(?:[A-Za-z_][A-Za-z0-9_]*)(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(pointerType)) {
518
+ const base = pointerType.split('.')[0];
519
+ if (!localTypeNames.has(base)) {
520
+ return `__webfTypes.${pointerType}`;
521
+ }
522
+ }
523
+ return pointerType;
524
+ }
525
+ if (type.isArray && typeof type.value === 'object' && !Array.isArray(type.value)) {
526
+ const elemType = getPointerType(type.value);
527
+ if (elemType === 'Type') return 'any[]';
528
+ if (typeof elemType === 'string' && elemType.startsWith('typeof ')) {
529
+ const ident = elemType.substring('typeof '.length).trim();
530
+ return `(typeof __webfTypes.${ident})[]`;
531
+ }
532
+ if (typeof elemType === 'string' && /^(?:[A-Za-z_][A-Za-z0-9_]*)(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(elemType)) {
533
+ const base = elemType.split('.')[0];
534
+ if (!localTypeNames.has(base)) {
535
+ return `__webfTypes.${elemType}[]`;
536
+ }
537
+ }
538
+ return `${elemType}[]`;
539
+ }
540
+ switch (type.value) {
541
+ case FunctionArgumentType.int:
542
+ case FunctionArgumentType.double:
543
+ return 'number';
544
+ case FunctionArgumentType.any:
545
+ return 'any';
546
+ case FunctionArgumentType.boolean:
547
+ return 'boolean';
548
+ case FunctionArgumentType.dom_string:
549
+ return 'string';
550
+ case FunctionArgumentType.void:
551
+ default:
552
+ return 'void';
553
+ }
554
+ };
555
+
292
556
  const content = _.template(readTemplate('react.component.tsx'))({
293
557
  className: className,
294
558
  properties: component.properties,
@@ -299,7 +563,7 @@ interface ${object.name} {
299
563
  blob,
300
564
  toReactEventName,
301
565
  toWebFTagName,
302
- generateReturnType,
566
+ generateReturnType: genRT,
303
567
  generateMethodDeclaration,
304
568
  generateMethodDeclarationWithDocs,
305
569
  generateEventHandlerType,
@@ -316,15 +580,23 @@ interface ${object.name} {
316
580
  });
317
581
 
318
582
  // Combine with shared imports at the top
319
- const result = [
583
+ // Compute relative import path to src/types and always include namespace import
584
+ const depth = (relativeDir || '').split('/').filter(p => p).length;
585
+ const upPath = '../'.repeat(depth);
586
+ const typesImport = `import * as __webfTypes from "${upPath}types";`;
587
+
588
+ debug(`[react] Generating combined components for ${blob.filename}.tsx; types import: ${upPath}types`);
589
+
590
+ let result = [
320
591
  'import React from "react";',
321
592
  createWebFComponentImport,
593
+ typesImport,
322
594
  '',
323
595
  dependencies,
324
596
  '',
325
597
  ...componentDefinitions
326
598
  ].filter(line => line !== undefined).join('\n');
327
-
599
+
328
600
  return result.split('\n').filter(str => {
329
601
  return str.trim().length > 0;
330
602
  }).join('\n');
package/src/vue.ts CHANGED
@@ -2,21 +2,65 @@ import _ from "lodash";
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
  import {ParameterType} from "./analyzer";
5
- import {ClassObject, FunctionArgumentType, FunctionDeclaration} from "./declaration";
5
+ import {ClassObject, FunctionArgumentType, FunctionDeclaration, ConstObject, EnumObject} from "./declaration";
6
6
  import {IDLBlob} from "./IDLBlob";
7
- import {getPointerType, isPointerType} from "./utils";
7
+ import { debug } from './logger';
8
+ import {getPointerType, isPointerType, isUnionType, trimNullTypeFromType} from "./utils";
8
9
 
9
10
  function readTemplate(name: string) {
10
11
  return fs.readFileSync(path.join(__dirname, '../templates/' + name + '.tpl'), {encoding: 'utf-8'});
11
12
  }
12
13
 
13
- function generateReturnType(type: ParameterType) {
14
+ function generateReturnType(type: ParameterType): string {
15
+ if (isUnionType(type)) {
16
+ const values = type.value as ParameterType[];
17
+ return values.map(v => {
18
+ if (v.value === FunctionArgumentType.null) {
19
+ return 'null';
20
+ }
21
+ if (typeof v.value === 'string') {
22
+ return `'${v.value}'`;
23
+ }
24
+ return 'any';
25
+ }).join(' | ');
26
+ }
27
+
28
+ // Handle unions like boolean | null, number | null, CustomType | null
29
+ if (Array.isArray(type.value)) {
30
+ const values = type.value as ParameterType[];
31
+ const hasNull = values.some(v => v.value === FunctionArgumentType.null);
32
+ if (hasNull) {
33
+ const nonNulls = values.filter(v => v.value !== FunctionArgumentType.null);
34
+ if (nonNulls.length === 0) {
35
+ return 'null';
36
+ }
37
+ const parts: string[] = nonNulls.map(v => generateReturnType(v));
38
+ const unique: string[] = Array.from(new Set(parts));
39
+ unique.push('null');
40
+ return unique.join(' | ');
41
+ }
42
+ // Complex non-null unions are rare; fall back to any
43
+ return 'any';
44
+ }
45
+
14
46
  if (isPointerType(type)) {
15
47
  const pointerType = getPointerType(type);
48
+ // Map Dart's `Type` (from TS typeof) to TS `any`
49
+ if (pointerType === 'Type') return 'any';
50
+ if (typeof pointerType === 'string' && pointerType.startsWith('typeof ')) {
51
+ const ident = pointerType.substring('typeof '.length).trim();
52
+ return `typeof __webfTypes.${ident}`;
53
+ }
16
54
  return pointerType;
17
55
  }
18
56
  if (type.isArray && typeof type.value === 'object' && !Array.isArray(type.value)) {
19
- return `${getPointerType(type.value)}[]`;
57
+ const elemType = getPointerType(type.value);
58
+ if (elemType === 'Type') return 'any[]';
59
+ if (typeof elemType === 'string' && elemType.startsWith('typeof ')) {
60
+ const ident = elemType.substring('typeof '.length).trim();
61
+ return `(typeof __webfTypes.${ident})[]`;
62
+ }
63
+ return `${elemType}[]`;
20
64
  }
21
65
  switch (type.value) {
22
66
  case FunctionArgumentType.int:
@@ -94,20 +138,43 @@ function generateVueComponent(blob: IDLBlob) {
94
138
  });
95
139
 
96
140
  const dependencies = others.map(object => {
97
- if (!object || !object.props) {
141
+ if (!object || !object.props || object.props.length === 0) {
98
142
  return '';
99
143
  }
100
- const props = object.props.map(prop => {
101
- if (prop.optional) {
102
- return `${prop.name}?: ${generateReturnType(prop.type)};`;
144
+
145
+ const interfaceLines: string[] = [];
146
+
147
+ if (object.documentation && object.documentation.trim().length > 0) {
148
+ interfaceLines.push('/**');
149
+ object.documentation.split('\n').forEach(line => {
150
+ interfaceLines.push(` * ${line}`);
151
+ });
152
+ interfaceLines.push(' */');
153
+ }
154
+
155
+ interfaceLines.push(`interface ${object.name} {`);
156
+
157
+ const propLines = object.props.map(prop => {
158
+ const lines: string[] = [];
159
+
160
+ if (prop.documentation && prop.documentation.trim().length > 0) {
161
+ lines.push(' /**');
162
+ prop.documentation.split('\n').forEach(line => {
163
+ lines.push(` * ${line}`);
164
+ });
165
+ lines.push(' */');
103
166
  }
104
- return `${prop.name}: ${generateReturnType(prop.type)};`;
105
- }).join('\n ');
106
167
 
107
- return `
108
- interface ${object.name} {
109
- ${props}
110
- }`;
168
+ const optionalToken = prop.optional ? '?' : '';
169
+ lines.push(` ${prop.name}${optionalToken}: ${generateReturnType(prop.type)};`);
170
+
171
+ return lines.join('\n');
172
+ });
173
+
174
+ interfaceLines.push(propLines.join('\n'));
175
+ interfaceLines.push('}');
176
+
177
+ return interfaceLines.join('\n');
111
178
  }).filter(dep => dep.trim() !== '').join('\n\n');
112
179
 
113
180
  const componentProperties = properties.length > 0 ? properties[0] : undefined;
@@ -145,6 +212,17 @@ interface ${object.name} {
145
212
  return result;
146
213
  }
147
214
 
215
+ function toVueTagName(className: string): string {
216
+ if (className.startsWith('WebF')) {
217
+ const withoutPrefix = className.substring(4);
218
+ return 'web-f-' + _.kebabCase(withoutPrefix);
219
+ } else if (className.startsWith('Flutter')) {
220
+ const withoutPrefix = className.substring(7);
221
+ return 'flutter-' + _.kebabCase(withoutPrefix);
222
+ }
223
+ return _.kebabCase(className);
224
+ }
225
+
148
226
  export function generateVueTypings(blobs: IDLBlob[]) {
149
227
  const componentNames = blobs.map(blob => {
150
228
  const classObjects = blob.objects as ClassObject[];
@@ -177,13 +255,52 @@ export function generateVueTypings(blobs: IDLBlob[]) {
177
255
  return component.length > 0;
178
256
  }).join('\n\n');
179
257
 
258
+ // Collect declare consts across blobs and render as exported ambient declarations
259
+ const consts = blobs
260
+ .flatMap(blob => blob.objects)
261
+ .filter(obj => obj instanceof ConstObject) as ConstObject[];
262
+
263
+ // Deduplicate by name keeping first occurrence
264
+ const uniqueConsts = new Map<string, ConstObject>();
265
+ consts.forEach(c => {
266
+ if (!uniqueConsts.has(c.name)) uniqueConsts.set(c.name, c);
267
+ });
268
+
269
+ const constDeclarations = Array.from(uniqueConsts.values())
270
+ .map(c => `export declare const ${c.name}: ${c.type};`)
271
+ .join('\n');
272
+
273
+ // Collect declare enums across blobs
274
+ const enums = blobs
275
+ .flatMap(blob => blob.objects)
276
+ .filter(obj => obj instanceof EnumObject) as EnumObject[];
277
+
278
+ const enumDeclarations = enums.map(e => {
279
+ const members = e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ');
280
+ return `export declare enum ${e.name} { ${members} }`;
281
+ }).join('\n');
282
+
283
+ // Always import the types namespace to support typeof references
284
+ const typesImport = `import * as __webfTypes from './src/types';`;
285
+ debug(`[vue] Generating typings; importing types from ./src/types`);
286
+
287
+ // Build mapping of template tag names to class names for GlobalComponents
288
+ const componentMetas = componentNames.map(className => ({
289
+ className,
290
+ tagName: toVueTagName(className),
291
+ }));
292
+
180
293
  const content = _.template(readTemplate('vue.components.d.ts'), {
181
294
  interpolate: /<%=([\s\S]+?)%>/g,
182
295
  evaluate: /<%([\s\S]+?)%>/g,
183
296
  escape: /<%-([\s\S]+?)%>/g
184
297
  })({
185
298
  componentNames,
299
+ componentMetas,
186
300
  components,
301
+ consts: constDeclarations,
302
+ enums: enumDeclarations,
303
+ typesImport,
187
304
  });
188
305
 
189
306
  return content.split('\n').filter(str => {
@@ -38,7 +38,7 @@ abstract class <%= className %>Bindings extends WidgetElement {
38
38
  <% var attributeName = _.kebabCase(prop.name); %>
39
39
  <% var propName = _.camelCase(prop.name); %>
40
40
  attributes['<%= attributeName %>'] = ElementAttributeProperty(
41
- getter: () => <%= generateAttributeGetter(propName, prop.type, prop.optional) %>,
41
+ getter: () => <%= generateAttributeGetter(propName, prop.type, prop.optional, prop) %>,
42
42
  setter: (value) => <%= generateAttributeSetter(propName, prop.type) %>,
43
43
  deleter: () => <%= generateAttributeDeleter(propName, prop.type, prop.optional) %>
44
44
  );
@@ -23,6 +23,7 @@
23
23
  "devDependencies": {
24
24
  "@types/react": "^19.1.0",
25
25
  "@types/react-dom": "^19.1.2",
26
+ "picomatch": "^4.0.2",
26
27
  "tsup": "^8.5.0",
27
28
  "typescript": "^5.8.3"
28
29
  }
@@ -5,6 +5,8 @@
5
5
  // https://vuejs.org/guide/extras/web-components
6
6
  import { EmitFn, PublicProps, HTMLAttributes } from 'vue';
7
7
 
8
+ <%= typesImport %>
9
+
8
10
  type EventMap = {
9
11
  [event: string]: Event
10
12
  }
@@ -19,6 +21,9 @@ type VueEventListeners<T extends EventMap> = {
19
21
  [K in keyof T as `on${Capitalize<string & K>}`]?: (event: T[K]) => any
20
22
  }
21
23
 
24
+ <%= consts %>
25
+ <%= enums %>
26
+
22
27
  type DefineCustomElement<
23
28
  ElementType,
24
29
  Events extends EventMap = {},
@@ -44,10 +49,10 @@ type DefineCustomElement<
44
49
 
45
50
  declare module 'vue' {
46
51
  interface GlobalComponents {
47
- <% componentNames.forEach(name => { %>
48
- '<%= name %>': DefineCustomElement<
49
- <%= name %>Props,
50
- <%= name %>Events
52
+ <% componentMetas.forEach(comp => { %>
53
+ '<%= comp.tagName %>': DefineCustomElement<
54
+ <%= comp.className %>Props,
55
+ <%= comp.className %>Events
51
56
  >
52
57
  <% }) %>
53
58
  }