@stencil/angular-output-target 1.4.0 → 1.5.0

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
  *
@@ -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
  }
package/dist/index.cjs CHANGED
@@ -179,21 +179,33 @@ function createPropertyDeclaration(prop, type, inlinePropertyAsSetter = false) {
179
179
  }
180
180
  }
181
181
  /**
182
- * Creates a formatted inputs text with required declaration.
182
+ * The transform function to reference in generated code, exported from
183
+ * `angular-component-lib/boolean-attribute`.
184
+ */
185
+ const INPUT_TRANSFORM_FUNCTION = 'nullableBooleanAttribute';
186
+ /**
187
+ * Creates a formatted inputs text with required and transform declarations.
188
+ *
189
+ * Inputs are emitted as bare strings unless they carry options, in which case Angular's object
190
+ * form is used (e.g. `{ name: 'myInput', required: true }`).
183
191
  *
184
- * @param prop A ComponentCompilerEvent or ComponentCompilerProperty to turn into a property declaration.
185
192
  * @param inputs The inputs of the Stencil component (e.g. [{name: 'myInput', required: true]).
186
193
  * @returns The inputs list declaration as a string.
187
194
  */
188
195
  function formatInputs(inputs) {
189
196
  return inputs
190
197
  .map((item) => {
198
+ const options = [];
191
199
  if (item.required) {
192
- return `{ name: '${item.name}', required: true }`;
200
+ options.push('required: true');
201
+ }
202
+ if (item.transform) {
203
+ options.push(`transform: ${INPUT_TRANSFORM_FUNCTION}`);
193
204
  }
194
- else {
205
+ if (options.length === 0) {
195
206
  return `'${item.name}'`;
196
207
  }
208
+ return `{ name: '${item.name}', ${options.join(', ')} }`;
197
209
  })
198
210
  .join(', ');
199
211
  }
@@ -782,6 +794,44 @@ stencilSetTagTransformer(\${transformerArg});
782
794
 
783
795
  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
796
  const __dirname$1 = path__default["default"].dirname(__filename$1);
797
+ const filterInternalProps = (prop) => !prop.internal;
798
+ /**
799
+ * Whether a property declares an input transform.
800
+ *
801
+ * This is the single definition of the rule. Both the generated declaration and the import that
802
+ * satisfies it are derived from it, so widening the rule can't leave a generated file referencing
803
+ * a transform it never imports.
804
+ *
805
+ * @param prop The property compiler metadata.
806
+ * @param booleanAttributes Whether boolean properties should declare an input transform.
807
+ * @returns `true` when the property should be transformed.
808
+ */
809
+ const isTransformedProp = (prop, booleanAttributes) => booleanAttributes && prop.type === 'boolean';
810
+ /**
811
+ * Maps a Stencil property to an Angular input declaration.
812
+ *
813
+ * Virtual properties are mapped with `booleanAttributes` left at its default of `false`. They
814
+ * carry a free-form type string and no `required` flag, so they are never transformed and always
815
+ * come out as optional.
816
+ *
817
+ * @param prop The property compiler metadata.
818
+ * @param booleanAttributes Whether boolean properties should declare an input transform.
819
+ * @returns The Angular input declaration.
820
+ */
821
+ const mapInputProp = (prop, booleanAttributes = false) => ({
822
+ name: prop.name,
823
+ required: prop.required ?? false,
824
+ transform: isTransformedProp(prop, booleanAttributes) ? true : undefined,
825
+ });
826
+ /**
827
+ * Whether any of a component's properties will declare an input transform, which determines
828
+ * whether the transform needs to be imported.
829
+ *
830
+ * @param components The components in the generated file.
831
+ * @param booleanAttributes Whether boolean properties should declare an input transform.
832
+ * @returns `true` when at least one property is transformed.
833
+ */
834
+ const usesInputTransform = (components, booleanAttributes) => components.some((cmpMeta) => (cmpMeta.properties ?? []).filter(filterInternalProps).some((prop) => isTransformedProp(prop, booleanAttributes)));
785
835
  async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components, config) {
786
836
  const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);
787
837
  const rootDir = config.rootDir;
