diff --git a/core/api.txt b/core/api.txt index 4c25d93691a..f4fe3f92905 100644 --- a/core/api.txt +++ b/core/api.txt @@ -558,7 +558,7 @@ ion-input,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secon ion-input,prop,counter,boolean,false,false,false ion-input,prop,counterFormatter,((inputLength: number, maxLength: number) => string) | undefined,undefined,false,false ion-input,prop,debounce,number | undefined,undefined,false,false -ion-input,prop,disabled,boolean,false,false,false +ion-input,prop,disabled,boolean,false,false,true ion-input,prop,enterkeyhint,"done" | "enter" | "go" | "next" | "previous" | "search" | "send" | undefined,undefined,false,false ion-input,prop,errorText,string | undefined,undefined,false,false ion-input,prop,fill,"outline" | "solid" | undefined,undefined,false,false @@ -575,7 +575,7 @@ ion-input,prop,multiple,boolean | undefined,undefined,false,false ion-input,prop,name,string,this.inputId,false,false ion-input,prop,pattern,string | undefined,undefined,false,false ion-input,prop,placeholder,string | undefined,undefined,false,false -ion-input,prop,readonly,boolean,false,false,false +ion-input,prop,readonly,boolean,false,false,true ion-input,prop,required,boolean,false,false,false ion-input,prop,shape,"round" | undefined,undefined,false,false ion-input,prop,spellcheck,boolean,false,false,false @@ -607,6 +607,12 @@ ion-input,css-prop,--placeholder-font-style ion-input,css-prop,--placeholder-font-weight ion-input,css-prop,--placeholder-opacity +ion-input-password-toggle,shadow +ion-input-password-toggle,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true +ion-input-password-toggle,prop,hideIcon,string | undefined,undefined,false,false +ion-input-password-toggle,prop,mode,"ios" | "md",undefined,false,false +ion-input-password-toggle,prop,showIcon,string | undefined,undefined,false,false + ion-item,shadow ion-item,prop,button,boolean,false,false,false ion-item,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 2726c417393..4b85721b497 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -1287,6 +1287,25 @@ export namespace Components { */ "value"?: string | number | null; } + interface IonInputPasswordToggle { + /** + * The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). + */ + "color"?: Color; + /** + * The icon that can be used to represent hiding a password. If not set, the "eyeOff" Ionicon will be used. + */ + "hideIcon"?: string; + /** + * The mode determines which platform styles to use. + */ + "mode"?: "ios" | "md"; + /** + * The icon that can be used to represent showing a password. If not set, the "eye" Ionicon will be used. + */ + "showIcon"?: string; + "type": TextFieldTypes; + } interface IonItem { /** * If `true`, a button tag will be rendered and the item will be tappable. @@ -3811,6 +3830,12 @@ declare global { prototype: HTMLIonInputElement; new (): HTMLIonInputElement; }; + interface HTMLIonInputPasswordToggleElement extends Components.IonInputPasswordToggle, HTMLStencilElement { + } + var HTMLIonInputPasswordToggleElement: { + prototype: HTMLIonInputPasswordToggleElement; + new (): HTMLIonInputPasswordToggleElement; + }; interface HTMLIonItemElement extends Components.IonItem, HTMLStencilElement { } var HTMLIonItemElement: { @@ -4635,6 +4660,7 @@ declare global { "ion-infinite-scroll": HTMLIonInfiniteScrollElement; "ion-infinite-scroll-content": HTMLIonInfiniteScrollContentElement; "ion-input": HTMLIonInputElement; + "ion-input-password-toggle": HTMLIonInputPasswordToggleElement; "ion-item": HTMLIonItemElement; "ion-item-divider": HTMLIonItemDividerElement; "ion-item-group": HTMLIonItemGroupElement; @@ -5993,6 +6019,25 @@ declare namespace LocalJSX { */ "value"?: string | number | null; } + interface IonInputPasswordToggle { + /** + * The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). + */ + "color"?: Color; + /** + * The icon that can be used to represent hiding a password. If not set, the "eyeOff" Ionicon will be used. + */ + "hideIcon"?: string; + /** + * The mode determines which platform styles to use. + */ + "mode"?: "ios" | "md"; + /** + * The icon that can be used to represent showing a password. If not set, the "eye" Ionicon will be used. + */ + "showIcon"?: string; + "type"?: TextFieldTypes; + } interface IonItem { /** * If `true`, a button tag will be rendered and the item will be tappable. @@ -8040,6 +8085,7 @@ declare namespace LocalJSX { "ion-infinite-scroll": IonInfiniteScroll; "ion-infinite-scroll-content": IonInfiniteScrollContent; "ion-input": IonInput; + "ion-input-password-toggle": IonInputPasswordToggle; "ion-item": IonItem; "ion-item-divider": IonItemDivider; "ion-item-group": IonItemGroup; @@ -8138,6 +8184,7 @@ declare module "@stencil/core" { "ion-infinite-scroll": LocalJSX.IonInfiniteScroll & JSXBase.HTMLAttributes; "ion-infinite-scroll-content": LocalJSX.IonInfiniteScrollContent & JSXBase.HTMLAttributes; "ion-input": LocalJSX.IonInput & JSXBase.HTMLAttributes; + "ion-input-password-toggle": LocalJSX.IonInputPasswordToggle & JSXBase.HTMLAttributes; "ion-item": LocalJSX.IonItem & JSXBase.HTMLAttributes; "ion-item-divider": LocalJSX.IonItemDivider & JSXBase.HTMLAttributes; "ion-item-group": LocalJSX.IonItemGroup & JSXBase.HTMLAttributes; diff --git a/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-scale-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-scale-ios-ltr-Mobile-Chrome-linux.png index d838af9af88..88c0d8e8f60 100644 Binary files a/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-scale-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/alert/test/a11y/alert.e2e.ts-snapshots/alert-scale-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/input-password-toggle/input-password-toggle.scss b/core/src/components/input-password-toggle/input-password-toggle.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/core/src/components/input-password-toggle/input-password-toggle.tsx b/core/src/components/input-password-toggle/input-password-toggle.tsx new file mode 100644 index 00000000000..a783cfc9af3 --- /dev/null +++ b/core/src/components/input-password-toggle/input-password-toggle.tsx @@ -0,0 +1,152 @@ +import type { ComponentInterface } from '@stencil/core'; +import { Component, Element, Host, Prop, h, Watch } from '@stencil/core'; +import { printIonWarning } from '@utils/logging'; +import { createColorClasses } from '@utils/theme'; +import { eyeOff, eye } from 'ionicons/icons'; + +import { getIonMode } from '../../global/ionic-global'; +import type { Color, TextFieldTypes } from '../../interface'; + +/** + * @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use. + */ +@Component({ + tag: 'ion-input-password-toggle', + /** + * Empty CSS files are required in order for the mode to be inherited to the + * inner ion-button. Otherwise, the setMode callback provided to Stencil will not get called + * and we will default to MD mode. + */ + styleUrls: { + ios: 'input-password-toggle.scss', + md: 'input-password-toggle.scss', + }, + shadow: true, +}) +export class InputPasswordToggle implements ComponentInterface { + private inputElRef!: HTMLIonInputElement | null; + + @Element() el!: HTMLIonInputElement; + + /** + * The color to use from your application's color palette. + * Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. + * For more information on colors, see [theming](/docs/theming/basics). + */ + @Prop({ reflect: true }) color?: Color; + + /** + * The icon that can be used to represent showing a password. If not set, the "eye" Ionicon will be used. + */ + @Prop() showIcon?: string; + + /** + * The icon that can be used to represent hiding a password. If not set, the "eyeOff" Ionicon will be used. + */ + @Prop() hideIcon?: string; + + /** + * @internal + */ + @Prop({ mutable: true }) type: TextFieldTypes = 'password'; + + /** + * Whenever the input type changes we need to re-run validation to ensure the password + * toggle is being used with the correct input type. If the application changes the type + * outside of this component we also need to re-render so the correct icon is shown. + */ + @Watch('type') + onTypeChange(newValue: TextFieldTypes) { + if (newValue !== 'text' && newValue !== 'password') { + printIonWarning( + `ion-input-password-toggle only supports inputs of type "text" or "password". Input of type "${newValue}" is not compatible.`, + this.el + ); + + return; + } + } + + connectedCallback() { + const { el } = this; + + const inputElRef = (this.inputElRef = el.closest('ion-input')); + + if (!inputElRef) { + printIonWarning( + 'No ancestor ion-input found for ion-input-password-toggle. This component must be slotted inside of an ion-input.', + el + ); + + return; + } + + /** + * Important: Set the type in connectedCallback because the default value + * of this.type may not always be accurate. Usually inputs have the "password" type + * but it is possible to have the input to initially have the "text" type. In that scenario + * the wrong icon will show briefly before switching to the correct icon. Setting the + * type here allows us to avoid that flicker. + */ + this.type = inputElRef.type; + } + + disconnectedCallback() { + this.inputElRef = null; + } + + private togglePasswordVisibility = () => { + const { inputElRef } = this; + + if (!inputElRef) { + return; + } + + inputElRef.type = inputElRef.type === 'text' ? 'password' : 'text'; + }; + + render() { + const { color, type } = this; + + const mode = getIonMode(this); + + const showPasswordIcon = this.showIcon ?? eye; + const hidePasswordIcon = this.hideIcon ?? eyeOff; + + const isPasswordVisible = type === 'text'; + + return ( + + { + /** + * This prevents mobile browsers from + * blurring the input when the password toggle + * button is activated. + */ + ev.preventDefault(); + }} + onClick={this.togglePasswordVisibility} + > + + + + ); + } +} diff --git a/core/src/components/input-password-toggle/test/a11y/input-password-toggle.e2e.ts b/core/src/components/input-password-toggle/test/a11y/input-password-toggle.e2e.ts new file mode 100644 index 00000000000..76b9f3d2837 --- /dev/null +++ b/core/src/components/input-password-toggle/test/a11y/input-password-toggle.e2e.ts @@ -0,0 +1,23 @@ +import AxeBuilder from '@axe-core/playwright'; +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +configs({ directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('input password toggle: a11y'), () => { + test('should not have accessibility violations', async ({ page }) => { + await page.setContent( + ` +
+ + + +
+ `, + config + ); + + const results = await new AxeBuilder({ page }).analyze(); + expect(results.violations).toEqual([]); + }); + }); +}); diff --git a/core/src/components/input-password-toggle/test/basic/index.html b/core/src/components/input-password-toggle/test/basic/index.html new file mode 100644 index 00000000000..e4232be7fc9 --- /dev/null +++ b/core/src/components/input-password-toggle/test/basic/index.html @@ -0,0 +1,76 @@ + + + + + Input - Toggle Password + + + + + + + + + + + + + + Input - Basic + + + + +
+
+

