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.
+
`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();
}
};