@@ -879,6 +929,14 @@ function generateProxies(components, pkgData, outputTarget, rootDir, config) {
879
929
  * The collection of named imports from the angular-component-lib/utils.
880
930
  */
881
931
  const componentLibImports = ['ProxyCmp'];
932
+ /**
933
+ * The input transform lives in its own module so that it carries no runtime imports.
934
+ */
935
+ let transformImport = '';
936
+ const booleanAttributes = outputTarget.booleanAttributes === true;
937
+ if (usesInputTransform(components, booleanAttributes)) {
938
+ transformImport = `\n${createImportStatement([INPUT_TRANSFORM_FUNCTION], './angular-component-lib/boolean-attribute')}`;
939
+ }
882
940
  if (includeSingleComponentAngularModules) {
883
941
  angularCoreImports.push('NgModule');
884
942
  }
@@ -886,7 +944,7 @@ function generateProxies(components, pkgData, outputTarget, rootDir, config) {
886
944
  /* auto-generated angular directive proxies */
887
945
  ${createImportStatement(angularCoreImports, '@angular/core')}
888
946
 
889
- ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n`;
947
+ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}${transformImport}\n`;
890
948
  /**
891
949
  * Generate JSX import type from correct location.
892
950
  * When using custom elements build, we need to import from
@@ -915,12 +973,6 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
915
973
  sourceImports = cmpImports.join('\n');
916
974
  }
917
975
  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
976
  const { componentCorePackage, customElementsDir } = outputTarget;
925
977
  for (let cmpMeta of components) {
926
978
  const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
@@ -928,9 +980,9 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
928
980
  if (cmpMeta.properties) {
929
981
  internalProps.push(...cmpMeta.properties.filter(filterInternalProps));
930
982
  }
931
- const inputs = internalProps.map(mapInputProp);
983
+ const inputs = internalProps.map((prop) => mapInputProp(prop, booleanAttributes));
932
984
  if (cmpMeta.virtualProperties) {
933
- inputs.push(...cmpMeta.virtualProperties.map(mapInputProp));
985
+ inputs.push(...cmpMeta.virtualProperties.map((prop) => mapInputProp(prop)));
934
986
  }
935
987
  const orderedInputs = sortBy(inputs, (cip) => cip.name);
936
988
  const methods = [];
@@ -977,11 +1029,18 @@ function generateComponentProxy(cmpMeta, pkgData, outputTarget, rootDir, config)
977
1029
  if (includeSingleComponentAngularModules) {
978
1030
  angularCoreImports.push('NgModule');
979
1031
  }
1032
+ const booleanAttributes = outputTarget.booleanAttributes === true;
1033
+ const componentLibImports = ['ProxyCmp'];
1034
+ // The input transform lives in its own module so that it carries no runtime imports.
1035
+ let transformImport = '';
1036
+ if (usesInputTransform([cmpMeta], booleanAttributes)) {
1037
+ transformImport = `\n${createImportStatement([INPUT_TRANSFORM_FUNCTION], './angular-component-lib/boolean-attribute')}`;
1038
+ }
980
1039
  const imports = `/* tslint:disable */
981
1040
  /* auto-generated angular directive proxies */
982
1041
  ${createImportStatement(angularCoreImports, '@angular/core')}
983
1042
 
984
- ${createImportStatement(['ProxyCmp'], './angular-component-lib/utils')}\n`;
1043
+ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}${transformImport}\n`;
985
1044
  // Type imports
986
1045
  const importLocation = componentCorePackage
987
1046
  ? getPathToComponentTypes(config, outputTarget)
@@ -993,18 +1052,13 @@ ${createImportStatement(['ProxyCmp'], './angular-component-lib/utils')}\n`;
993
1052
  sourceImport = `import { defineCustomElement as define${tagNameAsPascal} } from '${normalizePath(componentCorePackage)}/${customElementsDir}/${cmpMeta.tagName}.js';\n`;
994
1053
  }
995
1054
  // Generate component definition
996
- const filterInternalProps = (prop) => !prop.internal;
997
- const mapInputProp = (prop) => ({
998
- name: prop.name,
999
- required: prop.required ?? false,
1000
- });
1001
1055
  const internalProps = [];
1002
1056
  if (cmpMeta.properties) {
1003
1057
  internalProps.push(...cmpMeta.properties.filter(filterInternalProps));
1004
1058
  }
1005
- const inputs = internalProps.map(mapInputProp);
1059
+ const inputs = internalProps.map((prop) => mapInputProp(prop, booleanAttributes));
1006
1060
  if (cmpMeta.virtualProperties) {
1007
- inputs.push(...cmpMeta.virtualProperties.map(mapInputProp));
1061
+ inputs.push(...cmpMeta.virtualProperties.map((prop) => mapInputProp(prop)));
1008
1062
  }
1009
1063
  const orderedInputs = sortBy(inputs, (cip) => cip.name);
1010
1064
  const methods = [];
package/dist/index.js CHANGED
@@ -171,21 +171,33 @@ function createPropertyDeclaration(prop, type, inlinePropertyAsSetter = false) {
171
171
  }
172
172
  }
173
173
  /**
174
- * Creates a formatted inputs text with required declaration.
174
+ * The transform function to reference in generated code, exported from
175
+ * `angular-component-lib/boolean-attribute`.
176
+ */
177
+ const INPUT_TRANSFORM_FUNCTION = 'nullableBooleanAttribute';
178
+ /**
179
+ * Creates a formatted inputs text with required and transform declarations.
180
+ *
181
+ * Inputs are emitted as bare strings unless they carry options, in which case Angular's object
182
+ * form is used (e.g. `{ name: 'myInput', required: true }`).
175
183
  *
176
- * @param prop A ComponentCompilerEvent or ComponentCompilerProperty to turn into a property declaration.
177
184
  * @param inputs The inputs of the Stencil component (e.g. [{name: 'myInput', required: true]).
178
185
  * @returns The inputs list declaration as a string.
179
186
  */
180
187
  function formatInputs(inputs) {
181
188
  return inputs
182
189
  .map((item) => {
190
+ const options = [];
183
191
  if (item.required) {
184
- return `{ name: '${item.name}', required: true }`;
192
+ options.push('required: true');
193
+ }
194
+ if (item.transform) {
195
+ options.push(`transform: ${INPUT_TRANSFORM_FUNCTION}`);
185
196
  }
186
- else {
197
+ if (options.length === 0) {
187
198
  return `'${item.name}'`;
188
199
  }
200
+ return `{ name: '${item.name}', ${options.join(', ')} }`;
189
201
  })
190
202
  .join(', ');
191
203
  }
@@ -774,6 +786,44 @@ stencilSetTagTransformer(\${transformerArg});
774
786
 
775
787
  const __filename = fileURLToPath(import.meta.url);
776
788
  const __dirname = path.dirname(__filename);
789
+ const filterInternalProps = (prop) => !prop.internal;
790
+ /**
791
+ * Whether a property declares an input transform.
792
+ *
793
+ * This is the single definition of the rule. Both the generated declaration and the import that
794
+ * satisfies it are derived from it, so widening the rule can't leave a generated file referencing
795
+ * a transform it never imports.
796
+ *
797
+ * @param prop The property compiler metadata.
798
+ * @param booleanAttributes Whether boolean properties should declare an input transform.
799
+ * @returns `true` when the property should be transformed.
800
+ */
801
+ const isTransformedProp = (prop, booleanAttributes) => booleanAttributes && prop.type === 'boolean';
802
+ /**
803
+ * Maps a Stencil property to an Angular input declaration.
804
+ *
805
+ * Virtual properties are mapped with `booleanAttributes` left at its default of `false`. They
806
+ * carry a free-form type string and no `required` flag, so they are never transformed and always
807
+ * come out as optional.
808
+ *
809
+ * @param prop The property compiler metadata.
810
+ * @param booleanAttributes Whether boolean properties should declare an input transform.
811
+ * @returns The Angular input declaration.
812
+ */
813
+ const mapInputProp = (prop, booleanAttributes = false) => ({
814
+ name: prop.name,
815
+ required: prop.required ?? false,
816
+ transform: isTransformedProp(prop, booleanAttributes) ? true : undefined,
817
+ });
818
+ /**
819
+ * Whether any of a component's properties will declare an input transform, which determines
820
+ * whether the transform needs to be imported.
821
+ *
822
+ * @param components The components in the generated file.
823
+ * @param booleanAttributes Whether boolean properties should declare an input transform.
824
+ * @returns `true` when at least one property is transformed.
825
+ */
826
+ const usesInputTransform = (components, booleanAttributes) => components.some((cmpMeta) => (cmpMeta.properties ?? []).filter(filterInternalProps).some((prop) => isTransformedProp(prop, booleanAttributes)));
777
827
  async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components, config) {
778
828
  const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);
779
829
  const rootDir = config.rootDir;
@@ -871,6 +921,14 @@ function generateProxies(components, pkgData, outputTarget, rootDir, config) {
871
921
  * The collection of named imports from the angular-component-lib/utils.
872
922
  */
873
923
  const componentLibImports = ['ProxyCmp'];
924
+ /**
925
+ * The input transform lives in its own module so that it carries no runtime imports.
926
+ */
927
+ let transformImport = '';
928
+ const booleanAttributes = outputTarget.booleanAttributes === true;
929
+ if (usesInputTransform(components, booleanAttributes)) {
930
+ transformImport = `\n${createImportStatement([INPUT_TRANSFORM_FUNCTION], './angular-component-lib/boolean-attribute')}`;
931
+ }
874
932
  if (includeSingleComponentAngularModules) {
875
933
  angularCoreImports.push('NgModule');
876
934
  }
@@ -878,7 +936,7 @@ function generateProxies(components, pkgData, outputTarget, rootDir, config) {
878
936
  /* auto-generated angular directive proxies */
879
937
  ${createImportStatement(angularCoreImports, '@angular/core')}
880
938
 
881
- ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n`;
939
+ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}${transformImport}\n`;
882
940
  /**
883
941
  * Generate JSX import type from correct location.
884
942
  * When using custom elements build, we need to import from
@@ -907,12 +965,6 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
907
965
  sourceImports = cmpImports.join('\n');
908
966
  }
909
967
  const proxyFileOutput = [];
910
- const filterInternalProps = (prop) => !prop.internal;
911
- // Ensure that virtual properties has required as false.
912
- const mapInputProp = (prop) => ({
913
- name: prop.name,
914
- required: prop.required ?? false,
915
- });
916
968
  const { componentCorePackage, customElementsDir } = outputTarget;
917
969
  for (let cmpMeta of components) {
918
970
  const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
@@ -920,9 +972,9 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
920
972
  if (cmpMeta.properties) {
921
973
  internalProps.push(...cmpMeta.properties.filter(filterInternalProps));
922
974
  }
923
- const inputs = internalProps.map(mapInputProp);
975
+ const inputs = internalProps.map((prop) => mapInputProp(prop, booleanAttributes));
924
976
  if (cmpMeta.virtualProperties) {
925
- inputs.push(...cmpMeta.virtualProperties.map(mapInputProp));
977
+ inputs.push(...cmpMeta.virtualProperties.map((prop) => mapInputProp(prop)));
926
978
  }
927
979
  const orderedInputs = sortBy(inputs, (cip) => cip.name);
928
980
  const methods = [];
@@ -969,11 +1021,18 @@ function generateComponentProxy(cmpMeta, pkgData, outputTarget, rootDir, config)
969
1021
  if (includeSingleComponentAngularModules) {
970
1022
  angularCoreImports.push('NgModule');
971
1023
  }
1024
+ const booleanAttributes = outputTarget.booleanAttributes === true;
1025
+ const componentLibImports = ['ProxyCmp'];
1026
+ // The input transform lives in its own module so that it carries no runtime imports.
1027
+ let transformImport = '';
1028
+ if (usesInputTransform([cmpMeta], booleanAttributes)) {
1029
+ transformImport = `\n${createImportStatement([INPUT_TRANSFORM_FUNCTION], './angular-component-lib/boolean-attribute')}`;
1030
+ }
972
1031
  const imports = `/* tslint:disable */
973
1032
  /* auto-generated angular directive proxies */
974
1033
  ${createImportStatement(angularCoreImports, '@angular/core')}
975
1034
 
976
- ${createImportStatement(['ProxyCmp'], './angular-component-lib/utils')}\n`;
1035
+ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}${transformImport}\n`;
977
1036
  // Type imports
978
1037
  const importLocation = componentCorePackage
979
1038
  ? getPathToComponentTypes(config, outputTarget)
@@ -985,18 +1044,13 @@ ${createImportStatement(['ProxyCmp'], './angular-component-lib/utils')}\n`;
985
1044
  sourceImport = `import { defineCustomElement as define${tagNameAsPascal} } from '${normalizePath(componentCorePackage)}/${customElementsDir}/${cmpMeta.tagName}.js';\n`;
986
1045
  }
987
1046
  // Generate component definition
988
- const filterInternalProps = (prop) => !prop.internal;
989
- const mapInputProp = (prop) => ({
990
- name: prop.name,
991
- required: prop.required ?? false,
992
- });
993
1047
  const internalProps = [];
994
1048
  if (cmpMeta.properties) {
995
1049
  internalProps.push(...cmpMeta.properties.filter(filterInternalProps));
996
1050
  }
997
- const inputs = internalProps.map(mapInputProp);
1051
+ const inputs = internalProps.map((prop) => mapInputProp(prop, booleanAttributes));
998
1052
  if (cmpMeta.virtualProperties) {
999
- inputs.push(...cmpMeta.virtualProperties.map(mapInputProp));
1053
+ inputs.push(...cmpMeta.virtualProperties.map((prop) => mapInputProp(prop)));
1000
1054
  }
1001
1055
  const orderedInputs = sortBy(inputs, (cip) => cip.name);
1002
1056
  const methods = [];
@@ -3,11 +3,49 @@ import { fileURLToPath } from 'url';
3
3
  const __filename = fileURLToPath(import.meta.url);
4
4
  const __dirname = path.dirname(__filename);
5
5
  import { relativeImport, normalizePath, sortBy, readPackageJson, dashToPascalCase, createImportStatement, isOutputTypeCustomElementsBuild, OutputTypes, mapPropName, } from './utils';
6
- import { createAngularComponentDefinition, createComponentTypeDefinition } from './generate-angular-component';
6
+ import { createAngularComponentDefinition, createComponentTypeDefinition, INPUT_TRANSFORM_FUNCTION, } from './generate-angular-component';
7
7
  import { generateAngularDirectivesFile } from './generate-angular-directives-file';
8
8
  import generateValueAccessors from './generate-value-accessors';
9
9
  import { generateAngularModuleForComponent } from './generate-angular-modules';
10
10
  import { generateTransformTagScript } from './generate-transformtag-script';
11
+ const filterInternalProps = (prop) => !prop.internal;
12
+ /**
13
+ * Whether a property declares an input transform.
14
+ *
15
+ * This is the single definition of the rule. Both the generated declaration and the import that
16
+ * satisfies it are derived from it, so widening the rule can't leave a generated file referencing
17
+ * a transform it never imports.
18
+ *
19
+ * @param prop The property compiler metadata.
20
+ * @param booleanAttributes Whether boolean properties should declare an input transform.
21
+ * @returns `true` when the property should be transformed.
22
+ */
23
+ const isTransformedProp = (prop, booleanAttributes) => booleanAttributes && prop.type === 'boolean';
24
+ /**
25
+ * Maps a Stencil property to an Angular input declaration.
26
+ *
27
+ * Virtual properties are mapped with `booleanAttributes` left at its default of `false`. They
28
+ * carry a free-form type string and no `required` flag, so they are never transformed and always
29
+ * come out as optional.
30
+ *
31
+ * @param prop The property compiler metadata.
32
+ * @param booleanAttributes Whether boolean properties should declare an input transform.
33
+ * @returns The Angular input declaration.
34
+ */
35
+ const mapInputProp = (prop, booleanAttributes = false) => ({
36
+ name: prop.name,
37
+ required: prop.required ?? false,
38
+ transform: isTransformedProp(prop, booleanAttributes) ? true : undefined,
39
+ });
40
+ /**
41
+ * Whether any of a component's properties will declare an input transform, which determines
42
+ * whether the transform needs to be imported.
43
+ *
44
+ * @param components The components in the generated file.
45
+ * @param booleanAttributes Whether boolean properties should declare an input transform.
46
+ * @returns `true` when at least one property is transformed.
47
+ */
48
+ const usesInputTransform = (components, booleanAttributes) => components.some((cmpMeta) => (cmpMeta.properties ?? []).filter(filterInternalProps).some((prop) => isTransformedProp(prop, booleanAttributes)));
11
49
  export async function angularDirectiveProxyOutput(compilerCtx, outputTarget, components, config) {
12
50
  const filteredComponents = getFilteredComponents(outputTarget.excludeComponents, components);
13
51
  const rootDir = config.rootDir;
@@ -105,6 +143,14 @@ export function generateProxies(components, pkgData, outputTarget, rootDir, conf
105
143
  * The collection of named imports from the angular-component-lib/utils.
106
144
  */
107
145
  const componentLibImports = ['ProxyCmp'];
146
+ /**
147
+ * The input transform lives in its own module so that it carries no runtime imports.
148
+ */
149
+ let transformImport = '';
150
+ const booleanAttributes = outputTarget.booleanAttributes === true;
151
+ if (usesInputTransform(components, booleanAttributes)) {
152
+ transformImport = `\n${createImportStatement([INPUT_TRANSFORM_FUNCTION], './angular-component-lib/boolean-attribute')}`;
153
+ }
108
154
  if (includeSingleComponentAngularModules) {
109
155
  angularCoreImports.push('NgModule');
110
156
  }
@@ -112,7 +158,7 @@ export function generateProxies(components, pkgData, outputTarget, rootDir, conf
112
158
  /* auto-generated angular directive proxies */
113
159
  ${createImportStatement(angularCoreImports, '@angular/core')}
114
160
 
115
- ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n`;
161
+ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}${transformImport}\n`;
116
162
  /**
117
163
  * Generate JSX import type from correct location.
118
164
  * When using custom elements build, we need to import from
@@ -141,12 +187,6 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
141
187
  sourceImports = cmpImports.join('\n');
142
188
  }
143
189
  const proxyFileOutput = [];
144
- const filterInternalProps = (prop) => !prop.internal;
145
- // Ensure that virtual properties has required as false.
146
- const mapInputProp = (prop) => ({
147
- name: prop.name,
148
- required: prop.required ?? false,
149
- });
150
190
  const { componentCorePackage, customElementsDir } = outputTarget;
151
191
  for (let cmpMeta of components) {
152
192
  const tagNameAsPascal = dashToPascalCase(cmpMeta.tagName);
@@ -154,9 +194,9 @@ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}\n
154
194
  if (cmpMeta.properties) {
155
195
  internalProps.push(...cmpMeta.properties.filter(filterInternalProps));
156
196
  }
157
- const inputs = internalProps.map(mapInputProp);
197
+ const inputs = internalProps.map((prop) => mapInputProp(prop, booleanAttributes));
158
198
  if (cmpMeta.virtualProperties) {
159
- inputs.push(...cmpMeta.virtualProperties.map(mapInputProp));
199
+ inputs.push(...cmpMeta.virtualProperties.map((prop) => mapInputProp(prop)));
160
200
  }
161
201
  const orderedInputs = sortBy(inputs, (cip) => cip.name);
162
202
  const methods = [];
@@ -203,11 +243,18 @@ export function generateComponentProxy(cmpMeta, pkgData, outputTarget, rootDir,
203
243
  if (includeSingleComponentAngularModules) {
204
244
  angularCoreImports.push('NgModule');
205
245
  }
246
+ const booleanAttributes = outputTarget.booleanAttributes === true;
247
+ const componentLibImports = ['ProxyCmp'];
248
+ // The input transform lives in its own module so that it carries no runtime imports.
249
+ let transformImport = '';
250
+ if (usesInputTransform([cmpMeta], booleanAttributes)) {
251
+ transformImport = `\n${createImportStatement([INPUT_TRANSFORM_FUNCTION], './angular-component-lib/boolean-attribute')}`;
252
+ }
206
253
  const imports = `/* tslint:disable */
