diff --git a/BREAKING.md b/BREAKING.md index c94a4c10393..7f6fb4c0b47 100644 --- a/BREAKING.md +++ b/BREAKING.md @@ -25,6 +25,8 @@ This is a comprehensive list of the breaking changes introduced in the major ver - [Input Otp](#version-10x-input-otp) - [Radio Group](#version-10x-radio-group) - [Textarea](#version-10x-textarea) +- [Framework Specific](#version-10x-framework-specific) + - [Angular](#version-10x-angular)

Components

@@ -245,3 +247,32 @@ The internal wrappers that Ionic 9 introduced are no longer reachable as descend ``` To style the wrappers themselves rather than the slotted content, use `part="start"` and `part="end"`. + +

Framework Specific

+ +

Angular

+ +**Boolean Inputs Are Type Checked** + +Boolean inputs now declare an input transform, so attribute presence is an explicitly supported way to set them: + +```html + + + +``` + +Declaring the transform also makes Angular type check these inputs, which it did not do before. The generated proxies declare no class fields, so Angular had nothing to check a binding against and accepted any value. Bindings that pass a value outside `boolean | string | null | undefined` now fail to compile. The common case is a truthiness binding: + +```diff +- ++ +``` + +``` +error TS2322: Type 'number' is not assignable to type 'string | boolean | null | undefined'. +``` + +Coerce the expression to a boolean, with an explicit comparison or `!!value`. Bindings that already pass a boolean, a string, `null` or `undefined` are unaffected. + +Coercion also moves from Stencil to Angular, which changes the result for numbers. `0` and `NaN` previously became `false` and now become `true`, matching Angular's own `booleanAttribute`. An app without `strictTemplates` gets no compile error for the binding above, so an empty list now renders the item as tappable rather than plain. Coercing the expression fixes both the type error and the runtime change. diff --git a/core/package-lock.json b/core/package-lock.json index e70c81ee58e..e7abbf995eb 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -26,7 +26,7 @@ "@playwright/test": "^1.62.1", "@rollup/plugin-node-resolve": "^8.4.0", "@rollup/plugin-virtual": "^2.0.3", - "@stencil/angular-output-target": "^1.4.1", + "@stencil/angular-output-target": "^1.5.0", "@stencil/react-output-target": "^1.6.2", "@stencil/sass": "^3.0.9", "@stencil/vue-output-target": "0.14.2", @@ -2370,9 +2370,9 @@ } }, "node_modules/@stencil/angular-output-target": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@stencil/angular-output-target/-/angular-output-target-1.4.1.tgz", - "integrity": "sha512-sy+k9B5jcT6hpaVU9n+/ZUjz3qVr9PAdPgy2c3LFQouW1aFSa3Ng0O+Ia3wk7LMeL3vDetCn2X+p2ewdHGc1vA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@stencil/angular-output-target/-/angular-output-target-1.5.0.tgz", + "integrity": "sha512-zr3n6PP3Gcj5pEyJgn32eJkSXSW5sH6XuSe7mG/MbSCZ6Odds4J7zplBDAc7WgTIcaxY1xS0cR21q1SCncwpkA==", "dev": true, "license": "MIT", "peerDependencies": { diff --git a/core/package.json b/core/package.json index 488ae4a362e..49095462d3e 100644 --- a/core/package.json +++ b/core/package.json @@ -83,7 +83,7 @@ "@playwright/test": "^1.62.1", "@rollup/plugin-node-resolve": "^8.4.0", "@rollup/plugin-virtual": "^2.0.3", - "@stencil/angular-output-target": "^1.4.1", + "@stencil/angular-output-target": "^1.5.0", "@stencil/react-output-target": "^1.6.2", "@stencil/sass": "^3.0.9", "@stencil/vue-output-target": "0.14.2", diff --git a/core/stencil.config.ts b/core/stencil.config.ts index 5bbdf99f4dc..5cab7d951c2 100644 --- a/core/stencil.config.ts +++ b/core/stencil.config.ts @@ -34,6 +34,7 @@ const getAngularOutputTargets = () => { directivesArrayFile: '../packages/angular/src/lazy/directives/proxies-list.ts', excludeComponents, outputType: 'component', + booleanAttributes: true, }), angularOutputTarget({ componentCorePackage, @@ -66,6 +67,7 @@ const getAngularOutputTargets = () => { outputType: 'standalone', // Emit each component in a separate file rather than putting them all in one large file. esModules: true, + booleanAttributes: true, }) ]; } diff --git a/docs/component-guide.md b/docs/component-guide.md index 93202377c29..16887afec4f 100644 --- a/docs/component-guide.md +++ b/docs/component-guide.md @@ -959,6 +959,21 @@ For standalone components, create a directive in the [standalone package](/packa - For boolean inputs: See [ion-checkbox](/packages/angular/src/standalone/directives/checkbox.ts) or [ion-toggle](/packages/angular/src/standalone/directives/toggle.ts) - For select-like inputs: See [ion-select](/packages/angular/src/standalone/directives/select.ts) or [ion-radio-group](/packages/angular/src/standalone/directives/radio-group.ts) +Boolean inputs take the `nullableBooleanAttribute` transform, so that they can be set by attribute presence the same way they can on the generated proxies: + +```typescript +import { inputNames } from '@ionic/angular/common'; + +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; + +const NEW_COMPONENT_INPUTS = [{ name: 'disabled', transform: nullableBooleanAttribute }, 'mode']; +const NEW_COMPONENT_PROXY_INPUTS = inputNames(NEW_COMPONENT_INPUTS); +``` + +Pass `NEW_COMPONENT_INPUTS` to `@Component({ inputs })` and `NEW_COMPONENT_PROXY_INPUTS` to `@ProxyCmp({ inputs })`. Unlike Angular's own `booleanAttribute`, the transform passes `null` and `undefined` through rather than coercing them to `false`, because components frequently treat them as a state distinct from `false`. + +Wrappers under [`common/`](/packages/angular/src/common) import both by relative path instead: `nullableBooleanAttribute` from `../utils/boolean-attribute`, since the output target only copies `angular-component-lib/` next to the files it generates, and `inputNames` from `../utils/proxy`, since they sit inside the package that defines it. + After creating the directive, you need to export it in two places: 1. First, add your component to the directives export group in [`packages/angular/src/standalone/directives/index.ts`](/packages/angular/src/standalone/directives/index.ts): diff --git a/packages/angular/.prettierignore b/packages/angular/.prettierignore index daee675330e..3fbe2f57cab 100644 --- a/packages/angular/.prettierignore +++ b/packages/angular/.prettierignore @@ -5,3 +5,4 @@ proxies.ts src/lazy/directives/proxies-list.ts src/standalone/directives/ion-*.ts **/*/angular-component-lib/utils.ts +**/*/angular-component-lib/boolean-attribute.ts diff --git a/packages/angular/src/common/directives/navigation/back-button.ts b/packages/angular/src/common/directives/navigation/back-button.ts index 35a889d4d84..e6ce2c3ad6d 100644 --- a/packages/angular/src/common/directives/navigation/back-button.ts +++ b/packages/angular/src/common/directives/navigation/back-button.ts @@ -3,17 +3,29 @@ import type { Components } from '@ionic/core'; import { Config } from '../../providers/config'; import { NavController } from '../../providers/nav-controller'; -import { ProxyCmp } from '../../utils/proxy'; +import { nullableBooleanAttribute } from '../../utils/boolean-attribute'; +import { inputNames, ProxyCmp } from '../../utils/proxy'; import { IonRouterOutlet } from './router-outlet'; -const BACK_BUTTON_INPUTS = ['color', 'defaultHref', 'disabled', 'icon', 'mode', 'routerAnimation', 'text', 'type']; +const BACK_BUTTON_INPUTS = [ + 'color', + 'defaultHref', + { name: 'disabled', transform: nullableBooleanAttribute }, + 'icon', + 'mode', + 'routerAnimation', + 'text', + 'type', +]; + +const BACK_BUTTON_PROXY_INPUTS = inputNames(BACK_BUTTON_INPUTS); // eslint-disable-next-line @typescript-eslint/no-empty-object-type export declare interface IonBackButton extends Components.IonBackButton {} @ProxyCmp({ - inputs: BACK_BUTTON_INPUTS, + inputs: BACK_BUTTON_PROXY_INPUTS, }) @Directive({ // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property diff --git a/packages/angular/src/common/directives/navigation/nav.ts b/packages/angular/src/common/directives/navigation/nav.ts index 30cfa903a01..eb9deaa0ee5 100644 --- a/packages/angular/src/common/directives/navigation/nav.ts +++ b/packages/angular/src/common/directives/navigation/nav.ts @@ -10,9 +10,18 @@ import { import type { Components } from '@ionic/core'; import { AngularDelegate } from '../../providers/angular-delegate'; -import { ProxyCmp, proxyOutputs } from '../../utils/proxy'; +import { nullableBooleanAttribute } from '../../utils/boolean-attribute'; +import { inputNames, ProxyCmp, proxyOutputs } from '../../utils/proxy'; -const NAV_INPUTS = ['animated', 'animation', 'root', 'rootParams', 'swipeGesture']; +const NAV_INPUTS = [ + { name: 'animated', transform: nullableBooleanAttribute }, + 'animation', + 'root', + 'rootParams', + { name: 'swipeGesture', transform: nullableBooleanAttribute }, +]; + +const NAV_PROXY_INPUTS = inputNames(NAV_INPUTS); const NAV_METHODS = [ 'push', @@ -42,7 +51,7 @@ export declare interface IonNav extends Components.IonNav { } @ProxyCmp({ - inputs: NAV_INPUTS, + inputs: NAV_PROXY_INPUTS, methods: NAV_METHODS, }) @Directive({ diff --git a/packages/angular/src/common/directives/navigation/router-outlet.ts b/packages/angular/src/common/directives/navigation/router-outlet.ts index 71b60f5fdf5..79ec674b78c 100644 --- a/packages/angular/src/common/directives/navigation/router-outlet.ts +++ b/packages/angular/src/common/directives/navigation/router-outlet.ts @@ -29,6 +29,7 @@ import { distinctUntilChanged, filter, switchMap } from 'rxjs/operators'; import { Config } from '../../providers/config'; import { NavController } from '../../providers/nav-controller'; +import { nullableBooleanAttribute } from '../../utils/boolean-attribute'; import { StackController } from './stack-controller'; import { RouteView, StackDidChangeEvent, StackWillChangeEvent, getUrl, isTabSwitch } from './stack-utils'; @@ -39,7 +40,12 @@ import { RouteView, StackDidChangeEvent, StackWillChangeEvent, getUrl, isTabSwit selector: 'ion-router-outlet', exportAs: 'outlet', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'animation', 'mode', 'swipeGesture'], + inputs: [ + { name: 'animated', transform: nullableBooleanAttribute }, + 'animation', + 'mode', + { name: 'swipeGesture', transform: nullableBooleanAttribute }, + ], }) export abstract class IonRouterOutlet implements OnDestroy, OnInit { abstract outletContent: any; diff --git a/packages/angular/src/common/index.ts b/packages/angular/src/common/index.ts index 4130c3f978c..3d48eb16fa5 100644 --- a/packages/angular/src/common/index.ts +++ b/packages/angular/src/common/index.ts @@ -27,7 +27,7 @@ export { } from './directives/navigation/router-link-delegate'; export { IonTabs } from './directives/navigation/tabs'; -export { ProxyCmp } from './utils/proxy'; +export { ProxyCmp, inputNames } from './utils/proxy'; export { OverlayBaseController } from './utils/overlay'; export { IonicRouteStrategy } from './utils/routing'; diff --git a/packages/angular/src/common/overlays/modal.ts b/packages/angular/src/common/overlays/modal.ts index 12bf86ff5cf..3437040cb0b 100644 --- a/packages/angular/src/common/overlays/modal.ts +++ b/packages/angular/src/common/overlays/modal.ts @@ -9,7 +9,8 @@ import { } from '@angular/core'; import type { Components, ModalBreakpointChangeEventDetail, ModalDragEventDetail } from '@ionic/core/components'; -import { ProxyCmp, proxyOutputs } from '../utils/proxy'; +import { nullableBooleanAttribute } from '../utils/boolean-attribute'; +import { inputNames, ProxyCmp, proxyOutputs } from '../utils/proxy'; export declare interface IonModal extends Components.IonModal { /** @@ -63,30 +64,32 @@ export declare interface IonModal extends Components.IonModal { } const MODAL_INPUTS = [ - 'animated', - 'keepContentsMounted', + { name: 'animated', transform: nullableBooleanAttribute }, + { name: 'keepContentsMounted', transform: nullableBooleanAttribute }, 'backdropBreakpoint', - 'backdropDismiss', + { name: 'backdropDismiss', transform: nullableBooleanAttribute }, 'breakpoints', 'canDismiss', 'cssClass', 'enterAnimation', - 'expandToScroll', + { name: 'expandToScroll', transform: nullableBooleanAttribute }, 'event', - 'focusTrap', - 'handle', + { name: 'focusTrap', transform: nullableBooleanAttribute }, + { name: 'handle', transform: nullableBooleanAttribute }, 'handleBehavior', 'initialBreakpoint', - 'isOpen', - 'keyboardClose', + { name: 'isOpen', transform: nullableBooleanAttribute }, + { name: 'keyboardClose', transform: nullableBooleanAttribute }, 'leaveAnimation', 'mode', 'presentingElement', - 'showBackdrop', + { name: 'showBackdrop', transform: nullableBooleanAttribute }, 'translucent', 'trigger', ]; +const MODAL_PROXY_INPUTS = inputNames(MODAL_INPUTS); + const MODAL_METHODS = [ 'present', 'dismiss', @@ -97,7 +100,7 @@ const MODAL_METHODS = [ ]; @ProxyCmp({ - inputs: MODAL_INPUTS, + inputs: MODAL_PROXY_INPUTS, methods: MODAL_METHODS, }) /** diff --git a/packages/angular/src/common/overlays/popover.ts b/packages/angular/src/common/overlays/popover.ts index f4091a37c79..d69cf6e0cc7 100644 --- a/packages/angular/src/common/overlays/popover.ts +++ b/packages/angular/src/common/overlays/popover.ts @@ -9,7 +9,8 @@ import { } from '@angular/core'; import type { Components } from '@ionic/core/components'; -import { ProxyCmp, proxyOutputs } from '../utils/proxy'; +import { nullableBooleanAttribute } from '../utils/boolean-attribute'; +import { inputNames, ProxyCmp, proxyOutputs } from '../utils/proxy'; export declare interface IonPopover extends Components.IonPopover { /** @@ -48,21 +49,22 @@ export declare interface IonPopover extends Components.IonPopover { const POPOVER_INPUTS = [ 'alignment', - 'animated', - 'arrow', - 'keepContentsMounted', - 'backdropDismiss', + { name: 'animated', transform: nullableBooleanAttribute }, + { name: 'arrow', transform: nullableBooleanAttribute }, + { name: 'keepContentsMounted', transform: nullableBooleanAttribute }, + { name: 'backdropDismiss', transform: nullableBooleanAttribute }, 'cssClass', - 'dismissOnSelect', + { name: 'dismissOnSelect', transform: nullableBooleanAttribute }, 'enterAnimation', 'event', - 'focusTrap', - 'isOpen', - 'keyboardClose', + { name: 'focusTrap', transform: nullableBooleanAttribute }, + { name: 'isOpen', transform: nullableBooleanAttribute }, + { name: 'keyboardClose', transform: nullableBooleanAttribute }, + { name: 'keyboardEvents', transform: nullableBooleanAttribute }, 'leaveAnimation', 'mode', - 'showBackdrop', - 'translucent', + { name: 'showBackdrop', transform: nullableBooleanAttribute }, + { name: 'translucent', transform: nullableBooleanAttribute }, 'trigger', 'triggerAction', 'reference', @@ -70,10 +72,12 @@ const POPOVER_INPUTS = [ 'side', ]; +const POPOVER_PROXY_INPUTS = inputNames(POPOVER_INPUTS); + const POPOVER_METHODS = ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss']; @ProxyCmp({ - inputs: POPOVER_INPUTS, + inputs: POPOVER_PROXY_INPUTS, methods: POPOVER_METHODS, }) /** diff --git a/packages/angular/src/common/utils/boolean-attribute.ts b/packages/angular/src/common/utils/boolean-attribute.ts new file mode 100644 index 00000000000..1edbbdd30db --- /dev/null +++ b/packages/angular/src/common/utils/boolean-attribute.ts @@ -0,0 +1,31 @@ +/* + * Duplicates `angular-component-lib/boolean-attribute.ts`, which the output + * target copies next to each generated proxies file and so is not reachable + * from here. `proxy.ts` duplicates `ProxyCmp` for the same reason. Refer to + * the TODO at the top of `proxy.ts`. + */ + +/** + * Transforms a value to a boolean so that boolean properties can be set by attribute presence, + * e.g. `` instead of ``. + * + * Strings are coerced the same way Angular's `booleanAttribute` coerces them, so `''` (a bare + * attribute) becomes `true` and `'false'` becomes `false`. + * + * Angular's own `booleanAttribute` is not used because it coerces `null` and `undefined` to + * `false`. Components frequently treat those as a state distinct from `false`, such as + * `ion-item`'s `detail` resolving `undefined` to a computed default, and both reach inputs + * routinely from the `async` pipe before its first emission and from form control values. + * + * The parameter type is what Angular derives `ngAcceptInputType_*` from, so it decides which + * template bindings compile. Widening it to `unknown` would let any expression through. + * + * Declared as a function rather than an arrow constant because Angular has to resolve input + * transforms statically when compiling a library in partial compilation mode. + */ +export function nullableBooleanAttribute(value: boolean | string | null | undefined): boolean | null | undefined { + if (value === null || value === undefined) { + return value; + } + return typeof value === 'boolean' ? value : value !== 'false'; +} diff --git a/packages/angular/src/common/utils/proxy.ts b/packages/angular/src/common/utils/proxy.ts index b9070978db1..421c60f38af 100644 --- a/packages/angular/src/common/utils/proxy.ts +++ b/packages/angular/src/common/utils/proxy.ts @@ -28,6 +28,13 @@ export const proxyMethods = (Cmp: any, methods: string[]) => { }); }; +/** + * Extracts the plain names from an inputs array that may contain transform entries. + * `ProxyCmp` only needs the names, and runs at runtime rather than through the Angular compiler. + */ +export const inputNames = (inputs: (string | { name: string })[]): string[] => + inputs.map((input) => (typeof input === 'string' ? input : input.name)); + export const proxyOutputs = (instance: any, el: any, events: string[]) => { events.forEach((eventName) => (instance[eventName] = fromEvent(el, eventName))); }; diff --git a/packages/angular/src/lazy/directives/angular-component-lib/boolean-attribute.ts b/packages/angular/src/lazy/directives/angular-component-lib/boolean-attribute.ts new file mode 100644 index 00000000000..c116dd5bc7f --- /dev/null +++ b/packages/angular/src/lazy/directives/angular-component-lib/boolean-attribute.ts @@ -0,0 +1,43 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Transforms a value to a boolean so that boolean properties can be set by attribute presence, + * e.g. `` instead of ``. + * + * Strings are coerced the same way Angular's `booleanAttribute` coerces them, so `''` (a bare + * attribute) becomes `true` and `'false'` becomes `false`. + * + * Unlike Angular's `booleanAttribute`, `null` and `undefined` are passed through rather than + * coerced to `false`. Components frequently treat them as a state distinct from `false`: + * + * ```tsx + * // `undefined` means "decide based on the mode", which is not the same as `false` + * const showDetail = detail !== undefined ? detail : mode === 'ios'; + * + * // a strict comparison also behaves differently for `null` than it does for `false` + * const showHandle = handle !== false; + * ``` + * + * Both values reach inputs routinely in Angular templates, from the `async` pipe before its + * first emission and from form control values, so coercing them would change the behavior of + * bindings that work today. + * + * This is implemented here rather than imported from `@angular/core` so that consumers on + * Angular versions without `booleanAttribute` are unaffected by this file being generated. + * + * The parameter type is what Angular derives `ngAcceptInputType_*` from, so it decides which + * template bindings compile. Widening it to `unknown` would let any expression through. + * + * Declared as a function rather than an arrow constant because Angular has to resolve input + * transforms statically when compiling a library in partial compilation mode. + * + * This lives in its own file, separate from `utils.ts`, so that it carries no runtime imports. + * That keeps it independently type-checkable without pulling `rxjs` in for `proxyOutputs`. + */ +export function nullableBooleanAttribute(value: boolean | string | null | undefined): boolean | null | undefined { + if (value === null || value === undefined) { + return value; + } + return typeof value === 'boolean' ? value : value !== 'false'; +} diff --git a/packages/angular/src/lazy/directives/proxies.ts b/packages/angular/src/lazy/directives/proxies.ts index 67e09a3e135..885b667cfef 100644 --- a/packages/angular/src/lazy/directives/proxies.ts +++ b/packages/angular/src/lazy/directives/proxies.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, Output, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import { Components } from '@ionic/core'; @@ -15,7 +16,7 @@ import { Components } from '@ionic/core'; changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['disabled', 'mode', 'readonly', 'theme', 'toggleIcon', 'toggleIconSlot', 'value'], + inputs: [{ name: 'disabled', transform: nullableBooleanAttribute }, 'mode', { name: 'readonly', transform: nullableBooleanAttribute }, 'theme', 'toggleIcon', 'toggleIconSlot', 'value'], standalone: false }) export class IonAccordion { @@ -38,7 +39,7 @@ export declare interface IonAccordion extends Components.IonAccordion {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'disabled', 'expand', 'mode', 'multiple', 'readonly', 'shape', 'theme', 'value'], + inputs: [{ name: 'animated', transform: nullableBooleanAttribute }, { name: 'disabled', transform: nullableBooleanAttribute }, 'expand', 'mode', { name: 'multiple', transform: nullableBooleanAttribute }, { name: 'readonly', transform: nullableBooleanAttribute }, 'shape', 'theme', 'value'], outputs: ['ionChange'], standalone: false }) @@ -74,7 +75,7 @@ This event will not emit when programmatically setting the `value` property. changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'backdropDismiss', 'buttons', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', 'isOpen', 'keyboardClose', 'leaveAnimation', 'mode', 'subHeader', 'theme', 'translucent', 'trigger'], + inputs: [{ name: 'animated', transform: nullableBooleanAttribute }, { name: 'backdropDismiss', transform: nullableBooleanAttribute }, 'buttons', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', { name: 'isOpen', transform: nullableBooleanAttribute }, { name: 'keyboardClose', transform: nullableBooleanAttribute }, 'leaveAnimation', 'mode', 'subHeader', 'theme', { name: 'translucent', transform: nullableBooleanAttribute }, 'trigger'], outputs: ['ionActionSheetDidPresent', 'ionActionSheetWillPresent', 'ionActionSheetWillDismiss', 'ionActionSheetDidDismiss', 'didPresent', 'willPresent', 'willDismiss', 'didDismiss'], standalone: false }) @@ -147,7 +148,7 @@ Shorthand for ionActionSheetDidDismiss. changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'backdropDismiss', 'buttons', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', 'inputs', 'isOpen', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'subHeader', 'theme', 'translucent', 'trigger'], + inputs: [{ name: 'animated', transform: nullableBooleanAttribute }, { name: 'backdropDismiss', transform: nullableBooleanAttribute }, 'buttons', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', 'inputs', { name: 'isOpen', transform: nullableBooleanAttribute }, { name: 'keyboardClose', transform: nullableBooleanAttribute }, 'leaveAnimation', 'message', 'mode', 'subHeader', 'theme', { name: 'translucent', transform: nullableBooleanAttribute }, 'trigger'], outputs: ['ionAlertDidPresent', 'ionAlertWillPresent', 'ionAlertWillDismiss', 'ionAlertDidDismiss', 'didPresent', 'willPresent', 'willDismiss', 'didDismiss'], standalone: false }) @@ -243,7 +244,7 @@ export declare interface IonApp extends Components.IonApp {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['disabled', 'mode', 'shape', 'size', 'theme'], + inputs: [{ name: 'disabled', transform: nullableBooleanAttribute }, 'mode', 'shape', 'size', 'theme'], standalone: false }) export class IonAvatar { @@ -266,7 +267,7 @@ export declare interface IonAvatar extends Components.IonAvatar {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['mode', 'stopPropagation', 'tappable', 'theme', 'visible'], + inputs: ['mode', { name: 'stopPropagation', transform: nullableBooleanAttribute }, { name: 'tappable', transform: nullableBooleanAttribute }, 'theme', { name: 'visible', transform: nullableBooleanAttribute }], outputs: ['ionBackdropTap'], standalone: false }) @@ -321,7 +322,7 @@ export declare interface IonBadge extends Components.IonBadge {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['active', 'color', 'disabled', 'download', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'separator', 'target', 'theme'], + inputs: [{ name: 'active', transform: nullableBooleanAttribute }, 'color', { name: 'disabled', transform: nullableBooleanAttribute }, 'download', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', { name: 'separator', transform: nullableBooleanAttribute }, 'target', 'theme'], outputs: ['ionFocus', 'ionBlur'], standalone: false }) @@ -391,7 +392,7 @@ export declare interface IonBreadcrumbs extends Components.IonBreadcrumbs { changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['buttonType', 'color', 'disabled', 'download', 'expand', 'fill', 'form', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', 'strong', 'target', 'theme', 'type'], + inputs: ['buttonType', 'color', { name: 'disabled', transform: nullableBooleanAttribute }, 'download', 'expand', 'fill', 'form', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', { name: 'strong', transform: nullableBooleanAttribute }, 'target', 'theme', 'type'], outputs: ['ionFocus', 'ionBlur'], standalone: false }) @@ -428,7 +429,7 @@ export declare interface IonButton extends Components.IonButton { changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['collapse', 'mode', 'theme'], + inputs: [{ name: 'collapse', transform: nullableBooleanAttribute }, 'mode', 'theme'], standalone: false }) export class IonButtons { @@ -451,7 +452,7 @@ export declare interface IonButtons extends Components.IonButtons {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['button', 'color', 'disabled', 'download', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'target', 'theme', 'type'], + inputs: [{ name: 'button', transform: nullableBooleanAttribute }, 'color', { name: 'disabled', transform: nullableBooleanAttribute }, 'download', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'target', 'theme', 'type'], standalone: false }) export class IonCard { @@ -497,7 +498,7 @@ export declare interface IonCardContent extends Components.IonCardContent {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'mode', 'theme', 'translucent'], + inputs: ['color', 'mode', 'theme', { name: 'translucent', transform: nullableBooleanAttribute }], standalone: false }) export class IonCardHeader { @@ -566,7 +567,7 @@ export declare interface IonCardTitle extends Components.IonCardTitle {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['alignment', 'checked', 'color', 'disabled', 'errorText', 'helperText', 'indeterminate', 'justify', 'labelPlacement', 'mode', 'name', 'required', 'shape', 'size', 'theme', 'value'], + inputs: ['alignment', { name: 'checked', transform: nullableBooleanAttribute }, 'color', { name: 'disabled', transform: nullableBooleanAttribute }, 'errorText', 'helperText', { name: 'indeterminate', transform: nullableBooleanAttribute }, 'justify', 'labelPlacement', 'mode', 'name', { name: 'required', transform: nullableBooleanAttribute }, 'shape', 'size', 'theme', 'value'], outputs: ['ionChange', 'ionFocus', 'ionBlur'], standalone: false }) @@ -612,7 +613,7 @@ setting the `checked` property. changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'disabled', 'hue', 'mode', 'outline', 'shape', 'size', 'theme'], + inputs: ['color', { name: 'disabled', transform: nullableBooleanAttribute }, 'hue', 'mode', { name: 'outline', transform: nullableBooleanAttribute }, 'shape', 'size', 'theme'], standalone: false }) export class IonChip { @@ -659,7 +660,7 @@ export declare interface IonCol extends Components.IonCol {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'fixedSlotPlacement', 'forceOverscroll', 'fullscreen', 'mode', 'scrollEvents', 'scrollX', 'scrollY', 'theme'], + inputs: ['color', 'fixedSlotPlacement', { name: 'forceOverscroll', transform: nullableBooleanAttribute }, { name: 'fullscreen', transform: nullableBooleanAttribute }, 'mode', { name: 'scrollEvents', transform: nullableBooleanAttribute }, { name: 'scrollX', transform: nullableBooleanAttribute }, { name: 'scrollY', transform: nullableBooleanAttribute }, 'theme'], outputs: ['ionScrollStart', 'ionScroll', 'ionScrollEnd'], standalone: false }) @@ -707,7 +708,7 @@ Set `scrollEvents` to `true` to enable. changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['cancelText', 'clearText', 'color', 'dayValues', 'disabled', 'doneText', 'firstDayOfWeek', 'formatOptions', 'highlightedDates', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'multiple', 'name', 'preferWheel', 'presentation', 'readonly', 'showAdjacentDays', 'showClearButton', 'showDefaultButtons', 'showDefaultTimeLabel', 'showDefaultTitle', 'size', 'theme', 'titleSelectedDatesFormatter', 'value', 'yearValues'], + inputs: ['cancelText', 'clearText', 'color', 'dayValues', { name: 'disabled', transform: nullableBooleanAttribute }, 'doneText', 'firstDayOfWeek', 'formatOptions', 'highlightedDates', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', { name: 'multiple', transform: nullableBooleanAttribute }, 'name', { name: 'preferWheel', transform: nullableBooleanAttribute }, 'presentation', { name: 'readonly', transform: nullableBooleanAttribute }, { name: 'showAdjacentDays', transform: nullableBooleanAttribute }, { name: 'showClearButton', transform: nullableBooleanAttribute }, { name: 'showDefaultButtons', transform: nullableBooleanAttribute }, { name: 'showDefaultTimeLabel', transform: nullableBooleanAttribute }, { name: 'showDefaultTitle', transform: nullableBooleanAttribute }, 'size', 'theme', 'titleSelectedDatesFormatter', 'value', 'yearValues'], outputs: ['ionCancel', 'ionChange', 'ionFocus', 'ionBlur'], standalone: false }) @@ -757,7 +758,7 @@ This event will not emit when programmatically setting the `value` property. changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'datetime', 'disabled', 'mode', 'theme'], + inputs: ['color', 'datetime', { name: 'disabled', transform: nullableBooleanAttribute }, 'mode', 'theme'], standalone: false }) export class IonDatetimeButton { @@ -780,7 +781,7 @@ export declare interface IonDatetimeButton extends Components.IonDatetimeButton changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['inset', 'spacing'], + inputs: [{ name: 'inset', transform: nullableBooleanAttribute }, 'spacing'], standalone: false }) export class IonDivider { @@ -804,7 +805,7 @@ export declare interface IonDivider extends Components.IonDivider {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['activated', 'edge', 'horizontal', 'mode', 'theme', 'vertical'], + inputs: [{ name: 'activated', transform: nullableBooleanAttribute }, { name: 'edge', transform: nullableBooleanAttribute }, 'horizontal', 'mode', 'theme', 'vertical'], standalone: false }) export class IonFab { @@ -827,7 +828,7 @@ export declare interface IonFab extends Components.IonFab {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['activated', 'closeIcon', 'color', 'disabled', 'download', 'form', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'show', 'size', 'target', 'theme', 'translucent', 'type'], + inputs: [{ name: 'activated', transform: nullableBooleanAttribute }, 'closeIcon', 'color', { name: 'disabled', transform: nullableBooleanAttribute }, 'download', 'form', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', { name: 'show', transform: nullableBooleanAttribute }, 'size', 'target', 'theme', { name: 'translucent', transform: nullableBooleanAttribute }, 'type'], outputs: ['ionFocus', 'ionBlur'], standalone: false }) @@ -864,7 +865,7 @@ export declare interface IonFabButton extends Components.IonFabButton { changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['activated', 'mode', 'side', 'theme'], + inputs: [{ name: 'activated', transform: nullableBooleanAttribute }, 'mode', 'side', 'theme'], standalone: false }) export class IonFabList { @@ -887,7 +888,7 @@ export declare interface IonFabList extends Components.IonFabList {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['collapse', 'mode', 'scrollEffect', 'theme', 'translucent'], + inputs: ['collapse', 'mode', 'scrollEffect', 'theme', { name: 'translucent', transform: nullableBooleanAttribute }], standalone: false }) export class IonFooter { @@ -956,7 +957,7 @@ export declare interface IonGalleryItem extends Components.IonGalleryItem {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['fixed', 'mode', 'theme'], + inputs: [{ name: 'fixed', transform: nullableBooleanAttribute }, 'mode', 'theme'], standalone: false }) export class IonGrid { @@ -979,7 +980,7 @@ export declare interface IonGrid extends Components.IonGrid {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['collapse', 'divider', 'mode', 'scrollEffect', 'theme', 'translucent'], + inputs: ['collapse', { name: 'divider', transform: nullableBooleanAttribute }, 'mode', 'scrollEffect', 'theme', { name: 'translucent', transform: nullableBooleanAttribute }], standalone: false }) export class IonHeader { @@ -1002,7 +1003,7 @@ export declare interface IonHeader extends Components.IonHeader {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'flipRtl', 'icon', 'ios', 'lazy', 'md', 'mode', 'name', 'sanitize', 'size', 'src'], + inputs: ['color', { name: 'flipRtl', transform: nullableBooleanAttribute }, 'icon', 'ios', { name: 'lazy', transform: nullableBooleanAttribute }, 'md', 'mode', 'name', { name: 'sanitize', transform: nullableBooleanAttribute }, 'size', 'src'], standalone: false }) export class IonIcon { @@ -1068,7 +1069,7 @@ export declare interface IonImg extends Components.IonImg { changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['disabled', 'mode', 'position', 'preserveRerenderScrollPosition', 'theme', 'threshold'], + inputs: [{ name: 'disabled', transform: nullableBooleanAttribute }, 'mode', 'position', { name: 'preserveRerenderScrollPosition', transform: nullableBooleanAttribute }, 'theme', 'threshold'], outputs: ['ionInfinite'], standalone: false }) @@ -1127,7 +1128,7 @@ export declare interface IonInfiniteScrollContent extends Components.IonInfinite changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['autocapitalize', 'autocomplete', 'autocorrect', 'autofocus', 'clearInput', 'clearInputIcon', 'clearOnEdit', 'color', 'counter', 'counterFormatter', 'debounce', 'disabled', 'enterkeyhint', 'errorText', 'fill', 'helperText', 'inputmode', 'label', 'labelPlacement', 'max', 'maxlength', 'min', 'minlength', 'mode', 'multiple', 'name', 'pattern', 'placeholder', 'readonly', 'required', 'shape', 'size', 'spellcheck', 'step', 'theme', 'type', 'value'], + inputs: ['autocapitalize', 'autocomplete', { name: 'autocorrect', transform: nullableBooleanAttribute }, { name: 'autofocus', transform: nullableBooleanAttribute }, { name: 'clearInput', transform: nullableBooleanAttribute }, 'clearInputIcon', { name: 'clearOnEdit', transform: nullableBooleanAttribute }, 'color', { name: 'counter', transform: nullableBooleanAttribute }, 'counterFormatter', 'debounce', { name: 'disabled', transform: nullableBooleanAttribute }, 'enterkeyhint', 'errorText', 'fill', 'helperText', 'inputmode', 'label', 'labelPlacement', 'max', 'maxlength', 'min', 'minlength', 'mode', { name: 'multiple', transform: nullableBooleanAttribute }, 'name', 'pattern', 'placeholder', { name: 'readonly', transform: nullableBooleanAttribute }, { name: 'required', transform: nullableBooleanAttribute }, 'shape', 'size', { name: 'spellcheck', transform: nullableBooleanAttribute }, 'step', 'theme', 'type', 'value'], outputs: ['ionInput', 'ionChange', 'ionBlur', 'ionFocus'], standalone: false }) @@ -1195,7 +1196,7 @@ This event will not emit when programmatically setting the `value` property. changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['autocapitalize', 'color', 'disabled', 'fill', 'inputmode', 'length', 'mode', 'pattern', 'readonly', 'separators', 'shape', 'size', 'theme', 'type', 'value'], + inputs: ['autocapitalize', 'color', { name: 'disabled', transform: nullableBooleanAttribute }, 'fill', 'inputmode', 'length', 'mode', 'pattern', { name: 'readonly', transform: nullableBooleanAttribute }, 'separators', 'shape', 'size', 'theme', 'type', 'value'], outputs: ['ionInput', 'ionChange', 'ionComplete', 'ionBlur', 'ionFocus'], standalone: false }) @@ -1287,7 +1288,7 @@ export declare interface IonInputPasswordToggle extends Components.IonInputPassw changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['button', 'color', 'detail', 'detailIcon', 'disabled', 'download', 'href', 'lines', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'target', 'theme', 'type'], + inputs: [{ name: 'button', transform: nullableBooleanAttribute }, 'color', { name: 'detail', transform: nullableBooleanAttribute }, 'detailIcon', { name: 'disabled', transform: nullableBooleanAttribute }, 'download', 'href', 'lines', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'target', 'theme', 'type'], standalone: false }) export class IonItem { @@ -1310,7 +1311,7 @@ export declare interface IonItem extends Components.IonItem {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'mode', 'sticky', 'theme'], + inputs: ['color', 'mode', { name: 'sticky', transform: nullableBooleanAttribute }, 'theme'], standalone: false }) export class IonItemDivider { @@ -1356,7 +1357,7 @@ export declare interface IonItemGroup extends Components.IonItemGroup {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'disabled', 'download', 'expandable', 'href', 'hue', 'mode', 'rel', 'shape', 'target', 'theme', 'type'], + inputs: ['color', { name: 'disabled', transform: nullableBooleanAttribute }, 'download', { name: 'expandable', transform: nullableBooleanAttribute }, 'href', 'hue', 'mode', 'rel', 'shape', 'target', 'theme', 'type'], standalone: false }) export class IonItemOption { @@ -1412,7 +1413,7 @@ export declare interface IonItemOptions extends Components.IonItemOptions { changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['disabled', 'mode', 'theme'], + inputs: [{ name: 'disabled', transform: nullableBooleanAttribute }, 'mode', 'theme'], outputs: ['ionDrag'], standalone: false }) @@ -1468,7 +1469,7 @@ export declare interface IonLabel extends Components.IonLabel {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['inset', 'lines', 'mode', 'shape', 'theme'], + inputs: [{ name: 'inset', transform: nullableBooleanAttribute }, 'lines', 'mode', 'shape', 'theme'], standalone: false }) export class IonList { @@ -1515,7 +1516,7 @@ export declare interface IonListHeader extends Components.IonListHeader {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'backdropDismiss', 'cssClass', 'duration', 'enterAnimation', 'htmlAttributes', 'isOpen', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'showBackdrop', 'spinner', 'theme', 'translucent', 'trigger'], + inputs: [{ name: 'animated', transform: nullableBooleanAttribute }, { name: 'backdropDismiss', transform: nullableBooleanAttribute }, 'cssClass', 'duration', 'enterAnimation', 'htmlAttributes', { name: 'isOpen', transform: nullableBooleanAttribute }, { name: 'keyboardClose', transform: nullableBooleanAttribute }, 'leaveAnimation', 'message', 'mode', { name: 'showBackdrop', transform: nullableBooleanAttribute }, 'spinner', 'theme', { name: 'translucent', transform: nullableBooleanAttribute }, 'trigger'], outputs: ['ionLoadingDidPresent', 'ionLoadingWillPresent', 'ionLoadingWillDismiss', 'ionLoadingDidDismiss', 'didPresent', 'willPresent', 'willDismiss', 'didDismiss'], standalone: false }) @@ -1588,7 +1589,7 @@ Shorthand for ionLoadingDidDismiss. changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['contentId', 'disabled', 'maxEdgeStart', 'menuId', 'mode', 'side', 'swipeGesture', 'theme', 'type'], + inputs: ['contentId', { name: 'disabled', transform: nullableBooleanAttribute }, 'maxEdgeStart', 'menuId', 'mode', 'side', { name: 'swipeGesture', transform: nullableBooleanAttribute }, 'theme', 'type'], outputs: ['ionWillOpen', 'ionWillClose', 'ionDidOpen', 'ionDidClose'], standalone: false }) @@ -1636,7 +1637,7 @@ export declare interface IonMenu extends Components.IonMenu { changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['autoHide', 'color', 'disabled', 'menu', 'mode', 'theme', 'type'], + inputs: [{ name: 'autoHide', transform: nullableBooleanAttribute }, 'color', { name: 'disabled', transform: nullableBooleanAttribute }, 'menu', 'mode', 'theme', 'type'], standalone: false }) export class IonMenuButton { @@ -1659,7 +1660,7 @@ export declare interface IonMenuButton extends Components.IonMenuButton {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['autoHide', 'menu', 'mode', 'theme'], + inputs: [{ name: 'autoHide', transform: nullableBooleanAttribute }, 'menu', 'mode', 'theme'], standalone: false }) export class IonMenuToggle { @@ -1752,7 +1753,7 @@ export declare interface IonPicker extends Components.IonPicker {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'disabled', 'mode', 'theme', 'value'], + inputs: ['color', { name: 'disabled', transform: nullableBooleanAttribute }, 'mode', 'theme', 'value'], outputs: ['ionChange'], standalone: false }) @@ -1787,7 +1788,7 @@ This event will not emit when programmatically setting the `value` property. changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'disabled', 'mode', 'theme', 'value'], + inputs: ['color', { name: 'disabled', transform: nullableBooleanAttribute }, 'mode', 'theme', 'value'], standalone: false }) export class IonPickerColumnOption { @@ -1810,7 +1811,7 @@ export declare interface IonPickerColumnOption extends Components.IonPickerColum changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['buffer', 'color', 'mode', 'reversed', 'shape', 'theme', 'type', 'value'], + inputs: ['buffer', 'color', 'mode', { name: 'reversed', transform: nullableBooleanAttribute }, 'shape', 'theme', 'type', 'value'], standalone: false }) export class IonProgressBar { @@ -1833,7 +1834,7 @@ export declare interface IonProgressBar extends Components.IonProgressBar {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['alignment', 'color', 'disabled', 'justify', 'labelPlacement', 'mode', 'name', 'theme', 'value'], + inputs: ['alignment', 'color', { name: 'disabled', transform: nullableBooleanAttribute }, 'justify', 'labelPlacement', 'mode', 'name', 'theme', 'value'], outputs: ['ionFocus', 'ionBlur'], standalone: false }) @@ -1870,7 +1871,7 @@ export declare interface IonRadio extends Components.IonRadio { changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['allowEmptySelection', 'compareWith', 'errorText', 'helperText', 'mode', 'name', 'theme', 'value'], + inputs: [{ name: 'allowEmptySelection', transform: nullableBooleanAttribute }, 'compareWith', 'errorText', 'helperText', 'mode', 'name', 'theme', 'value'], outputs: ['ionChange'], standalone: false }) @@ -1905,7 +1906,7 @@ This event will not emit when programmatically setting the `value` property. changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['activeBarStart', 'color', 'debounce', 'disabled', 'dualKnobs', 'label', 'labelPlacement', 'max', 'min', 'mode', 'name', 'pin', 'pinFormatter', 'snaps', 'step', 'theme', 'ticks', 'value'], + inputs: ['activeBarStart', 'color', 'debounce', { name: 'disabled', transform: nullableBooleanAttribute }, { name: 'dualKnobs', transform: nullableBooleanAttribute }, 'label', 'labelPlacement', 'max', 'min', 'mode', 'name', { name: 'pin', transform: nullableBooleanAttribute }, 'pinFormatter', { name: 'snaps', transform: nullableBooleanAttribute }, 'step', 'theme', { name: 'ticks', transform: nullableBooleanAttribute }, 'value'], outputs: ['ionChange', 'ionInput', 'ionFocus', 'ionBlur', 'ionKnobMoveStart', 'ionKnobMoveEnd'], standalone: false }) @@ -1975,7 +1976,7 @@ mouse drag, touch gesture, or keyboard interaction. changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['closeDuration', 'disabled', 'mode', 'pullFactor', 'pullMax', 'pullMin', 'snapbackDuration', 'theme'], + inputs: ['closeDuration', { name: 'disabled', transform: nullableBooleanAttribute }, 'mode', 'pullFactor', 'pullMax', 'pullMin', 'snapbackDuration', 'theme'], outputs: ['ionRefresh', 'ionPull', 'ionStart', 'ionPullStart', 'ionPullEnd'], standalone: false }) @@ -2081,7 +2082,7 @@ export declare interface IonReorder extends Components.IonReorder {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['disabled', 'mode', 'theme'], + inputs: [{ name: 'disabled', transform: nullableBooleanAttribute }, 'mode', 'theme'], outputs: ['ionItemReorder', 'ionReorderStart', 'ionReorderMove', 'ionReorderEnd'], standalone: false }) @@ -2187,7 +2188,7 @@ export declare interface IonRow extends Components.IonRow {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'autocapitalize', 'autocomplete', 'autocorrect', 'cancelButtonIcon', 'cancelButtonText', 'clearIcon', 'color', 'debounce', 'disabled', 'enterkeyhint', 'inputmode', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'searchIcon', 'shape', 'showCancelButton', 'showClearButton', 'size', 'spellcheck', 'theme', 'type', 'value'], + inputs: [{ name: 'animated', transform: nullableBooleanAttribute }, 'autocapitalize', 'autocomplete', { name: 'autocorrect', transform: nullableBooleanAttribute }, 'cancelButtonIcon', 'cancelButtonText', 'clearIcon', 'color', 'debounce', { name: 'disabled', transform: nullableBooleanAttribute }, 'enterkeyhint', 'inputmode', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'searchIcon', 'shape', 'showCancelButton', 'showClearButton', 'size', { name: 'spellcheck', transform: nullableBooleanAttribute }, 'theme', 'type', 'value'], outputs: ['ionInput', 'ionChange', 'ionCancel', 'ionClear', 'ionBlur', 'ionFocus'], standalone: false }) @@ -2255,7 +2256,7 @@ This event will not emit when programmatically setting the `value` property. changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'disabled', 'mode', 'scrollable', 'selectOnFocus', 'swipeGesture', 'theme', 'value'], + inputs: ['color', { name: 'disabled', transform: nullableBooleanAttribute }, 'mode', { name: 'scrollable', transform: nullableBooleanAttribute }, { name: 'selectOnFocus', transform: nullableBooleanAttribute }, { name: 'swipeGesture', transform: nullableBooleanAttribute }, 'theme', 'value'], outputs: ['ionChange'], standalone: false }) @@ -2290,7 +2291,7 @@ This event will not emit when programmatically setting the `value` property. changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['contentId', 'disabled', 'layout', 'mode', 'theme', 'type', 'value'], + inputs: ['contentId', { name: 'disabled', transform: nullableBooleanAttribute }, 'layout', 'mode', 'theme', 'type', 'value'], standalone: false }) export class IonSegmentButton { @@ -2335,7 +2336,7 @@ export declare interface IonSegmentContent extends Components.IonSegmentContent changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['disabled', 'swipeGesture'], + inputs: [{ name: 'disabled', transform: nullableBooleanAttribute }, { name: 'swipeGesture', transform: nullableBooleanAttribute }], outputs: ['ionSegmentViewScroll'], standalone: false }) @@ -2369,7 +2370,7 @@ export declare interface IonSegmentView extends Components.IonSegmentView { changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['cancelIcon', 'cancelText', 'color', 'compareWith', 'disabled', 'errorText', 'expandedIcon', 'fill', 'helperText', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'required', 'selectedText', 'shape', 'size', 'theme', 'toggleIcon', 'value'], + inputs: [{ name: 'cancelIcon', transform: nullableBooleanAttribute }, 'cancelText', 'color', 'compareWith', { name: 'disabled', transform: nullableBooleanAttribute }, 'errorText', 'expandedIcon', 'fill', 'helperText', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'mode', { name: 'multiple', transform: nullableBooleanAttribute }, 'name', 'okText', 'placeholder', { name: 'required', transform: nullableBooleanAttribute }, 'selectedText', 'shape', 'size', 'theme', 'toggleIcon', 'value'], outputs: ['ionChange', 'ionCancel', 'ionDismiss', 'ionFocus', 'ionBlur'], standalone: false }) @@ -2424,7 +2425,7 @@ This event will not emit when programmatically setting the `value` property. changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['cancelIcon', 'cancelText', 'header', 'multiple', 'options'], + inputs: [{ name: 'cancelIcon', transform: nullableBooleanAttribute }, 'cancelText', 'header', { name: 'multiple', transform: nullableBooleanAttribute }, 'options'], standalone: false }) export class IonSelectModal { @@ -2447,7 +2448,7 @@ export declare interface IonSelectModal extends Components.IonSelectModal {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['description', 'disabled', 'justify', 'labelPlacement', 'mode', 'theme', 'value'], + inputs: ['description', { name: 'disabled', transform: nullableBooleanAttribute }, 'justify', 'labelPlacement', 'mode', 'theme', 'value'], standalone: false }) export class IonSelectOption { @@ -2470,7 +2471,7 @@ export declare interface IonSelectOption extends Components.IonSelectOption {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'mode', 'theme'], + inputs: [{ name: 'animated', transform: nullableBooleanAttribute }, 'mode', 'theme'], standalone: false }) export class IonSkeletonText { @@ -2493,7 +2494,7 @@ export declare interface IonSkeletonText extends Components.IonSkeletonText {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'duration', 'mode', 'name', 'paused', 'size', 'theme'], + inputs: ['color', 'duration', 'mode', 'name', { name: 'paused', transform: nullableBooleanAttribute }, 'size', 'theme'], standalone: false }) export class IonSpinner { @@ -2516,7 +2517,7 @@ export declare interface IonSpinner extends Components.IonSpinner {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['contentId', 'disabled', 'mode', 'theme', 'when'], + inputs: ['contentId', { name: 'disabled', transform: nullableBooleanAttribute }, 'mode', 'theme', 'when'], outputs: ['ionSplitPaneVisible'], standalone: false }) @@ -2572,7 +2573,7 @@ export declare interface IonTab extends Components.IonTab {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'expand', 'mode', 'scrollEffect', 'selectedTab', 'shape', 'theme', 'translucent'], + inputs: ['color', 'expand', 'mode', 'scrollEffect', 'selectedTab', 'shape', 'theme', { name: 'translucent', transform: nullableBooleanAttribute }], standalone: false }) export class IonTabBar { @@ -2595,7 +2596,7 @@ export declare interface IonTabBar extends Components.IonTabBar {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['disabled', 'download', 'href', 'layout', 'mode', 'rel', 'selected', 'shape', 'tab', 'target', 'theme'], + inputs: [{ name: 'disabled', transform: nullableBooleanAttribute }, 'download', 'href', 'layout', 'mode', 'rel', { name: 'selected', transform: nullableBooleanAttribute }, 'shape', 'tab', 'target', 'theme'], standalone: false }) export class IonTabButton { @@ -2642,7 +2643,7 @@ export declare interface IonText extends Components.IonText {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['autoGrow', 'autocapitalize', 'autofocus', 'clearOnEdit', 'color', 'cols', 'counter', 'counterFormatter', 'debounce', 'disabled', 'enterkeyhint', 'errorText', 'fill', 'helperText', 'inputmode', 'label', 'labelPlacement', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', 'readonly', 'required', 'rows', 'shape', 'size', 'spellcheck', 'theme', 'value', 'wrap'], + inputs: [{ name: 'autoGrow', transform: nullableBooleanAttribute }, 'autocapitalize', { name: 'autofocus', transform: nullableBooleanAttribute }, { name: 'clearOnEdit', transform: nullableBooleanAttribute }, 'color', 'cols', { name: 'counter', transform: nullableBooleanAttribute }, 'counterFormatter', 'debounce', { name: 'disabled', transform: nullableBooleanAttribute }, 'enterkeyhint', 'errorText', 'fill', 'helperText', 'inputmode', 'label', 'labelPlacement', 'maxlength', 'minlength', 'mode', 'name', 'placeholder', { name: 'readonly', transform: nullableBooleanAttribute }, { name: 'required', transform: nullableBooleanAttribute }, 'rows', 'shape', 'size', { name: 'spellcheck', transform: nullableBooleanAttribute }, 'theme', 'value', 'wrap'], outputs: ['ionChange', 'ionInput', 'ionBlur', 'ionFocus'], standalone: false }) @@ -2747,7 +2748,7 @@ export declare interface IonTitle extends Components.IonTitle {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'hue', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'shape', 'swipeGesture', 'theme', 'translucent', 'trigger'], + inputs: [{ name: 'animated', transform: nullableBooleanAttribute }, 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'hue', 'icon', { name: 'isOpen', transform: nullableBooleanAttribute }, { name: 'keyboardClose', transform: nullableBooleanAttribute }, 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'shape', 'swipeGesture', 'theme', { name: 'translucent', transform: nullableBooleanAttribute }, 'trigger'], outputs: ['ionToastDidPresent', 'ionToastWillPresent', 'ionToastWillDismiss', 'ionToastDidDismiss', 'didPresent', 'willPresent', 'willDismiss', 'didDismiss'], standalone: false }) @@ -2819,7 +2820,7 @@ Shorthand for ionToastDidDismiss. changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['alignment', 'checked', 'color', 'disabled', 'enableOnOffLabels', 'errorText', 'helperText', 'justify', 'labelPlacement', 'mode', 'name', 'required', 'theme', 'value'], + inputs: ['alignment', { name: 'checked', transform: nullableBooleanAttribute }, 'color', { name: 'disabled', transform: nullableBooleanAttribute }, { name: 'enableOnOffLabels', transform: nullableBooleanAttribute }, 'errorText', 'helperText', 'justify', 'labelPlacement', 'mode', 'name', { name: 'required', transform: nullableBooleanAttribute }, 'theme', 'value'], outputs: ['ionChange', 'ionFocus', 'ionBlur'], standalone: false }) diff --git a/packages/angular/src/standalone/directives/angular-component-lib/boolean-attribute.ts b/packages/angular/src/standalone/directives/angular-component-lib/boolean-attribute.ts new file mode 100644 index 00000000000..c116dd5bc7f --- /dev/null +++ b/packages/angular/src/standalone/directives/angular-component-lib/boolean-attribute.ts @@ -0,0 +1,43 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Transforms a value to a boolean so that boolean properties can be set by attribute presence, + * e.g. `` instead of ``. + * + * Strings are coerced the same way Angular's `booleanAttribute` coerces them, so `''` (a bare + * attribute) becomes `true` and `'false'` becomes `false`. + * + * Unlike Angular's `booleanAttribute`, `null` and `undefined` are passed through rather than + * coerced to `false`. Components frequently treat them as a state distinct from `false`: + * + * ```tsx + * // `undefined` means "decide based on the mode", which is not the same as `false` + * const showDetail = detail !== undefined ? detail : mode === 'ios'; + * + * // a strict comparison also behaves differently for `null` than it does for `false` + * const showHandle = handle !== false; + * ``` + * + * Both values reach inputs routinely in Angular templates, from the `async` pipe before its + * first emission and from form control values, so coercing them would change the behavior of + * bindings that work today. + * + * This is implemented here rather than imported from `@angular/core` so that consumers on + * Angular versions without `booleanAttribute` are unaffected by this file being generated. + * + * The parameter type is what Angular derives `ngAcceptInputType_*` from, so it decides which + * template bindings compile. Widening it to `unknown` would let any expression through. + * + * Declared as a function rather than an arrow constant because Angular has to resolve input + * transforms statically when compiling a library in partial compilation mode. + * + * This lives in its own file, separate from `utils.ts`, so that it carries no runtime imports. + * That keeps it independently type-checkable without pulling `rxjs` in for `proxyOutputs`. + */ +export function nullableBooleanAttribute(value: boolean | string | null | undefined): boolean | null | undefined { + if (value === null || value === undefined) { + return value; + } + return typeof value === 'boolean' ? value : value !== 'false'; +} diff --git a/packages/angular/src/standalone/directives/checkbox.ts b/packages/angular/src/standalone/directives/checkbox.ts index a905e050d5c..4d534e3f1aa 100644 --- a/packages/angular/src/standalone/directives/checkbox.ts +++ b/packages/angular/src/standalone/directives/checkbox.ts @@ -10,29 +10,33 @@ import { forwardRef, } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; -import { ValueAccessor, setIonicClasses } from '@ionic/angular/common'; +import { inputNames, setIonicClasses, ValueAccessor } from '@ionic/angular/common'; import type { CheckboxChangeEventDetail, Components } from '@ionic/core/components'; import { defineCustomElement } from '@ionic/core/components/ion-checkbox.js'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; const CHECKBOX_INPUTS = [ - 'checked', + { name: 'checked', transform: nullableBooleanAttribute }, 'color', - 'disabled', + { name: 'disabled', transform: nullableBooleanAttribute }, 'errorText', 'helperText', - 'indeterminate', + { name: 'indeterminate', transform: nullableBooleanAttribute }, 'justify', 'labelPlacement', 'mode', 'name', + { name: 'required', transform: nullableBooleanAttribute }, 'value', ]; +const CHECKBOX_PROXY_INPUTS = inputNames(CHECKBOX_INPUTS); + @ProxyCmp({ defineCustomElementFn: defineCustomElement, - inputs: CHECKBOX_INPUTS, + inputs: CHECKBOX_PROXY_INPUTS, }) @Component({ selector: 'ion-checkbox', diff --git a/packages/angular/src/standalone/directives/datetime.ts b/packages/angular/src/standalone/directives/datetime.ts index e808011dfd1..f8ccd7ec918 100644 --- a/packages/angular/src/standalone/directives/datetime.ts +++ b/packages/angular/src/standalone/directives/datetime.ts @@ -10,10 +10,11 @@ import { forwardRef, } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; -import { ValueAccessor } from '@ionic/angular/common'; +import { inputNames, ValueAccessor } from '@ionic/angular/common'; import type { DatetimeChangeEventDetail, Components } from '@ionic/core/components'; import { defineCustomElement } from '@ionic/core/components/ion-datetime.js'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; const DATETIME_INPUTS = [ @@ -21,7 +22,7 @@ const DATETIME_INPUTS = [ 'clearText', 'color', 'dayValues', - 'disabled', + { name: 'disabled', transform: nullableBooleanAttribute }, 'doneText', 'firstDayOfWeek', 'formatOptions', @@ -35,25 +36,27 @@ const DATETIME_INPUTS = [ 'minuteValues', 'mode', 'monthValues', - 'multiple', + { name: 'multiple', transform: nullableBooleanAttribute }, 'name', - 'preferWheel', + { name: 'preferWheel', transform: nullableBooleanAttribute }, 'presentation', - 'readonly', - 'showAdjacentDays', - 'showClearButton', - 'showDefaultButtons', - 'showDefaultTimeLabel', - 'showDefaultTitle', + { name: 'readonly', transform: nullableBooleanAttribute }, + { name: 'showAdjacentDays', transform: nullableBooleanAttribute }, + { name: 'showClearButton', transform: nullableBooleanAttribute }, + { name: 'showDefaultButtons', transform: nullableBooleanAttribute }, + { name: 'showDefaultTimeLabel', transform: nullableBooleanAttribute }, + { name: 'showDefaultTitle', transform: nullableBooleanAttribute }, 'size', 'titleSelectedDatesFormatter', 'value', 'yearValues', ]; +const DATETIME_PROXY_INPUTS = inputNames(DATETIME_INPUTS); + @ProxyCmp({ defineCustomElementFn: defineCustomElement, - inputs: DATETIME_INPUTS, + inputs: DATETIME_PROXY_INPUTS, methods: ['confirm', 'reset', 'cancel'], }) @Component({ diff --git a/packages/angular/src/standalone/directives/icon.ts b/packages/angular/src/standalone/directives/icon.ts index 132a3a30b29..3fc63bde395 100644 --- a/packages/angular/src/standalone/directives/icon.ts +++ b/packages/angular/src/standalone/directives/icon.ts @@ -1,18 +1,36 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { defineCustomElement as defineIonIcon } from 'ionicons/components/ion-icon.js'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import { ProxyCmp } from './angular-component-lib/utils'; +const ICON_INPUTS = [ + 'color', + { name: 'flipRtl', transform: nullableBooleanAttribute }, + 'icon', + 'ios', + { name: 'lazy', transform: nullableBooleanAttribute }, + 'md', + 'mode', + 'name', + { name: 'sanitize', transform: nullableBooleanAttribute }, + 'size', + 'src', +]; + +/* ProxyCmp only needs the names, and runs at runtime rather than through the Angular compiler. */ +const ICON_PROXY_INPUTS = ICON_INPUTS.map((input) => (typeof input === 'string' ? input : input.name)); + @ProxyCmp({ defineCustomElementFn: defineIonIcon, - inputs: ['color', 'flipRtl', 'icon', 'ios', 'lazy', 'md', 'mode', 'name', 'sanitize', 'size', 'src'], + inputs: ICON_PROXY_INPUTS, }) @Component({ selector: 'ion-icon', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'flipRtl', 'icon', 'ios', 'lazy', 'md', 'mode', 'name', 'sanitize', 'size', 'src'], + inputs: ICON_INPUTS, standalone: true, }) export class IonIcon { diff --git a/packages/angular/src/standalone/directives/input-otp.ts b/packages/angular/src/standalone/directives/input-otp.ts index fe6b983174f..dabec934993 100644 --- a/packages/angular/src/standalone/directives/input-otp.ts +++ b/packages/angular/src/standalone/directives/input-otp.ts @@ -10,7 +10,7 @@ import { forwardRef, } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; -import { ValueAccessor } from '@ionic/angular/common'; +import { inputNames, ValueAccessor } from '@ionic/angular/common'; import type { InputOtpInputEventDetail as IIonInputOtpInputEventDetail, InputOtpChangeEventDetail as IIonInputOtpChangeEventDetail, @@ -19,17 +19,18 @@ import type { } from '@ionic/core/components'; import { defineCustomElement } from '@ionic/core/components/ion-input-otp.js'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; const INPUT_OTP_INPUTS = [ 'autocapitalize', 'color', - 'disabled', + { name: 'disabled', transform: nullableBooleanAttribute }, 'fill', 'inputmode', 'length', 'pattern', - 'readonly', + { name: 'readonly', transform: nullableBooleanAttribute }, 'separators', 'shape', 'size', @@ -37,9 +38,11 @@ const INPUT_OTP_INPUTS = [ 'value', ]; +const INPUT_OTP_PROXY_INPUTS = inputNames(INPUT_OTP_INPUTS); + @ProxyCmp({ defineCustomElementFn: defineCustomElement, - inputs: INPUT_OTP_INPUTS, + inputs: INPUT_OTP_PROXY_INPUTS, methods: ['setFocus'], }) @Component({ diff --git a/packages/angular/src/standalone/directives/input.ts b/packages/angular/src/standalone/directives/input.ts index b05ca0e3bcf..cbe8ff2f1f5 100644 --- a/packages/angular/src/standalone/directives/input.ts +++ b/packages/angular/src/standalone/directives/input.ts @@ -10,7 +10,7 @@ import { forwardRef, } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; -import { ValueAccessor } from '@ionic/angular/common'; +import { inputNames, ValueAccessor } from '@ionic/angular/common'; import type { InputInputEventDetail as IIonInputInputInputEventDetail, InputChangeEventDetail as IIonInputInputChangeEventDetail, @@ -18,21 +18,22 @@ import type { } from '@ionic/core/components'; import { defineCustomElement } from '@ionic/core/components/ion-input.js'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; const INPUT_INPUTS = [ 'accept', 'autocapitalize', 'autocomplete', - 'autocorrect', - 'autofocus', - 'clearInput', - 'clearOnEdit', + { name: 'autocorrect', transform: nullableBooleanAttribute }, + { name: 'autofocus', transform: nullableBooleanAttribute }, + { name: 'clearInput', transform: nullableBooleanAttribute }, + { name: 'clearOnEdit', transform: nullableBooleanAttribute }, 'color', - 'counter', + { name: 'counter', transform: nullableBooleanAttribute }, 'counterFormatter', 'debounce', - 'disabled', + { name: 'disabled', transform: nullableBooleanAttribute }, 'enterkeyhint', 'errorText', 'fill', @@ -45,23 +46,25 @@ const INPUT_INPUTS = [ 'min', 'minlength', 'mode', - 'multiple', + { name: 'multiple', transform: nullableBooleanAttribute }, 'name', 'pattern', 'placeholder', - 'readonly', - 'required', + { name: 'readonly', transform: nullableBooleanAttribute }, + { name: 'required', transform: nullableBooleanAttribute }, 'shape', 'size', - 'spellcheck', + { name: 'spellcheck', transform: nullableBooleanAttribute }, 'step', 'type', 'value', ]; +const INPUT_PROXY_INPUTS = inputNames(INPUT_INPUTS); + @ProxyCmp({ defineCustomElementFn: defineCustomElement, - inputs: INPUT_INPUTS, + inputs: INPUT_PROXY_INPUTS, methods: ['setFocus', 'getInputElement'], }) @Component({ diff --git a/packages/angular/src/standalone/directives/ion-accordion-group.ts b/packages/angular/src/standalone/directives/ion-accordion-group.ts index 8278f1ef302..26f7b8d43a0 100644 --- a/packages/angular/src/standalone/directives/ion-accordion-group.ts +++ b/packages/angular/src/standalone/directives/ion-accordion-group.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone, EventEmitter, Output } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonAccordionGroup } from '@ionic/core/comp changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'disabled', 'expand', 'mode', 'multiple', 'readonly', 'shape', 'theme', 'value'], + inputs: [{ name: 'animated', transform: nullableBooleanAttribute }, { name: 'disabled', transform: nullableBooleanAttribute }, 'expand', 'mode', { name: 'multiple', transform: nullableBooleanAttribute }, { name: 'readonly', transform: nullableBooleanAttribute }, 'shape', 'theme', 'value'], outputs: ['ionChange'], }) export class IonAccordionGroup { diff --git a/packages/angular/src/standalone/directives/ion-accordion.ts b/packages/angular/src/standalone/directives/ion-accordion.ts index 12e392a2c4d..5828dd882bb 100644 --- a/packages/angular/src/standalone/directives/ion-accordion.ts +++ b/packages/angular/src/standalone/directives/ion-accordion.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonAccordion } from '@ionic/core/component changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['disabled', 'mode', 'readonly', 'theme', 'toggleIcon', 'toggleIconSlot', 'value'], + inputs: [{ name: 'disabled', transform: nullableBooleanAttribute }, 'mode', { name: 'readonly', transform: nullableBooleanAttribute }, 'theme', 'toggleIcon', 'toggleIconSlot', 'value'], }) export class IonAccordion { protected el: HTMLIonAccordionElement; diff --git a/packages/angular/src/standalone/directives/ion-action-sheet.ts b/packages/angular/src/standalone/directives/ion-action-sheet.ts index a61e72cffa3..defe90be74e 100644 --- a/packages/angular/src/standalone/directives/ion-action-sheet.ts +++ b/packages/angular/src/standalone/directives/ion-action-sheet.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone, EventEmitter, Output } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -18,7 +19,7 @@ import { defineCustomElement as defineIonActionSheet } from '@ionic/core/compone changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'backdropDismiss', 'buttons', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', 'isOpen', 'keyboardClose', 'leaveAnimation', 'mode', 'subHeader', 'theme', 'translucent', 'trigger'], + inputs: [{ name: 'animated', transform: nullableBooleanAttribute }, { name: 'backdropDismiss', transform: nullableBooleanAttribute }, 'buttons', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', { name: 'isOpen', transform: nullableBooleanAttribute }, { name: 'keyboardClose', transform: nullableBooleanAttribute }, 'leaveAnimation', 'mode', 'subHeader', 'theme', { name: 'translucent', transform: nullableBooleanAttribute }, 'trigger'], outputs: ['ionActionSheetDidPresent', 'ionActionSheetWillPresent', 'ionActionSheetWillDismiss', 'ionActionSheetDidDismiss', 'didPresent', 'willPresent', 'willDismiss', 'didDismiss'], }) export class IonActionSheet { diff --git a/packages/angular/src/standalone/directives/ion-alert.ts b/packages/angular/src/standalone/directives/ion-alert.ts index 153bdb2ca7a..2c793b184f7 100644 --- a/packages/angular/src/standalone/directives/ion-alert.ts +++ b/packages/angular/src/standalone/directives/ion-alert.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone, EventEmitter, Output } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -18,7 +19,7 @@ import { defineCustomElement as defineIonAlert } from '@ionic/core/components/io changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'backdropDismiss', 'buttons', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', 'inputs', 'isOpen', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'subHeader', 'theme', 'translucent', 'trigger'], + inputs: [{ name: 'animated', transform: nullableBooleanAttribute }, { name: 'backdropDismiss', transform: nullableBooleanAttribute }, 'buttons', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', 'inputs', { name: 'isOpen', transform: nullableBooleanAttribute }, { name: 'keyboardClose', transform: nullableBooleanAttribute }, 'leaveAnimation', 'message', 'mode', 'subHeader', 'theme', { name: 'translucent', transform: nullableBooleanAttribute }, 'trigger'], outputs: ['ionAlertDidPresent', 'ionAlertWillPresent', 'ionAlertWillDismiss', 'ionAlertDidDismiss', 'didPresent', 'willPresent', 'willDismiss', 'didDismiss'], }) export class IonAlert { diff --git a/packages/angular/src/standalone/directives/ion-avatar.ts b/packages/angular/src/standalone/directives/ion-avatar.ts index 423b8880870..768ffc588e2 100644 --- a/packages/angular/src/standalone/directives/ion-avatar.ts +++ b/packages/angular/src/standalone/directives/ion-avatar.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonAvatar } from '@ionic/core/components/i changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['disabled', 'mode', 'shape', 'size', 'theme'], + inputs: [{ name: 'disabled', transform: nullableBooleanAttribute }, 'mode', 'shape', 'size', 'theme'], }) export class IonAvatar { protected el: HTMLIonAvatarElement; diff --git a/packages/angular/src/standalone/directives/ion-backdrop.ts b/packages/angular/src/standalone/directives/ion-backdrop.ts index 33505eb3f01..a1a21f3dee1 100644 --- a/packages/angular/src/standalone/directives/ion-backdrop.ts +++ b/packages/angular/src/standalone/directives/ion-backdrop.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone, EventEmitter, Output } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonBackdrop } from '@ionic/core/components changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['mode', 'stopPropagation', 'tappable', 'theme', 'visible'], + inputs: ['mode', { name: 'stopPropagation', transform: nullableBooleanAttribute }, { name: 'tappable', transform: nullableBooleanAttribute }, 'theme', { name: 'visible', transform: nullableBooleanAttribute }], outputs: ['ionBackdropTap'], }) export class IonBackdrop { diff --git a/packages/angular/src/standalone/directives/ion-breadcrumb.ts b/packages/angular/src/standalone/directives/ion-breadcrumb.ts index 31c55d9e5bc..db3b35937d4 100644 --- a/packages/angular/src/standalone/directives/ion-breadcrumb.ts +++ b/packages/angular/src/standalone/directives/ion-breadcrumb.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone, EventEmitter, Output } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonBreadcrumb } from '@ionic/core/componen changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['active', 'color', 'disabled', 'download', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'separator', 'target', 'theme'], + inputs: [{ name: 'active', transform: nullableBooleanAttribute }, 'color', { name: 'disabled', transform: nullableBooleanAttribute }, 'download', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', { name: 'separator', transform: nullableBooleanAttribute }, 'target', 'theme'], outputs: ['ionFocus', 'ionBlur'], }) export class IonBreadcrumb { diff --git a/packages/angular/src/standalone/directives/ion-button.ts b/packages/angular/src/standalone/directives/ion-button.ts index 1f764900f69..d5f9b45a65f 100644 --- a/packages/angular/src/standalone/directives/ion-button.ts +++ b/packages/angular/src/standalone/directives/ion-button.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone, EventEmitter, Output } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonButton } from '@ionic/core/components/i changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['buttonType', 'color', 'disabled', 'download', 'expand', 'fill', 'form', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', 'strong', 'target', 'theme', 'type'], + inputs: ['buttonType', 'color', { name: 'disabled', transform: nullableBooleanAttribute }, 'download', 'expand', 'fill', 'form', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'size', { name: 'strong', transform: nullableBooleanAttribute }, 'target', 'theme', 'type'], outputs: ['ionFocus', 'ionBlur'], }) export class IonButton { diff --git a/packages/angular/src/standalone/directives/ion-buttons.ts b/packages/angular/src/standalone/directives/ion-buttons.ts index c4e9284a99b..f9d528cac04 100644 --- a/packages/angular/src/standalone/directives/ion-buttons.ts +++ b/packages/angular/src/standalone/directives/ion-buttons.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonButtons } from '@ionic/core/components/ changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['collapse', 'mode', 'theme'], + inputs: [{ name: 'collapse', transform: nullableBooleanAttribute }, 'mode', 'theme'], }) export class IonButtons { protected el: HTMLIonButtonsElement; diff --git a/packages/angular/src/standalone/directives/ion-card-header.ts b/packages/angular/src/standalone/directives/ion-card-header.ts index 92fd365a16f..632d825bd73 100644 --- a/packages/angular/src/standalone/directives/ion-card-header.ts +++ b/packages/angular/src/standalone/directives/ion-card-header.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonCardHeader } from '@ionic/core/componen changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'mode', 'theme', 'translucent'], + inputs: ['color', 'mode', 'theme', { name: 'translucent', transform: nullableBooleanAttribute }], }) export class IonCardHeader { protected el: HTMLIonCardHeaderElement; diff --git a/packages/angular/src/standalone/directives/ion-card.ts b/packages/angular/src/standalone/directives/ion-card.ts index 44c634020ed..7b17c614969 100644 --- a/packages/angular/src/standalone/directives/ion-card.ts +++ b/packages/angular/src/standalone/directives/ion-card.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonCard } from '@ionic/core/components/ion changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['button', 'color', 'disabled', 'download', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'target', 'theme', 'type'], + inputs: [{ name: 'button', transform: nullableBooleanAttribute }, 'color', { name: 'disabled', transform: nullableBooleanAttribute }, 'download', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'shape', 'target', 'theme', 'type'], }) export class IonCard { protected el: HTMLIonCardElement; diff --git a/packages/angular/src/standalone/directives/ion-chip.ts b/packages/angular/src/standalone/directives/ion-chip.ts index 06ddc81a617..769511d0014 100644 --- a/packages/angular/src/standalone/directives/ion-chip.ts +++ b/packages/angular/src/standalone/directives/ion-chip.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonChip } from '@ionic/core/components/ion changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'disabled', 'hue', 'mode', 'outline', 'shape', 'size', 'theme'], + inputs: ['color', { name: 'disabled', transform: nullableBooleanAttribute }, 'hue', 'mode', { name: 'outline', transform: nullableBooleanAttribute }, 'shape', 'size', 'theme'], }) export class IonChip { protected el: HTMLIonChipElement; diff --git a/packages/angular/src/standalone/directives/ion-content.ts b/packages/angular/src/standalone/directives/ion-content.ts index 67ab7940048..a583ecd431e 100644 --- a/packages/angular/src/standalone/directives/ion-content.ts +++ b/packages/angular/src/standalone/directives/ion-content.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone, EventEmitter, Output } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -18,7 +19,7 @@ import { defineCustomElement as defineIonContent } from '@ionic/core/components/ changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'fixedSlotPlacement', 'forceOverscroll', 'fullscreen', 'mode', 'scrollEvents', 'scrollX', 'scrollY', 'theme'], + inputs: ['color', 'fixedSlotPlacement', { name: 'forceOverscroll', transform: nullableBooleanAttribute }, { name: 'fullscreen', transform: nullableBooleanAttribute }, 'mode', { name: 'scrollEvents', transform: nullableBooleanAttribute }, { name: 'scrollX', transform: nullableBooleanAttribute }, { name: 'scrollY', transform: nullableBooleanAttribute }, 'theme'], outputs: ['ionScrollStart', 'ionScroll', 'ionScrollEnd'], }) export class IonContent { diff --git a/packages/angular/src/standalone/directives/ion-datetime-button.ts b/packages/angular/src/standalone/directives/ion-datetime-button.ts index 428e6db03f0..f8bc086c00e 100644 --- a/packages/angular/src/standalone/directives/ion-datetime-button.ts +++ b/packages/angular/src/standalone/directives/ion-datetime-button.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonDatetimeButton } from '@ionic/core/comp changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'datetime', 'disabled', 'mode', 'theme'], + inputs: ['color', 'datetime', { name: 'disabled', transform: nullableBooleanAttribute }, 'mode', 'theme'], }) export class IonDatetimeButton { protected el: HTMLIonDatetimeButtonElement; diff --git a/packages/angular/src/standalone/directives/ion-divider.ts b/packages/angular/src/standalone/directives/ion-divider.ts index 49d771173f4..6234d0aca28 100644 --- a/packages/angular/src/standalone/directives/ion-divider.ts +++ b/packages/angular/src/standalone/directives/ion-divider.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonDivider } from '@ionic/core/components/ changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['inset', 'spacing'], + inputs: [{ name: 'inset', transform: nullableBooleanAttribute }, 'spacing'], }) export class IonDivider { protected el: HTMLIonDividerElement; diff --git a/packages/angular/src/standalone/directives/ion-fab-button.ts b/packages/angular/src/standalone/directives/ion-fab-button.ts index 9145a4ba329..f3e4e3b46af 100644 --- a/packages/angular/src/standalone/directives/ion-fab-button.ts +++ b/packages/angular/src/standalone/directives/ion-fab-button.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone, EventEmitter, Output } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonFabButton } from '@ionic/core/component changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['activated', 'closeIcon', 'color', 'disabled', 'download', 'form', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'show', 'size', 'target', 'theme', 'translucent', 'type'], + inputs: [{ name: 'activated', transform: nullableBooleanAttribute }, 'closeIcon', 'color', { name: 'disabled', transform: nullableBooleanAttribute }, 'download', 'form', 'href', 'mode', 'rel', 'routerAnimation', 'routerDirection', { name: 'show', transform: nullableBooleanAttribute }, 'size', 'target', 'theme', { name: 'translucent', transform: nullableBooleanAttribute }, 'type'], outputs: ['ionFocus', 'ionBlur'], }) export class IonFabButton { diff --git a/packages/angular/src/standalone/directives/ion-fab-list.ts b/packages/angular/src/standalone/directives/ion-fab-list.ts index 3668da35637..d29ec64591b 100644 --- a/packages/angular/src/standalone/directives/ion-fab-list.ts +++ b/packages/angular/src/standalone/directives/ion-fab-list.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonFabList } from '@ionic/core/components/ changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['activated', 'mode', 'side', 'theme'], + inputs: [{ name: 'activated', transform: nullableBooleanAttribute }, 'mode', 'side', 'theme'], }) export class IonFabList { protected el: HTMLIonFabListElement; diff --git a/packages/angular/src/standalone/directives/ion-fab.ts b/packages/angular/src/standalone/directives/ion-fab.ts index 64bd6b59a60..682dca1eb49 100644 --- a/packages/angular/src/standalone/directives/ion-fab.ts +++ b/packages/angular/src/standalone/directives/ion-fab.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -18,7 +19,7 @@ import { defineCustomElement as defineIonFab } from '@ionic/core/components/ion- changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['activated', 'edge', 'horizontal', 'mode', 'theme', 'vertical'], + inputs: [{ name: 'activated', transform: nullableBooleanAttribute }, { name: 'edge', transform: nullableBooleanAttribute }, 'horizontal', 'mode', 'theme', 'vertical'], }) export class IonFab { protected el: HTMLIonFabElement; diff --git a/packages/angular/src/standalone/directives/ion-footer.ts b/packages/angular/src/standalone/directives/ion-footer.ts index a33dee3f146..c225b13b943 100644 --- a/packages/angular/src/standalone/directives/ion-footer.ts +++ b/packages/angular/src/standalone/directives/ion-footer.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonFooter } from '@ionic/core/components/i changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['collapse', 'mode', 'scrollEffect', 'theme', 'translucent'], + inputs: ['collapse', 'mode', 'scrollEffect', 'theme', { name: 'translucent', transform: nullableBooleanAttribute }], }) export class IonFooter { protected el: HTMLIonFooterElement; diff --git a/packages/angular/src/standalone/directives/ion-grid.ts b/packages/angular/src/standalone/directives/ion-grid.ts index e2ae3aa64fe..3d5f3ae7e52 100644 --- a/packages/angular/src/standalone/directives/ion-grid.ts +++ b/packages/angular/src/standalone/directives/ion-grid.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonGrid } from '@ionic/core/components/ion changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['fixed', 'mode', 'theme'], + inputs: [{ name: 'fixed', transform: nullableBooleanAttribute }, 'mode', 'theme'], }) export class IonGrid { protected el: HTMLIonGridElement; diff --git a/packages/angular/src/standalone/directives/ion-header.ts b/packages/angular/src/standalone/directives/ion-header.ts index e77b039692b..6f4942380ae 100644 --- a/packages/angular/src/standalone/directives/ion-header.ts +++ b/packages/angular/src/standalone/directives/ion-header.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonHeader } from '@ionic/core/components/i changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['collapse', 'divider', 'mode', 'scrollEffect', 'theme', 'translucent'], + inputs: ['collapse', { name: 'divider', transform: nullableBooleanAttribute }, 'mode', 'scrollEffect', 'theme', { name: 'translucent', transform: nullableBooleanAttribute }], }) export class IonHeader { protected el: HTMLIonHeaderElement; diff --git a/packages/angular/src/standalone/directives/ion-infinite-scroll.ts b/packages/angular/src/standalone/directives/ion-infinite-scroll.ts index 2e4d9b4cd96..3367f9b1b5f 100644 --- a/packages/angular/src/standalone/directives/ion-infinite-scroll.ts +++ b/packages/angular/src/standalone/directives/ion-infinite-scroll.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone, EventEmitter, Output } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -18,7 +19,7 @@ import { defineCustomElement as defineIonInfiniteScroll } from '@ionic/core/comp changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['disabled', 'mode', 'position', 'preserveRerenderScrollPosition', 'theme', 'threshold'], + inputs: [{ name: 'disabled', transform: nullableBooleanAttribute }, 'mode', 'position', { name: 'preserveRerenderScrollPosition', transform: nullableBooleanAttribute }, 'theme', 'threshold'], outputs: ['ionInfinite'], }) export class IonInfiniteScroll { diff --git a/packages/angular/src/standalone/directives/ion-item-divider.ts b/packages/angular/src/standalone/directives/ion-item-divider.ts index d6c03e0ca09..7f7c21c9399 100644 --- a/packages/angular/src/standalone/directives/ion-item-divider.ts +++ b/packages/angular/src/standalone/directives/ion-item-divider.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonItemDivider } from '@ionic/core/compone changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'mode', 'sticky', 'theme'], + inputs: ['color', 'mode', { name: 'sticky', transform: nullableBooleanAttribute }, 'theme'], }) export class IonItemDivider { protected el: HTMLIonItemDividerElement; diff --git a/packages/angular/src/standalone/directives/ion-item-option.ts b/packages/angular/src/standalone/directives/ion-item-option.ts index 6ae14174968..506cc17faf2 100644 --- a/packages/angular/src/standalone/directives/ion-item-option.ts +++ b/packages/angular/src/standalone/directives/ion-item-option.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonItemOption } from '@ionic/core/componen changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'disabled', 'download', 'expandable', 'href', 'hue', 'mode', 'rel', 'shape', 'target', 'theme', 'type'], + inputs: ['color', { name: 'disabled', transform: nullableBooleanAttribute }, 'download', { name: 'expandable', transform: nullableBooleanAttribute }, 'href', 'hue', 'mode', 'rel', 'shape', 'target', 'theme', 'type'], }) export class IonItemOption { protected el: HTMLIonItemOptionElement; diff --git a/packages/angular/src/standalone/directives/ion-item-sliding.ts b/packages/angular/src/standalone/directives/ion-item-sliding.ts index d4ee5b2dc9e..db0d07a1009 100644 --- a/packages/angular/src/standalone/directives/ion-item-sliding.ts +++ b/packages/angular/src/standalone/directives/ion-item-sliding.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone, EventEmitter, Output } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -18,7 +19,7 @@ import { defineCustomElement as defineIonItemSliding } from '@ionic/core/compone changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['disabled', 'mode', 'theme'], + inputs: [{ name: 'disabled', transform: nullableBooleanAttribute }, 'mode', 'theme'], outputs: ['ionDrag'], }) export class IonItemSliding { diff --git a/packages/angular/src/standalone/directives/ion-item.ts b/packages/angular/src/standalone/directives/ion-item.ts index ee35fcae062..fdcc2b81654 100644 --- a/packages/angular/src/standalone/directives/ion-item.ts +++ b/packages/angular/src/standalone/directives/ion-item.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonItem } from '@ionic/core/components/ion changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['button', 'color', 'detail', 'detailIcon', 'disabled', 'download', 'href', 'lines', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'target', 'theme', 'type'], + inputs: [{ name: 'button', transform: nullableBooleanAttribute }, 'color', { name: 'detail', transform: nullableBooleanAttribute }, 'detailIcon', { name: 'disabled', transform: nullableBooleanAttribute }, 'download', 'href', 'lines', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'target', 'theme', 'type'], }) export class IonItem { protected el: HTMLIonItemElement; diff --git a/packages/angular/src/standalone/directives/ion-list.ts b/packages/angular/src/standalone/directives/ion-list.ts index 0c6287fd9d3..a61888b29c5 100644 --- a/packages/angular/src/standalone/directives/ion-list.ts +++ b/packages/angular/src/standalone/directives/ion-list.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -18,7 +19,7 @@ import { defineCustomElement as defineIonList } from '@ionic/core/components/ion changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['inset', 'lines', 'mode', 'shape', 'theme'], + inputs: [{ name: 'inset', transform: nullableBooleanAttribute }, 'lines', 'mode', 'shape', 'theme'], }) export class IonList { protected el: HTMLIonListElement; diff --git a/packages/angular/src/standalone/directives/ion-loading.ts b/packages/angular/src/standalone/directives/ion-loading.ts index e2092ced2dd..24da22255d3 100644 --- a/packages/angular/src/standalone/directives/ion-loading.ts +++ b/packages/angular/src/standalone/directives/ion-loading.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone, EventEmitter, Output } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -18,7 +19,7 @@ import { defineCustomElement as defineIonLoading } from '@ionic/core/components/ changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'backdropDismiss', 'cssClass', 'duration', 'enterAnimation', 'htmlAttributes', 'isOpen', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'showBackdrop', 'spinner', 'theme', 'translucent', 'trigger'], + inputs: [{ name: 'animated', transform: nullableBooleanAttribute }, { name: 'backdropDismiss', transform: nullableBooleanAttribute }, 'cssClass', 'duration', 'enterAnimation', 'htmlAttributes', { name: 'isOpen', transform: nullableBooleanAttribute }, { name: 'keyboardClose', transform: nullableBooleanAttribute }, 'leaveAnimation', 'message', 'mode', { name: 'showBackdrop', transform: nullableBooleanAttribute }, 'spinner', 'theme', { name: 'translucent', transform: nullableBooleanAttribute }, 'trigger'], outputs: ['ionLoadingDidPresent', 'ionLoadingWillPresent', 'ionLoadingWillDismiss', 'ionLoadingDidDismiss', 'didPresent', 'willPresent', 'willDismiss', 'didDismiss'], }) export class IonLoading { diff --git a/packages/angular/src/standalone/directives/ion-menu-button.ts b/packages/angular/src/standalone/directives/ion-menu-button.ts index 224d594e576..782d80e7221 100644 --- a/packages/angular/src/standalone/directives/ion-menu-button.ts +++ b/packages/angular/src/standalone/directives/ion-menu-button.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonMenuButton } from '@ionic/core/componen changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['autoHide', 'color', 'disabled', 'menu', 'mode', 'theme', 'type'], + inputs: [{ name: 'autoHide', transform: nullableBooleanAttribute }, 'color', { name: 'disabled', transform: nullableBooleanAttribute }, 'menu', 'mode', 'theme', 'type'], }) export class IonMenuButton { protected el: HTMLIonMenuButtonElement; diff --git a/packages/angular/src/standalone/directives/ion-menu-toggle.ts b/packages/angular/src/standalone/directives/ion-menu-toggle.ts index 9c9202d084a..b067410ac54 100644 --- a/packages/angular/src/standalone/directives/ion-menu-toggle.ts +++ b/packages/angular/src/standalone/directives/ion-menu-toggle.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonMenuToggle } from '@ionic/core/componen changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['autoHide', 'menu', 'mode', 'theme'], + inputs: [{ name: 'autoHide', transform: nullableBooleanAttribute }, 'menu', 'mode', 'theme'], }) export class IonMenuToggle { protected el: HTMLIonMenuToggleElement; diff --git a/packages/angular/src/standalone/directives/ion-menu.ts b/packages/angular/src/standalone/directives/ion-menu.ts index 34900e3465a..e5c5b4dc7a2 100644 --- a/packages/angular/src/standalone/directives/ion-menu.ts +++ b/packages/angular/src/standalone/directives/ion-menu.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone, EventEmitter, Output } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -18,7 +19,7 @@ import { defineCustomElement as defineIonMenu } from '@ionic/core/components/ion changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['contentId', 'disabled', 'maxEdgeStart', 'menuId', 'mode', 'side', 'swipeGesture', 'theme', 'type'], + inputs: ['contentId', { name: 'disabled', transform: nullableBooleanAttribute }, 'maxEdgeStart', 'menuId', 'mode', 'side', { name: 'swipeGesture', transform: nullableBooleanAttribute }, 'theme', 'type'], outputs: ['ionWillOpen', 'ionWillClose', 'ionDidOpen', 'ionDidClose'], }) export class IonMenu { diff --git a/packages/angular/src/standalone/directives/ion-picker-column-option.ts b/packages/angular/src/standalone/directives/ion-picker-column-option.ts index 641139c3c10..4c67b616d9d 100644 --- a/packages/angular/src/standalone/directives/ion-picker-column-option.ts +++ b/packages/angular/src/standalone/directives/ion-picker-column-option.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonPickerColumnOption } from '@ionic/core/ changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'disabled', 'mode', 'theme', 'value'], + inputs: ['color', { name: 'disabled', transform: nullableBooleanAttribute }, 'mode', 'theme', 'value'], }) export class IonPickerColumnOption { protected el: HTMLIonPickerColumnOptionElement; diff --git a/packages/angular/src/standalone/directives/ion-picker-column.ts b/packages/angular/src/standalone/directives/ion-picker-column.ts index 88e2ca0dfe9..47b1595dfcb 100644 --- a/packages/angular/src/standalone/directives/ion-picker-column.ts +++ b/packages/angular/src/standalone/directives/ion-picker-column.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone, EventEmitter, Output } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -18,7 +19,7 @@ import { defineCustomElement as defineIonPickerColumn } from '@ionic/core/compon changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'disabled', 'mode', 'theme', 'value'], + inputs: ['color', { name: 'disabled', transform: nullableBooleanAttribute }, 'mode', 'theme', 'value'], outputs: ['ionChange'], }) export class IonPickerColumn { diff --git a/packages/angular/src/standalone/directives/ion-progress-bar.ts b/packages/angular/src/standalone/directives/ion-progress-bar.ts index 4203703db55..42fa1aef756 100644 --- a/packages/angular/src/standalone/directives/ion-progress-bar.ts +++ b/packages/angular/src/standalone/directives/ion-progress-bar.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonProgressBar } from '@ionic/core/compone changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['buffer', 'color', 'mode', 'reversed', 'shape', 'theme', 'type', 'value'], + inputs: ['buffer', 'color', 'mode', { name: 'reversed', transform: nullableBooleanAttribute }, 'shape', 'theme', 'type', 'value'], }) export class IonProgressBar { protected el: HTMLIonProgressBarElement; diff --git a/packages/angular/src/standalone/directives/ion-radio.ts b/packages/angular/src/standalone/directives/ion-radio.ts index 0c6d0f46de3..f8c4314e93a 100644 --- a/packages/angular/src/standalone/directives/ion-radio.ts +++ b/packages/angular/src/standalone/directives/ion-radio.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone, EventEmitter, Output } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonRadio } from '@ionic/core/components/io changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['alignment', 'color', 'disabled', 'justify', 'labelPlacement', 'mode', 'name', 'theme', 'value'], + inputs: ['alignment', 'color', { name: 'disabled', transform: nullableBooleanAttribute }, 'justify', 'labelPlacement', 'mode', 'name', 'theme', 'value'], outputs: ['ionFocus', 'ionBlur'], }) export class IonRadio { diff --git a/packages/angular/src/standalone/directives/ion-refresher.ts b/packages/angular/src/standalone/directives/ion-refresher.ts index bc8c4821959..8c455b9477d 100644 --- a/packages/angular/src/standalone/directives/ion-refresher.ts +++ b/packages/angular/src/standalone/directives/ion-refresher.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone, EventEmitter, Output } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -18,7 +19,7 @@ import { defineCustomElement as defineIonRefresher } from '@ionic/core/component changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['closeDuration', 'disabled', 'mode', 'pullFactor', 'pullMax', 'pullMin', 'snapbackDuration', 'theme'], + inputs: ['closeDuration', { name: 'disabled', transform: nullableBooleanAttribute }, 'mode', 'pullFactor', 'pullMax', 'pullMin', 'snapbackDuration', 'theme'], outputs: ['ionRefresh', 'ionPull', 'ionStart', 'ionPullStart', 'ionPullEnd'], }) export class IonRefresher { diff --git a/packages/angular/src/standalone/directives/ion-reorder-group.ts b/packages/angular/src/standalone/directives/ion-reorder-group.ts index 89b7e200da5..da36bbb09d1 100644 --- a/packages/angular/src/standalone/directives/ion-reorder-group.ts +++ b/packages/angular/src/standalone/directives/ion-reorder-group.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone, EventEmitter, Output } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -18,7 +19,7 @@ import { defineCustomElement as defineIonReorderGroup } from '@ionic/core/compon changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['disabled', 'mode', 'theme'], + inputs: [{ name: 'disabled', transform: nullableBooleanAttribute }, 'mode', 'theme'], outputs: ['ionItemReorder', 'ionReorderStart', 'ionReorderMove', 'ionReorderEnd'], }) export class IonReorderGroup { diff --git a/packages/angular/src/standalone/directives/ion-segment-button.ts b/packages/angular/src/standalone/directives/ion-segment-button.ts index 64f643afb15..ba2ee9488d4 100644 --- a/packages/angular/src/standalone/directives/ion-segment-button.ts +++ b/packages/angular/src/standalone/directives/ion-segment-button.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonSegmentButton } from '@ionic/core/compo changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['contentId', 'disabled', 'layout', 'mode', 'theme', 'type', 'value'], + inputs: ['contentId', { name: 'disabled', transform: nullableBooleanAttribute }, 'layout', 'mode', 'theme', 'type', 'value'], }) export class IonSegmentButton { protected el: HTMLIonSegmentButtonElement; diff --git a/packages/angular/src/standalone/directives/ion-segment-view.ts b/packages/angular/src/standalone/directives/ion-segment-view.ts index 0ac2736ef37..cd4c898fce6 100644 --- a/packages/angular/src/standalone/directives/ion-segment-view.ts +++ b/packages/angular/src/standalone/directives/ion-segment-view.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone, EventEmitter, Output } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonSegmentView } from '@ionic/core/compone changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['disabled', 'swipeGesture'], + inputs: [{ name: 'disabled', transform: nullableBooleanAttribute }, { name: 'swipeGesture', transform: nullableBooleanAttribute }], outputs: ['ionSegmentViewScroll'], }) export class IonSegmentView { diff --git a/packages/angular/src/standalone/directives/ion-select-modal.ts b/packages/angular/src/standalone/directives/ion-select-modal.ts index b81d2a36576..8529e0306f3 100644 --- a/packages/angular/src/standalone/directives/ion-select-modal.ts +++ b/packages/angular/src/standalone/directives/ion-select-modal.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonSelectModal } from '@ionic/core/compone changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['cancelIcon', 'cancelText', 'header', 'multiple', 'options'], + inputs: [{ name: 'cancelIcon', transform: nullableBooleanAttribute }, 'cancelText', 'header', { name: 'multiple', transform: nullableBooleanAttribute }, 'options'], }) export class IonSelectModal { protected el: HTMLIonSelectModalElement; diff --git a/packages/angular/src/standalone/directives/ion-select-option.ts b/packages/angular/src/standalone/directives/ion-select-option.ts index ceaedd70a6b..88262a94799 100644 --- a/packages/angular/src/standalone/directives/ion-select-option.ts +++ b/packages/angular/src/standalone/directives/ion-select-option.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonSelectOption } from '@ionic/core/compon changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['description', 'disabled', 'justify', 'labelPlacement', 'mode', 'theme', 'value'], + inputs: ['description', { name: 'disabled', transform: nullableBooleanAttribute }, 'justify', 'labelPlacement', 'mode', 'theme', 'value'], }) export class IonSelectOption { protected el: HTMLIonSelectOptionElement; diff --git a/packages/angular/src/standalone/directives/ion-skeleton-text.ts b/packages/angular/src/standalone/directives/ion-skeleton-text.ts index bf7de3107a7..9d232dd4f13 100644 --- a/packages/angular/src/standalone/directives/ion-skeleton-text.ts +++ b/packages/angular/src/standalone/directives/ion-skeleton-text.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonSkeletonText } from '@ionic/core/compon changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'mode', 'theme'], + inputs: [{ name: 'animated', transform: nullableBooleanAttribute }, 'mode', 'theme'], }) export class IonSkeletonText { protected el: HTMLIonSkeletonTextElement; diff --git a/packages/angular/src/standalone/directives/ion-spinner.ts b/packages/angular/src/standalone/directives/ion-spinner.ts index 7d46562640d..d6103340ea3 100644 --- a/packages/angular/src/standalone/directives/ion-spinner.ts +++ b/packages/angular/src/standalone/directives/ion-spinner.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonSpinner } from '@ionic/core/components/ changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'duration', 'mode', 'name', 'paused', 'size', 'theme'], + inputs: ['color', 'duration', 'mode', 'name', { name: 'paused', transform: nullableBooleanAttribute }, 'size', 'theme'], }) export class IonSpinner { protected el: HTMLIonSpinnerElement; diff --git a/packages/angular/src/standalone/directives/ion-split-pane.ts b/packages/angular/src/standalone/directives/ion-split-pane.ts index 36845819c8c..e410941f79e 100644 --- a/packages/angular/src/standalone/directives/ion-split-pane.ts +++ b/packages/angular/src/standalone/directives/ion-split-pane.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone, EventEmitter, Output } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonSplitPane } from '@ionic/core/component changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['contentId', 'disabled', 'mode', 'theme', 'when'], + inputs: ['contentId', { name: 'disabled', transform: nullableBooleanAttribute }, 'mode', 'theme', 'when'], outputs: ['ionSplitPaneVisible'], }) export class IonSplitPane { diff --git a/packages/angular/src/standalone/directives/ion-tab-bar.ts b/packages/angular/src/standalone/directives/ion-tab-bar.ts index 1f2d31b2243..82c0a6db0f6 100644 --- a/packages/angular/src/standalone/directives/ion-tab-bar.ts +++ b/packages/angular/src/standalone/directives/ion-tab-bar.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonTabBar } from '@ionic/core/components/i changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'expand', 'mode', 'scrollEffect', 'selectedTab', 'shape', 'theme', 'translucent'], + inputs: ['color', 'expand', 'mode', 'scrollEffect', 'selectedTab', 'shape', 'theme', { name: 'translucent', transform: nullableBooleanAttribute }], }) export class IonTabBar { protected el: HTMLIonTabBarElement; diff --git a/packages/angular/src/standalone/directives/ion-tab-button.ts b/packages/angular/src/standalone/directives/ion-tab-button.ts index 425a3ce3ab0..eeb650eee09 100644 --- a/packages/angular/src/standalone/directives/ion-tab-button.ts +++ b/packages/angular/src/standalone/directives/ion-tab-button.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -17,7 +18,7 @@ import { defineCustomElement as defineIonTabButton } from '@ionic/core/component changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['disabled', 'download', 'href', 'layout', 'mode', 'rel', 'selected', 'shape', 'tab', 'target', 'theme'], + inputs: [{ name: 'disabled', transform: nullableBooleanAttribute }, 'download', 'href', 'layout', 'mode', 'rel', { name: 'selected', transform: nullableBooleanAttribute }, 'shape', 'tab', 'target', 'theme'], }) export class IonTabButton { protected el: HTMLIonTabButtonElement; diff --git a/packages/angular/src/standalone/directives/ion-toast.ts b/packages/angular/src/standalone/directives/ion-toast.ts index fc73cf7ccc3..8fa0719b259 100644 --- a/packages/angular/src/standalone/directives/ion-toast.ts +++ b/packages/angular/src/standalone/directives/ion-toast.ts @@ -3,6 +3,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, NgZone, EventEmitter, Output } from '@angular/core'; import { ProxyCmp } from './angular-component-lib/utils'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import type { Components } from '@ionic/core/components'; @@ -18,7 +19,7 @@ import { defineCustomElement as defineIonToast } from '@ionic/core/components/io changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'hue', 'icon', 'isOpen', 'keyboardClose', 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'shape', 'swipeGesture', 'theme', 'translucent', 'trigger'], + inputs: [{ name: 'animated', transform: nullableBooleanAttribute }, 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'hue', 'icon', { name: 'isOpen', transform: nullableBooleanAttribute }, { name: 'keyboardClose', transform: nullableBooleanAttribute }, 'layout', 'leaveAnimation', 'message', 'mode', 'position', 'positionAnchor', 'shape', 'swipeGesture', 'theme', { name: 'translucent', transform: nullableBooleanAttribute }, 'trigger'], outputs: ['ionToastDidPresent', 'ionToastWillPresent', 'ionToastWillDismiss', 'ionToastDidDismiss', 'didPresent', 'willPresent', 'willDismiss', 'didDismiss'], }) export class IonToast { diff --git a/packages/angular/src/standalone/directives/radio-group.ts b/packages/angular/src/standalone/directives/radio-group.ts index f5e9a899aad..fbf7fa62338 100644 --- a/packages/angular/src/standalone/directives/radio-group.ts +++ b/packages/angular/src/standalone/directives/radio-group.ts @@ -10,17 +10,27 @@ import { forwardRef, } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; -import { ValueAccessor } from '@ionic/angular/common'; +import { inputNames, ValueAccessor } from '@ionic/angular/common'; import type { RadioGroupChangeEventDetail, Components } from '@ionic/core/components'; import { defineCustomElement } from '@ionic/core/components/ion-radio-group.js'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; -const RADIO_GROUP_INPUTS = ['allowEmptySelection', 'compareWith', 'errorText', 'helperText', 'name', 'value']; +const RADIO_GROUP_INPUTS = [ + { name: 'allowEmptySelection', transform: nullableBooleanAttribute }, + 'compareWith', + 'errorText', + 'helperText', + 'name', + 'value', +]; + +const RADIO_GROUP_PROXY_INPUTS = inputNames(RADIO_GROUP_INPUTS); @ProxyCmp({ defineCustomElementFn: defineCustomElement, - inputs: RADIO_GROUP_INPUTS, + inputs: RADIO_GROUP_PROXY_INPUTS, }) @Component({ selector: 'ion-radio-group', diff --git a/packages/angular/src/standalone/directives/range.ts b/packages/angular/src/standalone/directives/range.ts index 8016a7ddef2..b7affb09a0b 100644 --- a/packages/angular/src/standalone/directives/range.ts +++ b/packages/angular/src/standalone/directives/range.ts @@ -10,7 +10,7 @@ import { forwardRef, } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; -import { ValueAccessor } from '@ionic/angular/common'; +import { inputNames, ValueAccessor } from '@ionic/angular/common'; import type { RangeChangeEventDetail, RangeKnobMoveStartEventDetail, @@ -19,31 +19,34 @@ import type { } from '@ionic/core/components'; import { defineCustomElement } from '@ionic/core/components/ion-range.js'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; const RANGE_INPUTS = [ 'activeBarStart', 'color', 'debounce', - 'disabled', - 'dualKnobs', + { name: 'disabled', transform: nullableBooleanAttribute }, + { name: 'dualKnobs', transform: nullableBooleanAttribute }, 'label', 'labelPlacement', 'max', 'min', 'mode', 'name', - 'pin', + { name: 'pin', transform: nullableBooleanAttribute }, 'pinFormatter', - 'snaps', + { name: 'snaps', transform: nullableBooleanAttribute }, 'step', - 'ticks', + { name: 'ticks', transform: nullableBooleanAttribute }, 'value', ]; +const RANGE_PROXY_INPUTS = inputNames(RANGE_INPUTS); + @ProxyCmp({ defineCustomElementFn: defineCustomElement, - inputs: RANGE_INPUTS, + inputs: RANGE_PROXY_INPUTS, }) @Component({ selector: 'ion-range', diff --git a/packages/angular/src/standalone/directives/searchbar.ts b/packages/angular/src/standalone/directives/searchbar.ts index 1f9ca61d623..1281337c1d7 100644 --- a/packages/angular/src/standalone/directives/searchbar.ts +++ b/packages/angular/src/standalone/directives/searchbar.ts @@ -10,22 +10,23 @@ import { forwardRef, } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; -import { ValueAccessor } from '@ionic/angular/common'; +import { inputNames, ValueAccessor } from '@ionic/angular/common'; import type { SearchbarInputEventDetail, SearchbarChangeEventDetail, Components } from '@ionic/core/components'; import { defineCustomElement } from '@ionic/core/components/ion-searchbar.js'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; const SEARCHBAR_INPUTS = [ - 'animated', + { name: 'animated', transform: nullableBooleanAttribute }, 'autocomplete', - 'autocorrect', + { name: 'autocorrect', transform: nullableBooleanAttribute }, 'cancelButtonIcon', 'cancelButtonText', 'clearIcon', 'color', 'debounce', - 'disabled', + { name: 'disabled', transform: nullableBooleanAttribute }, 'enterkeyhint', 'inputmode', 'mode', @@ -34,14 +35,16 @@ const SEARCHBAR_INPUTS = [ 'searchIcon', 'showCancelButton', 'showClearButton', - 'spellcheck', + { name: 'spellcheck', transform: nullableBooleanAttribute }, 'type', 'value', ]; +const SEARCHBAR_PROXY_INPUTS = inputNames(SEARCHBAR_INPUTS); + @ProxyCmp({ defineCustomElementFn: defineCustomElement, - inputs: SEARCHBAR_INPUTS, + inputs: SEARCHBAR_PROXY_INPUTS, methods: ['setFocus', 'getInputElement'], }) @Component({ diff --git a/packages/angular/src/standalone/directives/segment.ts b/packages/angular/src/standalone/directives/segment.ts index 0220069ab92..e08a5277787 100644 --- a/packages/angular/src/standalone/directives/segment.ts +++ b/packages/angular/src/standalone/directives/segment.ts @@ -10,17 +10,28 @@ import { forwardRef, } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; -import { ValueAccessor } from '@ionic/angular/common'; +import { inputNames, ValueAccessor } from '@ionic/angular/common'; import type { SegmentChangeEventDetail, Components } from '@ionic/core/components'; import { defineCustomElement } from '@ionic/core/components/ion-segment.js'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; -const SEGMENT_INPUTS = ['color', 'disabled', 'mode', 'scrollable', 'selectOnFocus', 'swipeGesture', 'value']; +const SEGMENT_INPUTS = [ + 'color', + { name: 'disabled', transform: nullableBooleanAttribute }, + 'mode', + { name: 'scrollable', transform: nullableBooleanAttribute }, + { name: 'selectOnFocus', transform: nullableBooleanAttribute }, + { name: 'swipeGesture', transform: nullableBooleanAttribute }, + 'value', +]; + +const SEGMENT_PROXY_INPUTS = inputNames(SEGMENT_INPUTS); @ProxyCmp({ defineCustomElementFn: defineCustomElement, - inputs: SEGMENT_INPUTS, + inputs: SEGMENT_PROXY_INPUTS, }) @Component({ selector: 'ion-segment', diff --git a/packages/angular/src/standalone/directives/select.ts b/packages/angular/src/standalone/directives/select.ts index b6dac74ab80..7196f81770c 100644 --- a/packages/angular/src/standalone/directives/select.ts +++ b/packages/angular/src/standalone/directives/select.ts @@ -10,17 +10,19 @@ import { forwardRef, } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; -import { ValueAccessor } from '@ionic/angular/common'; +import { inputNames, ValueAccessor } from '@ionic/angular/common'; import type { SelectChangeEventDetail, Components } from '@ionic/core/components'; import { defineCustomElement } from '@ionic/core/components/ion-select.js'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; const SELECT_INPUTS = [ + { name: 'cancelIcon', transform: nullableBooleanAttribute }, 'cancelText', 'color', 'compareWith', - 'disabled', + { name: 'disabled', transform: nullableBooleanAttribute }, 'errorText', 'expandedIcon', 'fill', @@ -31,19 +33,22 @@ const SELECT_INPUTS = [ 'label', 'labelPlacement', 'mode', - 'multiple', + { name: 'multiple', transform: nullableBooleanAttribute }, 'name', 'okText', 'placeholder', + { name: 'required', transform: nullableBooleanAttribute }, 'selectedText', 'shape', 'toggleIcon', 'value', ]; +const SELECT_PROXY_INPUTS = inputNames(SELECT_INPUTS); + @ProxyCmp({ defineCustomElementFn: defineCustomElement, - inputs: SELECT_INPUTS, + inputs: SELECT_PROXY_INPUTS, methods: ['open'], }) @Component({ diff --git a/packages/angular/src/standalone/directives/textarea.ts b/packages/angular/src/standalone/directives/textarea.ts index 4afa1dacb79..f3145c6cc86 100644 --- a/packages/angular/src/standalone/directives/textarea.ts +++ b/packages/angular/src/standalone/directives/textarea.ts @@ -10,23 +10,24 @@ import { forwardRef, } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; -import { ValueAccessor } from '@ionic/angular/common'; +import { inputNames, ValueAccessor } from '@ionic/angular/common'; import type { TextareaChangeEventDetail, TextareaInputEventDetail, Components } from '@ionic/core/components'; import { defineCustomElement } from '@ionic/core/components/ion-textarea.js'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; const TEXTAREA_INPUTS = [ - 'autoGrow', + { name: 'autoGrow', transform: nullableBooleanAttribute }, 'autocapitalize', - 'autofocus', - 'clearOnEdit', + { name: 'autofocus', transform: nullableBooleanAttribute }, + { name: 'clearOnEdit', transform: nullableBooleanAttribute }, 'color', 'cols', - 'counter', + { name: 'counter', transform: nullableBooleanAttribute }, 'counterFormatter', 'debounce', - 'disabled', + { name: 'disabled', transform: nullableBooleanAttribute }, 'enterkeyhint', 'errorText', 'fill', @@ -39,18 +40,20 @@ const TEXTAREA_INPUTS = [ 'mode', 'name', 'placeholder', - 'readonly', - 'required', + { name: 'readonly', transform: nullableBooleanAttribute }, + { name: 'required', transform: nullableBooleanAttribute }, 'rows', 'shape', - 'spellcheck', + { name: 'spellcheck', transform: nullableBooleanAttribute }, 'value', 'wrap', ]; +const TEXTAREA_PROXY_INPUTS = inputNames(TEXTAREA_INPUTS); + @ProxyCmp({ defineCustomElementFn: defineCustomElement, - inputs: TEXTAREA_INPUTS, + inputs: TEXTAREA_PROXY_INPUTS, methods: ['setFocus', 'getInputElement'], }) @Component({ diff --git a/packages/angular/src/standalone/directives/toggle.ts b/packages/angular/src/standalone/directives/toggle.ts index a04899046bd..68732936c7a 100644 --- a/packages/angular/src/standalone/directives/toggle.ts +++ b/packages/angular/src/standalone/directives/toggle.ts @@ -10,29 +10,33 @@ import { forwardRef, } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; -import { ValueAccessor, setIonicClasses } from '@ionic/angular/common'; +import { inputNames, setIonicClasses, ValueAccessor } from '@ionic/angular/common'; import type { ToggleChangeEventDetail, Components } from '@ionic/core/components'; import { defineCustomElement } from '@ionic/core/components/ion-toggle.js'; +import { nullableBooleanAttribute } from './angular-component-lib/boolean-attribute'; import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils'; const TOGGLE_INPUTS = [ - 'checked', + { name: 'checked', transform: nullableBooleanAttribute }, 'color', - 'disabled', - 'enableOnOffLabels', + { name: 'disabled', transform: nullableBooleanAttribute }, + { name: 'enableOnOffLabels', transform: nullableBooleanAttribute }, 'errorText', 'helperText', 'justify', 'labelPlacement', 'mode', 'name', + { name: 'required', transform: nullableBooleanAttribute }, 'value', ]; +const TOGGLE_PROXY_INPUTS = inputNames(TOGGLE_INPUTS); + @ProxyCmp({ defineCustomElementFn: defineCustomElement, - inputs: TOGGLE_INPUTS, + inputs: TOGGLE_PROXY_INPUTS, }) @Component({ selector: 'ion-toggle', diff --git a/packages/angular/test/base/e2e/src/standalone/modal.spec.ts b/packages/angular/test/base/e2e/src/standalone/modal.spec.ts index 081ac8fc252..c9355a91d66 100644 --- a/packages/angular/test/base/e2e/src/standalone/modal.spec.ts +++ b/packages/angular/test/base/e2e/src/standalone/modal.spec.ts @@ -5,6 +5,15 @@ test.describe('Modals: Inline', () => { await page.goto('/standalone/modal'); }); + /* + * Pins the one behavior that distinguishes `nullableBooleanAttribute` from Angular's + * `booleanAttribute`. Attribute presence, `'false'` and an absent attribute all behave + * the same under either, because Stencil coerces them itself. + */ + test('should leave an undefined boolean input as undefined', async ({ page }) => { + await expect(page.locator('ion-modal')).toHaveJSProperty('focusTrap', undefined); + }); + test('should render modal', async ({ page }) => { await page.locator('button#open-modal').click(); diff --git a/packages/angular/test/base/src/app/standalone/modal/modal.component.html b/packages/angular/test/base/src/app/standalone/modal/modal.component.html index af65317ff2a..a6697d84c29 100644 --- a/packages/angular/test/base/src/app/standalone/modal/modal.component.html +++ b/packages/angular/test/base/src/app/standalone/modal/modal.component.html @@ -1,6 +1,7 @@ - + + diff --git a/packages/angular/test/base/src/app/standalone/modal/modal.component.ts b/packages/angular/test/base/src/app/standalone/modal/modal.component.ts index 9868c0b7140..396126a2594 100644 --- a/packages/angular/test/base/src/app/standalone/modal/modal.component.ts +++ b/packages/angular/test/base/src/app/standalone/modal/modal.component.ts @@ -8,4 +8,6 @@ import { IonModal } from '@ionic/angular'; imports: [IonModal] }) export class ModalComponent { + /* Reaches the input as `undefined`, the way an unresolved `async` pipe would. */ + unsetFocusTrap: boolean | undefined = undefined; }