Default

+ + + +
+
+

Custom Icon

+ + + +
+
+

Custom Mode/Color

+ + + +
+
+
+
+ + diff --git a/core/src/components/input-password-toggle/test/basic/input-password-toggle.e2e.ts b/core/src/components/input-password-toggle/test/basic/input-password-toggle.e2e.ts new file mode 100644 index 00000000000..637c9ac3c4d --- /dev/null +++ b/core/src/components/input-password-toggle/test/basic/input-password-toggle.e2e.ts @@ -0,0 +1,50 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('input password toggle: states'), () => { + test('should be hidden when inside of a readonly input', async ({ page }) => { + await page.setContent( + ` + + + + `, + config + ); + + const inputPasswordToggle = page.locator('ion-input-password-toggle'); + await expect(inputPasswordToggle).toBeHidden(); + }); + test('should be hidden when inside of a disabled input', async ({ page }) => { + await page.setContent( + ` + + + + `, + config + ); + + const inputPasswordToggle = page.locator('ion-input-password-toggle'); + await expect(inputPasswordToggle).toBeHidden(); + }); + }); + + test.describe(title('input password toggle: rendering'), () => { + test('should not have visual regressions', async ({ page }) => { + await page.setContent( + ` + + + + `, + config + ); + + const inputPasswordToggle = page.locator('ion-input-password-toggle'); + + await expect(inputPasswordToggle).toHaveScreenshot(screenshot('input-password-toggle')); + }); + }); +}); diff --git a/core/src/components/input-password-toggle/test/basic/input-password-toggle.e2e.ts-snapshots/input-password-toggle-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/input-password-toggle/test/basic/input-password-toggle.e2e.ts-snapshots/input-password-toggle-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..4861e066bab Binary files /dev/null and b/core/src/components/input-password-toggle/test/basic/input-password-toggle.e2e.ts-snapshots/input-password-toggle-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/input-password-toggle/test/basic/input-password-toggle.e2e.ts-snapshots/input-password-toggle-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/input-password-toggle/test/basic/input-password-toggle.e2e.ts-snapshots/input-password-toggle-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..d8fd77a7446 Binary files /dev/null and b/core/src/components/input-password-toggle/test/basic/input-password-toggle.e2e.ts-snapshots/input-password-toggle-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/input-password-toggle/test/basic/input-password-toggle.e2e.ts-snapshots/input-password-toggle-ios-ltr-Mobile-Safari-linux.png b/core/src/components/input-password-toggle/test/basic/input-password-toggle.e2e.ts-snapshots/input-password-toggle-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..ef43adce9eb Binary files /dev/null and b/core/src/components/input-password-toggle/test/basic/input-password-toggle.e2e.ts-snapshots/input-password-toggle-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/input-password-toggle/test/input-password-toggle.spec.tsx b/core/src/components/input-password-toggle/test/input-password-toggle.spec.tsx new file mode 100644 index 00000000000..9e771598e25 --- /dev/null +++ b/core/src/components/input-password-toggle/test/input-password-toggle.spec.tsx @@ -0,0 +1,115 @@ +import { h } from '@stencil/core'; +import { newSpecPage } from '@stencil/core/testing'; + +import { Input } from '../../input/input'; +import { InputPasswordToggle } from '../input-password-toggle'; +import { Button } from '../../button/button'; +import { initialize } from '../../../global/ionic-global'; + +describe('input password toggle', () => { + it('should toggle input type when clicked', async () => { + const page = await newSpecPage({ + components: [Input, InputPasswordToggle, Button], + template: () => ( + + + + ), + }); + + const inputPasswordToggle = page.body.querySelector('ion-input-password-toggle')!; + const button = inputPasswordToggle.shadowRoot!.querySelector('ion-button')!; + const input = page.body.querySelector('ion-input')!; + + expect(input.type).toBe('password'); + + button.click(); + await page.waitForChanges(); + + expect(input.type).toBe('text'); + + button.click(); + await page.waitForChanges(); + + expect(input.type).toBe('password'); + }); + + it('should render custom icons', async () => { + const page = await newSpecPage({ + components: [Input, InputPasswordToggle, Button], + template: () => ( + + + + ), + }); + + const inputPasswordToggle = page.body.querySelector('ion-input-password-toggle')!; + const button = inputPasswordToggle.shadowRoot!.querySelector('ion-button')!; + const icon = inputPasswordToggle.shadowRoot!.querySelector('ion-icon')!; + + // Grab the attribute to test since we are not actually passing in a valid SVG + expect(icon.getAttribute('icon')).toBe('show'); + + button.click(); + await page.waitForChanges(); + + expect(icon.getAttribute('icon')).toBe('hide'); + }); + + it('changing the type on the input should update the icon used in password toggle', async () => { + const page = await newSpecPage({ + components: [Input, InputPasswordToggle, Button], + template: () => ( + + + + ), + }); + + const inputPasswordToggle = page.body.querySelector('ion-input-password-toggle')!; + const input = page.body.querySelector('ion-input')!; + const icon = inputPasswordToggle.shadowRoot!.querySelector('ion-icon')!; + + // Grab the attribute to test since we are not actually passing in a valid SVG + expect(icon.getAttribute('icon')).toBe('show'); + + input.type = 'text'; + await page.waitForChanges(); + + expect(icon.getAttribute('icon')).toBe('hide'); + + input.type = 'password'; + await page.waitForChanges(); + + expect(icon.getAttribute('icon')).toBe('show'); + }); + + it('should inherit the mode and color to internal ionic components', async () => { + /** + * This initialize script tells Stencil how to determine the mode on components. + * This is required for any getIonMode internal logic to function properly in spec tests. + */ + initialize(); + + const page = await newSpecPage({ + components: [Input, InputPasswordToggle, Button], + template: () => ( + + + + ), + }); + + const inputPasswordToggle = page.body.querySelector('ion-input-password-toggle')!; + const button = inputPasswordToggle.shadowRoot!.querySelector('ion-button')!; + + await page.waitForChanges(); + + // mode is a virtual prop so we need to access it as an attribute + expect(button.getAttribute('mode')).toBe('ios'); + + // color is an actual prop so we can access the element property + expect(button.color).toBe('danger'); + }); +}); diff --git a/core/src/components/input/input.scss b/core/src/components/input/input.scss index af3ad3c467b..80ebc9896ae 100644 --- a/core/src/components/input/input.scss +++ b/core/src/components/input/input.scss @@ -605,3 +605,12 @@ margin-inline-start: $form-control-label-margin; margin-inline-end: 0; } + +/** + * The input password toggle component should be hidden when the input is readonly/disabled + * because it is not possible to edit a password. + */ +:host([disabled]) ::slotted(ion-input-password-toggle), +:host([readonly]) ::slotted(ion-input-password-toggle) { + display: none; +} diff --git a/core/src/components/input/input.tsx b/core/src/components/input/input.tsx index eeaf63beaec..15727fe0e04 100644 --- a/core/src/components/input/input.tsx +++ b/core/src/components/input/input.tsx @@ -130,7 +130,7 @@ export class Input implements ComponentInterface { /** * If `true`, the user cannot interact with the input. */ - @Prop() disabled = false; + @Prop({ reflect: true }) disabled = false; /** * A hint to the browser for which enter key to display. @@ -226,7 +226,7 @@ export class Input implements ComponentInterface { /** * If `true`, the user cannot modify the value. */ - @Prop() readonly = false; + @Prop({ reflect: true }) readonly = false; /** * If `true`, the user must fill in a value before submitting a form. @@ -254,6 +254,20 @@ export class Input implements ComponentInterface { */ @Prop() type: TextFieldTypes = 'text'; + /** + * Whenever the type on the input changes we need + * to update the internal type prop on the password + * toggle so that that correct icon is shown. + */ + @Watch('type') + onTypeChange() { + const passwordToggle = this.el.querySelector('ion-input-password-toggle'); + + if (passwordToggle) { + passwordToggle.type = this.type; + } + } + /** * The value of the input. */ @@ -344,6 +358,14 @@ export class Input implements ComponentInterface { componentDidLoad() { this.originalIonInput = this.ionInput; + + /** + * Set the type on the password toggle in the event that this input's + * type was set async and does not match the default type for the password toggle. + * This can happen when the type is bound using a JS framework binding syntax + * such as [type] in Angular. + */ + this.onTypeChange(); } componentDidRender() { diff --git a/packages/angular/src/directives/proxies-list.ts b/packages/angular/src/directives/proxies-list.ts index 172c4ec49d7..8a0d9eb03ec 100644 --- a/packages/angular/src/directives/proxies-list.ts +++ b/packages/angular/src/directives/proxies-list.ts @@ -36,6 +36,7 @@ export const DIRECTIVES = [ d.IonInfiniteScroll, d.IonInfiniteScrollContent, d.IonInput, + d.IonInputPasswordToggle, d.IonItem, d.IonItemDivider, d.IonItemGroup, diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts index d7eaecc6d81..8253b0876f2 100644 --- a/packages/angular/src/directives/proxies.ts +++ b/packages/angular/src/directives/proxies.ts @@ -1014,6 +1014,28 @@ where the user's interaction is typing. } +@ProxyCmp({ + inputs: ['color', 'hideIcon', 'mode', 'showIcon'] +}) +@Component({ + selector: 'ion-input-password-toggle', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'hideIcon', 'mode', 'showIcon'], +}) +export class IonInputPasswordToggle { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + + +export declare interface IonInputPasswordToggle extends Components.IonInputPasswordToggle {} + + @ProxyCmp({ inputs: ['button', 'color', 'detail', 'detailIcon', 'disabled', 'download', 'href', 'lines', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'target', 'type'] }) diff --git a/packages/angular/standalone/src/directives/proxies.ts b/packages/angular/standalone/src/directives/proxies.ts index a677f50007c..c319aa55271 100644 --- a/packages/angular/standalone/src/directives/proxies.ts +++ b/packages/angular/standalone/src/directives/proxies.ts @@ -36,6 +36,7 @@ import { defineCustomElement as defineIonHeader } from '@ionic/core/components/i import { defineCustomElement as defineIonImg } from '@ionic/core/components/ion-img.js'; import { defineCustomElement as defineIonInfiniteScroll } from '@ionic/core/components/ion-infinite-scroll.js'; import { defineCustomElement as defineIonInfiniteScrollContent } from '@ionic/core/components/ion-infinite-scroll-content.js'; +import { defineCustomElement as defineIonInputPasswordToggle } from '@ionic/core/components/ion-input-password-toggle.js'; import { defineCustomElement as defineIonItem } from '@ionic/core/components/ion-item.js'; import { defineCustomElement as defineIonItemDivider } from '@ionic/core/components/ion-item-divider.js'; import { defineCustomElement as defineIonItemGroup } from '@ionic/core/components/ion-item-group.js'; @@ -976,6 +977,30 @@ export class IonInfiniteScrollContent { export declare interface IonInfiniteScrollContent extends Components.IonInfiniteScrollContent {} +@ProxyCmp({ + defineCustomElementFn: defineIonInputPasswordToggle, + inputs: ['color', 'hideIcon', 'mode', 'showIcon'] +}) +@Component({ + selector: 'ion-input-password-toggle', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['color', 'hideIcon', 'mode', 'showIcon'], + standalone: true +}) +export class IonInputPasswordToggle { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + + +export declare interface IonInputPasswordToggle extends Components.IonInputPasswordToggle {} + + @ProxyCmp({ defineCustomElementFn: defineIonItem, inputs: ['button', 'color', 'detail', 'detailIcon', 'disabled', 'download', 'href', 'lines', 'mode', 'rel', 'routerAnimation', 'routerDirection', 'target', 'type'] diff --git a/packages/react/src/components/proxies.ts b/packages/react/src/components/proxies.ts index 54e5d4f605a..05800f38773 100644 --- a/packages/react/src/components/proxies.ts +++ b/packages/react/src/components/proxies.ts @@ -31,6 +31,7 @@ import { defineCustomElement as defineIonImg } from '@ionic/core/components/ion- import { defineCustomElement as defineIonInfiniteScroll } from '@ionic/core/components/ion-infinite-scroll.js'; import { defineCustomElement as defineIonInfiniteScrollContent } from '@ionic/core/components/ion-infinite-scroll-content.js'; import { defineCustomElement as defineIonInput } from '@ionic/core/components/ion-input.js'; +import { defineCustomElement as defineIonInputPasswordToggle } from '@ionic/core/components/ion-input-password-toggle.js'; import { defineCustomElement as defineIonItemDivider } from '@ionic/core/components/ion-item-divider.js'; import { defineCustomElement as defineIonItemGroup } from '@ionic/core/components/ion-item-group.js'; import { defineCustomElement as defineIonItemOptions } from '@ionic/core/components/ion-item-options.js'; @@ -99,6 +100,7 @@ export const IonImg = /*@__PURE__*/createReactComponent('ion-infinite-scroll', undefined, undefined, defineIonInfiniteScroll); export const IonInfiniteScrollContent = /*@__PURE__*/createReactComponent('ion-infinite-scroll-content', undefined, undefined, defineIonInfiniteScrollContent); export const IonInput = /*@__PURE__*/createReactComponent('ion-input', undefined, undefined, defineIonInput); +export const IonInputPasswordToggle = /*@__PURE__*/createReactComponent('ion-input-password-toggle', undefined, undefined, defineIonInputPasswordToggle); export const IonItemDivider = /*@__PURE__*/createReactComponent('ion-item-divider', undefined, undefined, defineIonItemDivider); export const IonItemGroup = /*@__PURE__*/createReactComponent('ion-item-group', undefined, undefined, defineIonItemGroup); export const IonItemOptions = /*@__PURE__*/createReactComponent('ion-item-options', undefined, undefined, defineIonItemOptions); diff --git a/packages/vue/src/proxies.ts b/packages/vue/src/proxies.ts index eab6fe4a361..30d640d44f2 100644 --- a/packages/vue/src/proxies.ts +++ b/packages/vue/src/proxies.ts @@ -35,6 +35,7 @@ import { defineCustomElement as defineIonImg } from '@ionic/core/components/ion- import { defineCustomElement as defineIonInfiniteScroll } from '@ionic/core/components/ion-infinite-scroll.js'; import { defineCustomElement as defineIonInfiniteScrollContent } from '@ionic/core/components/ion-infinite-scroll-content.js'; import { defineCustomElement as defineIonInput } from '@ionic/core/components/ion-input.js'; +import { defineCustomElement as defineIonInputPasswordToggle } from '@ionic/core/components/ion-input-password-toggle.js'; import { defineCustomElement as defineIonItem } from '@ionic/core/components/ion-item.js'; import { defineCustomElement as defineIonItemDivider } from '@ionic/core/components/ion-item-divider.js'; import { defineCustomElement as defineIonItemGroup } from '@ionic/core/components/ion-item-group.js'; @@ -436,6 +437,14 @@ export const IonInput = /*@__PURE__*/ defineContainer('ion-input-password-toggle', defineIonInputPasswordToggle, [ + 'color', + 'showIcon', + 'hideIcon', + 'type' +]); + + export const IonItem = /*@__PURE__*/ defineContainer('ion-item', defineIonItem, [ 'color', 'button',