@openwebf/webf 0.22.0 → 0.22.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/TYPING_GUIDE.md +208 -0
- package/bin/webf.js +1 -0
- package/package.json +1 -1
- package/src/IDLBlob.ts +2 -2
- package/src/analyzer.ts +19 -1
- package/src/commands.ts +201 -22
- package/src/dart.ts +36 -6
- package/src/declaration.ts +5 -0
- package/src/generator.ts +82 -14
- package/src/react.ts +197 -62
- package/templates/class.dart.tpl +3 -2
- package/templates/gitignore.tpl +8 -1
- package/templates/react.component.tsx.tpl +78 -26
- package/templates/react.index.ts.tpl +0 -1
- package/templates/react.package.json.tpl +3 -0
- package/test/commands.test.ts +0 -5
- package/test/generator.test.ts +29 -8
- package/test/packageName.test.ts +231 -0
- package/test/react.test.ts +94 -9
- package/templates/react.createComponent.tpl +0 -286
package/src/declaration.ts
CHANGED
package/src/generator.ts
CHANGED
|
@@ -64,6 +64,8 @@ interface GenerateOptions {
|
|
|
64
64
|
source: string;
|
|
65
65
|
target: string;
|
|
66
66
|
command: string;
|
|
67
|
+
exclude?: string[];
|
|
68
|
+
packageName?: string;
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
// Batch processing for file operations
|
|
@@ -99,11 +101,14 @@ function validatePaths(source: string, target: string): { source: string; target
|
|
|
99
101
|
return { source: normalizedSource, target: normalizedTarget };
|
|
100
102
|
}
|
|
101
103
|
|
|
102
|
-
function getTypeFiles(source: string): string[] {
|
|
104
|
+
function getTypeFiles(source: string, excludePatterns?: string[]): string[] {
|
|
103
105
|
try {
|
|
106
|
+
const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**'];
|
|
107
|
+
const ignore = excludePatterns ? [...defaultIgnore, ...excludePatterns] : defaultIgnore;
|
|
108
|
+
|
|
104
109
|
const files = glob.globSync("**/*.d.ts", {
|
|
105
110
|
cwd: source,
|
|
106
|
-
ignore:
|
|
111
|
+
ignore: ignore
|
|
107
112
|
});
|
|
108
113
|
|
|
109
114
|
return files.filter(file => !file.includes('global.d.ts'));
|
|
@@ -139,7 +144,7 @@ function createBlobs(typeFiles: string[], source: string, target: string): IDLBl
|
|
|
139
144
|
});
|
|
140
145
|
}
|
|
141
146
|
|
|
142
|
-
export async function dartGen({ source, target, command }: GenerateOptions) {
|
|
147
|
+
export async function dartGen({ source, target, command, exclude }: GenerateOptions) {
|
|
143
148
|
group('Dart Code Generation');
|
|
144
149
|
time('dartGen');
|
|
145
150
|
|
|
@@ -154,7 +159,7 @@ export async function dartGen({ source, target, command }: GenerateOptions) {
|
|
|
154
159
|
}
|
|
155
160
|
|
|
156
161
|
// Get type files
|
|
157
|
-
const typeFiles = getTypeFiles(normalizedSource);
|
|
162
|
+
const typeFiles = getTypeFiles(normalizedSource, exclude);
|
|
158
163
|
info(`Found ${typeFiles.length} type definition files`);
|
|
159
164
|
|
|
160
165
|
if (typeFiles.length === 0) {
|
|
@@ -232,7 +237,7 @@ export async function dartGen({ source, target, command }: GenerateOptions) {
|
|
|
232
237
|
info(`Output directory: ${normalizedTarget}`);
|
|
233
238
|
}
|
|
234
239
|
|
|
235
|
-
export async function reactGen({ source, target }: GenerateOptions) {
|
|
240
|
+
export async function reactGen({ source, target, exclude, packageName }: GenerateOptions) {
|
|
236
241
|
group('React Code Generation');
|
|
237
242
|
time('reactGen');
|
|
238
243
|
|
|
@@ -247,7 +252,7 @@ export async function reactGen({ source, target }: GenerateOptions) {
|
|
|
247
252
|
}
|
|
248
253
|
|
|
249
254
|
// Get type files
|
|
250
|
-
const typeFiles = getTypeFiles(normalizedSource);
|
|
255
|
+
const typeFiles = getTypeFiles(normalizedSource, exclude);
|
|
251
256
|
info(`Found ${typeFiles.length} type definition files`);
|
|
252
257
|
|
|
253
258
|
if (typeFiles.length === 0) {
|
|
@@ -288,9 +293,16 @@ export async function reactGen({ source, target }: GenerateOptions) {
|
|
|
288
293
|
|
|
289
294
|
await processFilesInBatch(blobs, 5, async (blob) => {
|
|
290
295
|
try {
|
|
291
|
-
const result = generateReactComponent(blob);
|
|
296
|
+
const result = generateReactComponent(blob, packageName, blob.relativeDir);
|
|
297
|
+
|
|
298
|
+
// Skip if no content was generated
|
|
299
|
+
if (!result || result.trim().length === 0) {
|
|
300
|
+
debug(`Skipped ${blob.filename} - no components found`);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
292
303
|
|
|
293
304
|
// Maintain the same directory structure as the .d.ts file
|
|
305
|
+
// Always put files under src/ directory
|
|
294
306
|
const outputDir = path.join(normalizedTarget, 'src', blob.relativeDir);
|
|
295
307
|
// Ensure the directory exists
|
|
296
308
|
if (!fs.existsSync(outputDir)) {
|
|
@@ -309,12 +321,68 @@ export async function reactGen({ source, target }: GenerateOptions) {
|
|
|
309
321
|
}
|
|
310
322
|
});
|
|
311
323
|
|
|
312
|
-
// Generate index file
|
|
313
|
-
const indexContent = generateReactIndex(blobs);
|
|
324
|
+
// Generate or update index file
|
|
314
325
|
const indexFilePath = path.join(normalizedTarget, 'src', 'index.ts');
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
326
|
+
const newExports = generateReactIndex(blobs);
|
|
327
|
+
|
|
328
|
+
if (fs.existsSync(indexFilePath)) {
|
|
329
|
+
// Read existing index file
|
|
330
|
+
const existingContent = fs.readFileSync(indexFilePath, 'utf-8');
|
|
331
|
+
const existingLines = existingContent.split('\n');
|
|
332
|
+
|
|
333
|
+
// Parse new exports
|
|
334
|
+
const newExportLines = newExports.split('\n').filter(line => line.startsWith('export '));
|
|
335
|
+
|
|
336
|
+
// Find which exports are missing
|
|
337
|
+
const missingExports: string[] = [];
|
|
338
|
+
for (const newExport of newExportLines) {
|
|
339
|
+
// Extract the export statement to check if it exists
|
|
340
|
+
const exportMatch = newExport.match(/export\s*{\s*([^}]+)\s*}\s*from\s*["']([^"']+)["']/);
|
|
341
|
+
if (exportMatch) {
|
|
342
|
+
const [, exportNames, modulePath] = exportMatch;
|
|
343
|
+
const exportedItems = exportNames.split(',').map(s => s.trim());
|
|
344
|
+
|
|
345
|
+
// Check if this exact export exists
|
|
346
|
+
const exists = existingLines.some(line => {
|
|
347
|
+
if (!line.startsWith('export ')) return false;
|
|
348
|
+
const lineMatch = line.match(/export\s*{\s*([^}]+)\s*}\s*from\s*["']([^"']+)["']/);
|
|
349
|
+
if (!lineMatch) return false;
|
|
350
|
+
const [, lineExportNames, lineModulePath] = lineMatch;
|
|
351
|
+
const lineExportedItems = lineExportNames.split(',').map(s => s.trim());
|
|
352
|
+
|
|
353
|
+
// Check if same module and same exports
|
|
354
|
+
return lineModulePath === modulePath &&
|
|
355
|
+
exportedItems.every(item => lineExportedItems.includes(item)) &&
|
|
356
|
+
lineExportedItems.every(item => exportedItems.includes(item));
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
if (!exists) {
|
|
360
|
+
missingExports.push(newExport);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// If there are missing exports, append them
|
|
366
|
+
if (missingExports.length > 0) {
|
|
367
|
+
let updatedContent = existingContent.trimRight();
|
|
368
|
+
if (!updatedContent.endsWith('\n')) {
|
|
369
|
+
updatedContent += '\n';
|
|
370
|
+
}
|
|
371
|
+
updatedContent += missingExports.join('\n') + '\n';
|
|
372
|
+
|
|
373
|
+
if (writeFileIfChanged(indexFilePath, updatedContent)) {
|
|
374
|
+
filesChanged++;
|
|
375
|
+
debug(`Updated: index.ts (added ${missingExports.length} exports)`);
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
debug(`Skipped: index.ts (all exports already exist)`);
|
|
379
|
+
}
|
|
380
|
+
} else {
|
|
381
|
+
// File doesn't exist, create it
|
|
382
|
+
if (writeFileIfChanged(indexFilePath, newExports)) {
|
|
383
|
+
filesChanged++;
|
|
384
|
+
debug(`Generated: index.ts`);
|
|
385
|
+
}
|
|
318
386
|
}
|
|
319
387
|
|
|
320
388
|
timeEnd('reactGen');
|
|
@@ -323,7 +391,7 @@ export async function reactGen({ source, target }: GenerateOptions) {
|
|
|
323
391
|
info('You can now import these components in your React project.');
|
|
324
392
|
}
|
|
325
393
|
|
|
326
|
-
export async function vueGen({ source, target }: GenerateOptions) {
|
|
394
|
+
export async function vueGen({ source, target, exclude }: GenerateOptions) {
|
|
327
395
|
group('Vue Typings Generation');
|
|
328
396
|
time('vueGen');
|
|
329
397
|
|
|
@@ -338,7 +406,7 @@ export async function vueGen({ source, target }: GenerateOptions) {
|
|
|
338
406
|
}
|
|
339
407
|
|
|
340
408
|
// Get type files
|
|
341
|
-
const typeFiles = getTypeFiles(normalizedSource);
|
|
409
|
+
const typeFiles = getTypeFiles(normalizedSource, exclude);
|
|
342
410
|
info(`Found ${typeFiles.length} type definition files`);
|
|
343
411
|
|
|
344
412
|
if (typeFiles.length === 0) {
|
package/src/react.ts
CHANGED
|
@@ -2,7 +2,7 @@ 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, TypeAliasObject} from "./declaration";
|
|
6
6
|
import {IDLBlob} from "./IDLBlob";
|
|
7
7
|
import {getPointerType, isPointerType} from "./utils";
|
|
8
8
|
|
|
@@ -53,6 +53,21 @@ function generateEventHandlerType(type: ParameterType) {
|
|
|
53
53
|
throw new Error('Unknown event type: ' + pointerType);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
function getEventType(type: ParameterType) {
|
|
57
|
+
if (!isPointerType(type)) {
|
|
58
|
+
return 'Event';
|
|
59
|
+
}
|
|
60
|
+
const pointerType = getPointerType(type);
|
|
61
|
+
if (pointerType === 'CustomEvent') {
|
|
62
|
+
return 'CustomEvent';
|
|
63
|
+
}
|
|
64
|
+
// For specific event types like MouseEvent, TouchEvent, etc.
|
|
65
|
+
if (pointerType.endsWith('Event')) {
|
|
66
|
+
return pointerType;
|
|
67
|
+
}
|
|
68
|
+
return 'Event';
|
|
69
|
+
}
|
|
70
|
+
|
|
56
71
|
function generateMethodDeclaration(method: FunctionDeclaration) {
|
|
57
72
|
var methodName = method.name;
|
|
58
73
|
var args = method.args.map(arg => {
|
|
@@ -69,8 +84,25 @@ function toReactEventName(name: string) {
|
|
|
69
84
|
return _.camelCase(eventName);
|
|
70
85
|
}
|
|
71
86
|
|
|
72
|
-
export function
|
|
73
|
-
|
|
87
|
+
export function toWebFTagName(className: string): string {
|
|
88
|
+
// Special handling for WebF prefix - treat it as a single unit
|
|
89
|
+
if (className.startsWith('WebF')) {
|
|
90
|
+
// Replace WebF with webf- and then kebab-case the rest
|
|
91
|
+
const withoutPrefix = className.substring(4);
|
|
92
|
+
return 'webf-' + _.kebabCase(withoutPrefix);
|
|
93
|
+
} else if (className.startsWith('Flutter')) {
|
|
94
|
+
// Handle Flutter prefix similarly
|
|
95
|
+
const withoutPrefix = className.substring(7);
|
|
96
|
+
return 'flutter-' + _.kebabCase(withoutPrefix);
|
|
97
|
+
}
|
|
98
|
+
// Default kebab-case for other components
|
|
99
|
+
return _.kebabCase(className);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function generateReactComponent(blob: IDLBlob, packageName?: string, relativeDir?: string) {
|
|
103
|
+
const classObjects = blob.objects.filter(obj => obj instanceof ClassObject) as ClassObject[];
|
|
104
|
+
const typeAliases = blob.objects.filter(obj => obj instanceof TypeAliasObject) as TypeAliasObject[];
|
|
105
|
+
|
|
74
106
|
const classObjectDictionary = Object.fromEntries(
|
|
75
107
|
classObjects.map(object => {
|
|
76
108
|
return [object.name, object];
|
|
@@ -89,66 +121,163 @@ export function generateReactComponent(blob: IDLBlob) {
|
|
|
89
121
|
&& !object.name.endsWith('Events');
|
|
90
122
|
});
|
|
91
123
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
124
|
+
// Include type aliases
|
|
125
|
+
const typeAliasDeclarations = typeAliases.map(typeAlias => {
|
|
126
|
+
return `type ${typeAlias.name} = ${typeAlias.type};`;
|
|
127
|
+
}).join('\n');
|
|
128
|
+
|
|
129
|
+
const dependencies = [
|
|
130
|
+
typeAliasDeclarations,
|
|
131
|
+
others.map(object => {
|
|
132
|
+
const props = object.props.map(prop => {
|
|
133
|
+
if (prop.optional) {
|
|
134
|
+
return `${prop.name}?: ${generateReturnType(prop.type)};`;
|
|
135
|
+
}
|
|
136
|
+
return `${prop.name}: ${generateReturnType(prop.type)};`;
|
|
137
|
+
}).join('\n ');
|
|
99
138
|
|
|
100
|
-
|
|
139
|
+
return `
|
|
101
140
|
interface ${object.name} {
|
|
102
141
|
${props}
|
|
103
142
|
}`;
|
|
104
|
-
|
|
143
|
+
}).join('\n\n')
|
|
144
|
+
].filter(Boolean).join('\n\n');
|
|
105
145
|
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
146
|
+
// Generate all components from this file
|
|
147
|
+
const components: string[] = [];
|
|
148
|
+
|
|
149
|
+
// Create a map of component names to their properties and events
|
|
150
|
+
const componentMap = new Map<string, { properties?: ClassObject, events?: ClassObject }>();
|
|
151
|
+
|
|
152
|
+
// Process all Properties interfaces
|
|
153
|
+
properties.forEach(prop => {
|
|
154
|
+
const componentName = prop.name.replace(/Properties$/, '');
|
|
155
|
+
if (!componentMap.has(componentName)) {
|
|
156
|
+
componentMap.set(componentName, {});
|
|
111
157
|
}
|
|
112
|
-
|
|
113
|
-
|
|
158
|
+
componentMap.get(componentName)!.properties = prop;
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Process all Events interfaces
|
|
162
|
+
events.forEach(event => {
|
|
163
|
+
const componentName = event.name.replace(/Events$/, '');
|
|
164
|
+
if (!componentMap.has(componentName)) {
|
|
165
|
+
componentMap.set(componentName, {});
|
|
114
166
|
}
|
|
115
|
-
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
|
|
167
|
+
componentMap.get(componentName)!.events = event;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// If we have multiple components, we need to generate a combined file
|
|
171
|
+
const componentEntries = Array.from(componentMap.entries());
|
|
172
|
+
|
|
173
|
+
if (componentEntries.length === 0) {
|
|
119
174
|
return '';
|
|
120
175
|
}
|
|
176
|
+
|
|
177
|
+
if (componentEntries.length === 1) {
|
|
178
|
+
// Single component - use existing template
|
|
179
|
+
const [className, component] = componentEntries[0];
|
|
180
|
+
|
|
181
|
+
// Determine the import path for createWebFComponent
|
|
182
|
+
const isReactCoreUI = packageName === '@openwebf/react-core-ui';
|
|
183
|
+
let createWebFComponentImport: string;
|
|
184
|
+
|
|
185
|
+
if (isReactCoreUI && relativeDir) {
|
|
186
|
+
// Calculate relative path from current file to utils/createWebFComponent
|
|
187
|
+
// Files are generated in src/<relativeDir>/ and need to import from src/utils/
|
|
188
|
+
const depth = relativeDir.split('/').filter(p => p).length;
|
|
189
|
+
const upPath = '../'.repeat(depth);
|
|
190
|
+
createWebFComponentImport = `import { createWebFComponent, WebFElementWithMethods } from "${upPath}utils/createWebFComponent";`;
|
|
191
|
+
} else {
|
|
192
|
+
createWebFComponentImport = `import { createWebFComponent, WebFElementWithMethods } from "@openwebf/react-core-ui";`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const templateContent = readTemplate('react.component.tsx')
|
|
196
|
+
.replace(
|
|
197
|
+
'import { createWebFComponent, WebFElementWithMethods } from "@openwebf/react-core-ui";',
|
|
198
|
+
createWebFComponentImport
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const content = _.template(templateContent)({
|
|
202
|
+
className: className,
|
|
203
|
+
properties: component.properties,
|
|
204
|
+
events: component.events,
|
|
205
|
+
classObjectDictionary,
|
|
206
|
+
dependencies,
|
|
207
|
+
blob,
|
|
208
|
+
toReactEventName,
|
|
209
|
+
toWebFTagName,
|
|
210
|
+
generateReturnType,
|
|
211
|
+
generateMethodDeclaration,
|
|
212
|
+
generateEventHandlerType,
|
|
213
|
+
getEventType,
|
|
214
|
+
});
|
|
121
215
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
216
|
+
return content.split('\n').filter(str => {
|
|
217
|
+
return str.trim().length > 0;
|
|
218
|
+
}).join('\n');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Multiple components - generate with shared imports
|
|
222
|
+
const componentDefinitions: string[] = [];
|
|
223
|
+
|
|
224
|
+
// Determine the import path for createWebFComponent
|
|
225
|
+
const isReactCoreUI = packageName === '@openwebf/react-core-ui';
|
|
226
|
+
let createWebFComponentImport: string;
|
|
227
|
+
|
|
228
|
+
if (isReactCoreUI && relativeDir) {
|
|
229
|
+
// Calculate relative path from current file to utils/createWebFComponent
|
|
230
|
+
// Files are generated in src/<relativeDir>/ and need to import from src/utils/
|
|
231
|
+
const depth = relativeDir.split('/').filter(p => p).length;
|
|
232
|
+
const upPath = '../'.repeat(depth);
|
|
233
|
+
createWebFComponentImport = `import { createWebFComponent, WebFElementWithMethods } from "${upPath}utils/createWebFComponent";`;
|
|
234
|
+
} else {
|
|
235
|
+
createWebFComponentImport = `import { createWebFComponent, WebFElementWithMethods } from "@openwebf/react-core-ui";`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
componentEntries.forEach(([className, component]) => {
|
|
239
|
+
const content = _.template(readTemplate('react.component.tsx'))({
|
|
240
|
+
className: className,
|
|
241
|
+
properties: component.properties,
|
|
242
|
+
events: component.events,
|
|
243
|
+
classObjectDictionary,
|
|
244
|
+
dependencies: '', // Dependencies will be at the top
|
|
245
|
+
blob,
|
|
246
|
+
toReactEventName,
|
|
247
|
+
toWebFTagName,
|
|
248
|
+
generateReturnType,
|
|
249
|
+
generateMethodDeclaration,
|
|
250
|
+
generateEventHandlerType,
|
|
251
|
+
getEventType,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Remove the import statements from all but the first component
|
|
255
|
+
const lines = content.split('\n');
|
|
256
|
+
const withoutImports = lines.filter(line => {
|
|
257
|
+
return !line.startsWith('import ');
|
|
258
|
+
}).join('\n');
|
|
259
|
+
|
|
260
|
+
componentDefinitions.push(withoutImports);
|
|
140
261
|
});
|
|
141
|
-
|
|
142
|
-
|
|
262
|
+
|
|
263
|
+
// Combine with shared imports at the top
|
|
264
|
+
const result = [
|
|
265
|
+
'import React from "react";',
|
|
266
|
+
createWebFComponentImport,
|
|
267
|
+
'',
|
|
268
|
+
dependencies,
|
|
269
|
+
'',
|
|
270
|
+
...componentDefinitions
|
|
271
|
+
].filter(line => line !== undefined).join('\n');
|
|
272
|
+
|
|
273
|
+
return result.split('\n').filter(str => {
|
|
143
274
|
return str.trim().length > 0;
|
|
144
275
|
}).join('\n');
|
|
145
|
-
|
|
146
|
-
return result;
|
|
147
276
|
}
|
|
148
277
|
|
|
149
278
|
export function generateReactIndex(blobs: IDLBlob[]) {
|
|
150
|
-
const components = blobs.
|
|
151
|
-
const classObjects = blob.objects as ClassObject[];
|
|
279
|
+
const components = blobs.flatMap(blob => {
|
|
280
|
+
const classObjects = blob.objects.filter(obj => obj instanceof ClassObject) as ClassObject[];
|
|
152
281
|
|
|
153
282
|
const properties = classObjects.filter(object => {
|
|
154
283
|
return object.name.endsWith('Properties');
|
|
@@ -157,25 +286,31 @@ export function generateReactIndex(blobs: IDLBlob[]) {
|
|
|
157
286
|
return object.name.endsWith('Events');
|
|
158
287
|
});
|
|
159
288
|
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
289
|
+
// Create a map of component names
|
|
290
|
+
const componentMap = new Map<string, boolean>();
|
|
291
|
+
|
|
292
|
+
// Add all components from Properties interfaces
|
|
293
|
+
properties.forEach(prop => {
|
|
294
|
+
const componentName = prop.name.replace(/Properties$/, '');
|
|
295
|
+
componentMap.set(componentName, true);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Add all components from Events interfaces
|
|
299
|
+
events.forEach(event => {
|
|
300
|
+
const componentName = event.name.replace(/Events$/, '');
|
|
301
|
+
componentMap.set(componentName, true);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Return an array of all components from this file
|
|
305
|
+
return Array.from(componentMap.keys()).map(className => ({
|
|
172
306
|
className: className,
|
|
173
307
|
fileName: blob.filename,
|
|
174
308
|
relativeDir: blob.relativeDir,
|
|
175
|
-
}
|
|
176
|
-
}).filter(
|
|
177
|
-
return
|
|
309
|
+
}));
|
|
310
|
+
}).filter(component => {
|
|
311
|
+
return component.className.length > 0;
|
|
178
312
|
});
|
|
313
|
+
|
|
179
314
|
const content = _.template(readTemplate('react.index.ts'))({
|
|
180
315
|
components,
|
|
181
316
|
});
|
package/templates/class.dart.tpl
CHANGED
|
@@ -33,8 +33,9 @@ abstract class <%= className %>Bindings extends WidgetElement {
|
|
|
33
33
|
<% var attributeName = _.kebabCase(prop.name); %>
|
|
34
34
|
<% var propName = _.camelCase(prop.name); %>
|
|
35
35
|
attributes['<%= attributeName %>'] = ElementAttributeProperty(
|
|
36
|
-
getter: () => <%= propName
|
|
37
|
-
setter: (value) => <%= generateAttributeSetter(propName, prop.type)
|
|
36
|
+
getter: () => <%= generateAttributeGetter(propName, prop.type, prop.optional) %>,
|
|
37
|
+
setter: (value) => <%= generateAttributeSetter(propName, prop.type) %>,
|
|
38
|
+
deleter: () => <%= generateAttributeDeleter(propName, prop.type, prop.optional) %>
|
|
38
39
|
);
|
|
39
40
|
<% }); %>
|
|
40
41
|
}
|
package/templates/gitignore.tpl
CHANGED
|
@@ -1,53 +1,105 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
<% if (events) { %>
|
|
6
|
-
import React, { EventHandler, SyntheticEvent } from "react";
|
|
7
|
-
<% } %>
|
|
8
|
-
import { createComponent } from "<%= utilsPath %>";
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { createWebFComponent, WebFElementWithMethods } from "@openwebf/react-core-ui";
|
|
9
3
|
|
|
10
4
|
<%= dependencies %>
|
|
11
5
|
|
|
12
|
-
interface <%= className %>Props {
|
|
6
|
+
export interface <%= className %>Props {
|
|
13
7
|
<% _.forEach(properties?.props, function(prop, index) { %>
|
|
14
8
|
<% var propName = _.camelCase(prop.name); %>
|
|
9
|
+
<% var attributeName = _.kebabCase(prop.name); %>
|
|
10
|
+
/**
|
|
11
|
+
* <%= propName %> property
|
|
12
|
+
<% if (prop.optional) { %>
|
|
13
|
+
* @default undefined
|
|
14
|
+
<% } %>
|
|
15
|
+
*/
|
|
15
16
|
<% if (prop.optional) { %>
|
|
16
17
|
<%= propName %>?: <%= generateReturnType(prop.type) %>;
|
|
17
18
|
<% } else { %>
|
|
18
19
|
<%= propName %>: <%= generateReturnType(prop.type) %>;
|
|
19
20
|
<% } %>
|
|
21
|
+
|
|
20
22
|
<% }); %>
|
|
21
23
|
<% _.forEach(events?.props, function(prop, index) { %>
|
|
22
24
|
<% var propName = toReactEventName(prop.name); %>
|
|
23
|
-
|
|
25
|
+
/**
|
|
26
|
+
* <%= prop.name %> event handler
|
|
27
|
+
*/
|
|
28
|
+
<%= propName %>?: (event: <%= getEventType(prop.type) %>) => void;
|
|
29
|
+
|
|
24
30
|
<% }); %>
|
|
31
|
+
/**
|
|
32
|
+
* Additional CSS styles
|
|
33
|
+
*/
|
|
34
|
+
style?: React.CSSProperties;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Children elements
|
|
38
|
+
*/
|
|
25
39
|
children?: React.ReactNode;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Additional CSS class names
|
|
43
|
+
*/
|
|
44
|
+
className?: string;
|
|
26
45
|
}
|
|
27
46
|
|
|
28
|
-
export interface <%= className %>Element extends
|
|
29
|
-
<% _.forEach(properties?.props, function(prop, index) { %>
|
|
30
|
-
<% var propName = _.camelCase(prop.name); %>
|
|
31
|
-
<% if (prop.optional) { %>
|
|
32
|
-
<%= propName %>?: <%= generateReturnType(prop.type) %>;
|
|
33
|
-
<% } else { %>
|
|
34
|
-
<%= propName %>: <%= generateReturnType(prop.type) %>;
|
|
35
|
-
<% } %>
|
|
36
|
-
<% }); %>
|
|
47
|
+
export interface <%= className %>Element extends WebFElementWithMethods<{
|
|
37
48
|
<% _.forEach(properties?.methods, function(method, index) { %>
|
|
38
49
|
<%= generateMethodDeclaration(method) %>
|
|
39
50
|
<% }); %>
|
|
40
|
-
}
|
|
51
|
+
}> {}
|
|
41
52
|
|
|
42
|
-
|
|
43
|
-
|
|
53
|
+
/**
|
|
54
|
+
* <%= className %> - WebF <%= className %> component
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```tsx
|
|
58
|
+
* <<%= className %>
|
|
59
|
+
* // Add example props here
|
|
60
|
+
* >
|
|
61
|
+
* Content
|
|
62
|
+
* </<%= className %>>
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export const <%= className %> = createWebFComponent<<%= className %>Element, <%= className %>Props>({
|
|
66
|
+
tagName: '<%= toWebFTagName(className) %>',
|
|
44
67
|
displayName: '<%= className %>',
|
|
68
|
+
|
|
69
|
+
// Map props to attributes
|
|
70
|
+
attributeProps: [<% _.forEach(properties?.props, function(prop, index) { %>
|
|
71
|
+
<% var propName = _.camelCase(prop.name); %>'<%= propName %>',<% }); %>
|
|
72
|
+
],
|
|
73
|
+
|
|
74
|
+
// Convert prop names to attribute names if needed
|
|
75
|
+
attributeMap: {
|
|
76
|
+
<% _.forEach(properties?.props, function(prop, index) { %>
|
|
77
|
+
<% var propName = _.camelCase(prop.name); %>
|
|
78
|
+
<% var attributeName = _.kebabCase(prop.name); %>
|
|
79
|
+
<% if (propName !== attributeName) { %>
|
|
80
|
+
<%= propName %>: '<%= attributeName %>',
|
|
81
|
+
<% } %>
|
|
82
|
+
<% }); %>
|
|
83
|
+
},
|
|
84
|
+
|
|
45
85
|
<% if (events) { %>
|
|
46
|
-
|
|
86
|
+
// Event handlers
|
|
87
|
+
events: [
|
|
47
88
|
<% _.forEach(events?.props, function(prop, index) { %>
|
|
48
89
|
<% var propName = toReactEventName(prop.name); %>
|
|
49
|
-
|
|
90
|
+
{
|
|
91
|
+
propName: '<%= propName %>',
|
|
92
|
+
eventName: '<%= prop.name %>',
|
|
93
|
+
handler: (callback) => (event) => {
|
|
94
|
+
callback((event as <%= getEventType(prop.type) %>));
|
|
95
|
+
},
|
|
96
|
+
},
|
|
50
97
|
<% }); %>
|
|
51
|
-
|
|
98
|
+
],
|
|
52
99
|
<% } %>
|
|
53
|
-
|
|
100
|
+
|
|
101
|
+
// Default prop values
|
|
102
|
+
defaultProps: {
|
|
103
|
+
// Add default values here
|
|
104
|
+
},
|
|
105
|
+
});
|
|
@@ -5,4 +5,3 @@
|
|
|
5
5
|
<% components.forEach(component => { %>
|
|
6
6
|
export { <%= component.className %>, <%= component.className %>Element } from "./<%= component.relativeDir ? component.relativeDir + '/' : '' %><%= component.fileName %>";
|
|
7
7
|
<% }); %>
|
|
8
|
-
export { createComponent } from "./utils/createComponent";
|