@stencil/angular-output-target 0.9.1 → 0.10.1

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/README.md CHANGED
@@ -49,3 +49,4 @@ export const config: Config = {
49
49
  | `excludeComponents` | An array of tag names to exclude from generating component wrappers for. This is helpful when have a custom framework implementation of a specific component or need to extend the base component wrapper behavior. |
50
50
  | `outputType` | Specifies the type of output to be generated. It can take one of the following values: <br />1. `component`: Generates all the component wrappers to be declared on an Angular module. This option is required for Stencil projects using the `dist` hydrated output.<br /> 2. `scam`: Generates a separate Angular module for each component.<br /> 3. `standalone`: Generates standalone component wrappers.<br /> Both `scam` and `standalone` options are compatible with the `dist-custom-elements` output. <br />Note: Please choose the appropriate `outputType` based on your project's requirements and the desired output structure. Defaults to `component`. |
51
51
  | `customElementsDir` | This is the directory where the custom elements are imported from when using the [Custom Elements Bundle](https://stenciljs.com/docs/custom-elements). Defaults to the `components` directory. Only applies for `outputType: "scam"` or `outputType: "standalone"`. |
52
+ | `inlineProperties` | Experimental. When true, tries to inline the properties of components. This is required to enable Angular Language Service to type-check and show jsdocs when using the components in html-templates. |
@@ -1,4 +1,4 @@
1
- import type { ComponentCompilerEvent } from '@stencil/core/internal';
1
+ import type { ComponentCompilerEvent, ComponentCompilerProperty } from '@stencil/core/internal';
2
2
  import type { OutputType } from './types';
3
3
  /**
4
4
  * Creates an Angular component declaration from formatted Stencil compiler metadata.
@@ -9,9 +9,10 @@ import type { OutputType } from './types';
9
9
  * @param methods The methods of the Stencil component. (e.g. ['myMethod']).
10
10
  * @param includeImportCustomElements Whether to define the component as a custom element.
11
11
  * @param standalone Whether to define the component as a standalone component.
12
+ * @param inlineComponentProps List of properties that should be inlined into the component definition.
12
13
  * @returns The component declaration as a string.
13
14
  */
14
- export declare const createAngularComponentDefinition: (tagName: string, inputs: readonly string[], outputs: readonly string[], methods: readonly string[], includeImportCustomElements?: boolean, standalone?: boolean) => string;
15
+ export declare const createAngularComponentDefinition: (tagName: string, inputs: readonly string[], outputs: readonly string[], methods: readonly string[], includeImportCustomElements?: boolean, standalone?: boolean, inlineComponentProps?: readonly ComponentCompilerProperty[]) => string;
15
16
  /**
16
17
  * Creates the component interface type definition.
17
18
  * @param outputType The output type.
@@ -1,4 +1,29 @@
1
1
  import { createComponentEventTypeImports, dashToPascalCase, formatToQuotedList } from './utils';
2
+ /**
3
+ * Creates a property declaration.
4
+ *
5
+ * @param prop A ComponentCompilerEvent or ComponentCompilerProperty to turn into a property declaration.
6
+ * @param type The name of the type (e.g. 'string')
7
+ * @param inlinePropertyAsSetter Inlines the entire property as an empty Setter, to aid Angulars Compilerp
8
+ * @returns The property declaration as a string.
9
+ */
10
+ function createPropertyDeclaration(prop, type, inlinePropertyAsSetter = false) {
11
+ const comment = createDocComment(prop.docs);
12
+ let eventName = prop.name;
13
+ if (/[-/]/.test(prop.name)) {
14
+ // If a member name includes a dash or a forward slash, we need to wrap it in quotes.
15
+ // https://github.com/ionic-team/stencil-ds-output-targets/issues/212
16
+ eventName = `'${prop.name}'`;
17
+ }
18
+ if (inlinePropertyAsSetter) {
19
+ return `${comment.length > 0 ? ` ${comment}` : ''}
20
+ set ${eventName}(_: ${type}) {};`;
21
+ }
22
+ else {
23
+ return `${comment.length > 0 ? ` ${comment}` : ''}
24
+ ${eventName}: ${type};`;
25
+ }
26
+ }
2
27
  /**
3
28
  * Creates an Angular component declaration from formatted Stencil compiler metadata.
4
29
  *
@@ -8,9 +33,10 @@ import { createComponentEventTypeImports, dashToPascalCase, formatToQuotedList }
8
33
  * @param methods The methods of the Stencil component. (e.g. ['myMethod']).
9
34
  * @param includeImportCustomElements Whether to define the component as a custom element.
10
35
  * @param standalone Whether to define the component as a standalone component.
36
+ * @param inlineComponentProps List of properties that should be inlined into the component definition.
11
37
  * @returns The component declaration as a string.
12
38
  */
13
- export const createAngularComponentDefinition = (tagName, inputs, outputs, methods, includeImportCustomElements = false, standalone = false) => {
39
+ export const createAngularComponentDefinition = (tagName, inputs, outputs, methods, includeImportCustomElements = false, standalone = false, inlineComponentProps = []) => {
14
40
  const tagNameAsPascal = dashToPascalCase(tagName);
15
41
  const hasInputs = inputs.length > 0;
16
42
  const hasOutputs = outputs.length > 0;
@@ -36,6 +62,8 @@ export const createAngularComponentDefinition = (tagName, inputs, outputs, metho
36
62
  if (standalone && includeImportCustomElements) {
37
63
  standaloneOption = `\n standalone: true`;
38
64
  }
65
+ const propertyDeclarations = inlineComponentProps.map((m) => createPropertyDeclaration(m, `Components.${tagNameAsPascal}['${m.name}']`, true));
66
+ const propertiesDeclarationText = [`protected el: HTML${tagNameAsPascal}Element;`, ...propertyDeclarations].join('\n ');
39
67
  /**
40
68
  * Notes on the generated output:
41
69
  * - We disable @angular-eslint/no-inputs-metadata-property, so that
@@ -52,7 +80,7 @@ export const createAngularComponentDefinition = (tagName, inputs, outputs, metho
52
80
  inputs: [${formattedInputs}],${standaloneOption}
53
81
  })
54
82
  export class ${tagNameAsPascal} {
55
- protected el: HTMLElement;
83
+ ${propertiesDeclarationText}
56
84
  constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
57
85
  c.detach();
58
86
  this.el = r.nativeElement;${hasOutputs
@@ -146,17 +174,7 @@ export const createComponentTypeDefinition = (outputType, tagNameAsPascal, event
146
174
  customElementsDir,
147
175
  outputType,
148
176
  });
149
- const eventTypes = publicEvents.map((event) => {
150
- const comment = createDocComment(event.docs);
151
- let eventName = event.name;
152
- if (/[-/]/.test(event.name)) {
153
- // If an event name includes a dash or a forward slash, we need to wrap it in quotes.
154
- // https://github.com/ionic-team/stencil-ds-output-targets/issues/212
155
- eventName = `'${event.name}'`;
156
- }
157
- return `${comment.length > 0 ? ` ${comment}` : ''}
158
- ${eventName}: EventEmitter<CustomEvent<${formatOutputType(tagNameAsPascal, event)}>>;`;
159
- });
177
+ const eventTypes = publicEvents.map((event) => createPropertyDeclaration(event, `EventEmitter<CustomEvent<${formatOutputType(tagNameAsPascal, event)}>>`));
160
178
  const interfaceDeclaration = `export declare interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {`;
161
179
  const typeDefinition = (eventTypeImports.length > 0 ? `${eventTypeImports + '\n\n'}` : '') +
162
180
  `${interfaceDeclaration}${eventTypes.length === 0
package/dist/index.cjs.js CHANGED
@@ -1,14 +1,8 @@
1
1
  'use strict';
2
2
 
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
3
  var path = require('path');
6
4
  var os = require('os');
7
5
 
8
- function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
9
-
10
- var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
11
-
12
6
  const OutputTypes = {
13
7
  Component: 'component',
14
8
  Scam: 'scam',
@@ -58,18 +52,18 @@ function normalizePath(str) {
58
52
  return str;
59
53
  }
60
54
  function relativeImport(pathFrom, pathTo, ext) {
61
- let relativePath = path__default["default"].relative(path__default["default"].dirname(pathFrom), path__default["default"].dirname(pathTo));
55
+ let relativePath = path.relative(path.dirname(pathFrom), path.dirname(pathTo));
62
56
  if (relativePath === '') {
63
57
  relativePath = '.';
64
58
  }
65
59
  else if (relativePath[0] !== '.') {
66
60
  relativePath = './' + relativePath;
67
61
  }
68
- return normalizePath(`${relativePath}/${path__default["default"].basename(pathTo, ext)}`);
62
+ return normalizePath(`${relativePath}/${path.basename(pathTo, ext)}`);
69
63
  }
70
64
  async function readPackageJson(config, rootDir) {
71
65
  var _a;
72
- const pkgJsonPath = path__default["default"].join(rootDir, 'package.json');
66
+ const pkgJsonPath = path.join(rootDir, 'package.json');
73
67
  let pkgJson;
74
68
  try {
75
69
  pkgJson = (await ((_a = config.sys) === null || _a === void 0 ? void 0 : _a.readFile(pkgJsonPath, 'utf8')));
@@ -144,6 +138,31 @@ const EXTENDED_PATH_REGEX = /^\\\\\?\\/;
144
138
  const NON_ASCII_REGEX = /[^\x00-\x80]+/;
145
139
  const SLASH_REGEX = /\\/g;
146
140
 
141
+ /**
142
+ * Creates a property declaration.
143
+ *
144
+ * @param prop A ComponentCompilerEvent or ComponentCompilerProperty to turn into a property declaration.
145
+ * @param type The name of the type (e.g. 'string')
146
+ * @param inlinePropertyAsSetter Inlines the entire property as an empty Setter, to aid Angulars Compilerp
147
+ * @returns The property declaration as a string.
148
+ */
149
+ function createPropertyDeclaration(prop, type, inlinePropertyAsSetter = false) {
150
+ const comment = createDocComment(prop.docs);
151
+ let eventName = prop.name;
152
+ if (/[-/]/.test(prop.name)) {
153
+ // If a member name includes a dash or a forward slash, we need to wrap it in quotes.
154
+ // https://github.com/ionic-team/stencil-ds-output-targets/issues/212
155
+ eventName = `'${prop.name}'`;
156
+ }
157
+ if (inlinePropertyAsSetter) {
158
+ return `${comment.length > 0 ? ` ${comment}` : ''}
159
+ set ${eventName}(_: ${type}) {};`;
160
+ }
161
+ else {
162
+ return `${comment.length > 0 ? ` ${comment}` : ''}
163
+ ${eventName}: ${type};`;
164
+ }
165
+ }
147
166
  /**
148
167
  * Creates an Angular component declaration from formatted Stencil compiler metadata.
149
168
  *
@@ -153,9 +172,10 @@ const SLASH_REGEX = /\\/g;
153
172
  * @param methods The methods of the Stencil component. (e.g. ['myMethod']).
154
173
  * @param includeImportCustomElements Whether to define the component as a custom element.
155
174
  * @param standalone Whether to define the component as a standalone component.
175
+ * @param inlineComponentProps List of properties that should be inlined into the component definition.
156
176
  * @returns The component declaration as a string.
157
177
  */
158
- const createAngularComponentDefinition = (tagName, inputs, outputs, methods, includeImportCustomElements = false, standalone = false) => {
178
+ const createAngularComponentDefinition = (tagName, inputs, outputs, methods, includeImportCustomElements = false, standalone = false, inlineComponentProps = []) => {
159
179
  const tagNameAsPascal = dashToPascalCase(tagName);
160
180
  const hasInputs = inputs.length > 0;
161
181
  const hasOutputs = outputs.length > 0;
@@ -181,6 +201,8 @@ const createAngularComponentDefinition = (tagName, inputs, outputs, methods, inc
181
201
  if (standalone && includeImportCustomElements) {
182
202
  standaloneOption = `\n standalone: true`;
183
203
  }
204
+ const propertyDeclarations = inlineComponentProps.map((m) => createPropertyDeclaration(m, `Components.${tagNameAsPascal}['${m.name}']`, true));
205
+ const propertiesDeclarationText = [`protected el: HTML${tagNameAsPascal}Element;`, ...propertyDeclarations].join('\n ');
184
206
  /**
185
207
  * Notes on the generated output:
186
208
  * - We disable @angular-eslint/no-inputs-metadata-property, so that
@@ -197,7 +219,7 @@ const createAngularComponentDefinition = (tagName, inputs, outputs, methods, inc
197
219
  inputs: [${formattedInputs}],${standaloneOption}
198
220
  })
199
221
  export class ${tagNameAsPascal} {
200
- protected el: HTMLElement;
222
+ ${propertiesDeclarationText}
201
223
  constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
202
224
  c.detach();
203
225
  this.el = r.nativeElement;${hasOutputs
@@ -291,17 +313,7 @@ const createComponentTypeDefinition = (outputType, tagNameAsPascal, events, comp
291
313
  customElementsDir,
292
314
  outputType,
293
315
  });
294
- const eventTypes = publicEvents.map((event) => {
295
- const comment = createDocComment(event.docs);
296
- let eventName = event.name;
297
- if (/[-/]/.test(event.name)) {
298
- // If an event name includes a dash or a forward slash, we need to wrap it in quotes.
299
- // https://github.com/ionic-team/stencil-ds-output-targets/issues/212
300
- eventName = `'${event.name}'`;
301
- }
302
- return `${comment.length > 0 ? ` ${comment}` : ''}
303
- ${eventName}: EventEmitter<CustomEvent<${formatOutputType(tagNameAsPascal, event)}>>;`;
304
- });
316
+ const eventTypes = publicEvents.map((event) => createPropertyDeclaration(event, `EventEmitter<CustomEvent<${formatOutputType(tagNameAsPascal, event)}>>`));
305
317
  const interfaceDeclaration = `export declare interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {`;
306
318
  const typeDefinition = (eventTypeImports.length > 0 ? `${eventTypeImports + '\n\n'}` : '') +
307
319
  `${interfaceDeclaration}${eventTypes.length === 0
@@ -336,7 +348,7 @@ async function generateValueAccessors(compilerCtx, components, outputTarget, con
336
348
  if (!Array.isArray(outputTarget.valueAccessorConfigs) || outputTarget.valueAccessorConfigs.length === 0) {
337
349
  return;
338
350
  }
339
- const targetDir = path__default["default"].dirname(outputTarget.directivesProxyFile);
351
+ const targetDir = path.dirname(outputTarget.directivesProxyFile);
340
352
  const normalizedValueAccessors = outputTarget.valueAccessorConfigs.reduce((allAccessors, va) => {
341
353
  const elementSelectors = Array.isArray(va.elementSelectors) ? va.elementSelectors : [va.elementSelectors];
342
354
  const type = va.type;
@@ -354,8 +366,8 @@ async function generateValueAccessors(compilerCtx, components, outputTarget, con
354
366
  await Promise.all(Object.keys(normalizedValueAccessors).map(async (type) => {
355
367
  const valueAccessorType = type; // Object.keys converts to string
356
368
  const targetFileName = `${type}-value-accessor.ts`;
357
- const targetFilePath = path__default["default"].join(targetDir, targetFileName);
358
- const srcFilePath = path__default["default"].join(__dirname, '../resources/control-value-accessors/', targetFileName);
369
+ const targetFilePath = path.join(targetDir, targetFileName);
370
+ const srcFilePath = path.join(__dirname, '../resources/control-value-accessors/', targetFileName);
359
371
  const srcFileContents = await compilerCtx.fs.readFile(srcFilePath);
360
372
  const finalText = createValueAccessor(srcFileContents, normalizedValueAccessors[valueAccessorType], outputTarget.outputType);
361
373
  await compilerCtx.fs.writeFile(targetFilePath, finalText);
@@ -375,14 +387,14 @@ function copyResources$1(config, resourcesFilesToCopy, directory) {
375
387
  }
376
388
  const copyTasks = resourcesFilesToCopy.map((rf) => {
377
389
  return {
378
- src: path__default["default"].join(__dirname, '../resources/control-value-accessors/', rf),
379
- dest: path__default["default"].join(directory, rf),
390
+ src: path.join(__dirname, '../resources/control-value-accessors/', rf),
391
+ dest: path.join(directory, rf),
380
392
  keepDirStructure: false,
381
393
  warn: false,
382
394
  ignore: [],
383
395
  };
384
396
  });
385
- return config.sys.copy(copyTasks, path__default["default"].join(directory));
397
+ return config.sys.copy(copyTasks, path.join(directory));
386
398
  }
387
399
  const VALUE_ACCESSOR_SELECTORS = `<VALUE_ACCESSOR_SELECTORS>`;
388
400
  const VALUE_ACCESSOR_EVENT = `<VALUE_ACCESSOR_EVENT>`;
@@ -426,8 +438,8 @@ async function copyResources(config, outputTarget) {
426
438
  if (!config.sys || !config.sys.copy || !config.sys.glob) {
427
439
  throw new Error('stencil is not properly initialized at this step. Notify the developer');
428
440
  }
429
- const srcDirectory = path__default["default"].join(__dirname, '..', 'angular-component-lib');
430
- const destDirectory = path__default["default"].join(path__default["default"].dirname(outputTarget.directivesProxyFile), 'angular-component-lib');
441
+ const srcDirectory = path.join(__dirname, '..', 'angular-component-lib');
442
+ const destDirectory = path.join(path.dirname(outputTarget.directivesProxyFile), 'angular-component-lib');
431
443
  return config.sys.copy([
432
444
  {
433
445
  src: srcDirectory,
@@ -439,8 +451,8 @@ async function copyResources(config, outputTarget) {
439
451
  ], srcDirectory);
440
452
  }
441
453
  function generateProxies(components, pkgData, outputTarget, rootDir) {
442
- const distTypesDir = path__default["default"].dirname(pkgData.types);
443
- const dtsFilePath = path__default["default"].join(rootDir, distTypesDir, GENERATED_DTS);
454
+ const distTypesDir = path.dirname(pkgData.types);
455
+ const dtsFilePath = path.join(rootDir, distTypesDir, GENERATED_DTS);
444
456
  const { outputType } = outputTarget;
445
457
  const componentsTypeFile = relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
446
458
  const includeSingleComponentAngularModules = outputType === OutputTypes.Scam;
@@ -504,10 +516,11 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
504
516
  const { componentCorePackage, customElementsDir } = outputTarget;
505
517
  for (let cmpMeta of components) {
506
518
  const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
507
- const inputs = [];
519
+ const internalProps = [];
508
520
  if (cmpMeta.properties) {
509
- inputs.push(...cmpMeta.properties.filter(filterInternalProps).map(mapPropName));
521
+ internalProps.push(...cmpMeta.properties.filter(filterInternalProps));
510
522
  }
523
+ const inputs = internalProps.map(mapPropName);
511
524
  if (cmpMeta.virtualProperties) {
512
525
  inputs.push(...cmpMeta.virtualProperties.map(mapPropName));
513
526
  }
@@ -520,13 +533,14 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
520
533
  if (cmpMeta.methods) {
521
534
  methods.push(...cmpMeta.methods.filter(filterInternalProps).map(mapPropName));
522
535
  }
536
+ const inlineComponentProps = outputTarget.inlineProperties ? internalProps : [];
523
537
  /**
524
538
  * For each component, we need to generate:
525
539
  * 1. The @Component decorated class
526
540
  * 2. Optionally the @NgModule decorated class (if includeSingleComponentAngularModules is true)
527
541
  * 3. The component interface (using declaration merging for types).
528
542
  */
529
- const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, inputs, outputs, methods, isCustomElementsBuild, isStandaloneBuild);
543
+ const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, inputs, outputs, methods, isCustomElementsBuild, isStandaloneBuild, inlineComponentProps);
530
544
  const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
531
545
  const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir);
532
546
  proxyFileOutput.push(componentDefinition, '\n');
@@ -564,11 +578,11 @@ function normalizeOutputTarget(config, outputTarget) {
564
578
  if (outputTarget.directivesProxyFile == null) {
565
579
  throw new Error('directivesProxyFile is required. Please set it in the Stencil config.');
566
580
  }
567
- if (outputTarget.directivesProxyFile && !path__default["default"].isAbsolute(outputTarget.directivesProxyFile)) {
568
- results.directivesProxyFile = normalizePath(path__default["default"].join(config.rootDir, outputTarget.directivesProxyFile));
581
+ if (outputTarget.directivesProxyFile && !path.isAbsolute(outputTarget.directivesProxyFile)) {
582
+ results.directivesProxyFile = normalizePath(path.join(config.rootDir, outputTarget.directivesProxyFile));
569
583
  }
570
- if (outputTarget.directivesArrayFile && !path__default["default"].isAbsolute(outputTarget.directivesArrayFile)) {
571
- results.directivesArrayFile = normalizePath(path__default["default"].join(config.rootDir, outputTarget.directivesArrayFile));
584
+ if (outputTarget.directivesArrayFile && !path.isAbsolute(outputTarget.directivesArrayFile)) {
585
+ results.directivesArrayFile = normalizePath(path.join(config.rootDir, outputTarget.directivesArrayFile));
572
586
  }
573
587
  if (outputTarget.includeSingleComponentAngularModules !== undefined) {
574
588
  throw new Error("The 'includeSingleComponentAngularModules' option has been removed. Please use 'outputType' instead.");
package/dist/index.js CHANGED
@@ -136,6 +136,31 @@ const EXTENDED_PATH_REGEX = /^\\\\\?\\/;
136
136
  const NON_ASCII_REGEX = /[^\x00-\x80]+/;
137
137
  const SLASH_REGEX = /\\/g;
138
138
 
139
+ /**
140
+ * Creates a property declaration.
141
+ *
142
+ * @param prop A ComponentCompilerEvent or ComponentCompilerProperty to turn into a property declaration.
143
+ * @param type The name of the type (e.g. 'string')
144
+ * @param inlinePropertyAsSetter Inlines the entire property as an empty Setter, to aid Angulars Compilerp
145
+ * @returns The property declaration as a string.
146
+ */
147
+ function createPropertyDeclaration(prop, type, inlinePropertyAsSetter = false) {
148
+ const comment = createDocComment(prop.docs);
149
+ let eventName = prop.name;
150
+ if (/[-/]/.test(prop.name)) {
151
+ // If a member name includes a dash or a forward slash, we need to wrap it in quotes.
152
+ // https://github.com/ionic-team/stencil-ds-output-targets/issues/212
153
+ eventName = `'${prop.name}'`;
154
+ }
155
+ if (inlinePropertyAsSetter) {
156
+ return `${comment.length > 0 ? ` ${comment}` : ''}
157
+ set ${eventName}(_: ${type}) {};`;
158
+ }
159
+ else {
160
+ return `${comment.length > 0 ? ` ${comment}` : ''}
161
+ ${eventName}: ${type};`;
162
+ }
163
+ }
139
164
  /**
140
165
  * Creates an Angular component declaration from formatted Stencil compiler metadata.
141
166
  *
@@ -145,9 +170,10 @@ const SLASH_REGEX = /\\/g;
145
170
  * @param methods The methods of the Stencil component. (e.g. ['myMethod']).
146
171
  * @param includeImportCustomElements Whether to define the component as a custom element.
147
172
  * @param standalone Whether to define the component as a standalone component.
173
+ * @param inlineComponentProps List of properties that should be inlined into the component definition.
148
174
  * @returns The component declaration as a string.
149
175
  */
150
- const createAngularComponentDefinition = (tagName, inputs, outputs, methods, includeImportCustomElements = false, standalone = false) => {
176
+ const createAngularComponentDefinition = (tagName, inputs, outputs, methods, includeImportCustomElements = false, standalone = false, inlineComponentProps = []) => {
151
177
  const tagNameAsPascal = dashToPascalCase(tagName);
152
178
  const hasInputs = inputs.length > 0;
153
179
  const hasOutputs = outputs.length > 0;
@@ -173,6 +199,8 @@ const createAngularComponentDefinition = (tagName, inputs, outputs, methods, inc
173
199
  if (standalone && includeImportCustomElements) {
174
200
  standaloneOption = `\n standalone: true`;
175
201
  }
202
+ const propertyDeclarations = inlineComponentProps.map((m) => createPropertyDeclaration(m, `Components.${tagNameAsPascal}['${m.name}']`, true));
203
+ const propertiesDeclarationText = [`protected el: HTML${tagNameAsPascal}Element;`, ...propertyDeclarations].join('\n ');
176
204
  /**
177
205
  * Notes on the generated output:
178
206
  * - We disable @angular-eslint/no-inputs-metadata-property, so that
@@ -189,7 +217,7 @@ const createAngularComponentDefinition = (tagName, inputs, outputs, methods, inc
189
217
  inputs: [${formattedInputs}],${standaloneOption}
190
218
  })
191
219
  export class ${tagNameAsPascal} {
192
- protected el: HTMLElement;
220
+ ${propertiesDeclarationText}
193
221
  constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
194
222
  c.detach();
195
223
  this.el = r.nativeElement;${hasOutputs
@@ -283,17 +311,7 @@ const createComponentTypeDefinition = (outputType, tagNameAsPascal, events, comp
283
311
  customElementsDir,
284
312
  outputType,
285
313
  });
286
- const eventTypes = publicEvents.map((event) => {
287
- const comment = createDocComment(event.docs);
288
- let eventName = event.name;
289
- if (/[-/]/.test(event.name)) {
290
- // If an event name includes a dash or a forward slash, we need to wrap it in quotes.
291
- // https://github.com/ionic-team/stencil-ds-output-targets/issues/212
292
- eventName = `'${event.name}'`;
293
- }
294
- return `${comment.length > 0 ? ` ${comment}` : ''}
295
- ${eventName}: EventEmitter<CustomEvent<${formatOutputType(tagNameAsPascal, event)}>>;`;
296
- });
314
+ const eventTypes = publicEvents.map((event) => createPropertyDeclaration(event, `EventEmitter<CustomEvent<${formatOutputType(tagNameAsPascal, event)}>>`));
297
315
  const interfaceDeclaration = `export declare interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {`;
298
316
  const typeDefinition = (eventTypeImports.length > 0 ? `${eventTypeImports + '\n\n'}` : '') +
299
317
  `${interfaceDeclaration}${eventTypes.length === 0
@@ -496,10 +514,11 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
496
514
  const { componentCorePackage, customElementsDir } = outputTarget;
497
515
  for (let cmpMeta of components) {
498
516
  const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
499
- const inputs = [];
517
+ const internalProps = [];
500
518
  if (cmpMeta.properties) {
501
- inputs.push(...cmpMeta.properties.filter(filterInternalProps).map(mapPropName));
519
+ internalProps.push(...cmpMeta.properties.filter(filterInternalProps));
502
520
  }
521
+ const inputs = internalProps.map(mapPropName);
503
522
  if (cmpMeta.virtualProperties) {
504
523
  inputs.push(...cmpMeta.virtualProperties.map(mapPropName));
505
524
  }
@@ -512,13 +531,14 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
512
531
  if (cmpMeta.methods) {
513
532
  methods.push(...cmpMeta.methods.filter(filterInternalProps).map(mapPropName));
514
533
  }
534
+ const inlineComponentProps = outputTarget.inlineProperties ? internalProps : [];
515
535
  /**
516
536
  * For each component, we need to generate:
517
537
  * 1. The @Component decorated class
518
538
  * 2. Optionally the @NgModule decorated class (if includeSingleComponentAngularModules is true)
519
539
  * 3. The component interface (using declaration merging for types).
520
540
  */
521
- const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, inputs, outputs, methods, isCustomElementsBuild, isStandaloneBuild);
541
+ const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, inputs, outputs, methods, isCustomElementsBuild, isStandaloneBuild, inlineComponentProps);
522
542
  const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
523
543
  const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir);
524
544
  proxyFileOutput.push(componentDefinition, '\n');
@@ -101,10 +101,11 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
101
101
  const { componentCorePackage, customElementsDir } = outputTarget;
102
102
  for (let cmpMeta of components) {
103
103
  const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
104
- const inputs = [];
104
+ const internalProps = [];
105
105
  if (cmpMeta.properties) {
106
- inputs.push(...cmpMeta.properties.filter(filterInternalProps).map(mapPropName));
106
+ internalProps.push(...cmpMeta.properties.filter(filterInternalProps));
107
107
  }
108
+ const inputs = internalProps.map(mapPropName);
108
109
  if (cmpMeta.virtualProperties) {
109
110
  inputs.push(...cmpMeta.virtualProperties.map(mapPropName));
110
111
  }
@@ -117,13 +118,14 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
117
118
  if (cmpMeta.methods) {
118
119
  methods.push(...cmpMeta.methods.filter(filterInternalProps).map(mapPropName));
119
120
  }
121
+ const inlineComponentProps = outputTarget.inlineProperties ? internalProps : [];
120
122
  /**
121
123
  * For each component, we need to generate:
122
124
  * 1. The @Component decorated class
123
125
  * 2. Optionally the @NgModule decorated class (if includeSingleComponentAngularModules is true)
124
126
  * 3. The component interface (using declaration merging for types).
125
127
  */
126
- const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, inputs, outputs, methods, isCustomElementsBuild, isStandaloneBuild);
128
+ const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, inputs, outputs, methods, isCustomElementsBuild, isStandaloneBuild, inlineComponentProps);
127
129
  const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
128
130
  const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir);
129
131
  proxyFileOutput.push(componentDefinition, '\n');
package/dist/types.d.ts CHANGED
@@ -27,6 +27,12 @@ export interface OutputTargetAngular {
27
27
  * - `standalone` - Generate a component with the `standalone` flag set to `true`.
28
28
  */
29
29
  outputType?: OutputType;
30
+ /**
31
+ * Experimental (!)
32
+ * When true, tries to inline the properties of components. This is required to enable Angular Language Service
33
+ * to type-check and show jsdocs when using the components in html-templates.
34
+ */
35
+ inlineProperties?: boolean;
30
36
  }
31
37
  export type ValueAccessorTypes = 'text' | 'radio' | 'select' | 'number' | 'boolean';
32
38
  export interface ValueAccessorConfig {
package/package.json CHANGED
@@ -1,9 +1,16 @@
1
1
  {
2
2
  "name": "@stencil/angular-output-target",
3
- "version": "0.9.1",
3
+ "version": "0.10.1",
4
4
  "description": "Angular output target for @stencil/core components.",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.cjs.js"
12
+ }
13
+ },
7
14
  "types": "dist/index.d.ts",
8
15
  "files": [
9
16
  "dist/",
@@ -27,8 +34,8 @@
27
34
  "prettier.base": "prettier \"./({angular-component-lib,src,test,__tests__}/**/*.{ts,tsx,js,jsx})|*.{ts,tsx,js,jsx}\"",
28
35
  "prettier.dry-run": "pnpm run prettier.base --list-different",
29
36
  "release": "np",
30
- "test": "jest --passWithNoTests",
31
- "test.watch": "jest --watch"
37
+ "test": "vitest --run",
38
+ "test.watch": "vitest"
32
39
  },
33
40
  "repository": {
34
41
  "type": "git",
@@ -44,29 +51,14 @@
44
51
  "@angular/core": "8.2.14",
45
52
  "@angular/forms": "8.2.14",
46
53
  "@types/node": "^18.0.0",
47
- "jest": "^27.0.0",
48
- "jest-environment-jsdom": "^27.0.0",
49
54
  "npm-run-all2": "^6.2.4",
50
55
  "rimraf": "^5.0.0",
51
56
  "rollup": "^2.23.1",
52
- "typescript": "~5.0.4"
57
+ "typescript": "~5.7.0",
58
+ "vitest": "^2.1.4"
53
59
  },
54
60
  "peerDependencies": {
55
61
  "@stencil/core": ">=2.0.0 || >=3 || >= 4.0.0-beta.0 || >= 4.0.0"
56
62
  },
57
- "jest": {
58
- "transform": {
59
- "^.+\\.(js|ts|tsx)$": "<rootDir>/test/jest.preprocessor.js"
60
- },
61
- "testRegex": "(\\.(test|spec))\\.(ts?|tsx?|jsx?)$",
62
- "moduleFileExtensions": [
63
- "ts",
64
- "tsx",
65
- "js",
66
- "json",
67
- "jsx"
68
- ],
69
- "testURL": "http://localhost"
70
- },
71
63
  "gitHead": "a3588e905186a0e86e7f88418fd5b2f9531b55e0"
72
64
  }
@@ -21,7 +21,7 @@ export class BooleanValueAccessor extends ValueAccessor {
21
21
  constructor(el: ElementRef) {
22
22
  super(el);
23
23
  }
24
- writeValue(value: any) {
24
+ override writeValue(value: any) {
25
25
  this.el.nativeElement.checked = this.lastValue = value == null ? false : value;
26
26
  }
27
27
  }
@@ -21,7 +21,7 @@ export class NumericValueAccessor extends ValueAccessor {
21
21
  constructor(el: ElementRef) {
22
22
  super(el);
23
23
  }
24
- registerOnChange(fn: (_: number | null) => void) {
24
+ override registerOnChange(fn: (_: number | null) => void) {
25
25
  super.registerOnChange(value => {
26
26
  fn(value === '' ? null : parseFloat(value));
27
27
  });