mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 03:00:58 +08:00
feat(checkbox): ionChange fires on user interaction (#25923)
BREAKING CHANGE: `ionChange` is no longer emitted when the `checked` property of `ion-checkbox` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking or tapping the checkbox.
This commit is contained in:
@ -15,6 +15,7 @@ This is a comprehensive list of the breaking changes introduced in the major ver
|
|||||||
- [Browser and Platform Support](#version-7x-browser-platform-support)
|
- [Browser and Platform Support](#version-7x-browser-platform-support)
|
||||||
- [Components](#version-7x-components)
|
- [Components](#version-7x-components)
|
||||||
- [Accordion Group](#version-7x-accordion-group)
|
- [Accordion Group](#version-7x-accordion-group)
|
||||||
|
- [Checkbox](#version-7x-checkbox)
|
||||||
- [Input](#version-7x-input)
|
- [Input](#version-7x-input)
|
||||||
- [Overlays](#version-7x-overlays)
|
- [Overlays](#version-7x-overlays)
|
||||||
- [Range](#version-7x-range)
|
- [Range](#version-7x-range)
|
||||||
@ -56,6 +57,10 @@ This section details the desktop browser, JavaScript framework, and mobile platf
|
|||||||
|
|
||||||
`ionChange` is no longer emitted when the `value` of `ion-accordion-group` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking or tapping the accordion header.
|
`ionChange` is no longer emitted when the `value` of `ion-accordion-group` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking or tapping the accordion header.
|
||||||
|
|
||||||
|
<h4 id="version-7x-checkbox">Checkbox</h4>
|
||||||
|
|
||||||
|
`ionChange` is no longer emitted when the `checked` property of `ion-checkbox` is modified externally. `ionChange` is only emitted from user committed changes, such as clicking or tapping the checkbox.
|
||||||
|
|
||||||
<h4 id="version-7x-input">Input</h4>
|
<h4 id="version-7x-input">Input</h4>
|
||||||
|
|
||||||
`ionChange` is no longer emitted when the `value` of `ion-input` is modified externally. `ionChange` is only emitted from user committed changes, such as typing in the input and the input losing focus or from clicking the clear action within the input.
|
`ionChange` is no longer emitted when the `value` of `ion-input` is modified externally. `ionChange` is only emitted from user committed changes, such as typing in the input and the input losing focus or from clicking the clear action within the input.
|
||||||
|
@ -388,7 +388,10 @@ export class IonCardTitle {
|
|||||||
import type { CheckboxChangeEventDetail as ICheckboxCheckboxChangeEventDetail } from '@ionic/core';
|
import type { CheckboxChangeEventDetail as ICheckboxCheckboxChangeEventDetail } from '@ionic/core';
|
||||||
export declare interface IonCheckbox extends Components.IonCheckbox {
|
export declare interface IonCheckbox extends Components.IonCheckbox {
|
||||||
/**
|
/**
|
||||||
* Emitted when the checked property has changed.
|
* Emitted when the checked property has changed
|
||||||
|
as a result of a user action such as a click.
|
||||||
|
This event will not emit when programmatically
|
||||||
|
setting the checked property.
|
||||||
*/
|
*/
|
||||||
ionChange: EventEmitter<CustomEvent<ICheckboxCheckboxChangeEventDetail>>;
|
ionChange: EventEmitter<CustomEvent<ICheckboxCheckboxChangeEventDetail>>;
|
||||||
/**
|
/**
|
||||||
|
@ -38,7 +38,7 @@ describe('Inputs', () => {
|
|||||||
it('change values should update angular', () => {
|
it('change values should update angular', () => {
|
||||||
cy.get('#reset-button').click();
|
cy.get('#reset-button').click();
|
||||||
|
|
||||||
cy.get('ion-checkbox').invoke('prop', 'checked', true);
|
cy.get('ion-checkbox#first-checkbox').click();
|
||||||
cy.get('ion-toggle').invoke('prop', 'checked', true);
|
cy.get('ion-toggle').invoke('prop', 'checked', true);
|
||||||
|
|
||||||
cy.get('ion-input').eq(0).type('hola');
|
cy.get('ion-input').eq(0).type('hola');
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Checkbox</ion-label>
|
<ion-label>Checkbox</ion-label>
|
||||||
<ion-checkbox [(ngModel)]="checkbox" slot="start"></ion-checkbox>
|
<ion-checkbox [(ngModel)]="checkbox" slot="start" id="first-checkbox"></ion-checkbox>
|
||||||
<ion-note slot="end" id="checkbox-note">{{checkbox}}</ion-note>
|
<ion-note slot="end" id="checkbox-note">{{checkbox}}</ion-note>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
|
2
core/src/components.d.ts
vendored
2
core/src/components.d.ts
vendored
@ -4314,7 +4314,7 @@ declare namespace LocalJSX {
|
|||||||
*/
|
*/
|
||||||
"onIonBlur"?: (event: IonCheckboxCustomEvent<void>) => void;
|
"onIonBlur"?: (event: IonCheckboxCustomEvent<void>) => void;
|
||||||
/**
|
/**
|
||||||
* Emitted when the checked property has changed.
|
* Emitted when the checked property has changed as a result of a user action such as a click. This event will not emit when programmatically setting the checked property.
|
||||||
*/
|
*/
|
||||||
"onIonChange"?: (event: IonCheckboxCustomEvent<CheckboxChangeEventDetail>) => void;
|
"onIonChange"?: (event: IonCheckboxCustomEvent<CheckboxChangeEventDetail>) => void;
|
||||||
/**
|
/**
|
||||||
|
@ -63,7 +63,10 @@ export class Checkbox implements ComponentInterface {
|
|||||||
@Prop() value: any | null = 'on';
|
@Prop() value: any | null = 'on';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when the checked property has changed.
|
* Emitted when the checked property has changed
|
||||||
|
* as a result of a user action such as a click.
|
||||||
|
* This event will not emit when programmatically
|
||||||
|
* setting the checked property.
|
||||||
*/
|
*/
|
||||||
@Event() ionChange!: EventEmitter<CheckboxChangeEventDetail>;
|
@Event() ionChange!: EventEmitter<CheckboxChangeEventDetail>;
|
||||||
|
|
||||||
@ -88,11 +91,7 @@ export class Checkbox implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Watch('checked')
|
@Watch('checked')
|
||||||
checkedChanged(isChecked: boolean) {
|
checkedChanged() {
|
||||||
this.ionChange.emit({
|
|
||||||
checked: isChecked,
|
|
||||||
value: this.value,
|
|
||||||
});
|
|
||||||
this.emitStyle();
|
this.emitStyle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,11 +113,25 @@ export class Checkbox implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onClick = (ev: any) => {
|
/**
|
||||||
|
* Sets the checked property and emits
|
||||||
|
* the ionChange event. Use this to update the
|
||||||
|
* checked state in response to user-generated
|
||||||
|
* actions such as a click.
|
||||||
|
*/
|
||||||
|
private setChecked = (state: boolean) => {
|
||||||
|
const isChecked = (this.checked = state);
|
||||||
|
this.ionChange.emit({
|
||||||
|
checked: isChecked,
|
||||||
|
value: this.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private toggleChecked = (ev: any) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
this.setFocus();
|
this.setFocus();
|
||||||
this.checked = !this.checked;
|
this.setChecked(!this.checked);
|
||||||
this.indeterminate = false;
|
this.indeterminate = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -153,7 +166,6 @@ export class Checkbox implements ComponentInterface {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Host
|
<Host
|
||||||
onClick={this.onClick}
|
|
||||||
aria-labelledby={label ? labelId : null}
|
aria-labelledby={label ? labelId : null}
|
||||||
aria-checked={`${checked}`}
|
aria-checked={`${checked}`}
|
||||||
aria-hidden={disabled ? 'true' : null}
|
aria-hidden={disabled ? 'true' : null}
|
||||||
@ -176,6 +188,7 @@ export class Checkbox implements ComponentInterface {
|
|||||||
aria-checked={`${checked}`}
|
aria-checked={`${checked}`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
id={inputId}
|
id={inputId}
|
||||||
|
onChange={this.toggleChecked}
|
||||||
onFocus={() => this.onFocus()}
|
onFocus={() => this.onFocus()}
|
||||||
onBlur={() => this.onBlur()}
|
onBlur={() => this.onBlur()}
|
||||||
ref={(focusEl) => (this.focusEl = focusEl)}
|
ref={(focusEl) => (this.focusEl = focusEl)}
|
||||||
|
@ -10,3 +10,52 @@ test.describe('checkbox: basic', () => {
|
|||||||
expect(await page.screenshot()).toMatchSnapshot(`checkbox-basic-${page.getSnapshotSettings()}.png`);
|
expect(await page.screenshot()).toMatchSnapshot(`checkbox-basic-${page.getSnapshotSettings()}.png`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe('checkbox: ionChange', () => {
|
||||||
|
test.beforeEach(({ skip }) => {
|
||||||
|
skip.rtl();
|
||||||
|
});
|
||||||
|
test('should fire ionChange when interacting with checkbox', async ({ page }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<ion-checkbox value="my-checkbox"></ion-checkbox>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const ionChange = await page.spyOnEvent('ionChange');
|
||||||
|
const checkbox = page.locator('ion-checkbox');
|
||||||
|
|
||||||
|
await checkbox.click();
|
||||||
|
await expect(ionChange).toHaveReceivedEventDetail({ value: 'my-checkbox', checked: true });
|
||||||
|
|
||||||
|
await checkbox.click();
|
||||||
|
await expect(ionChange).toHaveReceivedEventDetail({ value: 'my-checkbox', checked: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fire ionChange when interacting with checkbox in item', async ({ page }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<ion-item>
|
||||||
|
<ion-checkbox value="my-checkbox"></ion-checkbox>
|
||||||
|
</ion-item>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const ionChange = await page.spyOnEvent('ionChange');
|
||||||
|
const item = page.locator('ion-item');
|
||||||
|
|
||||||
|
await item.click();
|
||||||
|
await expect(ionChange).toHaveReceivedEventDetail({ value: 'my-checkbox', checked: true });
|
||||||
|
|
||||||
|
await item.click();
|
||||||
|
await expect(ionChange).toHaveReceivedEventDetail({ value: 'my-checkbox', checked: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not fire when programmatically setting a value', async ({ page }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<ion-checkbox value="my-checkbox"></ion-checkbox>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const ionChange = await page.spyOnEvent('ionChange');
|
||||||
|
const checkbox = page.locator('ion-checkbox');
|
||||||
|
|
||||||
|
await checkbox.evaluate((el: HTMLIonCheckboxElement) => (el.checked = true));
|
||||||
|
await expect(ionChange).not.toHaveReceivedEvent();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -6,10 +6,9 @@ test.describe('datetime: color', () => {
|
|||||||
await page.goto('/src/components/datetime/test/color');
|
await page.goto('/src/components/datetime/test/color');
|
||||||
|
|
||||||
const colorSelect = page.locator('ion-select');
|
const colorSelect = page.locator('ion-select');
|
||||||
const darkModeToggle = page.locator('ion-checkbox');
|
|
||||||
const datetime = page.locator('ion-datetime');
|
const datetime = page.locator('ion-datetime');
|
||||||
|
|
||||||
await darkModeToggle.evaluate((el: HTMLIonCheckboxElement) => (el.checked = true));
|
await page.evaluate(() => document.body.classList.toggle('dark'));
|
||||||
await page.waitForChanges();
|
await page.waitForChanges();
|
||||||
|
|
||||||
expect(await datetime.first().screenshot()).toMatchSnapshot(
|
expect(await datetime.first().screenshot()).toMatchSnapshot(
|
||||||
@ -19,7 +18,7 @@ test.describe('datetime: color', () => {
|
|||||||
`datetime-color-custom-dark-${page.getSnapshotSettings()}.png`
|
`datetime-color-custom-dark-${page.getSnapshotSettings()}.png`
|
||||||
);
|
);
|
||||||
|
|
||||||
await darkModeToggle.evaluate((el: HTMLIonCheckboxElement) => (el.checked = false));
|
await page.evaluate(() => document.body.classList.toggle('dark'));
|
||||||
await colorSelect.evaluate((el: HTMLIonSelectElement) => (el.value = 'danger'));
|
await colorSelect.evaluate((el: HTMLIonSelectElement) => (el.value = 'danger'));
|
||||||
await page.waitForChanges();
|
await page.waitForChanges();
|
||||||
|
|
||||||
@ -30,7 +29,7 @@ test.describe('datetime: color', () => {
|
|||||||
`datetime-color-custom-light-color-${page.getSnapshotSettings()}.png`
|
`datetime-color-custom-light-color-${page.getSnapshotSettings()}.png`
|
||||||
);
|
);
|
||||||
|
|
||||||
await darkModeToggle.evaluate((el: HTMLIonCheckboxElement) => (el.checked = true));
|
await page.evaluate(() => document.body.classList.toggle('dark'));
|
||||||
await page.waitForChanges();
|
await page.waitForChanges();
|
||||||
|
|
||||||
expect(await datetime.first().screenshot()).toMatchSnapshot(
|
expect(await datetime.first().screenshot()).toMatchSnapshot(
|
||||||
|
Reference in New Issue
Block a user