@openwebf/webf 0.22.5 → 0.22.8

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/analyzer.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import ts, {HeritageClause, ScriptTarget, VariableStatement} from 'typescript';
2
+ import { TSDocParser, TextRange, DocComment } from '@microsoft/tsdoc';
2
3
  import {IDLBlob} from './IDLBlob';
3
4
  import {
4
5
  ClassObject,
@@ -30,6 +31,9 @@ const sourceFileCache = new Map<string, ts.SourceFile>();
30
31
  // Cache for type conversions to avoid redundant processing
31
32
  const typeConversionCache = new Map<string, ParameterType>();
32
33
 
34
+ // TSDoc parser instance
35
+ const tsdocParser = new TSDocParser();
36
+
33
37
  // Type mapping constants for better performance
34
38
  const BASIC_TYPE_MAP: Partial<Record<ts.SyntaxKind, FunctionArgumentType>> = {
35
39
  [ts.SyntaxKind.StringKeyword]: FunctionArgumentType.dom_string,
@@ -62,7 +66,7 @@ export function analyzer(blob: IDLBlob, definedPropertyCollector: DefinedPropert
62
66
  blob.objects = sourceFile.statements
63
67
  .map(statement => {
64
68
  try {
65
- return walkProgram(blob, statement, definedPropertyCollector, unionTypeCollector);
69
+ return walkProgram(blob, statement, sourceFile, definedPropertyCollector, unionTypeCollector);
66
70
  } catch (error) {
67
71
  console.error(`Error processing statement in ${blob.source}:`, error);
68
72
  return null;
@@ -98,6 +102,118 @@ function getInterfaceName(statement: ts.Statement): string {
98
102
  return statement.name.escapedText as string;
99
103
  }
100
104
 
105
+ function getJSDocComment(node: ts.Node, sourceFile: ts.SourceFile): string | undefined {
106
+
107
+ const sourceText = sourceFile.getFullText();
108
+ const nodeStart = node.getFullStart();
109
+ const nodePos = node.getStart(sourceFile);
110
+
111
+ // Get the text between full start and actual start (includes leading trivia)
112
+ const leadingText = sourceText.substring(nodeStart, nodePos);
113
+
114
+
115
+ // Find JSDoc comment in the leading text
116
+ const jsDocMatch = leadingText.match(/\/\*\*([\s\S]*?)\*\/\s*$/);
117
+ if (!jsDocMatch) return undefined;
118
+
119
+ // Extract the full JSDoc comment including delimiters
120
+ const commentText = jsDocMatch[0];
121
+ const commentStartPos = nodeStart + leadingText.lastIndexOf(commentText);
122
+
123
+ // Create a TextRange for the comment
124
+ const textRange = TextRange.fromStringRange(
125
+ sourceText,
126
+ commentStartPos,
127
+ commentStartPos + commentText.length
128
+ );
129
+
130
+ // Parse the JSDoc comment using TSDoc
131
+ const parserContext = tsdocParser.parseRange(textRange);
132
+ const docComment = parserContext.docComment;
133
+
134
+ // For now, always use the raw comment to preserve all tags including @default
135
+ // TSDoc parser doesn't handle @default tags properly out of the box
136
+
137
+ // Fallback to raw comment if TSDoc parsing fails
138
+ const comment = jsDocMatch[1]
139
+ .split('\n')
140
+ .map(line => line.replace(/^\s*\*\s?/, ''))
141
+ .join('\n')
142
+ .trim();
143
+ return comment || undefined;
144
+ }
145
+
146
+ // Helper function to render TSDoc nodes to string
147
+ function renderDocNodes(nodes: ReadonlyArray<any>): string {
148
+ return nodes.map(node => {
149
+ if (node.kind === 'PlainText') {
150
+ return node.text;
151
+ } else if (node.kind === 'SoftBreak') {
152
+ return '\n';
153
+ } else if (node.kind === 'Paragraph') {
154
+ return renderDocNodes(node.nodes);
155
+ }
156
+ return '';
157
+ }).join('').trim();
158
+ }
159
+
160
+ // Special function to get the first JSDoc comment in a file for the first interface
161
+ function getFirstInterfaceJSDoc(statement: ts.InterfaceDeclaration, sourceFile: ts.SourceFile): string | undefined {
162
+
163
+ // Find all interfaces in the file
164
+ const interfaces: ts.InterfaceDeclaration[] = [];
165
+ ts.forEachChild(sourceFile, child => {
166
+ if (ts.isInterfaceDeclaration(child)) {
167
+ interfaces.push(child);
168
+ }
169
+ });
170
+
171
+ // If this is the first interface, check for a file-level JSDoc
172
+ if (interfaces.length > 0 && interfaces[0] === statement) {
173
+ const sourceText = sourceFile.getFullText();
174
+ const firstInterfacePos = statement.getFullStart();
175
+
176
+ // Get all text before the first interface
177
+ const textBeforeInterface = sourceText.substring(0, firstInterfacePos);
178
+
179
+ // Find the last JSDoc comment before the interface
180
+ const jsDocMatches = textBeforeInterface.match(/\/\*\*([\s\S]*?)\*\//g);
181
+ if (jsDocMatches && jsDocMatches.length > 0) {
182
+ const lastJsDoc = jsDocMatches[jsDocMatches.length - 1];
183
+ const commentStartPos = textBeforeInterface.lastIndexOf(lastJsDoc);
184
+
185
+ // Create a TextRange for the comment
186
+ const textRange = TextRange.fromStringRange(
187
+ sourceText,
188
+ commentStartPos,
189
+ commentStartPos + lastJsDoc.length
190
+ );
191
+
192
+ // Parse the JSDoc comment using TSDoc
193
+ const parserContext = tsdocParser.parseRange(textRange);
194
+ const docComment = parserContext.docComment;
195
+
196
+ // Extract the parsed content
197
+ if (docComment.summarySection) {
198
+ const summary = renderDocNodes(docComment.summarySection.nodes);
199
+ if (summary) return summary;
200
+ }
201
+
202
+ // Fallback to raw comment
203
+ const comment = lastJsDoc
204
+ .replace(/^\/\*\*/, '')
205
+ .replace(/\*\/$/, '')
206
+ .split('\n')
207
+ .map(line => line.replace(/^\s*\*\s?/, ''))
208
+ .join('\n')
209
+ .trim();
210
+ return comment || undefined;
211
+ }
212
+ }
213
+
214
+ return undefined;
215
+ }
216
+
101
217
  function getHeritageType(heritage: HeritageClause): string | null {
102
218
  if (!heritage.types.length) return null;
103
219
 
@@ -217,11 +333,15 @@ function getParameterBaseType(type: ts.TypeNode, mode?: ParameterMode): Paramete
217
333
  }
218
334
 
219
335
  if (type.kind === ts.SyntaxKind.LiteralType) {
220
- // Handle literal types - check if it's a null literal
336
+ // Handle literal types
221
337
  const literalType = type as ts.LiteralTypeNode;
222
338
  if (literalType.literal.kind === ts.SyntaxKind.NullKeyword) {
223
339
  return FunctionArgumentType.null;
224
340
  }
341
+ if (literalType.literal.kind === ts.SyntaxKind.StringLiteral) {
342
+ // Return the string literal value itself
343
+ return (literalType.literal as ts.StringLiteral).text;
344
+ }
225
345
  return FunctionArgumentType.any;
226
346
  }
227
347
 
@@ -411,10 +531,10 @@ function isParamsReadOnly(m: ts.PropertySignature): boolean {
411
531
  return m.modifiers.some(k => k.kind === ts.SyntaxKind.ReadonlyKeyword);
412
532
  }
413
533
 
414
- function walkProgram(blob: IDLBlob, statement: ts.Statement, definedPropertyCollector: DefinedPropertyCollector, unionTypeCollector: UnionTypeCollector) {
534
+ function walkProgram(blob: IDLBlob, statement: ts.Statement, sourceFile: ts.SourceFile, definedPropertyCollector: DefinedPropertyCollector, unionTypeCollector: UnionTypeCollector) {
415
535
  switch(statement.kind) {
416
536
  case ts.SyntaxKind.InterfaceDeclaration:
417
- return processInterfaceDeclaration(statement as ts.InterfaceDeclaration, blob, definedPropertyCollector, unionTypeCollector);
537
+ return processInterfaceDeclaration(statement as ts.InterfaceDeclaration, blob, sourceFile, definedPropertyCollector, unionTypeCollector);
418
538
 
419
539
  case ts.SyntaxKind.VariableStatement:
420
540
  return processVariableStatement(statement as VariableStatement, unionTypeCollector);
@@ -444,12 +564,19 @@ function processTypeAliasDeclaration(
444
564
  function processInterfaceDeclaration(
445
565
  statement: ts.InterfaceDeclaration,
446
566
  blob: IDLBlob,
567
+ sourceFile: ts.SourceFile,
447
568
  definedPropertyCollector: DefinedPropertyCollector,
448
569
  unionTypeCollector: UnionTypeCollector
449
570
  ): ClassObject | null {
450
571
  const interfaceName = statement.name.escapedText.toString();
451
572
  const obj = new ClassObject();
452
573
 
574
+ // Capture JSDoc comment for the interface
575
+ const directComment = getJSDocComment(statement, sourceFile);
576
+ const fileComment = getFirstInterfaceJSDoc(statement, sourceFile);
577
+ obj.documentation = directComment || fileComment;
578
+
579
+
453
580
  // Process heritage clauses
454
581
  if (statement.heritageClauses) {
455
582
  const heritage = statement.heritageClauses[0];
@@ -470,7 +597,7 @@ function processInterfaceDeclaration(
470
597
 
471
598
  // Process members in batches for better performance
472
599
  const members = Array.from(statement.members);
473
- processMembersBatch(members, obj, definedPropertyCollector, unionTypeCollector);
600
+ processMembersBatch(members, obj, sourceFile, definedPropertyCollector, unionTypeCollector);
474
601
 
475
602
  ClassObject.globalClassMap[interfaceName] = obj;
476
603
 
@@ -480,12 +607,13 @@ function processInterfaceDeclaration(
480
607
  function processMembersBatch(
481
608
  members: ts.TypeElement[],
482
609
  obj: ClassObject,
610
+ sourceFile: ts.SourceFile,
483
611
  definedPropertyCollector: DefinedPropertyCollector,
484
612
  unionTypeCollector: UnionTypeCollector
485
613
  ): void {
486
614
  for (const member of members) {
487
615
  try {
488
- processMember(member, obj, definedPropertyCollector, unionTypeCollector);
616
+ processMember(member, obj, sourceFile, definedPropertyCollector, unionTypeCollector);
489
617
  } catch (error) {
490
618
  console.error(`Error processing member:`, error);
491
619
  }
@@ -495,24 +623,25 @@ function processMembersBatch(
495
623
  function processMember(
496
624
  member: ts.TypeElement,
497
625
  obj: ClassObject,
626
+ sourceFile: ts.SourceFile,
498
627
  definedPropertyCollector: DefinedPropertyCollector,
499
628
  unionTypeCollector: UnionTypeCollector
500
629
  ): void {
501
630
  switch(member.kind) {
502
631
  case ts.SyntaxKind.PropertySignature:
503
- processPropertySignature(member as ts.PropertySignature, obj, definedPropertyCollector, unionTypeCollector);
632
+ processPropertySignature(member as ts.PropertySignature, obj, sourceFile, definedPropertyCollector, unionTypeCollector);
504
633
  break;
505
634
 
506
635
  case ts.SyntaxKind.MethodSignature:
507
- processMethodSignature(member as ts.MethodSignature, obj, unionTypeCollector);
636
+ processMethodSignature(member as ts.MethodSignature, obj, sourceFile, unionTypeCollector);
508
637
  break;
509
638
 
510
639
  case ts.SyntaxKind.IndexSignature:
511
- processIndexSignature(member as ts.IndexSignatureDeclaration, obj, unionTypeCollector);
640
+ processIndexSignature(member as ts.IndexSignatureDeclaration, obj, sourceFile, unionTypeCollector);
512
641
  break;
513
642
 
514
643
  case ts.SyntaxKind.ConstructSignature:
515
- processConstructSignature(member as ts.ConstructSignatureDeclaration, obj, unionTypeCollector);
644
+ processConstructSignature(member as ts.ConstructSignatureDeclaration, obj, sourceFile, unionTypeCollector);
516
645
  break;
517
646
  }
518
647
  }
@@ -520,12 +649,14 @@ function processMember(
520
649
  function processPropertySignature(
521
650
  member: ts.PropertySignature,
522
651
  obj: ClassObject,
652
+ sourceFile: ts.SourceFile,
523
653
  definedPropertyCollector: DefinedPropertyCollector,
524
654
  unionTypeCollector: UnionTypeCollector
525
655
  ): void {
526
656
  const prop = new PropsDeclaration();
527
657
  prop.name = getPropName(member.name!, prop);
528
658
  prop.readonly = isParamsReadOnly(member);
659
+ prop.documentation = getJSDocComment(member, sourceFile);
529
660
 
530
661
  definedPropertyCollector.properties.add(prop.name);
531
662
 
@@ -577,10 +708,12 @@ function createAsyncProperty(prop: PropsDeclaration, mode: ParameterMode): Props
577
708
  function processMethodSignature(
578
709
  member: ts.MethodSignature,
579
710
  obj: ClassObject,
711
+ sourceFile: ts.SourceFile,
580
712
  unionTypeCollector: UnionTypeCollector
581
713
  ): void {
582
714
  const f = new FunctionDeclaration();
583
715
  f.name = getPropName(member.name!);
716
+ f.documentation = getJSDocComment(member, sourceFile);
584
717
  f.args = member.parameters.map(params =>
585
718
  paramsNodeToArguments(params, unionTypeCollector)
586
719
  );
@@ -639,6 +772,7 @@ function createAsyncMethod(
639
772
  function processIndexSignature(
640
773
  member: ts.IndexSignatureDeclaration,
641
774
  obj: ClassObject,
775
+ sourceFile: ts.SourceFile,
642
776
  unionTypeCollector: UnionTypeCollector
643
777
  ): void {
644
778
  const prop = new IndexedPropertyDeclaration();
@@ -659,6 +793,7 @@ function processIndexSignature(
659
793
  function processConstructSignature(
660
794
  member: ts.ConstructSignatureDeclaration,
661
795
  obj: ClassObject,
796
+ sourceFile: ts.SourceFile,
662
797
  unionTypeCollector: UnionTypeCollector
663
798
  ): void {
664
799
  const c = new FunctionDeclaration();
package/src/commands.ts CHANGED
@@ -395,6 +395,9 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
395
395
  // Determine if we need to create a new project
396
396
  const needsProjectCreation = !hasPackageJson || !hasGlobalDts || !hasTsConfig;
397
397
 
398
+ // Track if this is an existing project (has all required files)
399
+ const isExistingProject = hasPackageJson && hasGlobalDts && hasTsConfig;
400
+
398
401
  let framework = options.framework;
399
402
  let packageName = options.packageName;
400
403
 
@@ -620,7 +623,7 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
620
623
  // Handle npm publishing if requested via command line option
621
624
  if (options.publishToNpm && framework) {
622
625
  try {
623
- await buildAndPublishPackage(resolvedDistPath, options.npmRegistry);
626
+ await buildAndPublishPackage(resolvedDistPath, options.npmRegistry, isExistingProject);
624
627
  } catch (error) {
625
628
  console.error('\nError during npm publish:', error);
626
629
  process.exit(1);
@@ -655,7 +658,8 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
655
658
  try {
656
659
  await buildAndPublishPackage(
657
660
  resolvedDistPath,
658
- registryAnswer.registry || undefined
661
+ registryAnswer.registry || undefined,
662
+ isExistingProject
659
663
  );
660
664
  } catch (error) {
661
665
  console.error('\nError during npm publish:', error);
@@ -760,20 +764,40 @@ async function buildPackage(packagePath: string): Promise<void> {
760
764
  }
761
765
  }
762
766
 
763
- async function buildAndPublishPackage(packagePath: string, registry?: string): Promise<void> {
767
+ async function buildAndPublishPackage(packagePath: string, registry?: string, isExistingProject: boolean = false): Promise<void> {
764
768
  const packageJsonPath = path.join(packagePath, 'package.json');
765
769
 
766
770
  if (!fs.existsSync(packageJsonPath)) {
767
771
  throw new Error(`No package.json found in ${packagePath}`);
768
772
  }
769
773
 
770
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
774
+ let packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
771
775
  const packageName = packageJson.name;
772
- const packageVersion = packageJson.version;
776
+ let packageVersion = packageJson.version;
773
777
 
774
778
  // First, ensure dependencies are installed and build the package
775
779
  await buildPackage(packagePath);
776
780
 
781
+ // If this is an existing project, increment the patch version before publishing
782
+ if (isExistingProject) {
783
+ console.log(`\nIncrementing version for existing project...`);
784
+ const versionResult = spawnSync(NPM, ['version', 'patch', '--no-git-tag-version'], {
785
+ cwd: packagePath,
786
+ encoding: 'utf-8',
787
+ stdio: 'pipe'
788
+ });
789
+
790
+ if (versionResult.status !== 0) {
791
+ console.error('Failed to increment version:', versionResult.stderr);
792
+ throw new Error('Failed to increment version');
793
+ }
794
+
795
+ // Re-read package.json to get the new version
796
+ packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
797
+ packageVersion = packageJson.version;
798
+ console.log(`Version updated to ${packageVersion}`);
799
+ }
800
+
777
801
  // Set registry if provided
778
802
  if (registry) {
779
803
  console.log(`\nUsing npm registry: ${registry}`);
@@ -45,6 +45,7 @@ export class PropsDeclaration {
45
45
  isSymbol?: boolean;
46
46
  readonly: boolean;
47
47
  optional: boolean;
48
+ documentation?: string;
48
49
  }
49
50
 
50
51
  export class IndexedPropertyDeclaration extends PropsDeclaration {
@@ -77,6 +78,7 @@ export class ClassObject {
77
78
  staticMethods: FunctionDeclaration[] = [];
78
79
  construct?: FunctionDeclaration;
79
80
  kind: ClassObjectKind = ClassObjectKind.interface;
81
+ documentation?: string;
80
82
  }
81
83
 
82
84
  export class FunctionObject {
package/src/react.ts CHANGED
@@ -4,13 +4,16 @@ import path from 'path';
4
4
  import {ParameterType} from "./analyzer";
5
5
  import {ClassObject, FunctionArgumentType, FunctionDeclaration, TypeAliasObject} from "./declaration";
6
6
  import {IDLBlob} from "./IDLBlob";
7
- import {getPointerType, isPointerType} from "./utils";
7
+ import {getPointerType, isPointerType, isUnionType} from "./utils";
8
8
 
9
9
  function readTemplate(name: string) {
10
10
  return fs.readFileSync(path.join(__dirname, '../templates/' + name + '.tpl'), {encoding: 'utf-8'});
11
11
  }
12
12
 
13
13
  function generateReturnType(type: ParameterType) {
14
+ if (isUnionType(type)) {
15
+ return (type.value as ParameterType[]).map(v => `'${v.value}'`).join(' | ');
16
+ }
14
17
  if (isPointerType(type)) {
15
18
  const pointerType = getPointerType(type);
16
19
  return pointerType;
@@ -79,6 +82,20 @@ function generateMethodDeclaration(method: FunctionDeclaration) {
79
82
  return `${methodName}(${args}): ${returnType};`;
80
83
  }
81
84
 
85
+ function generateMethodDeclarationWithDocs(method: FunctionDeclaration, indent: string = ''): string {
86
+ let result = '';
87
+ if (method.documentation) {
88
+ result += `${indent}/**\n`;
89
+ const docLines = method.documentation.split('\n');
90
+ docLines.forEach(line => {
91
+ result += `${indent} * ${line}\n`;
92
+ });
93
+ result += `${indent} */\n`;
94
+ }
95
+ result += `${indent}${generateMethodDeclaration(method)}`;
96
+ return result;
97
+ }
98
+
82
99
  function toReactEventName(name: string) {
83
100
  const eventName = 'on-' + name;
84
101
  return _.camelCase(eventName);
@@ -115,10 +132,14 @@ export function generateReactComponent(blob: IDLBlob, packageName?: string, rela
115
132
  const events = classObjects.filter(object => {
116
133
  return object.name.endsWith('Events');
117
134
  });
135
+ const methods = classObjects.filter(object => {
136
+ return object.name.endsWith('Methods');
137
+ });
118
138
 
119
139
  const others = classObjects.filter(object => {
120
140
  return !object.name.endsWith('Properties')
121
- && !object.name.endsWith('Events');
141
+ && !object.name.endsWith('Events')
142
+ && !object.name.endsWith('Methods');
122
143
  });
123
144
 
124
145
  // Include type aliases
@@ -128,6 +149,21 @@ export function generateReactComponent(blob: IDLBlob, packageName?: string, rela
128
149
 
129
150
  const dependencies = [
130
151
  typeAliasDeclarations,
152
+ // Include Methods interfaces as dependencies
153
+ methods.map(object => {
154
+ const methodDeclarations = object.methods.map(method => {
155
+ return generateMethodDeclarationWithDocs(method, ' ');
156
+ }).join('\n');
157
+
158
+ let interfaceDoc = '';
159
+ if (object.documentation) {
160
+ interfaceDoc = `/**\n${object.documentation.split('\n').map(line => ` * ${line}`).join('\n')}\n */\n`;
161
+ }
162
+
163
+ return `${interfaceDoc}interface ${object.name} {
164
+ ${methodDeclarations}
165
+ }`;
166
+ }).join('\n\n'),
131
167
  others.map(object => {
132
168
  const props = object.props.map(prop => {
133
169
  if (prop.optional) {
@@ -146,8 +182,8 @@ interface ${object.name} {
146
182
  // Generate all components from this file
147
183
  const components: string[] = [];
148
184
 
149
- // Create a map of component names to their properties and events
150
- const componentMap = new Map<string, { properties?: ClassObject, events?: ClassObject }>();
185
+ // Create a map of component names to their properties, events, and methods
186
+ const componentMap = new Map<string, { properties?: ClassObject, events?: ClassObject, methods?: ClassObject }>();
151
187
 
152
188
  // Process all Properties interfaces
153
189
  properties.forEach(prop => {
@@ -167,6 +203,15 @@ interface ${object.name} {
167
203
  componentMap.get(componentName)!.events = event;
168
204
  });
169
205
 
206
+ // Process all Methods interfaces
207
+ methods.forEach(method => {
208
+ const componentName = method.name.replace(/Methods$/, '');
209
+ if (!componentMap.has(componentName)) {
210
+ componentMap.set(componentName, {});
211
+ }
212
+ componentMap.get(componentName)!.methods = method;
213
+ });
214
+
170
215
  // If we have multiple components, we need to generate a combined file
171
216
  const componentEntries = Array.from(componentMap.entries());
172
217
 
@@ -202,6 +247,7 @@ interface ${object.name} {
202
247
  className: className,
203
248
  properties: component.properties,
204
249
  events: component.events,
250
+ methods: component.methods,
205
251
  classObjectDictionary,
206
252
  dependencies,
207
253
  blob,
@@ -209,6 +255,7 @@ interface ${object.name} {
209
255
  toWebFTagName,
210
256
  generateReturnType,
211
257
  generateMethodDeclaration,
258
+ generateMethodDeclarationWithDocs,
212
259
  generateEventHandlerType,
213
260
  getEventType,
214
261
  });
@@ -240,6 +287,7 @@ interface ${object.name} {
240
287
  className: className,
241
288
  properties: component.properties,
242
289
  events: component.events,
290
+ methods: component.methods,
243
291
  classObjectDictionary,
244
292
  dependencies: '', // Dependencies will be at the top
245
293
  blob,
@@ -247,6 +295,7 @@ interface ${object.name} {
247
295
  toWebFTagName,
248
296
  generateReturnType,
249
297
  generateMethodDeclaration,
298
+ generateMethodDeclarationWithDocs,
250
299
  generateEventHandlerType,
251
300
  getEventType,
252
301
  });
@@ -7,12 +7,18 @@ export interface <%= className %>Props {
7
7
  <% _.forEach(properties?.props, function(prop, index) { %>
8
8
  <% var propName = _.camelCase(prop.name); %>
9
9
  <% var attributeName = _.kebabCase(prop.name); %>
10
+ <% if (prop.documentation) { %>
11
+ /**
12
+ * <%= prop.documentation.split('\n').join('\n * ') %>
13
+ */
14
+ <% } else { %>
10
15
  /**
11
16
  * <%= propName %> property
12
17
  <% if (prop.optional) { %>
13
18
  * @default undefined
14
19
  <% } %>
15
20
  */
21
+ <% } %>
16
22
  <% if (prop.optional) { %>
17
23
  <%= propName %>?: <%= generateReturnType(prop.type) %>;
18
24
  <% } else { %>
@@ -22,9 +28,15 @@ export interface <%= className %>Props {
22
28
  <% }); %>
23
29
  <% _.forEach(events?.props, function(prop, index) { %>
24
30
  <% var propName = toReactEventName(prop.name); %>
31
+ <% if (prop.documentation) { %>
32
+ /**
33
+ * <%= prop.documentation.split('\n').join('\n * ') %>
34
+ */
35
+ <% } else { %>
25
36
  /**
26
37
  * <%= prop.name %> event handler
27
38
  */
39
+ <% } %>
28
40
  <%= propName %>?: (event: <%= getEventType(prop.type) %>) => void;
29
41
 
30
42
  <% }); %>
@@ -44,24 +56,63 @@ export interface <%= className %>Props {
44
56
  className?: string;
45
57
  }
46
58
 
59
+ <% if (methods && methods.methods.length > 0) { %>
60
+ /**
61
+ * Element interface with methods accessible via ref
62
+ * @example
63
+ * ```tsx
64
+ * const ref = useRef<<%= className %>Element>(null);
65
+ * // Call methods on the element
66
+ * ref.current?.finishRefresh('success');
67
+ * ```
68
+ */
69
+ <% } %>
47
70
  export interface <%= className %>Element extends WebFElementWithMethods<{
48
- <% _.forEach(properties?.methods, function(method, index) { %>
49
- <%= generateMethodDeclaration(method) %>
71
+ <% _.forEach(methods?.methods, function(method, index) { %>
72
+ <%= generateMethodDeclarationWithDocs(method, ' ') %>
50
73
  <% }); %>
51
74
  }> {}
52
75
 
76
+ <% if (properties?.documentation || methods?.documentation || events?.documentation) { %>
77
+ <% const docs = properties?.documentation || methods?.documentation || events?.documentation; %>
78
+ /**
79
+ * <%= docs %>
80
+ *
81
+ * @example
82
+ * ```tsx<% if (methods && methods.methods.length > 0) { %>
83
+ * const ref = useRef<<%= className %>Element>(null);<% } %>
84
+ *
85
+ * <<%= className %><% if (methods && methods.methods.length > 0) { %>
86
+ * ref={ref}<% } %>
87
+ * // Add props here
88
+ * >
89
+ * Content
90
+ * </<%= className %>><% if (methods && methods.methods.length > 0) { %>
91
+ *
92
+ * // Call methods on the element
93
+ * ref.current?.finishRefresh('success');<% } %>
94
+ * ```
95
+ */
96
+ <% } else { %>
53
97
  /**
54
98
  * <%= className %> - WebF <%= className %> component
55
99
  *
56
100
  * @example
57
- * ```tsx
58
- * <<%= className %>
59
- * // Add example props here
101
+ * ```tsx<% if (methods && methods.methods.length > 0) { %>
102
+ * const ref = useRef<<%= className %>Element>(null);<% } %>
103
+ *
104
+ * <<%= className %><% if (methods && methods.methods.length > 0) { %>
105
+ * ref={ref}<% } %>
106
+ * // Add props here
60
107
  * >
61
108
  * Content
62
- * </<%= className %>>
109
+ * </<%= className %>><% if (methods && methods.methods.length > 0) { %>
110
+ *
111
+ * // Call methods on the element
112
+ * ref.current?.finishRefresh('success');<% } %>
63
113
  * ```
64
114
  */
115
+ <% } %>
65
116
  export const <%= className %> = createWebFComponent<<%= className %>Element, <%= className %>Props>({
66
117
  tagName: '<%= toWebFTagName(className) %>',
67
118
  displayName: '<%= className %>',