@stencil/angular-output-target 1.4.1 → 1.5.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
@@ -57,3 +57,92 @@ export const config: Config = {
57
57
  | `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`. |
58
58
  | `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"`. |
59
59
  | `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. |
60
+ | `booleanAttributes` | When `true`, boolean properties are declared with an Angular input transform so they can be set by attribute presence, e.g. `<my-component disabled>` instead of `<my-component [disabled]="true">`. Type-checking the attribute form also requires `inlineProperties`. Defaults to `false`. Refer to [Boolean attributes](#boolean-attributes). |
61
+
62
+ ## Boolean attributes
63
+
64
+ Angular resolves a bare attribute to the empty string, so a boolean property has to be bound
65
+ explicitly by default:
66
+
67
+ ```html
68
+ <!-- Type 'string' is not assignable to type 'boolean' under strictTemplates -->
69
+ <my-component disabled></my-component>
70
+
71
+ <my-component [disabled]="true"></my-component>
72
+ ```
73
+
74
+ Setting `booleanAttributes: true` declares boolean properties with an Angular input transform,
75
+ which accepts the attribute form and coerces it:
76
+
77
+ ```html
78
+ <my-component disabled></my-component>
79
+ <my-component disabled="false"></my-component>
80
+ <my-component [disabled]="isDisabled"></my-component>
81
+ ```
82
+
83
+ The transform used is `nullableBooleanAttribute` rather than Angular's `booleanAttribute`. It
84
+ coerces strings identically, but passes `null` and `undefined` through instead of coercing them to
85
+ `false`. Components frequently treat those as a state distinct from `false`:
86
+
87
+ ```tsx
88
+ // `undefined` means "decide based on the mode", which is not the same as `false`
89
+ const showDetail = detail !== undefined ? detail : mode === 'ios';
90
+
91
+ // a strict comparison also behaves differently for `null` than it does for `false`
92
+ const showHandle = handle !== false;
93
+ ```
94
+
95
+ Both values reach inputs routinely, from the `async` pipe before its first emission and from form
96
+ control values, so coercing them would change the behavior of bindings that work today.
97
+
98
+ ### Type-checking requires `inlineProperties`
99
+
100
+ This option changes what the Angular compiler accepts, so it only has a visible effect where the
101
+ compiler type-checks the wrapper's inputs in the first place. That needs `inlineProperties`
102
+ enabled as well:
103
+
104
+ ```ts
105
+ angularOutputTarget({
106
+ // ...
107
+ booleanAttributes: true,
108
+ inlineProperties: true,
109
+ });
110
+ ```
111
+
112
+ Without `inlineProperties` the generated wrappers declare no typed members for their inputs, so
113
+ Angular does not check these bindings at all and there is no `TS2322` for the transform to
114
+ resolve.
115
+
116
+ The transform itself still runs either way. Angular applies it from the component definition's
117
+ input map, which does not depend on a declared class member, so enabling `booleanAttributes` on
118
+ its own moves boolean coercion from Stencil to the transform without the compiler checking what
119
+ you pass. The two agree on strings, booleans, `null` and `undefined`. If you bind a value outside
120
+ those, prefer enabling `inlineProperties` too so that `ngAcceptInputType_*` rejects it at compile
121
+ time rather than silently coercing it.
122
+
123
+ Note that `inlineProperties` is itself experimental, so enabling both is opting into that. With
124
+ `booleanAttributes` on its own you still get the attribute syntax at runtime, just without the
125
+ compiler checking it.
126
+
127
+ ### Limitations
128
+
129
+ A property is only transformed when Stencil reports its type as exactly `boolean`. Three kinds of
130
+ property fall outside that, and they behave differently from each other. In all three the property
131
+ receives the attribute's empty string rather than `true`, so bind them explicitly when you need a
132
+ boolean.
133
+
134
+ **Virtual properties** are not transformed and get no inlined class member either, so Angular has
135
+ nothing to check the binding against and skips it. A bare attribute compiles, and the property
136
+ receives `''`. Nothing warns you.
137
+
138
+ **Properties declared as `any`** are not transformed, but they do get an inlined member typed
139
+ `any`, so `''` is assignable. A bare attribute compiles, and the property receives `''`. Nothing
140
+ warns you here either.
141
+
142
+ **Properties whose type unions `boolean` with something else**, such as `boolean | 'auto'`, are
143
+ reported to the output target as `any`, so they get no transform, but their inlined member keeps
144
+ the real union type. This is the only case the compiler catches:
145
+
146
+ ```
147
+ TS2322: Type '""' is not assignable to type 'boolean | "auto"'
148
+ ```
@@ -0,0 +1,43 @@
1
+ /* eslint-disable */
2
+ /* tslint:disable */
3
+
4
+ /**
5
+ * Transforms a value to a boolean so that boolean properties can be set by attribute presence,
6
+ * e.g. `<my-component disabled>` instead of `<my-component [disabled]="true">`.
7
+ *
8
+ * Strings are coerced the same way Angular's `booleanAttribute` coerces them, so `''` (a bare
9
+ * attribute) becomes `true` and `'false'` becomes `false`.
10
+ *
11
+ * Unlike Angular's `booleanAttribute`, `null` and `undefined` are passed through rather than
12
+ * coerced to `false`. Components frequently treat them as a state distinct from `false`:
13
+ *
14
+ * ```tsx
15
+ * // `undefined` means "decide based on the mode", which is not the same as `false`
16
+ * const showDetail = detail !== undefined ? detail : mode === 'ios';
17
+ *
18
+ * // a strict comparison also behaves differently for `null` than it does for `false`
19
+ * const showHandle = handle !== false;
20
+ * ```
21
+ *
22
+ * Both values reach inputs routinely in Angular templates, from the `async` pipe before its
23
+ * first emission and from form control values, so coercing them would change the behavior of
24
+ * bindings that work today.
25
+ *
26
+ * This is implemented here rather than imported from `@angular/core` so that consumers on
27
+ * Angular versions without `booleanAttribute` are unaffected by this file being generated.
28
+ *
29
+ * The parameter type is what Angular derives `ngAcceptInputType_*` from, so it decides which
30
+ * template bindings compile. Widening it to `unknown` would let any expression through.
31
+ *
32
+ * Declared as a function rather than an arrow constant because Angular has to resolve input
33
+ * transforms statically when compiling a library in partial compilation mode.
34
+ *
35
+ * This lives in its own file, separate from `utils.ts`, so that it carries no runtime imports.
36
+ * That keeps it independently type-checkable without pulling `rxjs` in for `proxyOutputs`.
37
+ */
38
+ export function nullableBooleanAttribute(value: boolean | string | null | undefined): boolean | null | undefined {
39
+ if (value === null || value === undefined) {
40
+ return value;
41
+ }
42
+ return typeof value === 'boolean' ? value : value !== 'false';
43
+ }
@@ -1,5 +1,10 @@
1
1
  import type { ComponentCompilerEvent, ComponentCompilerProperty } from '@stencil/core/internal';
2
2
  import type { ComponentInputProperty, OutputType } from './types';
3
+ /**
4
+ * The transform function to reference in generated code, exported from
5
+ * `angular-component-lib/boolean-attribute`.
6
+ */
7
+ export declare const INPUT_TRANSFORM_FUNCTION = "nullableBooleanAttribute";
3
8
  /**
4
9
  * Creates an Angular component declaration from formatted Stencil compiler metadata.
5
10
  *
@@ -20,6 +25,7 @@ export declare const createAngularComponentDefinition: (tagName: string, inputs:
20
25
  * @param events The events to generate the interface properties for.
21
26
  * @param componentCorePackage The component core package.
22
27
  * @param customElementsDir The custom elements directory.
28
+ * @param exportMaps Import through the package's `exports` map instead of a deep path.
23
29
  * @returns The component interface type definition as a string.
24
30
  */
25
- export declare const createComponentTypeDefinition: (outputType: OutputType, tagNameAsPascal: string, events: readonly ComponentCompilerEvent[], componentCorePackage: string, customElementsDir?: string) => string;
31
+ export declare const createComponentTypeDefinition: (outputType: OutputType, tagNameAsPascal: string, events: readonly ComponentCompilerEvent[], componentCorePackage: string, customElementsDir?: string, exportMaps?: boolean) => string;
@@ -25,21 +25,33 @@ function createPropertyDeclaration(prop, type, inlinePropertyAsSetter = false) {
25
25
  }
26
26
  }
27
27
  /**
28
- * Creates a formatted inputs text with required declaration.
28
+ * The transform function to reference in generated code, exported from
29
+ * `angular-component-lib/boolean-attribute`.
30
+ */
31
+ export const INPUT_TRANSFORM_FUNCTION = 'nullableBooleanAttribute';
32
+ /**
33
+ * Creates a formatted inputs text with required and transform declarations.
34
+ *
35
+ * Inputs are emitted as bare strings unless they carry options, in which case Angular's object
36
+ * form is used (e.g. `{ name: 'myInput', required: true }`).
29
37
  *
30
- * @param prop A ComponentCompilerEvent or ComponentCompilerProperty to turn into a property declaration.
31
38
  * @param inputs The inputs of the Stencil component (e.g. [{name: 'myInput', required: true]).
32
39
  * @returns The inputs list declaration as a string.
33
40
  */
34
41
  function formatInputs(inputs) {
35
42
  return inputs
36
43
  .map((item) => {
44
+ const options = [];
37
45
  if (item.required) {
38
- return `{ name: '${item.name}', required: true }`;
46
+ options.push('required: true');
47
+ }
48
+ if (item.transform) {
49
+ options.push(`transform: ${INPUT_TRANSFORM_FUNCTION}`);
39
50
  }
40
- else {
51
+ if (options.length === 0) {
41
52
  return `'${item.name}'`;
42
53
  }
54
+ return `{ name: '${item.name}', ${options.join(', ')} }`;
43
55
  })
44
56
  .join(', ');
45
57
  }
@@ -200,14 +212,16 @@ const createDocComment = (doc) => {
200
212
  * @param events The events to generate the interface properties for.
201
213
  * @param componentCorePackage The component core package.
202
214
  * @param customElementsDir The custom elements directory.
215
+ * @param exportMaps Import through the package's `exports` map instead of a deep path.
203
216
  * @returns The component interface type definition as a string.
204
217
  */
205
- export const createComponentTypeDefinition = (outputType, tagNameAsPascal, events, componentCorePackage, customElementsDir) => {
218
+ export const createComponentTypeDefinition = (outputType, tagNameAsPascal, events, componentCorePackage, customElementsDir, exportMaps = false) => {
206
219
  const publicEvents = events.filter((ev) => !ev.internal);
207
220
  const eventTypeImports = createComponentEventTypeImports(tagNameAsPascal, publicEvents, {
208
221
  componentCorePackage,
209
222
  customElementsDir,
210
223
  outputType,
224
+ exportMaps,
211
225
  });
212
226
  const eventTypes = publicEvents.map((event) => createPropertyDeclaration(event, `EventEmitter<${tagNameAsPascal}CustomEvent<${formatOutputType(tagNameAsPascal, event)}>>`));
213
227
  const interfaceDeclaration = `export declare interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {`;
@@ -5,4 +5,6 @@ import type { OutputTargetAngular } from './types';
5
5
  * This script patches component selectors in the built Angular library to use the
6
6
  * transformed tag names (e.g., 'my-component' -> 'v1-my-component').
7
7
  */
8
- export declare function generateTransformTagScript(compilerCtx: CompilerCtx, components: ComponentCompilerMeta[], outputTarget: OutputTargetAngular, packageName: string): Promise<void>;
8
+ export declare function generateTransformTagScript(compilerCtx: CompilerCtx, components: ComponentCompilerMeta[], outputTarget: OutputTargetAngular,
9
+ /** Import through the package's `exports` map (`<pkg>/standalone`) instead of a deep path. */
10
+ exportMaps?: boolean): Promise<void>;
@@ -5,10 +5,14 @@ import { dashToPascalCase } from './utils';
5
5
  * This script patches component selectors in the built Angular library to use the
6
6
  * transformed tag names (e.g., 'my-component' -> 'v1-my-component').
7
7
  */
8
- export async function generateTransformTagScript(compilerCtx, components, outputTarget, packageName) {
8
+ export async function generateTransformTagScript(compilerCtx, components, outputTarget,
9
+ /** Import through the package's `exports` map (`<pkg>/standalone`) instead of a deep path. */
10
+ exportMaps = false) {
9
11
  const scriptsDirectory = path.join(path.dirname(outputTarget.directivesProxyFile), '../../scripts');
10
12
  const customElementsDir = outputTarget.customElementsDir || 'dist/components';
11
- const stencilImportPath = `${outputTarget.componentCorePackage}/${customElementsDir}/index.js`;
13
+ const stencilImportPath = exportMaps
14
+ ? `${outputTarget.componentCorePackage}/standalone`
15
+ : `${outputTarget.componentCorePackage}/${customElementsDir}/index.js`;
12
16
  // Generate the mappings object
13
17
  const mappings = components
14
18
  .map((component) => {
package/dist/index.cjs CHANGED
@@ -126,7 +126,9 @@ const createComponentEventTypeImports = (componentTagName, events, options) => {
126
126
  const imports = [];
127
127
  const namedImports = new Set();
128
128
  const isCustomElementsBuild = isOutputTypeCustomElementsBuild(options.outputType);
129
- const importPathName = normalizePath(componentCorePackage) + (isCustomElementsBuild ? `/${customElementsDir}` : '');
129
+ const exportMapEntry = isCustomElementsBuild ? '/standalone' : '/loader';
130
+ const importPathName = normalizePath(componentCorePackage) +
131
+ (options.exportMaps ? exportMapEntry : isCustomElementsBuild ? `/${customElementsDir}` : '');
130
132
  /**
131
133
  * Each component's events are typed with the per-component CustomEvent type
132
134
  * generated by Stencil (e.g. `MyComponentCustomEvent<Detail>`), which carries a
@@ -179,21 +181,33 @@ function createPropertyDeclaration(prop, type, inlinePropertyAsSetter = false) {
179
181
  }
180
182
  }
181
183
  /**
182
- * Creates a formatted inputs text with required declaration.
184
+ * The transform function to reference in generated code, exported from
185
+ * `angular-component-lib/boolean-attribute`.
186
+ */
187
+ const INPUT_TRANSFORM_FUNCTION = 'nullableBooleanAttribute';
188
+ /**
189
+ * Creates a formatted inputs text with required and transform declarations.
190
+ *
191
+ * Inputs are emitted as bare strings unless they carry options, in which case Angular's object
192
+ * form is used (e.g. `{ name: 'myInput', required: true }`).
183
193
  *
184
- * @param prop A ComponentCompilerEvent or ComponentCompilerProperty to turn into a property declaration.
185
194
  * @param inputs The inputs of the Stencil component (e.g. [{name: 'myInput', required: true]).
186
195
  * @returns The inputs list declaration as a string.
187
196
  */
188
197
  function formatInputs(inputs) {
189
198
  return inputs
190
199
  .map((item) => {
200
+ const options = [];
191
201
  if (item.required) {
192
- return `{ name: '${item.name}', required: true }`;
202
+ options.push('required: true');
203
+ }
204
+ if (item.transform) {
205
+ options.push(`transform: ${INPUT_TRANSFORM_FUNCTION}`);
193
206
  }
194
- else {
207
+ if (options.length === 0) {
195
208
  return `'${item.name}'`;
196
209
  }
210
+ return `{ name: '${item.name}', ${options.join(', ')} }`;
197
211
  })
198
212
  .join(', ');
199
213
  }
@@ -354,14 +368,16 @@ const createDocComment = (doc) => {
354
368
  * @param events The events to generate the interface properties for.
355
369
  * @param componentCorePackage The component core package.
356
370
  * @param customElementsDir The custom elements directory.
371
+ * @param exportMaps Import through the package's `exports` map instead of a deep path.
357
372
  * @returns The component interface type definition as a string.
358
373
  */
359
- const createComponentTypeDefinition = (outputType, tagNameAsPascal, events, componentCorePackage, customElementsDir) => {
374
+ const createComponentTypeDefinition = (outputType, tagNameAsPascal, events, componentCorePackage, customElementsDir, exportMaps = false) => {
360
375
  const publicEvents = events.filter((ev) => !ev.internal);
361
376
  const eventTypeImports = createComponentEventTypeImports(tagNameAsPascal, publicEvents, {
362
377
  componentCorePackage,
363
378
  customElementsDir,
364
379
  outputType,
380
+ exportMaps,
365
381
  });
366
382
  const eventTypes = publicEvents.map((event) => createPropertyDeclaration(event, `EventEmitter<${tagNameAsPascal}CustomEvent<${formatOutputType(tagNameAsPascal, event)}>>`));
367
383
  const interfaceDeclaration = `export declare interface ${tagNameAsPascal} extends Components.${tagNameAsPascal} {`;
@@ -479,10 +495,14 @@ export class ${moduleClassName} { }`;
479
495
  * This script patches component selectors in the built Angular library to use the
480
496
  * transformed tag names (e.g., 'my-component' -> 'v1-my-component').
481
497
  */
482
- async function generateTransformTagScript(compilerCtx, components, outputTarget, packageName) {
498
+ async function generateTransformTagScript(compilerCtx, components, outputTarget,
499
+ /** Import through the package's `exports` map (`<pkg>/standalone`) instead of a deep path. */
500
+ exportMaps = false) {
483
501
  const scriptsDirectory = path__default["default"].join(path__default["default"].dirname(outputTarget.directivesProxyFile), '../../scripts');
484
502
  const customElementsDir = outputTarget.customElementsDir || 'dist/components';
485
- const stencilImportPath = `${outputTarget.componentCorePackage}/${customElementsDir}/index.js`;
503
+ const stencilImportPath = exportMaps
504
+ ? `${outputTarget.componentCorePackage}/standalone`
505
+ : `${outputTarget.componentCorePackage}/${customElementsDir}/index.js`;
486
506
  // Generate the mappings object
487
507
  const mappings = components
488
508
  .map((component) => {
@@ -782,6 +802,73 @@ stencilSetTagTransformer(\${transformerArg});
782
802
 
783
803
  const __filename$1 = url.fileURLToPath((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT' && document.currentScript.src || new URL('index.cjs', document.baseURI).href)));
784
804
  const __dirname$1 = path__default["default"].dirname(__filename$1);
805
+ const filterInternalProps = (prop) => !prop.internal;
806
+ /**
807
+ * Whether a property declares an input transform.
808
+ *
809
+ * This is the single definition of the rule. Both the generated declaration and the import that
810
+ * satisfies it are derived from it, so widening the rule can't leave a generated file referencing
811
+ * a transform it never imports.
812
+ *
813
+ * @param prop The property compiler metadata.
814
+ * @param booleanAttributes Whether boolean properties should declare an input transform.
815
+ * @returns `true` when the property should be transformed.
816
+ */
817
+ const isTransformedProp = (prop, booleanAttributes) => booleanAttributes && prop.type === 'boolean';
818
+ /**
819
+ * Maps a Stencil property to an Angular input declaration.
820
+ *
821
+ * Virtual properties are mapped with `booleanAttributes` left at its default of `false`. They
822
+ * carry a free-form type string and no `required` flag, so they are never transformed and always
823
+ * come out as optional.
824
+ *
825
+ * @param prop The property compiler metadata.
826
+ * @param booleanAttributes Whether boolean properties should declare an input transform.
827
+ * @returns The Angular input declaration.
828
+ */
829
+ const mapInputProp = (prop, booleanAttributes = false) => ({
830
+ name: prop.name,
831
+ required: prop.required ?? false,
832
+ transform: isTransformedProp(prop, booleanAttributes) ? true : undefined,
833
+ });
834
+ /**
835
+ * Whether any of a component's properties will declare an input transform, which determines
836
+ * whether the transform needs to be imported.
837
+ *
838
+ * @param components The components in the generated file.
839
+ * @param booleanAttributes Whether boolean properties should declare an input transform.
840
+ * @returns `true` when at least one property is transformed.
841
+ */
842
+ const usesInputTransform = (components, booleanAttributes) => components.some((cmpMeta) => (cmpMeta.properties ?? []).filter(filterInternalProps).some((prop) => isTransformedProp(prop, booleanAttributes)));
843
+ /**
844
+ * Whether the Stencil project generates a package.json `exports` map. Deep `dist/` imports are
845
+ * blocked for consumers then, so the proxies import through the map's entries instead
846
+ * (`<pkg>/<tag>`, `<pkg>/loader`, `<pkg>/standalone`).
847
+ */
848
+ function usesExportMaps(config) {
849
+ // Stencil v5 only: v4's map has no `./standalone` entry, and its root types aren't
850
+ // guaranteed to export the component types.
851
+ const isV5 = (config.outputTargets || []).some((o) => ['loader-bundle', 'standalone', 'ssr', 'types'].includes(o.type));
852
+ return isV5 && config.generateExportMaps === true;
853
+ }
854
+ /**
855
+ * The module specifier a proxy imports a component's `defineCustomElement` from.
856
+ */
857
+ function getComponentModule(config, outputTarget, tagName) {
858
+ const basePkg = normalizePath(outputTarget.componentCorePackage);
859
+ if (usesExportMaps(config)) {
860
+ return `${basePkg}/${tagName}`;
861
+ }
862
+ return `${basePkg}/${outputTarget.customElementsDir}/${tagName}.js`;
863
+ }
864
+ /**
865
+ * The relative import of the project's own `components.d.ts`, for when there's no
866
+ * `componentCorePackage`. Only resolved when needed: package.json may have no `types` field.
867
+ */
868
+ function getComponentsTypeFile(pkgData, outputTarget, rootDir) {
869
+ const dtsFilePath = path__default["default"].join(rootDir, path__default["default"].dirname(pkgData.types), GENERATED_DTS);
870
+ return relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
871
+ }
785
872
  async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components, config) {
786
873
  const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);
787
874
  const rootDir = config.rootDir;
@@ -815,27 +902,7 @@ async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components
815
902
  }
816
903
  // Generate transformer script if transformTag is enabled
817
904
  if (outputTarget.transformTag) {
818
- // Read the Angular library's package.json to get its name
819
- // directivesProxyFile is like: projects/library/src/directives/proxies.ts
820
- // We need to go up to: projects/library/package.json
821
- const angularLibraryDir = path__default["default"].dirname(path__default["default"].dirname(path__default["default"].dirname(outputTarget.directivesProxyFile)));
822
- const angularPkgJsonPath = path__default["default"].join(angularLibraryDir, 'package.json');
823
- let angularPackageName = '';
824
- try {
825
- const angularPkgJson = JSON.parse(await compilerCtx.fs.readFile(angularPkgJsonPath));
826
- if (angularPkgJson.name) {
827
- angularPackageName = angularPkgJson.name;
828
- }
829
- }
830
- catch (e) {
831
- throw new Error(`Could not read Angular library package.json at ${angularPkgJsonPath}. ` +
832
- `The package name is required to generate the transformTag patch script.`);
833
- }
834
- if (!angularPackageName) {
835
- throw new Error(`Angular library package.json at ${angularPkgJsonPath} does not have a "name" field. ` +
836
- `The package name is required to generate the transformTag patch script.`);
837
- }
838
- tasks.push(generateTransformTagScript(compilerCtx, filteredComponents, outputTarget));
905
+ tasks.push(generateTransformTagScript(compilerCtx, filteredComponents, outputTarget, usesExportMaps(config)));
839
906
  }
840
907
  await Promise.all(tasks);
841
908
  }
@@ -859,10 +926,7 @@ async function copyResources(config, outputTarget) {
859
926
  ], srcDirectory);
860
927
  }
861
928
  function generateProxies(components, pkgData, outputTarget, rootDir, config) {
862
- const distTypesDir = path__default["default"].dirname(pkgData.types);
863
- const dtsFilePath = path__default["default"].join(rootDir, distTypesDir, GENERATED_DTS);
864
929
  const { outputType } = outputTarget;
865
- const componentsTypeFile = relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
866
930
  const includeSingleComponentAngularModules = outputType === OutputTypes.Scam;
867
931
  const isCustomElementsBuild = isOutputTypeCustomElementsBuild(outputType);
868
932
  const isStandaloneBuild = outputType === OutputTypes.Standalone;
@@ -879,6 +943,14 @@ function generateProxies(components, pkgData, outputTarget, rootDir, config) {
879
943
  * The collection of named imports from the angular-component-lib/utils.
880
944
  */
881
945
  const componentLibImports = ['ProxyCmp'];
946
+ /**
947
+ * The input transform lives in its own module so that it carries no runtime imports.
948
+ */
949
+ let transformImport = '';
950
+ const booleanAttributes = outputTarget.booleanAttributes === true;
951
+ if (usesInputTransform(components, booleanAttributes)) {
952
+ transformImport = `\n${createImportStatement([INPUT_TRANSFORM_FUNCTION], './angular-component-lib/boolean-attribute')}`;
953
+ }
882
954
  if (includeSingleComponentAngularModules) {
883
955
  angularCoreImports.push('NgModule');
884
956
  }
@@ -886,7 +958,7 @@ function generateProxies(components, pkgData, outputTarget, rootDir, config) {
886
958
  /* auto-generated angular directive proxies */
887
959
  ${createImportStatement(angularCoreImports, '@angular/core')}
888
960
 
889
- ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n`;
961
+ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}${transformImport}\n`;
890
962
  /**
891
963
  * Generate JSX import type from correct location.
892
964
  * When using custom elements build, we need to import from
@@ -896,7 +968,7 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
896
968
  const generateTypeImports = () => {
897
969
  const importLocation = outputTarget.componentCorePackage
898
970
  ? getPathToComponentTypes(config, outputTarget)
899
- : normalizePath(componentsTypeFile);
971
+ : normalizePath(getComponentsTypeFile(pkgData, outputTarget, rootDir));
900
972
  return `import ${isCustomElementsBuild ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`;
901
973
  };
902
974
  const typeImports = generateTypeImports();
@@ -910,17 +982,11 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
910
982
  if (isCustomElementsBuild && outputTarget.componentCorePackage !== undefined) {
911
983
  const cmpImports = components.map((component) => {
912
984
  const pascalImport = dashToPascalCase(component.tagName);
913
- return `import { defineCustomElement as define${pascalImport} } from '${normalizePath(outputTarget.componentCorePackage)}/${outputTarget.customElementsDir}/${component.tagName}.js';`;
985
+ return `import { defineCustomElement as define${pascalImport} } from '${getComponentModule(config, outputTarget, component.tagName)}';`;
914
986
  });
915
987
  sourceImports = cmpImports.join('\n');
916
988
  }
917
989
  const proxyFileOutput = [];
918
- const filterInternalProps = (prop) => !prop.internal;
919
- // Ensure that virtual properties has required as false.
920
- const mapInputProp = (prop) => ({
921
- name: prop.name,
922
- required: prop.required ?? false,
923
- });
924
990
  const { componentCorePackage, customElementsDir } = outputTarget;
925
991
  for (let cmpMeta of components) {
926
992
  const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
@@ -928,9 +994,9 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
928
994
  if (cmpMeta.properties) {
929
995
  internalProps.push(...cmpMeta.properties.filter(filterInternalProps));
930
996
  }
931
- const inputs = internalProps.map(mapInputProp);
997
+ const inputs = internalProps.map((prop) => mapInputProp(prop, booleanAttributes));
932
998
  if (cmpMeta.virtualProperties) {
933
- inputs.push(...cmpMeta.virtualProperties.map(mapInputProp));
999
+ inputs.push(...cmpMeta.virtualProperties.map((prop) => mapInputProp(prop)));
934
1000
  }
935
1001
  const orderedInputs = sortBy(inputs, (cip) => cip.name);
936
1002
  const methods = [];
@@ -946,7 +1012,7 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
946
1012
  */
947
1013
  const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, orderedInputs, methods, isCustomElementsBuild, isStandaloneBuild, inlineComponentProps, cmpMeta.events || []);
948
1014
  const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
949
- const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir);
1015
+ const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir, usesExportMaps(config));
950
1016
  proxyFileOutput.push(componentDefinition, '\n');
951
1017
  if (includeSingleComponentAngularModules) {
952
1018
  proxyFileOutput.push(moduleDefinition, '\n');
@@ -961,9 +1027,6 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
961
1027
  */
962
1028
  function generateComponentProxy(cmpMeta, pkgData, outputTarget, rootDir, config) {
963
1029
  const { outputType, componentCorePackage, customElementsDir } = outputTarget;
964
- const distTypesDir = path__default["default"].dirname(pkgData.types);
965
- const dtsFilePath = path__default["default"].join(rootDir, distTypesDir, GENERATED_DTS);
966
- const componentsTypeFile = relativeImport(outputTarget.directivesProxyFile, dtsFilePath, '.d.ts');
967
1030
  const includeSingleComponentAngularModules = outputType === OutputTypes.Scam;
968
1031
  const isCustomElementsBuild = isOutputTypeCustomElementsBuild(outputType);
969
1032
  const isStandaloneBuild = outputType === OutputTypes.Standalone;
@@ -977,34 +1040,36 @@ function generateComponentProxy(cmpMeta, pkgData, outputTarget, rootDir, config)
977
1040
  if (includeSingleComponentAngularModules) {
978
1041
  angularCoreImports.push('NgModule');
979
1042
  }
1043
+ const booleanAttributes = outputTarget.booleanAttributes === true;
1044
+ const componentLibImports = ['ProxyCmp'];
1045
+ // The input transform lives in its own module so that it carries no runtime imports.
1046
+ let transformImport = '';
1047
+ if (usesInputTransform([cmpMeta], booleanAttributes)) {
1048
+ transformImport = `\n${createImportStatement([INPUT_TRANSFORM_FUNCTION], './angular-component-lib/boolean-attribute')}`;
1049
+ }
980
1050
  const imports = `/* tslint:disable */
981
1051
  /* auto-generated angular directive proxies */
982
1052
  ${createImportStatement(angularCoreImports, '@angular/core')}
983
1053
 
984
- ${createImportStatement(['ProxyCmp'], './angular-component-lib/utils')}\n`;
1054
+ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}${transformImport}\n`;
985
1055
  // Type imports
986
1056
  const importLocation = componentCorePackage
987
1057
  ? getPathToComponentTypes(config, outputTarget)
988
- : normalizePath(componentsTypeFile);
1058
+ : normalizePath(getComponentsTypeFile(pkgData, outputTarget, rootDir));
989
1059
  const typeImports = `import ${isCustomElementsBuild ? 'type ' : ''}{ ${IMPORT_TYPES} } from '${importLocation}';\n`;
990
1060
  // defineCustomElement import
991
1061
  let sourceImport = '';
992
1062
  if (isCustomElementsBuild && componentCorePackage !== undefined) {
993
- sourceImport = `import { defineCustomElement as define${tagNameAsPascal} } from '${normalizePath(componentCorePackage)}/${customElementsDir}/${cmpMeta.tagName}.js';\n`;
1063
+ sourceImport = `import { defineCustomElement as define${tagNameAsPascal} } from '${getComponentModule(config, outputTarget, cmpMeta.tagName)}';\n`;
994
1064
  }
995
1065
  // Generate component definition
996
- const filterInternalProps = (prop) => !prop.internal;
997
- const mapInputProp = (prop) => ({
998
- name: prop.name,
999
- required: prop.required ?? false,
1000
- });
1001
1066
  const internalProps = [];
1002
1067
  if (cmpMeta.properties) {
1003
1068
  internalProps.push(...cmpMeta.properties.filter(filterInternalProps));
1004
1069
  }
1005
- const inputs = internalProps.map(mapInputProp);
1070
+ const inputs = internalProps.map((prop) => mapInputProp(prop, booleanAttributes));
1006
1071
  if (cmpMeta.virtualProperties) {
1007
- inputs.push(...cmpMeta.virtualProperties.map(mapInputProp));
1072
+ inputs.push(...cmpMeta.virtualProperties.map((prop) => mapInputProp(prop)));
1008
1073
  }
1009
1074
  const orderedInputs = sortBy(inputs, (cip) => cip.name);
1010
1075
  const methods = [];
@@ -1014,7 +1079,7 @@ ${createImportStatement(['ProxyCmp'], './angular-component-lib/utils')}\n`;
1014
1079
  const inlineComponentProps = outputTarget.inlineProperties ? internalProps : [];
1015
1080
  const componentDefinition = createAngularComponentDefinition(cmpMeta.tagName, orderedInputs, methods, isCustomElementsBuild, isStandaloneBuild, inlineComponentProps, cmpMeta.events || []);
1016
1081
  const moduleDefinition = generateAngularModuleForComponent(cmpMeta.tagName);
1017
- const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir);
1082
+ const componentTypeDefinition = createComponentTypeDefinition(outputType, tagNameAsPascal, cmpMeta.events, componentCorePackage, customElementsDir, usesExportMaps(config));
1018
1083
  const proxyFileOutput = [componentDefinition, '\n'];
1019
1084
  if (includeSingleComponentAngularModules) {
1020
1085
  proxyFileOutput.push(moduleDefinition, '\n');
@@ -1045,6 +1110,12 @@ function generateBarrelFile(components, outputTarget) {
1045
1110
  }
1046
1111
  function getPathToComponentTypes(config, outputTarget) {
1047
1112
  const basePkg = outputTarget.componentCorePackage || '';
1113
+ // The entry of the output the proxies are built on always exports the component types - the
1114
+ // package root only does when the project has no `src/index.ts`, or that file re-exports them
1115
+ if (usesExportMaps(config)) {
1116
+ const entry = isOutputTypeCustomElementsBuild(outputTarget.outputType) ? 'standalone' : 'loader';
1117
+ return `${normalizePath(basePkg)}/${entry}`;
1118
+ }
1048
1119
  // in v5, all types (including components.d.ts) are generated in the dist/types directory
1049
1120
  const typesTarget = config.outputTargets?.find((o) => o.type === 'types');
1050
1121
  if (typesTarget) {