@stencil/angular-output-target 0.0.0-dev.11698339436.1d39048b
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/LICENSE.md +9 -0
- package/README.md +51 -0
- package/angular-component-lib/utils.ts +57 -0
- package/dist/generate-angular-component.d.ts +24 -0
- package/dist/generate-angular-component.js +153 -0
- package/dist/generate-angular-directives-file.d.ts +3 -0
- package/dist/generate-angular-directives-file.js +20 -0
- package/dist/generate-angular-modules.d.ts +6 -0
- package/dist/generate-angular-modules.js +17 -0
- package/dist/generate-value-accessors.d.ts +8 -0
- package/dist/generate-value-accessors.js +56 -0
- package/dist/index.cjs.js +560 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +552 -0
- package/dist/output-angular.d.ts +4 -0
- package/dist/output-angular.js +138 -0
- package/dist/plugin.d.ts +4 -0
- package/dist/plugin.js +37 -0
- package/dist/types.d.ts +40 -0
- package/dist/types.js +0 -0
- package/dist/utils.d.ts +44 -0
- package/dist/utils.js +138 -0
- package/package.json +65 -0
- package/resources/control-value-accessors/boolean-value-accessor.ts +27 -0
- package/resources/control-value-accessors/number-value-accessor.ts +29 -0
- package/resources/control-value-accessors/radio-value-accessor.ts +24 -0
- package/resources/control-value-accessors/select-value-accessor.ts +24 -0
- package/resources/control-value-accessors/text-value-accessor.ts +24 -0
- package/resources/control-value-accessors/value-accessor.ts +39 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019-present Drifty Co.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# @stencil/angular-output-target
|
|
2
|
+
|
|
3
|
+
Stencil can generate Angular component wrappers for your web components. This allows your Stencil components to be used within an Angular application. The benefits of using Stencil's component wrappers over the standard web components include:
|
|
4
|
+
|
|
5
|
+
- Angular component wrappers will be detached from change detection, preventing unnecessary repaints of your web component.
|
|
6
|
+
- Web component events will be converted to RxJS observables to align with Angular's @Output() and will not emit across component boundaries.
|
|
7
|
+
- Optionally, form control web components can be used as control value accessors with Angular's reactive forms or [ngModel].
|
|
8
|
+
|
|
9
|
+
For a detailed guide on how to add the angular output target to a project, visit: https://stenciljs.com/docs/angular.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @stencil/angular-output-target
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
In your `stencil.config.ts` add the following configuration to the `outputTargets` section:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { Config } from '@stencil/core';
|
|
23
|
+
import { angularOutputTarget } from '@stencil/angular-output-target';
|
|
24
|
+
|
|
25
|
+
export const config: Config = {
|
|
26
|
+
namespace: 'demo',
|
|
27
|
+
outputTargets: [
|
|
28
|
+
angularOutputTarget({
|
|
29
|
+
componentCorePackage: 'component-library',
|
|
30
|
+
directivesProxyFile: '../component-library-angular/src/directives/proxies.ts',
|
|
31
|
+
directivesArrayFile: '../component-library-angular/src/directives/index.ts',
|
|
32
|
+
}),
|
|
33
|
+
{
|
|
34
|
+
type: 'dist',
|
|
35
|
+
esmLoaderPath: '../loader',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Config Options
|
|
42
|
+
|
|
43
|
+
| Property | Description |
|
|
44
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
45
|
+
| `componentCorePackage` | The NPM package name of your Stencil component library. This package is used as a dependency for your Angular wrappers. |
|
|
46
|
+
| `directivesProxyFile` | The output file of all the component wrappers generated by the output target. This file path should point to a location within your Angular library/project. |
|
|
47
|
+
| `directivesArrayFile` | The output file of a constant of all the generated component wrapper classes. Used for easily declaring and exporting the generated components from an `NgModule`. This file path should point to a location within your Angular library/project. |
|
|
48
|
+
| `valueAccessorConfigs` | The configuration object for how individual web components behave with Angular control value accessors. |
|
|
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
|
+
| `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
|
+
| `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"`. |
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/* tslint:disable */
|
|
3
|
+
import { fromEvent } from 'rxjs';
|
|
4
|
+
|
|
5
|
+
export const proxyInputs = (Cmp: any, inputs: string[]) => {
|
|
6
|
+
const Prototype = Cmp.prototype;
|
|
7
|
+
inputs.forEach((item) => {
|
|
8
|
+
Object.defineProperty(Prototype, item, {
|
|
9
|
+
get() {
|
|
10
|
+
return this.el[item];
|
|
11
|
+
},
|
|
12
|
+
set(val: any) {
|
|
13
|
+
this.z.runOutsideAngular(() => (this.el[item] = val));
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const proxyMethods = (Cmp: any, methods: string[]) => {
|
|
20
|
+
const Prototype = Cmp.prototype;
|
|
21
|
+
methods.forEach((methodName) => {
|
|
22
|
+
Prototype[methodName] = function () {
|
|
23
|
+
const args = arguments;
|
|
24
|
+
return this.z.runOutsideAngular(() => this.el[methodName].apply(this.el, args));
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const proxyOutputs = (instance: any, el: any, events: string[]) => {
|
|
30
|
+
events.forEach((eventName) => (instance[eventName] = fromEvent(el, eventName)));
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const defineCustomElement = (tagName: string, customElement: any) => {
|
|
34
|
+
if (customElement !== undefined && typeof customElements !== 'undefined' && !customElements.get(tagName)) {
|
|
35
|
+
customElements.define(tagName, customElement);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// tslint:disable-next-line: only-arrow-functions
|
|
40
|
+
export function ProxyCmp(opts: { defineCustomElementFn?: () => void; inputs?: any; methods?: any }) {
|
|
41
|
+
const decorator = function (cls: any) {
|
|
42
|
+
const { defineCustomElementFn, inputs, methods } = opts;
|
|
43
|
+
|
|
44
|
+
if (defineCustomElementFn !== undefined) {
|
|
45
|
+
defineCustomElementFn();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (inputs) {
|
|
49
|
+
proxyInputs(cls, inputs);
|
|
50
|
+
}
|
|
51
|
+
if (methods) {
|
|
52
|
+
proxyMethods(cls, methods);
|
|
53
|
+
}
|
|
54
|
+
return cls;
|
|
55
|
+
};
|
|
56
|
+
return decorator;
|
|
57
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ComponentCompilerEvent } from '@stencil/core/internal';
|
|
2
|
+
import type { OutputType } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Creates an Angular component declaration from formatted Stencil compiler metadata.
|
|
5
|
+
*
|
|
6
|
+
* @param tagName The tag name of the component.
|
|
7
|
+
* @param inputs The inputs of the Stencil component (e.g. ['myInput']).
|
|
8
|
+
* @param outputs The outputs/events of the Stencil component. (e.g. ['myOutput']).
|
|
9
|
+
* @param methods The methods of the Stencil component. (e.g. ['myMethod']).
|
|
10
|
+
* @param includeImportCustomElements Whether to define the component as a custom element.
|
|
11
|
+
* @param standalone Whether to define the component as a standalone component.
|
|
12
|
+
* @returns The component declaration as a string.
|
|
13
|
+
*/
|
|
14
|
+
export declare const createAngularComponentDefinition: (tagName: string, inputs: readonly string[], outputs: readonly string[], methods: readonly string[], includeImportCustomElements?: boolean, standalone?: boolean) => string;
|
|
15
|
+
/**
|
|
16
|
+
* Creates the component interface type definition.
|
|
17
|
+
* @param outputType The output type.
|
|
18
|
+
* @param tagNameAsPascal The tag name as PascalCase.
|
|
19
|
+
* @param events The events to generate the interface properties for.
|
|
20
|
+
* @param componentCorePackage The component core package.
|
|
21
|
+
* @param customElementsDir The custom elements directory.
|
|
22
|
+
* @returns The component interface type definition as a string.
|
|
23
|
+
*/
|
|
24
|
+
export declare const createComponentTypeDefinition: (outputType: OutputType, tagNameAsPascal: string, events: readonly ComponentCompilerEvent[], componentCorePackage: string, customElementsDir?: string | undefined) => string;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { createComponentEventTypeImports, dashToPascalCase, formatToQuotedList } from './utils';
|
|
2
|
+
/**
|
|
3
|
+
* Creates an Angular component declaration from formatted Stencil compiler metadata.
|
|
4
|
+
*
|
|
5
|
+
* @param tagName The tag name of the component.
|
|
6
|
+
* @param inputs The inputs of the Stencil component (e.g. ['myInput']).
|
|
7
|
+
* @param outputs The outputs/events of the Stencil component. (e.g. ['myOutput']).
|
|
8
|
+
* @param methods The methods of the Stencil component. (e.g. ['myMethod']).
|
|
9
|
+
* @param includeImportCustomElements Whether to define the component as a custom element.
|
|
10
|
+
* @param standalone Whether to define the component as a standalone component.
|
|
11
|
+
* @returns The component declaration as a string.
|
|
12
|
+
*/
|
|
13
|
+
export const createAngularComponentDefinition = (tagName, inputs, outputs, methods, includeImportCustomElements = false, standalone = false) => {
|
|
14
|
+
const tagNameAsPascal = dashToPascalCase(tagName);
|
|
15
|
+
const hasInputs = inputs.length > 0;
|
|
16
|
+
const hasOutputs = outputs.length > 0;
|
|
17
|
+
const hasMethods = methods.length > 0;
|
|
18
|
+
// Formats the input strings into comma separated, single quoted values.
|
|
19
|
+
const formattedInputs = formatToQuotedList(inputs);
|
|
20
|
+
// Formats the output strings into comma separated, single quoted values.
|
|
21
|
+
const formattedOutputs = formatToQuotedList(outputs);
|
|
22
|
+
// Formats the method strings into comma separated, single quoted values.
|
|
23
|
+
const formattedMethods = formatToQuotedList(methods);
|
|
24
|
+
const proxyCmpOptions = [];
|
|
25
|
+
if (includeImportCustomElements) {
|
|
26
|
+
const defineCustomElementFn = `define${tagNameAsPascal}`;
|
|
27
|
+
proxyCmpOptions.push(`\n defineCustomElementFn: ${defineCustomElementFn}`);
|
|
28
|
+
}
|
|
29
|
+
if (hasInputs) {
|
|
30
|
+
proxyCmpOptions.push(`\n inputs: [${formattedInputs}]`);
|
|
31
|
+
}
|
|
32
|
+
if (hasMethods) {
|
|
33
|
+
proxyCmpOptions.push(`\n methods: [${formattedMethods}]`);
|
|
34
|
+
}
|
|
35
|
+
let standaloneOption = '';
|
|
36
|
+
if (standalone && includeImportCustomElements) {
|
|
37
|
+
standaloneOption = `\n standalone: true`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Notes on the generated output:
|
|
41
|
+
* - We disable @angular-eslint/no-inputs-metadata-property, so that
|
|
42
|
+
* Angular does not complain about the inputs property. The output target
|
|
43
|
+
* uses the inputs property to define the inputs of the component instead of
|
|
44
|
+
* having to use the @Input decorator (and manually define the type and default value).
|
|
45
|
+
*/
|
|
46
|
+
const output = `@ProxyCmp({${proxyCmpOptions.join(',')}\n})
|
|
47
|
+
@Component({
|
|
48
|
+
selector: '${tagName}',
|
|
49
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
50
|
+
template: '<ng-content></ng-content>',
|
|
51
|
+
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
|
52
|
+
inputs: [${formattedInputs}],${standaloneOption}
|
|
53
|
+
})
|
|
54
|
+
export class ${tagNameAsPascal} {
|
|
55
|
+
protected el: HTMLElement;
|
|
56
|
+
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
|
|
57
|
+
c.detach();
|
|
58
|
+
this.el = r.nativeElement;${hasOutputs
|
|
59
|
+
? `
|
|
60
|
+
proxyOutputs(this, this.el, [${formattedOutputs}]);`
|
|
61
|
+
: ''}
|
|
62
|
+
}
|
|
63
|
+
}`;
|
|
64
|
+
return output;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Sanitizes and formats the component event type.
|
|
68
|
+
* @param componentClassName The class name of the component (e.g. 'MyComponent')
|
|
69
|
+
* @param event The Stencil component event.
|
|
70
|
+
* @returns The sanitized event type as a string.
|
|
71
|
+
*/
|
|
72
|
+
const formatOutputType = (componentClassName, event) => {
|
|
73
|
+
const prefix = `I${componentClassName}`;
|
|
74
|
+
/**
|
|
75
|
+
* The original attribute contains the original type defined by the devs.
|
|
76
|
+
* This regexp normalizes the reference, by removing linebreaks,
|
|
77
|
+
* replacing consecutive spaces with a single space, and adding a single space after commas.
|
|
78
|
+
*/
|
|
79
|
+
return Object.entries(event.complexType.references)
|
|
80
|
+
.filter(([_, refObject]) => refObject.location === 'local' || refObject.location === 'import')
|
|
81
|
+
.reduce((type, [src, dst]) => {
|
|
82
|
+
let renamedType = type;
|
|
83
|
+
if (!type.startsWith(prefix)) {
|
|
84
|
+
renamedType = `I${componentClassName}${type}`;
|
|
85
|
+
}
|
|
86
|
+
return (renamedType
|
|
87
|
+
.replace(new RegExp(`^${src}$`, 'g'), `${dst}`)
|
|
88
|
+
// Capture all instances of the `src` field surrounded by non-word characters on each side and join them.
|
|
89
|
+
.replace(new RegExp(`([^\\w])${src}([^\\w])`, 'g'), (v, p1, p2) => {
|
|
90
|
+
if ((dst === null || dst === void 0 ? void 0 : dst.location) === 'import') {
|
|
91
|
+
/**
|
|
92
|
+
* Replaces a complex type reference within a generic type.
|
|
93
|
+
* For example, remapping a type like `EventEmitter<CustomEvent<MyEvent<T>>>` to
|
|
94
|
+
* `EventEmitter<CustomEvent<IMyComponentMyEvent<IMyComponentT>>>`.
|
|
95
|
+
*/
|
|
96
|
+
return [p1, `I${componentClassName}${v.substring(1, v.length - 1)}`, p2].join('');
|
|
97
|
+
}
|
|
98
|
+
return [p1, dst, p2].join('');
|
|
99
|
+
}));
|
|
100
|
+
}, event.complexType.original
|
|
101
|
+
.replace(/\n/g, ' ')
|
|
102
|
+
.replace(/\s{2,}/g, ' ')
|
|
103
|
+
.replace(/,\s*/g, ', '));
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Creates a formatted comment block based on the JS doc comment.
|
|
107
|
+
* @param doc The compiler jsdoc.
|
|
108
|
+
* @returns The formatted comment block as a string.
|
|
109
|
+
*/
|
|
110
|
+
const createDocComment = (doc) => {
|
|
111
|
+
if (doc.text.trim().length === 0 && doc.tags.length === 0) {
|
|
112
|
+
return '';
|
|
113
|
+
}
|
|
114
|
+
return `/**
|
|
115
|
+
* ${doc.text}${doc.tags.length > 0 ? ' ' : ''}${doc.tags.map((tag) => `@${tag.name} ${tag.text}`)}
|
|
116
|
+
*/`;
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Creates the component interface type definition.
|
|
120
|
+
* @param outputType The output type.
|
|
121
|
+
* @param tagNameAsPascal The tag name as PascalCase.
|
|
122
|
+
* @param events The events to generate the interface properties for.
|
|
123
|
+
* @param componentCorePackage The component core package.
|
|
124
|
+
* @param customElementsDir The custom elements directory.
|
|
125
|
+
* @returns The component interface type definition as a string.
|
|
126
|
+
*/
|
|
127
|
+
export const createComponentTypeDefinition = (outputType, tagNameAsPascal, events, componentCorePackage, customElementsDir) => {
|
|
128
|
+
const publicEvents = events.filter((ev) => !ev.internal);
|
|
129
|
+
const eventTypeImports = createComponentEventTypeImports(tagNameAsPascal, publicEvents, {
|
|
130
|
+
componentCorePackage,
|
|
131
|
+
customElementsDir,
|
|
132
|
+
outputType,
|
|
133
|
+
});
|
|
134
|
+
const eventTypes = publicEvents.map((event) => {
|
|
135
|
+
const comment = createDocComment(event.docs);
|
|
136
|
+
let eventName = event.name;
|
|
137
|
+
if (event.name.includes('-')) {
|
|
138
|
+
// If an event name includes a dash, we need to wrap it in quotes.
|
|
139
|
+
// https://github.com/ionic-team/stencil-ds-output-targets/issues/212
|
|
140
|
+
eventName = `'${event.name}'`;
|
|
141
|
+
}
|
|
142
|
+
return `${comment.length > 0 ? ` ${comment}` : ''}
|
|
143
|
+
${eventName}: EventEmitter<CustomEvent<${formatOutputType(tagNameAsPascal, event)}>>;`;
|
|
144
|
+
});
|
|
145
|
+
const interfaceDeclaration = `export declare interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {`;
|
|
146
|
+
const typeDefinition = (eventTypeImports.length > 0 ? `${eventTypeImports + '\n\n'}` : '') +
|
|
147
|
+
`${interfaceDeclaration}${eventTypes.length === 0
|
|
148
|
+
? '}'
|
|
149
|
+
: `
|
|
150
|
+
${eventTypes.join('\n')}
|
|
151
|
+
}`}`;
|
|
152
|
+
return typeDefinition;
|
|
153
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { OutputTargetAngular } from './types';
|
|
2
|
+
import type { CompilerCtx, ComponentCompilerMeta } from '@stencil/core/internal';
|
|
3
|
+
export declare function generateAngularDirectivesFile(compilerCtx: CompilerCtx, components: ComponentCompilerMeta[], outputTarget: OutputTargetAngular): Promise<any>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { dashToPascalCase, relativeImport } from './utils';
|
|
2
|
+
export function generateAngularDirectivesFile(compilerCtx, components, outputTarget) {
|
|
3
|
+
// Only create the file if it is defined in the stencil configuration
|
|
4
|
+
if (!outputTarget.directivesArrayFile) {
|
|
5
|
+
return Promise.resolve();
|
|
6
|
+
}
|
|
7
|
+
const proxyPath = relativeImport(outputTarget.directivesArrayFile, outputTarget.directivesProxyFile, '.ts');
|
|
8
|
+
const directives = components
|
|
9
|
+
.map((cmpMeta) => dashToPascalCase(cmpMeta.tagName))
|
|
10
|
+
.map((className) => `d.${className}`)
|
|
11
|
+
.join(',\n ');
|
|
12
|
+
const c = `
|
|
13
|
+
import * as d from '${proxyPath}';
|
|
14
|
+
|
|
15
|
+
export const DIRECTIVES = [
|
|
16
|
+
${directives}
|
|
17
|
+
];
|
|
18
|
+
`;
|
|
19
|
+
return compilerCtx.fs.writeFile(outputTarget.directivesArrayFile, c);
|
|
20
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates an Angular module declaration for a component wrapper.
|
|
3
|
+
* @param componentTagName The tag name of the Stencil component.
|
|
4
|
+
* @returns The Angular module declaration as a string.
|
|
5
|
+
*/
|
|
6
|
+
export declare const generateAngularModuleForComponent: (componentTagName: string) => string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { dashToPascalCase } from './utils';
|
|
2
|
+
/**
|
|
3
|
+
* Creates an Angular module declaration for a component wrapper.
|
|
4
|
+
* @param componentTagName The tag name of the Stencil component.
|
|
5
|
+
* @returns The Angular module declaration as a string.
|
|
6
|
+
*/
|
|
7
|
+
export const generateAngularModuleForComponent = (componentTagName) => {
|
|
8
|
+
const tagNameAsPascal = dashToPascalCase(componentTagName);
|
|
9
|
+
const componentClassName = `${tagNameAsPascal}`;
|
|
10
|
+
const moduleClassName = `${tagNameAsPascal}Module`;
|
|
11
|
+
const moduleDefinition = `@NgModule({
|
|
12
|
+
declarations: [${componentClassName}],
|
|
13
|
+
exports: [${componentClassName}]
|
|
14
|
+
})
|
|
15
|
+
export class ${moduleClassName} { }`;
|
|
16
|
+
return moduleDefinition;
|
|
17
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { OutputTargetAngular } from './types';
|
|
2
|
+
import type { CompilerCtx, ComponentCompilerMeta, Config } from '@stencil/core/internal';
|
|
3
|
+
export interface ValueAccessor {
|
|
4
|
+
elementSelectors: string[];
|
|
5
|
+
eventTargets: [string, string][];
|
|
6
|
+
}
|
|
7
|
+
export default function generateValueAccessors(compilerCtx: CompilerCtx, components: ComponentCompilerMeta[], outputTarget: OutputTargetAngular, config: Config): Promise<void>;
|
|
8
|
+
export declare function createValueAccessor(srcFileContents: string, valueAccessor: ValueAccessor): string;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { EOL } from 'os';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
export default async function generateValueAccessors(compilerCtx, components, outputTarget, config) {
|
|
4
|
+
if (!Array.isArray(outputTarget.valueAccessorConfigs) || outputTarget.valueAccessorConfigs.length === 0) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
const targetDir = path.dirname(outputTarget.directivesProxyFile);
|
|
8
|
+
const normalizedValueAccessors = outputTarget.valueAccessorConfigs.reduce((allAccessors, va) => {
|
|
9
|
+
const elementSelectors = Array.isArray(va.elementSelectors) ? va.elementSelectors : [va.elementSelectors];
|
|
10
|
+
const type = va.type;
|
|
11
|
+
let allElementSelectors = [];
|
|
12
|
+
let allEventTargets = [];
|
|
13
|
+
if (allAccessors.hasOwnProperty(type)) {
|
|
14
|
+
allElementSelectors = allAccessors[type].elementSelectors;
|
|
15
|
+
allEventTargets = allAccessors[type].eventTargets;
|
|
16
|
+
}
|
|
17
|
+
return Object.assign(Object.assign({}, allAccessors), { [type]: {
|
|
18
|
+
elementSelectors: allElementSelectors.concat(elementSelectors),
|
|
19
|
+
eventTargets: allEventTargets.concat([[va.event, va.targetAttr]]),
|
|
20
|
+
} });
|
|
21
|
+
}, {});
|
|
22
|
+
await Promise.all(Object.keys(normalizedValueAccessors).map(async (type) => {
|
|
23
|
+
const valueAccessorType = type; // Object.keys converts to string
|
|
24
|
+
const targetFileName = `${type}-value-accessor.ts`;
|
|
25
|
+
const targetFilePath = path.join(targetDir, targetFileName);
|
|
26
|
+
const srcFilePath = path.join(__dirname, '../resources/control-value-accessors/', targetFileName);
|
|
27
|
+
const srcFileContents = await compilerCtx.fs.readFile(srcFilePath);
|
|
28
|
+
const finalText = createValueAccessor(srcFileContents, normalizedValueAccessors[valueAccessorType]);
|
|
29
|
+
await compilerCtx.fs.writeFile(targetFilePath, finalText);
|
|
30
|
+
}));
|
|
31
|
+
await copyResources(config, ['value-accessor.ts'], targetDir);
|
|
32
|
+
}
|
|
33
|
+
export function createValueAccessor(srcFileContents, valueAccessor) {
|
|
34
|
+
const hostContents = valueAccessor.eventTargets.map((listItem) => VALUE_ACCESSOR_EVENTTARGETS.replace(VALUE_ACCESSOR_EVENT, listItem[0]).replace(VALUE_ACCESSOR_TARGETATTR, listItem[1]));
|
|
35
|
+
return srcFileContents
|
|
36
|
+
.replace(VALUE_ACCESSOR_SELECTORS, valueAccessor.elementSelectors.join(', '))
|
|
37
|
+
.replace(VALUE_ACCESSOR_EVENTTARGETS, hostContents.join(`,${EOL}`));
|
|
38
|
+
}
|
|
39
|
+
function copyResources(config, resourcesFilesToCopy, directory) {
|
|
40
|
+
if (!config.sys || !config.sys.copy) {
|
|
41
|
+
throw new Error('stencil is not properly intialized at this step. Notify the developer');
|
|
42
|
+
}
|
|
43
|
+
const copyTasks = resourcesFilesToCopy.map((rf) => {
|
|
44
|
+
return {
|
|
45
|
+
src: path.join(__dirname, '../resources/control-value-accessors/', rf),
|
|
46
|
+
dest: path.join(directory, rf),
|
|
47
|
+
keepDirStructure: false,
|
|
48
|
+
warn: false,
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
return config.sys.copy(copyTasks, path.join(directory));
|
|
52
|
+
}
|
|
53
|
+
const VALUE_ACCESSOR_SELECTORS = `<VALUE_ACCESSOR_SELECTORS>`;
|
|
54
|
+
const VALUE_ACCESSOR_EVENT = `<VALUE_ACCESSOR_EVENT>`;
|
|
55
|
+
const VALUE_ACCESSOR_TARGETATTR = '<VALUE_ACCESSOR_TARGETATTR>';
|
|
56
|
+
const VALUE_ACCESSOR_EVENTTARGETS = ` '(<VALUE_ACCESSOR_EVENT>)': 'handleChangeEvent($event.target.<VALUE_ACCESSOR_TARGETATTR>)'`;
|