From 85d3bd99be3ae0f33a480e256381f7125f3389fd Mon Sep 17 00:00:00 2001 From: Amanda Johnston <90629384+amandaejohnston@users.noreply.github.com> Date: Mon, 10 Oct 2022 09:55:58 -0500 Subject: [PATCH] feat(toggle): ionChange will only emit from user committed changes (#26078) Co-authored-by: Sean Perkins --- BREAKING.md | 5 ++++ angular/src/directives/proxies.ts | 3 ++- angular/test/base/e2e/src/inputs.spec.ts | 2 +- core/src/components.d.ts | 2 +- .../toggle/test/basic/toggle.e2e.ts | 4 +-- core/src/components/toggle/toggle.tsx | 27 +++++++++++-------- 6 files changed, 27 insertions(+), 16 deletions(-) diff --git a/BREAKING.md b/BREAKING.md index cec61730f8..d833b25849 100644 --- a/BREAKING.md +++ b/BREAKING.md @@ -25,6 +25,7 @@ This is a comprehensive list of the breaking changes introduced in the major ver - [Select](#version-7x-select) - [Slides](#version-7x-slides) - [Textarea](#version-7x-textarea) + - [Toggle](#version-7x-toggle) - [Virtual Scroll](#version-7x-virtual-scroll) - [JavaScript Frameworks](#version-7x-javascript-frameworks) - [React](#version-7x-react) @@ -147,6 +148,10 @@ Developers using these components will need to migrate to using Swiper.js direct - `ionInput` dispatches an event detail of `null` when the textarea is cleared as a result of `clear-on-edit="true"`. +

Toggle

+ +- `ionChange` is no longer emitted when the `checked` property of `ion-toggle` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking the toggle to set it on or off. +

Virtual Scroll

`ion-virtual-scroll` has been removed from Ionic. diff --git a/angular/src/directives/proxies.ts b/angular/src/directives/proxies.ts index 9f483acba4..74d9c9ffce 100644 --- a/angular/src/directives/proxies.ts +++ b/angular/src/directives/proxies.ts @@ -1909,7 +1909,8 @@ export class IonTitle { import type { ToggleChangeEventDetail as IToggleToggleChangeEventDetail } from '@ionic/core'; export declare interface IonToggle extends Components.IonToggle { /** - * Emitted when the value property has changed. + * Emitted when the user switches the toggle on or off. Does not emit +when programmatically changing the value of the `checked` property. */ ionChange: EventEmitter>; /** diff --git a/angular/test/base/e2e/src/inputs.spec.ts b/angular/test/base/e2e/src/inputs.spec.ts index 740a4675dd..76d0a0d325 100644 --- a/angular/test/base/e2e/src/inputs.spec.ts +++ b/angular/test/base/e2e/src/inputs.spec.ts @@ -39,7 +39,7 @@ describe('Inputs', () => { cy.get('#reset-button').click(); cy.get('ion-checkbox#first-checkbox').click(); - cy.get('ion-toggle').invoke('prop', 'checked', true); + cy.get('ion-toggle').first().click(); cy.get('ion-input').eq(0).type('hola'); cy.get('ion-input input').eq(0).blur(); diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 4e6c01e8bb..44ce417087 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -6725,7 +6725,7 @@ declare namespace LocalJSX { */ "onIonBlur"?: (event: IonToggleCustomEvent) => void; /** - * Emitted when the value property has changed. + * Emitted when the user switches the toggle on or off. Does not emit when programmatically changing the value of the `checked` property. */ "onIonChange"?: (event: IonToggleCustomEvent) => void; /** diff --git a/core/src/components/toggle/test/basic/toggle.e2e.ts b/core/src/components/toggle/test/basic/toggle.e2e.ts index e2ab551a29..bc38e2e12c 100644 --- a/core/src/components/toggle/test/basic/toggle.e2e.ts +++ b/core/src/components/toggle/test/basic/toggle.e2e.ts @@ -52,14 +52,14 @@ test.describe('toggle: basic', () => { }); }); - test('should fire change event if checked prop is changed directly', async ({ page }) => { + test('should not fire change event if checked prop is changed directly', async ({ page }) => { const toggle = page.locator('#orange'); const ionChange = await page.spyOnEvent('ionChange'); await toggle.evaluate((el: HTMLIonToggleElement) => (el.checked = true)); await page.waitForChanges(); - expect(ionChange).toHaveReceivedEvent(); + expect(ionChange).toHaveReceivedEventTimes(0); }); test('should pass properties down to hidden input', async ({ page }) => { diff --git a/core/src/components/toggle/toggle.tsx b/core/src/components/toggle/toggle.tsx index d7e941d0a1..2db57420da 100644 --- a/core/src/components/toggle/toggle.tsx +++ b/core/src/components/toggle/toggle.tsx @@ -70,7 +70,8 @@ export class Toggle implements ComponentInterface { @Prop() enableOnOffLabels: boolean | undefined = undefined; /** - * Emitted when the value property has changed. + * Emitted when the user switches the toggle on or off. Does not emit + * when programmatically changing the value of the `checked` property. */ @Event() ionChange!: EventEmitter; @@ -90,14 +91,6 @@ export class Toggle implements ComponentInterface { */ @Event() ionStyle!: EventEmitter; - @Watch('checked') - checkedChanged(isChecked: boolean) { - this.ionChange.emit({ - checked: isChecked, - value: this.value, - }); - } - @Watch('disabled') disabledChanged() { this.emitStyle(); @@ -106,6 +99,18 @@ export class Toggle implements ComponentInterface { } } + private toggleChecked() { + const { checked, value } = this; + + const isNowChecked = !checked; + this.checked = isNowChecked; + + this.ionChange.emit({ + checked: isNowChecked, + value, + }); + } + async connectedCallback() { this.gesture = (await import('../../utils/gesture')).createGesture({ el: this.el, @@ -146,7 +151,7 @@ export class Toggle implements ComponentInterface { private onMove(detail: GestureDetail) { if (shouldToggle(isRTL(this.el), this.checked, detail.deltaX, -10)) { - this.checked = !this.checked; + this.toggleChecked(); hapticSelection(); } } @@ -172,7 +177,7 @@ export class Toggle implements ComponentInterface { ev.preventDefault(); if (this.lastDrag + 300 < Date.now()) { - this.checked = !this.checked; + this.toggleChecked(); } };