@openwebf/webf 0.24.1 → 0.24.3

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/commands.ts CHANGED
@@ -177,8 +177,8 @@ const moduleTsConfig = fs.readFileSync(
177
177
  'utf-8'
178
178
  );
179
179
 
180
- const moduleTsUpConfig = fs.readFileSync(
181
- path.resolve(__dirname, '../templates/module.tsup.config.ts.tpl'),
180
+ const moduleTsDownConfig = fs.readFileSync(
181
+ path.resolve(__dirname, '../templates/module.tsdown.config.ts.tpl'),
182
182
  'utf-8'
183
183
  );
184
184
  const reactPackageJson = fs.readFileSync(
@@ -191,8 +191,8 @@ const reactTsConfig = fs.readFileSync(
191
191
  'utf-8'
192
192
  );
193
193
 
194
- const reactTsUpConfig = fs.readFileSync(
195
- path.resolve(__dirname, '../templates/react.tsup.config.ts.tpl'),
194
+ const reactTsDownConfig = fs.readFileSync(
195
+ path.resolve(__dirname, '../templates/react.tsdown.config.ts.tpl'),
196
196
  'utf-8'
197
197
  );
198
198
 
@@ -239,6 +239,40 @@ function readFlutterPackageMetadata(packagePath: string): FlutterPackageMetadata
239
239
  }
240
240
  }
241
241
 
242
+ function copyReadmeToPackageRoot(params: {
243
+ sourceRoot: string;
244
+ targetRoot: string;
245
+ }): { copied: boolean; sourcePath?: string; targetPath: string } {
246
+ const { sourceRoot, targetRoot } = params;
247
+ const targetPath = path.join(targetRoot, 'README.md');
248
+
249
+ if (fs.existsSync(targetPath)) {
250
+ return { copied: false, targetPath };
251
+ }
252
+
253
+ const candidateNames = ['README.md', 'Readme.md', 'readme.md'];
254
+ let sourcePath: string | null = null;
255
+ for (const candidate of candidateNames) {
256
+ const abs = path.join(sourceRoot, candidate);
257
+ if (fs.existsSync(abs)) {
258
+ sourcePath = abs;
259
+ break;
260
+ }
261
+ }
262
+
263
+ if (!sourcePath) {
264
+ return { copied: false, targetPath };
265
+ }
266
+
267
+ try {
268
+ const content = fs.readFileSync(sourcePath, 'utf-8');
269
+ writeFileIfChanged(targetPath, content);
270
+ return { copied: true, sourcePath, targetPath };
271
+ } catch {
272
+ return { copied: false, targetPath };
273
+ }
274
+ }
275
+
242
276
  // Copy markdown docs that match .d.ts basenames from source to the built dist folder,
243
277
  // and generate an aggregated README.md in the dist directory.
244
278
  async function copyMarkdownDocsToDist(params: {
@@ -419,9 +453,9 @@ function createCommand(target: string, options: { framework: string; packageName
419
453
  const tsConfigContent = _.template(reactTsConfig)({});
420
454
  writeFileIfChanged(tsConfigPath, tsConfigContent);
421
455
 
422
- const tsupConfigPath = path.join(target, 'tsup.config.ts');
423
- const tsupConfigContent = _.template(reactTsUpConfig)({});
424
- writeFileIfChanged(tsupConfigPath, tsupConfigContent);
456
+ const tsdownConfigPath = path.join(target, 'tsdown.config.ts');
457
+ const tsdownConfigContent = _.template(reactTsDownConfig)({});
458
+ writeFileIfChanged(tsdownConfigPath, tsdownConfigContent);
425
459
 
426
460
  const gitignorePath = path.join(target, '.gitignore');
427
461
  const gitignoreContent = _.template(gitignore)({});
@@ -498,9 +532,9 @@ function createModuleProject(target: string, options: { packageName: string; met
498
532
  const tsConfigContent = _.template(moduleTsConfig)({});
499
533
  writeFileIfChanged(tsConfigPath, tsConfigContent);
500
534
 
501
- const tsupConfigPath = path.join(target, 'tsup.config.ts');
502
- const tsupConfigContent = _.template(moduleTsUpConfig)({});
503
- writeFileIfChanged(tsupConfigPath, tsupConfigContent);
535
+ const tsdownConfigPath = path.join(target, 'tsdown.config.ts');
536
+ const tsdownConfigContent = _.template(moduleTsDownConfig)({});
537
+ writeFileIfChanged(tsdownConfigPath, tsdownConfigContent);
504
538
 
505
539
  if (!skipGitignore) {
506
540
  const gitignorePath = path.join(target, '.gitignore');
@@ -780,6 +814,17 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
780
814
  // Auto-initialize typings in the output directory if needed
781
815
  ensureInitialized(resolvedDistPath);
782
816
 
817
+ // Copy README.md from the source Flutter package into the npm package root (so `npm publish` includes it).
818
+ if (options.flutterPackageSrc) {
819
+ const { copied } = copyReadmeToPackageRoot({
820
+ sourceRoot: options.flutterPackageSrc,
821
+ targetRoot: resolvedDistPath,
822
+ });
823
+ if (copied) {
824
+ console.log('📄 Copied README.md to package root');
825
+ }
826
+ }
827
+
783
828
  console.log(`\nGenerating ${framework} code from ${options.flutterPackageSrc}...`);
784
829
 
785
830
  await dartGen({
@@ -1019,7 +1064,7 @@ async function generateModuleCommand(distPath: string, options: GenerateOptions)
1019
1064
  packageName = packageNameAnswer.packageName;
1020
1065
  }
1021
1066
 
1022
- // Prevent npm scaffolding (package.json, tsup.config.ts, etc.) from being written into
1067
+ // Prevent npm scaffolding (package.json, tsdown.config.ts, etc.) from being written into
1023
1068
  // the Flutter package itself. Force users to choose a separate output directory.
1024
1069
  if (resolvedDistPath === flutterPackageSrc) {
1025
1070
  console.error('\n❌ Output directory must not be the Flutter package root.');
@@ -1070,6 +1115,15 @@ async function generateModuleCommand(distPath: string, options: GenerateOptions)
1070
1115
  command,
1071
1116
  });
1072
1117
 
1118
+ // Copy README.md from the source Flutter package into the npm package root
1119
+ const { copied } = copyReadmeToPackageRoot({
1120
+ sourceRoot: flutterPackageSrc,
1121
+ targetRoot: resolvedDistPath,
1122
+ });
1123
+ if (copied) {
1124
+ console.log('📄 Copied README.md to package root');
1125
+ }
1126
+
1073
1127
  console.log('\nModule code generation completed successfully!');
1074
1128
 
1075
1129
  try {
package/src/generator.ts CHANGED
@@ -329,7 +329,8 @@ export async function reactGen({ source, target, exclude, packageName }: Generat
329
329
  const newExports = generateReactIndex(blobs);
330
330
 
331
331
  // Build desired export map: moduleSpecifier -> Set of names
332
- const desiredExports = new Map<string, Set<string>>();
332
+ const desiredValueExports = new Map<string, Set<string>>();
333
+ const desiredTypeExports = new Map<string, Set<string>>();
333
334
  const components = blobs.flatMap(blob => {
334
335
  const classObjects = blob.objects.filter(obj => obj instanceof ClassObject) as ClassObject[];
335
336
  const properties = classObjects.filter(object => object.name.endsWith('Properties'));
@@ -351,10 +352,10 @@ export async function reactGen({ source, target, exclude, packageName }: Generat
351
352
  }
352
353
  for (const { className, fileName, relativeDir } of unique.values()) {
353
354
  const spec = `./${relativeDir ? `${relativeDir}/` : ''}${fileName}`;
354
- if (!desiredExports.has(spec)) desiredExports.set(spec, new Set());
355
- const set = desiredExports.get(spec)!;
356
- set.add(className);
357
- set.add(`${className}Element`);
355
+ if (!desiredValueExports.has(spec)) desiredValueExports.set(spec, new Set());
356
+ if (!desiredTypeExports.has(spec)) desiredTypeExports.set(spec, new Set());
357
+ desiredValueExports.get(spec)!.add(className);
358
+ desiredTypeExports.get(spec)!.add(`${className}Element`);
358
359
  }
359
360
 
360
361
  if (!fs.existsSync(indexFilePath)) {
@@ -376,22 +377,41 @@ export async function reactGen({ source, target, exclude, packageName }: Generat
376
377
  ? stmt.moduleSpecifier.text
377
378
  : undefined;
378
379
  if (!moduleSpecifier) continue;
379
- const desired = desiredExports.get(moduleSpecifier);
380
- if (!desired) continue;
380
+ const desiredValues = desiredValueExports.get(moduleSpecifier);
381
+ const desiredTypes = desiredTypeExports.get(moduleSpecifier);
382
+ if (!desiredValues && !desiredTypes) continue;
383
+ const declIsTypeOnly = Boolean((stmt as unknown as { isTypeOnly?: boolean }).isTypeOnly);
381
384
  for (const el of stmt.exportClause.elements) {
382
385
  const name = el.name.getText(sourceFile);
383
- if (desired.has(name)) desired.delete(name);
386
+ const specIsTypeOnly = Boolean((el as unknown as { isTypeOnly?: boolean }).isTypeOnly);
387
+ const isTypeOnly = declIsTypeOnly || specIsTypeOnly;
388
+ if (isTypeOnly) {
389
+ if (desiredTypes?.has(name)) desiredTypes.delete(name);
390
+ } else {
391
+ if (desiredValues?.has(name)) desiredValues.delete(name);
392
+ }
384
393
  }
385
394
  }
386
395
  }
387
396
 
388
397
  // Prepare new export lines for any remaining names
389
398
  const lines: string[] = [];
390
- for (const [spec, names] of desiredExports) {
391
- const missing = Array.from(names);
392
- if (missing.length === 0) continue;
399
+ const specs = new Set<string>([
400
+ ...desiredValueExports.keys(),
401
+ ...desiredTypeExports.keys()
402
+ ]);
403
+
404
+ for (const spec of specs) {
405
+ const missingValues = Array.from(desiredValueExports.get(spec) ?? []);
406
+ const missingTypes = Array.from(desiredTypeExports.get(spec) ?? []);
407
+ if (missingValues.length === 0 && missingTypes.length === 0) continue;
393
408
  const specEscaped = spec.replace(/\\/g, '/');
394
- lines.push(`export { ${missing.join(', ')} } from "${specEscaped}";`);
409
+ if (missingValues.length > 0) {
410
+ lines.push(`export { ${missingValues.join(', ')} } from "${specEscaped}";`);
411
+ }
412
+ if (missingTypes.length > 0) {
413
+ lines.push(`export type { ${missingTypes.join(', ')} } from "${specEscaped}";`);
414
+ }
395
415
  }
396
416
 
397
417
  if (lines.length > 0) {
package/src/module.ts CHANGED
@@ -12,10 +12,24 @@ interface ModuleMethodSpec {
12
12
  documentation?: string;
13
13
  }
14
14
 
15
+ interface ModuleEventSpec {
16
+ name: string;
17
+ eventTypeText: string;
18
+ extraTypeText: string;
19
+ documentation?: string;
20
+ }
21
+
15
22
  interface ModuleDefinition {
16
23
  interfaceName: string;
17
24
  moduleName: string;
18
25
  methods: ModuleMethodSpec[];
26
+ events?: {
27
+ interfaceName: string;
28
+ eventNameTypeName: string;
29
+ eventArgsTypeName: string;
30
+ listenerTypeName: string;
31
+ events: ModuleEventSpec[];
32
+ };
19
33
  supportingStatements: ts.Statement[];
20
34
  sourceFile: ts.SourceFile;
21
35
  }
@@ -32,13 +46,12 @@ function parseModuleDefinition(modulePath: string): ModuleDefinition {
32
46
 
33
47
  let interfaceDecl: ts.InterfaceDeclaration | undefined;
34
48
  const supporting: ts.Statement[] = [];
49
+ const webfInterfaceDecls: ts.InterfaceDeclaration[] = [];
35
50
 
36
51
  for (const stmt of sourceFile.statements) {
37
52
  if (ts.isInterfaceDeclaration(stmt)) {
38
53
  const name = stmt.name.text;
39
- if (!interfaceDecl && name.startsWith('WebF')) {
40
- interfaceDecl = stmt;
41
- }
54
+ if (name.startsWith('WebF')) webfInterfaceDecls.push(stmt);
42
55
  supporting.push(stmt);
43
56
  } else if (
44
57
  ts.isTypeAliasDeclaration(stmt) ||
@@ -49,6 +62,17 @@ function parseModuleDefinition(modulePath: string): ModuleDefinition {
49
62
  }
50
63
  }
51
64
 
65
+ // Prefer the "main module interface": first WebF* interface that declares methods.
66
+ if (!interfaceDecl) {
67
+ interfaceDecl = webfInterfaceDecls.find(decl =>
68
+ decl.members.some(member => ts.isMethodSignature(member))
69
+ );
70
+ }
71
+
72
+ if (!interfaceDecl) {
73
+ interfaceDecl = webfInterfaceDecls[0];
74
+ }
75
+
52
76
  if (!interfaceDecl) {
53
77
  throw new Error(
54
78
  `No interface starting with "WebF" found in module interface file: ${modulePath}`
@@ -100,13 +124,93 @@ function parseModuleDefinition(modulePath: string): ModuleDefinition {
100
124
  });
101
125
  }
102
126
 
127
+ // Optional module events declaration:
128
+ // `interface WebF<ModuleName>ModuleEvents { scanResult: [Event, Payload]; }`
129
+ const eventsInterfaceName = `${interfaceName}ModuleEvents`;
130
+ const eventsDecl = sourceFile.statements.find(
131
+ stmt => ts.isInterfaceDeclaration(stmt) && stmt.name.text === eventsInterfaceName
132
+ ) as ts.InterfaceDeclaration | undefined;
133
+
134
+ let events:
135
+ | {
136
+ interfaceName: string;
137
+ eventNameTypeName: string;
138
+ eventArgsTypeName: string;
139
+ listenerTypeName: string;
140
+ events: ModuleEventSpec[];
141
+ }
142
+ | undefined;
143
+
144
+ if (eventsDecl) {
145
+ const eventSpecs: ModuleEventSpec[] = [];
146
+ for (const member of eventsDecl.members) {
147
+ if (!ts.isPropertySignature(member) || !member.name) continue;
148
+
149
+ const rawName = member.name.getText(sourceFile);
150
+ const eventName = rawName.replace(/['"]/g, '');
151
+
152
+ let eventTypeText = 'Event';
153
+ let extraTypeText = 'any';
154
+
155
+ if (member.type) {
156
+ if (ts.isTupleTypeNode(member.type) && member.type.elements.length === 2) {
157
+ eventTypeText = printer.printNode(
158
+ ts.EmitHint.Unspecified,
159
+ member.type.elements[0],
160
+ sourceFile
161
+ );
162
+ extraTypeText = printer.printNode(
163
+ ts.EmitHint.Unspecified,
164
+ member.type.elements[1],
165
+ sourceFile
166
+ );
167
+ } else {
168
+ eventTypeText = printer.printNode(ts.EmitHint.Unspecified, member.type, sourceFile);
169
+ }
170
+ }
171
+
172
+ let documentation: string | undefined;
173
+ const jsDocs = (member as any).jsDoc as ts.JSDoc[] | undefined;
174
+ if (jsDocs && jsDocs.length > 0) {
175
+ documentation = jsDocs
176
+ .map(doc => doc.comment)
177
+ .filter(Boolean)
178
+ .join('\n');
179
+ }
180
+
181
+ eventSpecs.push({
182
+ name: eventName,
183
+ eventTypeText,
184
+ extraTypeText,
185
+ documentation,
186
+ });
187
+ }
188
+
189
+ if (eventSpecs.length > 0) {
190
+ events = {
191
+ interfaceName: eventsInterfaceName,
192
+ eventNameTypeName: `${interfaceName}ModuleEventName`,
193
+ eventArgsTypeName: `${interfaceName}ModuleEventArgs`,
194
+ listenerTypeName: `${interfaceName}ModuleEventListener`,
195
+ events: eventSpecs,
196
+ };
197
+ }
198
+ }
199
+
103
200
  if (methods.length === 0) {
104
201
  throw new Error(
105
202
  `Interface ${interfaceName} in ${modulePath} does not declare any methods`
106
203
  );
107
204
  }
108
205
 
109
- return { interfaceName, moduleName, methods, supportingStatements: supporting, sourceFile };
206
+ return {
207
+ interfaceName,
208
+ moduleName,
209
+ methods,
210
+ events,
211
+ supportingStatements: supporting,
212
+ sourceFile,
213
+ };
110
214
  }
111
215
 
112
216
  function buildTypesFile(def: ModuleDefinition): string {
@@ -144,6 +248,24 @@ function buildTypesFile(def: ModuleDefinition): string {
144
248
  }
145
249
 
146
250
  lines.push('');
251
+
252
+ if (def.events) {
253
+ const { interfaceName, eventNameTypeName, eventArgsTypeName, listenerTypeName } = def.events;
254
+
255
+ lines.push(`export type ${eventNameTypeName} = Extract<keyof ${interfaceName}, string>;`);
256
+ lines.push(
257
+ `export type ${eventArgsTypeName}<K extends ${eventNameTypeName} = ${eventNameTypeName}> =`
258
+ );
259
+ lines.push(` ${interfaceName}[K] extends readonly [infer E, infer X]`);
260
+ lines.push(` ? [event: (E & { type: K }), extra: X]`);
261
+ lines.push(` : [event: (${interfaceName}[K] & { type: K }), extra: any];`);
262
+ lines.push('');
263
+ lines.push(`export type ${listenerTypeName} = (...args: {`);
264
+ lines.push(` [K in ${eventNameTypeName}]: ${eventArgsTypeName}<K>;`);
265
+ lines.push(`}[${eventNameTypeName}]) => any;`);
266
+ lines.push('');
267
+ }
268
+
147
269
  // Ensure file is treated as a module even if no declarations were emitted.
148
270
  lines.push('export {};');
149
271
  return lines.join('\n');
@@ -180,6 +302,10 @@ function buildIndexFile(def: ModuleDefinition): string {
180
302
  typeImportNames.add(name);
181
303
  }
182
304
  }
305
+ if (def.events) {
306
+ typeImportNames.add(def.events.eventNameTypeName);
307
+ typeImportNames.add(def.events.eventArgsTypeName);
308
+ }
183
309
  const typeImportsSorted = Array.from(typeImportNames).sort();
184
310
  if (typeImportsSorted.length > 0) {
185
311
  lines.push(
@@ -200,6 +326,64 @@ function buildIndexFile(def: ModuleDefinition): string {
200
326
  lines.push(' }');
201
327
  lines.push('');
202
328
 
329
+ if (def.events) {
330
+ lines.push(' private static _moduleListenerInstalled = false;');
331
+ lines.push(
332
+ ' private static _listeners: Record<string, Set<(event: Event, extra: any) => any>> = Object.create(null);'
333
+ );
334
+ lines.push('');
335
+
336
+ lines.push(
337
+ ` static addListener<K extends ${def.events.eventNameTypeName}>(type: K, listener: (...args: ${def.events.eventArgsTypeName}<K>) => any): () => void {`
338
+ );
339
+ lines.push(
340
+ " if (typeof webf === 'undefined' || typeof (webf as any).addWebfModuleListener !== 'function') {"
341
+ );
342
+ lines.push(
343
+ " throw new Error('WebF module event API is not available. Make sure you are running in WebF runtime.');"
344
+ );
345
+ lines.push(' }');
346
+ lines.push('');
347
+ lines.push(' if (!this._moduleListenerInstalled) {');
348
+ lines.push(
349
+ ` (webf as any).addWebfModuleListener('${def.moduleName}', (event: Event, extra: any) => {`
350
+ );
351
+ lines.push(' const set = this._listeners[event.type];');
352
+ lines.push(' if (!set) return;');
353
+ lines.push(' for (const fn of set) { fn(event, extra); }');
354
+ lines.push(' });');
355
+ lines.push(' this._moduleListenerInstalled = true;');
356
+ lines.push(' }');
357
+ lines.push('');
358
+ lines.push(' (this._listeners[type] ??= new Set()).add(listener as any);');
359
+ lines.push('');
360
+ lines.push(' const cls = this;');
361
+ lines.push(' return () => {');
362
+ lines.push(' const set = cls._listeners[type];');
363
+ lines.push(' if (!set) return;');
364
+ lines.push(' set.delete(listener as any);');
365
+ lines.push(' if (set.size === 0) { delete cls._listeners[type]; }');
366
+ lines.push('');
367
+ lines.push(' if (Object.keys(cls._listeners).length === 0) {');
368
+ lines.push(' cls.removeListener();');
369
+ lines.push(' }');
370
+ lines.push(' };');
371
+ lines.push(' }');
372
+ lines.push('');
373
+
374
+ lines.push(' static removeListener(): void {');
375
+ lines.push(' this._listeners = Object.create(null);');
376
+ lines.push(' this._moduleListenerInstalled = false;');
377
+ lines.push(
378
+ " if (typeof webf === 'undefined' || typeof (webf as any).removeWebfModuleListener !== 'function') {"
379
+ );
380
+ lines.push(' return;');
381
+ lines.push(' }');
382
+ lines.push(` (webf as any).removeWebfModuleListener('${def.moduleName}');`);
383
+ lines.push(' }');
384
+ lines.push('');
385
+ }
386
+
203
387
  for (const method of def.methods) {
204
388
  if (method.documentation) {
205
389
  lines.push(' /**');
@@ -246,6 +430,11 @@ function buildIndexFile(def: ModuleDefinition): string {
246
430
  typeExportNames.add(stmt.name.text);
247
431
  }
248
432
  }
433
+ if (def.events) {
434
+ typeExportNames.add(def.events.eventNameTypeName);
435
+ typeExportNames.add(def.events.eventArgsTypeName);
436
+ typeExportNames.add(def.events.listenerTypeName);
437
+ }
249
438
  const sorted = Array.from(typeExportNames).sort();
250
439
  if (sorted.length) {
251
440
  lines.push(' ' + sorted.join(','));
@@ -359,6 +548,9 @@ function buildDartBindings(def: ModuleDefinition, command: string): string {
359
548
  lines.push('// Generated by `webf module-codegen`');
360
549
  lines.push('');
361
550
  lines.push("import 'package:webf/module.dart';");
551
+ if (def.events) {
552
+ lines.push("import 'package:webf/dom.dart';");
553
+ }
362
554
  if (
363
555
  def.methods.some(m =>
364
556
  m.params.some(p => isTsByteArrayUnion(p.typeText))
@@ -371,7 +563,11 @@ function buildDartBindings(def: ModuleDefinition, command: string): string {
371
563
  // Generate Dart classes for supporting TS interfaces (compound option types).
372
564
  const optionInterfaces: ts.InterfaceDeclaration[] = [];
373
565
  for (const stmt of def.supportingStatements) {
374
- if (ts.isInterfaceDeclaration(stmt) && stmt.name.text !== def.interfaceName) {
566
+ if (
567
+ ts.isInterfaceDeclaration(stmt) &&
568
+ stmt.name.text !== def.interfaceName &&
569
+ stmt.name.text !== def.events?.interfaceName
570
+ ) {
375
571
  optionInterfaces.push(stmt);
376
572
  }
377
573
  }
@@ -477,6 +673,27 @@ function buildDartBindings(def: ModuleDefinition, command: string): string {
477
673
  lines.push(` String get name => '${def.moduleName}';`);
478
674
  lines.push('');
479
675
 
676
+ if (def.events) {
677
+ for (const evt of def.events.events) {
678
+ const methodName = `emit${_.upperFirst(_.camelCase(evt.name))}`;
679
+
680
+ const mappedExtra = mapTsParamTypeToDart(evt.extraTypeText, optionTypeNames);
681
+ const dataParamType = mappedExtra.optionClassName
682
+ ? `${mappedExtra.optionClassName}?`
683
+ : 'dynamic';
684
+
685
+ lines.push(` dynamic ${methodName}({Event? event, ${dataParamType} data}) {`);
686
+ if (mappedExtra.optionClassName) {
687
+ lines.push(' final mapped = data?.toMap();');
688
+ lines.push(` return dispatchEvent(event: event ?? Event('${evt.name}'), data: mapped);`);
689
+ } else {
690
+ lines.push(` return dispatchEvent(event: event ?? Event('${evt.name}'), data: data);`);
691
+ }
692
+ lines.push(' }');
693
+ lines.push('');
694
+ }
695
+ }
696
+
480
697
  for (const method of def.methods) {
481
698
  const dartMethodName = _.camelCase(method.name);
482
699
  let dartReturnType = mapTsReturnTypeToDart(method.returnTypeText);
@@ -2,13 +2,25 @@
2
2
  "name": "<%= packageName %>",
3
3
  "version": "<%= version %>",
4
4
  "description": "<%= description %>",
5
- "main": "dist/index.js",
5
+ "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",
7
- "types": "dist/index.d.ts",
7
+ "types": "dist/index.d.mts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.mts",
12
+ "default": "./dist/index.mjs"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.cts",
16
+ "default": "./dist/index.cjs"
17
+ }
18
+ }
19
+ },
8
20
  "files": ["dist"],
9
21
  "scripts": {
10
- "build": "tsup",
11
- "dev": "tsup --watch",
22
+ "build": "tsdown",
23
+ "dev": "tsdown --watch",
12
24
  "clean": "rimraf dist",
13
25
  "prepublishOnly": "npm run build"
14
26
  },
@@ -26,11 +38,10 @@
26
38
  },
27
39
  "devDependencies": {
28
40
  "rimraf": "^5.0.0",
29
- "tsup": "^8.5.0",
41
+ "tsdown": "^0.19.0",
30
42
  "typescript": "^5.8.3"
31
43
  },
32
44
  "publishConfig": {
33
45
  "access": "public"
34
46
  }
35
47
  }
36
-
@@ -1,13 +1,8 @@
1
- import { defineConfig } from 'tsup';
2
-
3
- export default defineConfig({
1
+ module.exports = {
4
2
  entry: ['src/index.ts'],
5
3
  format: ['cjs', 'esm'],
6
4
  dts: true,
7
5
  clean: true,
8
- splitting: false,
9
6
  sourcemap: true,
10
- minify: false,
11
7
  external: [],
12
- });
13
-
8
+ };
@@ -63,12 +63,16 @@ export interface <%= className %>Props {
63
63
 
64
64
  <% if (methods && methods.methods.length > 0) { %>
65
65
  /**
66
- * Element interface with methods accessible via ref
66
+ * Element interface with methods/properties accessible via ref
67
67
  * @example
68
68
  * ```tsx
69
69
  * const ref = useRef<<%= className %>Element>(null);
70
70
  * // Call methods on the element
71
71
  * ref.current?.finishRefresh('success');
72
+ <% if (properties && properties.props && properties.props.length > 0) { %>
73
+ * // Access properties
74
+ * console.log(ref.current?.<%= _.camelCase(properties.props[0].name || 'someProp') %>);
75
+ <% } %>
72
76
  * ```
73
77
  */
74
78
  <% } %>
@@ -76,7 +80,19 @@ export interface <%= className %>Element extends WebFElementWithMethods<{
76
80
  <% _.forEach(methods?.methods, function(method, index) { %>
77
81
  <%= generateMethodDeclarationWithDocs(method, ' ') %>
78
82
  <% }); %>
79
- }> {}
83
+ }> {
84
+ <% _.forEach(properties?.props, function(prop, index) { %>
85
+ <% var propName = _.camelCase(prop.name); %>
86
+ <% if (prop.documentation) { %>
87
+ /** <%= prop.documentation.split('\n')[0] %> */
88
+ <% } %>
89
+ <% if (prop.readonly) { %>
90
+ readonly <%= propName %><% if (prop.optional) { %>?<% } %>: <%= generateReturnType(prop.type) %>;
91
+ <% } else { %>
92
+ <%= propName %><% if (prop.optional) { %>?<% } %>: <%= generateReturnType(prop.type) %>;
93
+ <% } %>
94
+ <% }); %>
95
+ }
80
96
 
81
97
  <% if (properties?.documentation || methods?.documentation || events?.documentation) { %>
82
98
  <% const docs = properties?.documentation || methods?.documentation || events?.documentation; %>
@@ -2,12 +2,24 @@
2
2
  "name": "<%= packageName %>",
3
3
  "version": "<%= version %>",
4
4
  "description": "<%= description %>",
5
- "main": "dist/index.js",
5
+ "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",
7
- "types": "dist/index.d.ts",
8
- "files": ["dist"],
7
+ "types": "dist/index.d.mts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.mts",
12
+ "default": "./dist/index.mjs"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.cts",
16
+ "default": "./dist/index.cjs"
17
+ }
18
+ }
19
+ },
20
+ "files": ["dist", "README.md"],
9
21
  "scripts": {
10
- "build": "tsup"
22
+ "build": "tsdown"
11
23
  },
12
24
  "keywords": [],
13
25
  "author": "",
@@ -23,7 +35,7 @@
23
35
  "@types/react": "^19.1.0",
24
36
  "@types/react-dom": "^19.1.2",
25
37
  "picomatch": "^4.0.2",
26
- "tsup": "^8.5.0",
38
+ "tsdown": "^0.19.0",
27
39
  "typescript": "^5.8.3"
28
40
  }
29
41
  }
@@ -1,10 +1,8 @@
1
- import { defineConfig } from 'tsup'
2
-
3
- export default defineConfig({
1
+ module.exports = {
4
2
  entry: ['src/index.ts'],
5
3
  format: ['esm', 'cjs'],
6
4
  dts: true,
7
5
  sourcemap: true,
8
6
  clean: true,
9
7
  external: ['react', 'react-dom', '@openwebf/react-core-ui'],
10
- })
8
+ };
@@ -4,7 +4,7 @@
4
4
  "description": "<%= description %>",
5
5
  "main": "",
6
6
  "types": "index.d.ts",
7
- "files": ["index.d.ts"],
7
+ "files": ["index.d.ts", "README.md"],
8
8
  "keywords": [],
9
9
  "author": "",
10
10
  "license": "ISC",