@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.
@@ -0,0 +1,138 @@
1
+ import path from 'path';
2
+ import { relativeImport, normalizePath, sortBy, readPackageJson, dashToPascalCase, createImportStatement, isOutputTypeCustomElementsBuild, OutputTypes, } from './utils';
3
+ import { createAngularComponentDefinition, createComponentTypeDefinition } from './generate-angular-component';
4
+ import { generateAngularDirectivesFile } from './generate-angular-directives-file';
5
+ import generateValueAccessors from './generate-value-accessors';
6
+ import { generateAngularModuleForComponent } from './generate-angular-modules';
7
+ export async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components, config) {
8
+ const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);
9
+ const rootDir = config.rootDir;
10
+ const pkgData = await readPackageJson(config, rootDir);
11
+ const finalText = generateProxies(filteredComponents, pkgData, outputTarget, config.rootDir);
12
+ await Promise.all([
13
+ compilerCtx.fs.writeFile(outputTarget.directivesProxyFile, finalText),
14
+ copyResources(config, outputTarget),
15
+ generateAngularDirectivesFile(compilerCtx, filteredComponents, outputTarget),
16
+ generateValueAccessors(compilerCtx, filteredComponents, outputTarget, config),
17
+ ]);
18
+ }
19
+ function getFilteredComponents(excludeComponents = [], cmps) {
20
+ return sortBy(cmps, (cmp) => cmp.tagName).filter((c) => !excludeComponents.includes(c.tagName) && !c.internal);
21
+ }
22
+ async function copyResources(config, outputTarget) {
23
+ if (!config.sys || !config.sys.copy || !config.sys.glob) {
24
+ throw new Error('stencil is not properly initialized at this step. Notify the developer');
25
+ }
26
+ const srcDirectory = path.join(__dirname, '..', 'angular-component-lib');
27
+ const destDirectory = path.join(path.dirname(outputTarget.directivesProxyFile), 'angular-component-lib');
28
+ return config.sys.copy([
29
+ {
30
+ src: srcDirectory,
31
+ dest: destDirectory,
32
+ keepDirStructure: false,
33
+ warn: false,
34
+ },
35
+ ], srcDirectory);
36
+ }
37
+ export function generateProxies(components, pkgData, outputTarget, rootDir) {
38
+ const distTypesDir = path.dirname(pkgData.types);
39
+ const dtsFilePath = path.join(rootDir, distTypesDir, GENERATED_DTS);
40
+ const { outputType } = outputTarget;
41
+ const componentsTypeFile = relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
42
+ const includeSingleComponentAngularModules = outputType === OutputTypes.Scam;
43
+ const isCustomElementsBuild = isOutputTypeCustomElementsBuild(outputType);
44
+ const isStandaloneBuild = outputType === OutputTypes.Standalone;
45
+ const includeOutputImports = components.some((component) => component.events.some((event) => !event.internal));
46
+ /**
47
+ * The collection of named imports from @angular/core.
48
+ */
49
+ const angularCoreImports = ['ChangeDetectionStrategy', 'ChangeDetectorRef', 'Component', 'ElementRef'];
50
+ if (includeOutputImports) {
51
+ angularCoreImports.push('EventEmitter');
52
+ }
53
+ angularCoreImports.push('NgZone');
54
+ /**
55
+ * The collection of named imports from the angular-component-lib/utils.
56
+ */
57
+ const componentLibImports = ['ProxyCmp'];
58
+ if (includeOutputImports) {
59
+ componentLibImports.push('proxyOutputs');
60
+ }
61
+ if (includeSingleComponentAngularModules) {
62
+ angularCoreImports.push('NgModule');
63
+ }
64
+ const imports = `/* tslint:disable */
65
+ /* auto-generated angular directive proxies */
66
+ ${createImportStatement(angularCoreImports, '@angular/core')}
67
+
68
+ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n`;
69
+ /**
70
+ * Generate JSX import type from correct location.
71
+ * When using custom elements build, we need to import from
72
+ * either the "components" directory or customElementsDir
73
+ * otherwise we risk bundlers pulling in lazy loaded imports.
74
+ */
75
+ const generateTypeImports = () => {
76
+ let importLocation = outputTarget.componentCorePackage
77
+ ? normalizePath(outputTarget.componentCorePackage)
78
+ : normalizePath(componentsTypeFile);
79
+ importLocation += isCustomElementsBuild ? `/${outputTarget.customElementsDir}` : '';
80
+ return `import ${isCustomElementsBuild ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`;
81
+ };
82
+ const typeImports = generateTypeImports();
83
+ let sourceImports = '';
84
+ /**
85
+ * Build an array of Custom Elements build imports and namespace them
86
+ * so that they do not conflict with the Angular wrapper names. For example,
87
+ * IonButton would be imported as IonButtonCmp so as to not conflict with the
88
+ * IonButton Angular Component that takes in the Web Component as a parameter.
89
+ */
90
+ if (isCustomElementsBuild && outputTarget.componentCorePackage !== undefined) {
91
+ const cmpImports = components.map((component) => {
92
+ const pascalImport = dashToPascalCase(component.tagName);
93
+ return `import { defineCustomElement as define${pascalImport} } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir}/${component.tagName}.js';`;
94
+ });
95
+ sourceImports = cmpImports.join('\n');
96
+ }
97
+ const proxyFileOutput = [];
98
+ const filterInternalProps = (prop) => !prop.internal;
99
+ const mapPropName = (prop) => prop.name;
100
+ const { componentCorePackage, customElementsDir } = outputTarget;
101
+ for (let cmpMeta of components) {
102
+ const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
103
+ const inputs = [];
104
+ if (cmpMeta.properties) {
105
+ inputs.push(...cmpMeta.properties.filter(filterInternalProps).map(mapPropName));
106
+ }
107
+ if (cmpMeta.virtualProperties) {
108
+ inputs.push(...cmpMeta.virtualProperties.map(mapPropName));
109
+ }
110
+ inputs.sort();
111
+ const outputs = [];
112
+ if (cmpMeta.events) {
113
+ outputs.push(...cmpMeta.events.filter(filterInternalProps).map(mapPropName));
114
+ }
115
+ const methods = [];
116
+ if (cmpMeta.methods) {
117
+ methods.push(...cmpMeta.methods.filter(filterInternalProps).map(mapPropName));
118
+ }
119
+ /**
120
+ * For each component, we need to generate:
121
+ * 1. The @Component decorated class
122
+ * 2. Optionally the @NgModule decorated class (if includeSingleComponentAngularModules is true)
123
+ * 3. The component interface (using declaration merging for types).
124
+ */
125
+ const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, inputs, outputs, methods, isCustomElementsBuild, isStandaloneBuild);
126
+ const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
127
+ const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir);
128
+ proxyFileOutput.push(componentDefinition, '\n');
129
+ if (includeSingleComponentAngularModules) {
130
+ proxyFileOutput.push(moduleDefinition, '\n');
131
+ }
132
+ proxyFileOutput.push(componentTypeDefinition, '\n');
133
+ }
134
+ const final = [imports, typeImports, sourceImports, ...proxyFileOutput];
135
+ return final.join('\n') + '\n';
136
+ }
137
+ const GENERATED_DTS = 'components.d.ts';
138
+ const IMPORT_TYPES = 'Components';
@@ -0,0 +1,4 @@
1
+ import type { Config, OutputTargetCustom } from '@stencil/core/internal';
2
+ import type { OutputTargetAngular } from './types';
3
+ export declare const angularOutputTarget: (outputTarget: OutputTargetAngular) => OutputTargetCustom;
4
+ export declare function normalizeOutputTarget(config: Config, outputTarget: OutputTargetAngular): OutputTargetAngular;
package/dist/plugin.js ADDED
@@ -0,0 +1,37 @@
1
+ import { OutputTypes, normalizePath } from './utils';
2
+ import { angularDirectiveProxyOutput } from './output-angular';
3
+ import path from 'path';
4
+ export const angularOutputTarget = (outputTarget) => {
5
+ let validatedOutputTarget;
6
+ return {
7
+ type: 'custom',
8
+ name: 'angular-library',
9
+ validate(config) {
10
+ validatedOutputTarget = normalizeOutputTarget(config, outputTarget);
11
+ },
12
+ async generator(config, compilerCtx, buildCtx) {
13
+ const timespan = buildCtx.createTimeSpan(`generate angular proxies started`, true);
14
+ await angularDirectiveProxyOutput(compilerCtx, validatedOutputTarget, buildCtx.components, config);
15
+ timespan.finish(`generate angular proxies finished`);
16
+ },
17
+ };
18
+ };
19
+ export function normalizeOutputTarget(config, outputTarget) {
20
+ const results = Object.assign(Object.assign({}, outputTarget), { excludeComponents: outputTarget.excludeComponents || [], valueAccessorConfigs: outputTarget.valueAccessorConfigs || [], customElementsDir: outputTarget.customElementsDir || 'components', outputType: outputTarget.outputType || OutputTypes.Component });
21
+ if (config.rootDir == null) {
22
+ throw new Error('rootDir is not set and it should be set by stencil itself');
23
+ }
24
+ if (outputTarget.directivesProxyFile == null) {
25
+ throw new Error('directivesProxyFile is required. Please set it in the Stencil config.');
26
+ }
27
+ if (outputTarget.directivesProxyFile && !path.isAbsolute(outputTarget.directivesProxyFile)) {
28
+ results.directivesProxyFile = normalizePath(path.join(config.rootDir, outputTarget.directivesProxyFile));
29
+ }
30
+ if (outputTarget.directivesArrayFile && !path.isAbsolute(outputTarget.directivesArrayFile)) {
31
+ results.directivesArrayFile = normalizePath(path.join(config.rootDir, outputTarget.directivesArrayFile));
32
+ }
33
+ if (outputTarget.includeSingleComponentAngularModules !== undefined) {
34
+ throw new Error("The 'includeSingleComponentAngularModules' option has been removed. Please use 'outputType' instead.");
35
+ }
36
+ return results;
37
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The type of output that can be generated with the Angular output target.
3
+ * - `component` - Generate many component wrappers tied to a single Angular module (lazy/hydrated approach).
4
+ * - `scam` - Generate a Single Component Angular Module for each component.
5
+ * - `standalone` - Generate a component with the `standalone` flag set to `true`.
6
+ */
7
+ export declare type OutputType = 'component' | 'scam' | 'standalone';
8
+ export interface OutputTargetAngular {
9
+ /**
10
+ * The package name of the component library.
11
+ * This is used to generate the import statements.
12
+ */
13
+ componentCorePackage: string;
14
+ /**
15
+ * The path to the proxy file that will be generated. This can be an absolute path
16
+ * or a relative path from the root directory of the Stencil library.
17
+ */
18
+ directivesProxyFile: string;
19
+ directivesArrayFile?: string;
20
+ valueAccessorConfigs?: ValueAccessorConfig[];
21
+ excludeComponents?: string[];
22
+ customElementsDir?: string;
23
+ /**
24
+ * The type of output that should be generated.
25
+ * - `component` - Generate many component wrappers tied to a single Angular module (lazy/hydrated approach).
26
+ * - `scam` - Generate a Single Component Angular Module for each component.
27
+ * - `standalone` - Generate a component with the `standalone` flag set to `true`.
28
+ */
29
+ outputType?: OutputType;
30
+ }
31
+ export declare type ValueAccessorTypes = 'text' | 'radio' | 'select' | 'number' | 'boolean';
32
+ export interface ValueAccessorConfig {
33
+ elementSelectors: string | string[];
34
+ event: string;
35
+ targetAttr: string;
36
+ type: ValueAccessorTypes;
37
+ }
38
+ export interface PackageJSON {
39
+ types: string;
40
+ }
package/dist/types.js ADDED
File without changes
@@ -0,0 +1,44 @@
1
+ import { ComponentCompilerEvent, Config } from '@stencil/core/internal';
2
+ import { OutputType, PackageJSON } from './types';
3
+ export declare const OutputTypes: {
4
+ [key: string]: OutputType;
5
+ };
6
+ export declare const toLowerCase: (str: string) => string;
7
+ export declare const dashToPascalCase: (str: string) => string;
8
+ export declare function sortBy<T>(array: T[], prop: (item: T) => string): T[];
9
+ export declare function normalizePath(str: string): string;
10
+ export declare function relativeImport(pathFrom: string, pathTo: string, ext?: string): string;
11
+ export declare function isRelativePath(path: string): boolean | "";
12
+ export declare function readPackageJson(config: Config, rootDir: string): Promise<PackageJSON>;
13
+ /**
14
+ * Formats an array of strings to a string of quoted, comma separated values.
15
+ * @param list The list of unformatted strings to format
16
+ * @returns The formatted array of strings. (e.g. ['foo', 'bar']) => `'foo', 'bar'`
17
+ */
18
+ export declare const formatToQuotedList: (list: readonly string[]) => string;
19
+ /**
20
+ * Creates an import statement for a list of named imports from a module.
21
+ * @param imports The list of named imports.
22
+ * @param module The module to import from.
23
+ *
24
+ * @returns The import statement as a string.
25
+ */
26
+ export declare const createImportStatement: (imports: string[], module: string) => string;
27
+ /**
28
+ * Checks if the outputType is for the custom elements build.
29
+ * @param outputType The output type.
30
+ * @returns `true` if the output type is for the custom elements build.
31
+ */
32
+ export declare const isOutputTypeCustomElementsBuild: (outputType: OutputType) => boolean;
33
+ /**
34
+ * Creates the collection of import statements for a component based on the component's events type dependencies.
35
+ * @param componentTagName The tag name of the component (pascal case).
36
+ * @param events The events compiler metadata.
37
+ * @param options The options for generating the import statements (e.g. whether to import from the custom elements directory).
38
+ * @returns The import statements as an array of strings.
39
+ */
40
+ export declare const createComponentEventTypeImports: (componentTagName: string, events: readonly ComponentCompilerEvent[], options: {
41
+ componentCorePackage: string;
42
+ customElementsDir?: string;
43
+ outputType: OutputType;
44
+ }) => string;
package/dist/utils.js ADDED
@@ -0,0 +1,138 @@
1
+ import path from 'path';
2
+ export const OutputTypes = {
3
+ Component: 'component',
4
+ Scam: 'scam',
5
+ Standalone: 'standalone',
6
+ };
7
+ export const toLowerCase = (str) => str.toLowerCase();
8
+ export const dashToPascalCase = (str) => toLowerCase(str)
9
+ .split('-')
10
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
11
+ .join('');
12
+ export function sortBy(array, prop) {
13
+ return array.slice().sort((a, b) => {
14
+ const nameA = prop(a);
15
+ const nameB = prop(b);
16
+ if (nameA < nameB)
17
+ return -1;
18
+ if (nameA > nameB)
19
+ return 1;
20
+ return 0;
21
+ });
22
+ }
23
+ export function normalizePath(str) {
24
+ // Convert Windows backslash paths to slash paths: foo\\bar ➔ foo/bar
25
+ // https://github.com/sindresorhus/slash MIT
26
+ // By Sindre Sorhus
27
+ if (typeof str !== 'string') {
28
+ throw new Error(`invalid path to normalize`);
29
+ }
30
+ str = str.trim();
31
+ if (EXTENDED_PATH_REGEX.test(str) || NON_ASCII_REGEX.test(str)) {
32
+ return str;
33
+ }
34
+ str = str.replace(SLASH_REGEX, '/');
35
+ // always remove the trailing /
36
+ // this makes our file cache look ups consistent
37
+ if (str.charAt(str.length - 1) === '/') {
38
+ const colonIndex = str.indexOf(':');
39
+ if (colonIndex > -1) {
40
+ if (colonIndex < str.length - 2) {
41
+ str = str.substring(0, str.length - 1);
42
+ }
43
+ }
44
+ else if (str.length > 1) {
45
+ str = str.substring(0, str.length - 1);
46
+ }
47
+ }
48
+ return str;
49
+ }
50
+ export function relativeImport(pathFrom, pathTo, ext) {
51
+ let relativePath = path.relative(path.dirname(pathFrom), path.dirname(pathTo));
52
+ if (relativePath === '') {
53
+ relativePath = '.';
54
+ }
55
+ else if (relativePath[0] !== '.') {
56
+ relativePath = './' + relativePath;
57
+ }
58
+ return normalizePath(`${relativePath}/${path.basename(pathTo, ext)}`);
59
+ }
60
+ export function isRelativePath(path) {
61
+ return path && path.startsWith('.');
62
+ }
63
+ export async function readPackageJson(config, rootDir) {
64
+ var _a;
65
+ const pkgJsonPath = path.join(rootDir, 'package.json');
66
+ let pkgJson;
67
+ try {
68
+ pkgJson = (await ((_a = config.sys) === null || _a === void 0 ? void 0 : _a.readFile(pkgJsonPath, 'utf8')));
69
+ }
70
+ catch (e) {
71
+ throw new Error(`Missing "package.json" file for distribution: ${pkgJsonPath}`);
72
+ }
73
+ let pkgData;
74
+ try {
75
+ pkgData = JSON.parse(pkgJson);
76
+ }
77
+ catch (e) {
78
+ throw new Error(`Error parsing package.json: ${pkgJsonPath}, ${e}`);
79
+ }
80
+ return pkgData;
81
+ }
82
+ /**
83
+ * Formats an array of strings to a string of quoted, comma separated values.
84
+ * @param list The list of unformatted strings to format
85
+ * @returns The formatted array of strings. (e.g. ['foo', 'bar']) => `'foo', 'bar'`
86
+ */
87
+ export const formatToQuotedList = (list) => list.map((item) => `'${item}'`).join(', ');
88
+ /**
89
+ * Creates an import statement for a list of named imports from a module.
90
+ * @param imports The list of named imports.
91
+ * @param module The module to import from.
92
+ *
93
+ * @returns The import statement as a string.
94
+ */
95
+ export const createImportStatement = (imports, module) => {
96
+ if (imports.length === 0) {
97
+ return '';
98
+ }
99
+ return `import { ${imports.join(', ')} } from '${module}';`;
100
+ };
101
+ /**
102
+ * Checks if the outputType is for the custom elements build.
103
+ * @param outputType The output type.
104
+ * @returns `true` if the output type is for the custom elements build.
105
+ */
106
+ export const isOutputTypeCustomElementsBuild = (outputType) => {
107
+ return outputType === OutputTypes.Standalone || outputType === OutputTypes.Scam;
108
+ };
109
+ /**
110
+ * Creates the collection of import statements for a component based on the component's events type dependencies.
111
+ * @param componentTagName The tag name of the component (pascal case).
112
+ * @param events The events compiler metadata.
113
+ * @param options The options for generating the import statements (e.g. whether to import from the custom elements directory).
114
+ * @returns The import statements as an array of strings.
115
+ */
116
+ export const createComponentEventTypeImports = (componentTagName, events, options) => {
117
+ const { componentCorePackage, customElementsDir } = options;
118
+ const imports = [];
119
+ const namedImports = new Set();
120
+ const isCustomElementsBuild = isOutputTypeCustomElementsBuild(options.outputType);
121
+ const importPathName = normalizePath(componentCorePackage) + (isCustomElementsBuild ? `/${customElementsDir}` : '');
122
+ events.forEach((event) => {
123
+ Object.entries(event.complexType.references).forEach(([typeName, refObject]) => {
124
+ if (refObject.location === 'local' || refObject.location === 'import') {
125
+ const newTypeName = `I${componentTagName}${typeName}`;
126
+ // Prevents duplicate imports for the same type.
127
+ if (!namedImports.has(newTypeName)) {
128
+ imports.push(`import type { ${typeName} as ${newTypeName} } from '${importPathName}';`);
129
+ namedImports.add(newTypeName);
130
+ }
131
+ }
132
+ });
133
+ });
134
+ return imports.join('\n');
135
+ };
136
+ const EXTENDED_PATH_REGEX = /^\\\\\?\\/;
137
+ const NON_ASCII_REGEX = /[^\x00-\x80]+/;
138
+ const SLASH_REGEX = /\\/g;
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@stencil/angular-output-target",
3
+ "version": "0.0.0-dev.11698339436.1d39048b",
4
+ "description": "Angular output target for @stencil/core components.",
5
+ "main": "dist/index.cjs.js",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist/",
10
+ "resources/",
11
+ "angular-component-lib/"
12
+ ],
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "scripts": {
17
+ "prepublishOnly": "npm run build",
18
+ "prebuild": "rimraf ./dist && npm run test",
19
+ "build": "tsc && npm run rollup",
20
+ "watch": "tsc --watch",
21
+ "rollup": "rollup -c",
22
+ "version": "npm run build",
23
+ "prettier": "npm run prettier.base -- --write",
24
+ "prettier.base": "prettier \"./({angular-component-lib,src,test,__tests__}/**/*.{ts,tsx,js,jsx})|*.{ts,tsx,js,jsx}\"",
25
+ "prettier.dry-run": "npm run prettier.base -- --list-different",
26
+ "release": "np",
27
+ "test": "jest --passWithNoTests",
28
+ "test.watch": "jest --watch"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/ionic-team/stencil-ds-output-targets.git"
33
+ },
34
+ "author": "Ionic Team",
35
+ "homepage": "https://stenciljs.com/",
36
+ "license": "MIT",
37
+ "bugs": {
38
+ "url": "https://github.com/ionic-team/stencil-ds-output-targets/issues"
39
+ },
40
+ "devDependencies": {
41
+ "@angular/core": "8.2.14",
42
+ "@angular/forms": "8.2.14"
43
+ },
44
+ "peerDependencies": {
45
+ "@stencil/core": ">=2.0.0 || >=3 || >= 4.0.0-beta.0 || >= 4.0.0"
46
+ },
47
+ "jest": {
48
+ "transform": {
49
+ "^.+\\.(js|ts|tsx)$": "<rootDir>/test/jest.preprocessor.js"
50
+ },
51
+ "testRegex": "(\\.(test|spec))\\.(ts?|tsx?|jsx?)$",
52
+ "moduleFileExtensions": [
53
+ "ts",
54
+ "tsx",
55
+ "js",
56
+ "json",
57
+ "jsx"
58
+ ],
59
+ "testURL": "http://localhost"
60
+ },
61
+ "gitHead": "d39048b3fa0fd6757b016ff0c7a23e76c4e2a438",
62
+ "volta": {
63
+ "extends": "../../package.json"
64
+ }
65
+ }
@@ -0,0 +1,27 @@
1
+ import { Directive, ElementRef } from '@angular/core';
2
+ import { NG_VALUE_ACCESSOR } from '@angular/forms';
3
+
4
+ import { ValueAccessor } from './value-accessor';
5
+
6
+ @Directive({
7
+ /* tslint:disable-next-line:directive-selector */
8
+ selector: '<VALUE_ACCESSOR_SELECTORS>',
9
+ host: {
10
+ '(<VALUE_ACCESSOR_EVENT>)': 'handleChangeEvent($event.target.<VALUE_ACCESSOR_TARGETATTR>)'
11
+ },
12
+ providers: [
13
+ {
14
+ provide: NG_VALUE_ACCESSOR,
15
+ useExisting: BooleanValueAccessor,
16
+ multi: true
17
+ }
18
+ ]
19
+ })
20
+ export class BooleanValueAccessor extends ValueAccessor {
21
+ constructor(el: ElementRef) {
22
+ super(el);
23
+ }
24
+ writeValue(value: any) {
25
+ this.el.nativeElement.checked = this.lastValue = value == null ? false : value;
26
+ }
27
+ }
@@ -0,0 +1,29 @@
1
+ import { Directive, ElementRef } from '@angular/core';
2
+ import { NG_VALUE_ACCESSOR } from '@angular/forms';
3
+
4
+ import { ValueAccessor } from './value-accessor';
5
+
6
+ @Directive({
7
+ /* tslint:disable-next-line:directive-selector */
8
+ selector: '<VALUE_ACCESSOR_SELECTORS>',
9
+ host: {
10
+ '(<VALUE_ACCESSOR_EVENT>)': 'handleChangeEvent($event.target.<VALUE_ACCESSOR_TARGETATTR>)'
11
+ },
12
+ providers: [
13
+ {
14
+ provide: NG_VALUE_ACCESSOR,
15
+ useExisting: NumericValueAccessor,
16
+ multi: true
17
+ }
18
+ ]
19
+ })
20
+ export class NumericValueAccessor extends ValueAccessor {
21
+ constructor(el: ElementRef) {
22
+ super(el);
23
+ }
24
+ registerOnChange(fn: (_: number | null) => void) {
25
+ super.registerOnChange(value => {
26
+ fn(value === '' ? null : parseFloat(value));
27
+ });
28
+ }
29
+ }
@@ -0,0 +1,24 @@
1
+ import { Directive, ElementRef } from '@angular/core';
2
+ import { NG_VALUE_ACCESSOR } from '@angular/forms';
3
+
4
+ import { ValueAccessor } from './value-accessor';
5
+
6
+ @Directive({
7
+ /* tslint:disable-next-line:directive-selector */
8
+ selector: '<VALUE_ACCESSOR_SELECTORS>',
9
+ host: {
10
+ '(<VALUE_ACCESSOR_EVENT>)': 'handleChangeEvent($event.target.<VALUE_ACCESSOR_TARGETATTR>)'
11
+ },
12
+ providers: [
13
+ {
14
+ provide: NG_VALUE_ACCESSOR,
15
+ useExisting: RadioValueAccessor,
16
+ multi: true
17
+ }
18
+ ]
19
+ })
20
+ export class RadioValueAccessor extends ValueAccessor {
21
+ constructor(el: ElementRef) {
22
+ super(el);
23
+ }
24
+ }
@@ -0,0 +1,24 @@
1
+ import { Directive, ElementRef } from '@angular/core';
2
+ import { NG_VALUE_ACCESSOR } from '@angular/forms';
3
+
4
+ import { ValueAccessor } from './value-accessor';
5
+
6
+ @Directive({
7
+ /* tslint:disable-next-line:directive-selector */
8
+ selector: '<VALUE_ACCESSOR_SELECTORS>',
9
+ host: {
10
+ '(<VALUE_ACCESSOR_EVENT>)': 'handleChangeEvent($event.target.<VALUE_ACCESSOR_TARGETATTR>)'
11
+ },
12
+ providers: [
13
+ {
14
+ provide: NG_VALUE_ACCESSOR,
15
+ useExisting: SelectValueAccessor,
16
+ multi: true
17
+ }
18
+ ]
19
+ })
20
+ export class SelectValueAccessor extends ValueAccessor {
21
+ constructor(el: ElementRef) {
22
+ super(el);
23
+ }
24
+ }
@@ -0,0 +1,24 @@
1
+ import { Directive, ElementRef } from '@angular/core';
2
+ import { NG_VALUE_ACCESSOR } from '@angular/forms';
3
+
4
+ import { ValueAccessor } from './value-accessor';
5
+
6
+ @Directive({
7
+ /* tslint:disable-next-line:directive-selector */
8
+ selector: '<VALUE_ACCESSOR_SELECTORS>',
9
+ host: {
10
+ '(<VALUE_ACCESSOR_EVENT>)': 'handleChangeEvent($event.target.<VALUE_ACCESSOR_TARGETATTR>)'
11
+ },
12
+ providers: [
13
+ {
14
+ provide: NG_VALUE_ACCESSOR,
15
+ useExisting: TextValueAccessor,
16
+ multi: true
17
+ }
18
+ ]
19
+ })
20
+ export class TextValueAccessor extends ValueAccessor {
21
+ constructor(el: ElementRef) {
22
+ super(el);
23
+ }
24
+ }
@@ -0,0 +1,39 @@
1
+ import { Directive, ElementRef, HostListener } from '@angular/core';
2
+ import { ControlValueAccessor } from '@angular/forms';
3
+
4
+ @Directive({})
5
+ export class ValueAccessor implements ControlValueAccessor {
6
+
7
+ private onChange: (value: any) => void = () => {/**/};
8
+ private onTouched: () => void = () => {/**/};
9
+ protected lastValue: any;
10
+
11
+ constructor(protected el: ElementRef) {}
12
+
13
+ writeValue(value: any) {
14
+ this.el.nativeElement.value = this.lastValue = value == null ? '' : value;
15
+ }
16
+
17
+ handleChangeEvent(value: any) {
18
+ if (value !== this.lastValue) {
19
+ this.lastValue = value;
20
+ this.onChange(value);
21
+ }
22
+ }
23
+
24
+ @HostListener('focusout')
25
+ _handleBlurEvent() {
26
+ this.onTouched();
27
+ }
28
+
29
+ registerOnChange(fn: (value: any) => void) {
30
+ this.onChange = fn;
31
+ }
32
+ registerOnTouched(fn: () => void) {
33
+ this.onTouched = fn;
34
+ }
35
+
36
+ setDisabledState(isDisabled: boolean) {
37
+ this.el.nativeElement.disabled = isDisabled;
38
+ }
39
+ }