207
254
  /* auto-generated angular directive proxies */
208
255
  ${createImportStatement(angularCoreImports, '@angular/core')}
209
256
 
210
- ${createImportStatement(['ProxyCmp'], './angular-component-lib/utils')}\n`;
257
+ ${createImportStatement(componentLibImports, './angular-component-lib/utils')}${transformImport}\n`;
211
258
  // Type imports
212
259
  const importLocation = componentCorePackage
213
260
  ? getPathToComponentTypes(config, outputTarget)
@@ -219,18 +266,13 @@ ${createImportStatement(['ProxyCmp'], './angular-component-lib/utils')}\n`;
219
266
  sourceImport = `import { defineCustomElement as define${tagNameAsPascal} } from '${normalizePath(componentCorePackage)}/${customElementsDir}/${cmpMeta.tagName}.js';\n`;
220
267
  }
221
268
  // Generate component definition
222
- const filterInternalProps = (prop) => !prop.internal;
223
- const mapInputProp = (prop) => ({
224
- name: prop.name,
225
- required: prop.required ?? false,
226
- });
227
269
  const internalProps = [];
228
270
  if (cmpMeta.properties) {
229
271
  internalProps.push(...cmpMeta.properties.filter(filterInternalProps));
230
272
  }
231
- const inputs = internalProps.map(mapInputProp);
273
+ const inputs = internalProps.map((prop) => mapInputProp(prop, booleanAttributes));
232
274
  if (cmpMeta.virtualProperties) {
233
- inputs.push(...cmpMeta.virtualProperties.map(mapInputProp));
275
+ inputs.push(...cmpMeta.virtualProperties.map((prop) => mapInputProp(prop)));
234
276
  }
235
277
  const orderedInputs = sortBy(inputs, (cip) => cip.name);
236
278
  const methods = [];
package/dist/types.d.ts CHANGED
@@ -94,6 +94,43 @@ export interface OutputTargetAngular {
94
94
  * @default false
95
95
  */
96
96
  esModules?: boolean;
97
+ /**
98
+ * When `true`, boolean properties are declared with an Angular input transform so they can
99
+ * be set using attribute presence syntax:
100
+ *
101
+ * ```html
102
+ * <!-- with booleanAttributes enabled -->
103
+ * <my-component disabled></my-component>
104
+ *
105
+ * <!-- otherwise the value has to be bound explicitly -->
106
+ * <my-component [disabled]="true"></my-component>
107
+ * ```
108
+ *
109
+ * Without a transform Angular resolves the bare attribute to the empty string and reports
110
+ * `Type 'string' is not assignable to type 'boolean'` under `strictTemplates`.
111
+ *
112
+ * Compile-time checking of these bindings also requires `inlineProperties`. Without it the
113
+ * generated wrappers declare no typed members for their inputs, so Angular never checks them
114
+ * and there is no error for the transform to resolve.
115
+ *
116
+ * The transform still runs either way, because Angular applies it from the component
117
+ * definition's input map rather than from a declared class member. Enabling this on its own
118
+ * therefore moves boolean coercion from Stencil to the transform with nothing checking what
119
+ * callers pass, so prefer enabling `inlineProperties` alongside it.
120
+ *
121
+ * The transform coerces strings the same way Angular's `booleanAttribute` does, but passes
122
+ * `null` and `undefined` through instead of coercing them to `false`. Refer to
123
+ * `nullableBooleanAttribute`.
124
+ *
125
+ * Properties are only transformed when Stencil reports their type as exactly `boolean`.
126
+ * Virtual properties, properties declared as `any`, and types that union `boolean` with
127
+ * something else (such as `boolean | 'auto'`, reported as `any`) get no transform, and the
128
+ * property receives the attribute's empty string rather than `true`. Only the union case fails
129
+ * to compile; the other two are silent. Refer to the README for the differences.
130
+ *
131
+ * @default false
132
+ */
133
+ booleanAttributes?: boolean;
97
134
  }
98
135
  export type ValueAccessorTypes = 'text' | 'radio' | 'select' | 'number' | 'boolean';
99
136
  export interface ValueAccessorConfig {
@@ -108,4 +145,8 @@ export interface PackageJSON {
108
145
  export interface ComponentInputProperty {
109
146
  name: string;
110
147
  required: boolean;
148
+ /**
149
+ * Whether to declare an input transform so the property can be set by attribute presence.
150
+ */
151
+ transform?: boolean;
111
152
  }
package/dist/wizard.d.ts CHANGED
@@ -4,6 +4,6 @@ export declare const wizard: {
4
4
  id: string;
5
5
  displayName: string;
6
6
  description: string;
7
- run({ config, workspaceRoot, prompts, nypm }: WizardContext): Promise<void>;
7
+ run({ config, workspaceRoot, prompts, nypm, openStencilConfig }: WizardContext): Promise<void>;
8
8
  };
9
9
  };
package/dist/wizard.js CHANGED
@@ -1,54 +1,6 @@
1
- import { Project, SyntaxKind } from 'ts-morph';
2
1
  import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
3
2
  import { join, relative, dirname } from 'node:path';
4
3
  // ---------------------------------------------------------------------------
5
- // ts-morph helpers
6
- // ---------------------------------------------------------------------------
7
- function amendStencilConfig(configPath, targetCode) {
8
- const project = new Project({ skipAddingFilesFromTsConfig: true });
9
- const src = project.addSourceFileAtPath(configPath);
10
- if (!src.getImportDeclaration('@stencil/angular-output-target')) {
11
- src.addImportDeclaration({
12
- moduleSpecifier: '@stencil/angular-output-target',
13
- namedImports: ['angularOutputTarget'],
14
- });
15
- }
16
- const existingProp = src
17
- .getDescendantsOfKind(SyntaxKind.PropertyAssignment)
18
- .find((p) => p.getName() === 'outputTargets');
19
- if (!existingProp) {
20
- const configObj = src.getVariableDeclaration('config')?.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression) ??
21
- src
22
- .getDescendantsOfKind(SyntaxKind.ObjectLiteralExpression)
23
- .find((obj) => obj.getProperty('namespace') !== undefined);
24
- if (!configObj)
25
- throw new Error('Could not find Stencil config object in stencil.config.ts');
26
- configObj.addPropertyAssignment({ name: 'outputTargets', initializer: '[]' });
27
- }
28
- const prop = src.getDescendantsOfKind(SyntaxKind.PropertyAssignment).find((p) => p.getName() === 'outputTargets');
29
- const arr = prop.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression);
30
- if (!arr)
31
- throw new Error('outputTargets is not an array literal in stencil.config.ts');
32
- const existing = arr.getText();
33
- const alreadyAdded = existing.includes('angularOutputTarget(');
34
- // Filter out any existing angularOutputTarget call so it can be replaced cleanly
35
- const elements = arr
36
- .getElements()
37
- .map((e) => e.getText().trim())
38
- .filter((e) => !e.includes('angularOutputTarget('));
39
- const hasStandalone = existing.includes("type: 'standalone'") ||
40
- existing.includes('type: "standalone"') ||
41
- existing.includes("type: 'dist-custom-elements'") ||
42
- existing.includes('type: "dist-custom-elements"');
43
- if (!hasStandalone)
44
- elements.push("{ type: 'standalone' }");
45
- elements.push(targetCode);
46
- prop.setInitializer(`[\n${elements.map((e) => ` ${e}`).join(',\n')},\n]`);
47
- src.formatText();
48
- src.saveSync();
49
- return !alreadyAdded;
50
- }
51
- // ---------------------------------------------------------------------------
52
4
  // pnpm build approval
53
5
  // ---------------------------------------------------------------------------
54
6
  async function allowPnpmBuild(pkg, rootDir, workspaceRoot) {
@@ -194,19 +146,12 @@ export const wizard = {
194
146
  id: '@stencil/angular-output-target',
195
147
  displayName: 'Angular',
196
148
  description: 'Angular component wrappers for your Stencil components',
197
- async run({ config, workspaceRoot, prompts, nypm }) {
149
+ async run({ config, workspaceRoot, prompts, nypm, openStencilConfig }) {
198
150
  const { intro, outro, text, select, confirm, spinner, isCancel, cancel, log } = prompts;
199
151
  intro('Angular output target');
200
- const stencilConfigPath = join(config.rootDir, 'stencil.config.ts');
201
152
  // Guard: already configured?
202
- const guardProject = new Project({ skipAddingFilesFromTsConfig: true });
203
- const alreadyConfigured = guardProject
204
- .addSourceFileAtPath(stencilConfigPath)
205
- .getDescendantsOfKind(SyntaxKind.PropertyAssignment)
206
- .find((p) => p.getName() === 'outputTargets')
207
- ?.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression)
208
- ?.getText()
209
- .includes('angularOutputTarget(');
153
+ const editor = await openStencilConfig();
154
+ const alreadyConfigured = editor.outputTargetsContains('angularOutputTarget(');
210
155
  if (alreadyConfigured) {
211
156
  const redo = await confirm({
212
157
  message: 'angularOutputTarget is already in stencil.config.ts — reconfigure?',
@@ -219,18 +164,21 @@ export const wizard = {
219
164
  }
220
165
  // Where should the wrapper package live?
221
166
  let wrapperDir;
222
- const defaultName = `${config.fsNamespace}-angular`;
167
+ let wrapperPackageName = `${config.fsNamespace}-angular`;
223
168
  if (workspaceRoot) {
224
169
  const rawName = await text({
225
170
  message: 'Wrapper package name?',
226
- placeholder: defaultName,
227
- defaultValue: defaultName,
171
+ placeholder: wrapperPackageName,
172
+ defaultValue: wrapperPackageName,
228
173
  });
229
174
  if (isCancel(rawName)) {
230
175
  cancel('Setup cancelled.');
231
176
  return;
232
177
  }
233
- wrapperDir = join(dirname(config.rootDir), rawName);
178
+ wrapperPackageName = rawName;
179
+ // Directory is already namespaced by living under packages/, so keep it
180
+ // short rather than repeating the fsNamespace prefix from the package name.
181
+ wrapperDir = join(dirname(config.rootDir), 'angular');
234
182
  }
235
183
  else {
236
184
  const defaultRel = `../${config.fsNamespace}-angular`;
@@ -271,8 +219,15 @@ export const wizard = {
271
219
  const s = spinner();
272
220
  s.start(`Scaffolding wrapper package at ${relative(workspaceRoot ?? config.rootDir, wrapperDir)}`);
273
221
  try {
274
- const corePkgVersion = workspaceRoot ? 'workspace:*' : `file:${relative(wrapperDir, config.rootDir)}`;
275
- await scaffoldWrapperPackage(wrapperDir, `${config.fsNamespace}-angular`, componentCorePackage, corePkgVersion, config.fsNamespace, selectedOutputType);
222
+ // workspace:* only resolves under pnpm/yarn-berry/bun — npm and yarn classic reject
223
+ // the protocol outright (even for devDependencies that are never installed), so only
224
+ // use it when the detected package manager actually understands it.
225
+ const pm = workspaceRoot ? await nypm.detectPackageManager(workspaceRoot) : undefined;
226
+ const supportsWorkspaceProtocol = pm?.name === 'pnpm' || pm?.name === 'bun' || (pm?.name === 'yarn' && pm.majorVersion !== '1');
227
+ const corePkgVersion = supportsWorkspaceProtocol
228
+ ? 'workspace:*'
229
+ : `file:${relative(wrapperDir, config.rootDir)}`;
230
+ await scaffoldWrapperPackage(wrapperDir, wrapperPackageName, componentCorePackage, corePkgVersion, config.fsNamespace, selectedOutputType);
276
231
  s.stop('Wrapper package scaffolded');
277
232
  }
278
233
  catch (e) {
@@ -301,10 +256,20 @@ export const wizard = {
301
256
  ...(selectedOutputType !== 'standalone' ? [`outputType: '${selectedOutputType}'`] : []),
302
257
  ];
303
258
  const targetCode = `angularOutputTarget({\n ${lines.join(',\n ')},\n})`;
304
- // Amend stencil.config.ts in one pass so formatText() sees the final array
259
+ // Amend stencil.config.ts
305
260
  try {
306
- const added = amendStencilConfig(stencilConfigPath, targetCode);
307
- log.success(added ? 'stencil.config.ts updated' : 'angularOutputTarget already present — no changes made');
261
+ editor.addImport('@stencil/angular-output-target', ['angularOutputTarget']);
262
+ const hasStandalone = editor.outputTargetsContains("type: 'standalone'") ||
263
+ editor.outputTargetsContains('type: "standalone"') ||
264
+ editor.outputTargetsContains("type: 'dist-custom-elements'") ||
265
+ editor.outputTargetsContains('type: "dist-custom-elements"');
266
+ if (!hasStandalone)
267
+ editor.addOutputTarget("{ type: 'standalone' }");
268
+ if (!editor.replaceOutputTarget('angularOutputTarget(', targetCode)) {
269
+ editor.addOutputTarget(targetCode);
270
+ }
271
+ await editor.save();
272
+ log.success('stencil.config.ts updated');
308
273
  }
309
274
  catch (e) {
310
275
  log.warn(`Could not automatically update stencil.config.ts (${e}). Add manually:\n\n` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stencil/angular-output-target",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Angular output target for @stencil/core components.",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",
@@ -55,23 +55,21 @@
55
55
  "bugs": {
56
56
  "url": "https://github.com/stenciljs/output-targets/issues"
57
57
  },
58
- "dependencies": {
59
- "ts-morph": "^28.0.0"
60
- },
61
58
  "devDependencies": {
62
- "@stencil/cli": "^5.0.0 || >=5.0.0-alpha.0 <5.0.0-next.0",
63
59
  "@angular/core": "8.2.14",
64
60
  "@angular/forms": "8.2.14",
61
+ "@stencil/cli": "^5.0.0 || >=5.0.0-alpha.17 <5.0.0-next.0",
65
62
  "@stencil/core": "4.41.0",
66
- "@types/node": "^18.0.0",
67
- "npm-run-all2": "^6.2.4",
68
- "rimraf": "^5.0.0",
69
- "rollup": "^2.23.1",
70
- "typescript": "~5.7.0",
71
- "vitest": "^2.1.4"
63
+ "@types/node": "^18.19.130",
64
+ "npm-run-all2": "^6.2.6",
65
+ "rimraf": "^5.0.10",
66
+ "rollup": "^2.80.0",
67
+ "stencil-output-targets-shared": "workspace:*",
68
+ "typescript": "~5.7.3",
69
+ "vitest": "^2.1.9"
72
70
  },
73
71
  "peerDependencies": {
74
- "@stencil/core": ">=2.0.0 || >=3 || >=4.0.0 || ^5.0.0 || >=5.0.0-alpha.0 <5.0.0-next.0"
72
+ "@stencil/core": ">=2.0.0 || >=3 || >=4.0.0 || ^5.0.0 || >=5.0.0-alpha.17 <5.0.0-next.0"
75
73
  },
76
74
  "gitHead": "a3588e905186a0e86e7f88418fd5b2f9531b55e0"
77
75
  }