From c70432e6934bcf1d570e1f7cf671c52d2bb52a8b Mon Sep 17 00:00:00 2001 From: Liam DeBeasi Date: Mon, 9 Oct 2023 17:05:09 -0400 Subject: [PATCH] fix(checkbox, radio, toggle): disabled elements are not interactive (#28294) Issue number: resolves #28293 --------- ## What is the current behavior? Disabled toggles, radios, and checkboxes can still be enabled by manually dispatching a click event on them. ## What is the new behavior? - Toggles, radios, and checkboxes no longer activate if `disabled` is set to `true` ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information Dev build: `7.4.4-dev.11696545130.1171e7a9` --- core/src/components/checkbox/checkbox.tsx | 4 ++++ .../components/checkbox/test/checkbox.spec.ts | 24 +++++++++++++++++++ .../components/radio-group/radio-group.tsx | 2 +- core/src/components/radio/radio.tsx | 6 ++++- core/src/components/radio/test/radio.spec.ts | 24 +++++++++++++++++++ .../src/components/toggle/test/toggle.spec.ts | 21 ++++++++++++++++ core/src/components/toggle/toggle.tsx | 4 ++++ 7 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 core/src/components/checkbox/test/checkbox.spec.ts diff --git a/core/src/components/checkbox/checkbox.tsx b/core/src/components/checkbox/checkbox.tsx index a90ed53c45..36acb354ed 100644 --- a/core/src/components/checkbox/checkbox.tsx +++ b/core/src/components/checkbox/checkbox.tsx @@ -211,6 +211,10 @@ export class Checkbox implements ComponentInterface { }; private onClick = (ev: MouseEvent) => { + if (this.disabled) { + return; + } + this.toggleChecked(ev); }; diff --git a/core/src/components/checkbox/test/checkbox.spec.ts b/core/src/components/checkbox/test/checkbox.spec.ts new file mode 100644 index 0000000000..5dab5e284f --- /dev/null +++ b/core/src/components/checkbox/test/checkbox.spec.ts @@ -0,0 +1,24 @@ +import { newSpecPage } from '@stencil/core/testing'; + +import { Checkbox } from '../checkbox'; + +describe('ion-checkbox: disabled', () => { + it('clicking disabled checkbox should not toggle checked state', async () => { + const page = await newSpecPage({ + components: [Checkbox], + html: ` + Checkbox + `, + }); + + const checkbox = page.body.querySelector('ion-checkbox'); + + expect(checkbox.checked).toBe(false); + + checkbox.click(); + + await page.waitForChanges(); + + expect(checkbox.checked).toBe(false); + }); +}); diff --git a/core/src/components/radio-group/radio-group.tsx b/core/src/components/radio-group/radio-group.tsx index b4fabb9d4c..9a61befae3 100644 --- a/core/src/components/radio-group/radio-group.tsx +++ b/core/src/components/radio-group/radio-group.tsx @@ -113,7 +113,7 @@ export class RadioGroup implements ComponentInterface { * using the `name` attribute. */ const selectedRadio = ev.target && (ev.target as HTMLElement).closest('ion-radio'); - if (selectedRadio) { + if (selectedRadio && selectedRadio.disabled === false) { const currentValue = this.value; const newValue = selectedRadio.value; if (newValue !== currentValue) { diff --git a/core/src/components/radio/radio.tsx b/core/src/components/radio/radio.tsx index b43f2c2c55..e22a47b34f 100644 --- a/core/src/components/radio/radio.tsx +++ b/core/src/components/radio/radio.tsx @@ -200,7 +200,11 @@ export class Radio implements ComponentInterface { }; private onClick = () => { - const { radioGroup, checked } = this; + const { radioGroup, checked, disabled } = this; + + if (disabled) { + return; + } /** * The legacy control uses a native input inside diff --git a/core/src/components/radio/test/radio.spec.ts b/core/src/components/radio/test/radio.spec.ts index 88b4547109..113a01dc5f 100644 --- a/core/src/components/radio/test/radio.spec.ts +++ b/core/src/components/radio/test/radio.spec.ts @@ -31,3 +31,27 @@ describe('ion-radio', () => { expect(radio.classList.contains('radio-checked')).toBe(true); }); }); + +describe('ion-radio: disabled', () => { + it('clicking disabled radio should not set checked state', async () => { + const page = await newSpecPage({ + components: [Radio, RadioGroup], + html: ` + + Radio + + `, + }); + + const radio = page.body.querySelector('ion-radio'); + const radioGroup = page.body.querySelector('ion-radio-group'); + + expect(radioGroup.value).toBe(undefined); + + radio.click(); + + await page.waitForChanges(); + + expect(radioGroup.value).toBe(undefined); + }); +}); diff --git a/core/src/components/toggle/test/toggle.spec.ts b/core/src/components/toggle/test/toggle.spec.ts index 6e6eedd619..d09d0c208d 100644 --- a/core/src/components/toggle/test/toggle.spec.ts +++ b/core/src/components/toggle/test/toggle.spec.ts @@ -41,3 +41,24 @@ describe('toggle', () => { }); }); }); + +describe('ion-toggle: disabled', () => { + it('clicking disabled toggle should not toggle checked state', async () => { + const page = await newSpecPage({ + components: [Toggle], + html: ` + Toggle + `, + }); + + const toggle = page.body.querySelector('ion-toggle'); + + expect(toggle.checked).toBe(false); + + toggle.click(); + + await page.waitForChanges(); + + expect(toggle.checked).toBe(false); + }); +}); diff --git a/core/src/components/toggle/toggle.tsx b/core/src/components/toggle/toggle.tsx index 8ccfd9975f..62ba1ec618 100644 --- a/core/src/components/toggle/toggle.tsx +++ b/core/src/components/toggle/toggle.tsx @@ -259,6 +259,10 @@ export class Toggle implements ComponentInterface { } private onClick = (ev: MouseEvent) => { + if (this.disabled) { + return; + } + ev.preventDefault(); if (this.lastDrag + 300 < Date.now()) {