Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions core/src/components/radio-group/radio-group.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Build, Component, Element, Event, Host, Listen, Method, Prop, State, Watch, h } from '@stencil/core';
import { checkInvalidState } from '@utils/forms';
import { checkInvalidState, isOptionSelected } from '@utils/forms';
import { renderHiddenInput } from '@utils/helpers';

import { getIonMode } from '../../global/ionic-global';
Expand Down Expand Up @@ -104,7 +104,7 @@ export class RadioGroup implements ComponentInterface {

// Get the first radio that is not disabled and the checked one
const first = radios.find((radio) => !radio.disabled);
const checked = radios.find((radio) => radio.value === value && !radio.disabled);
const checked = radios.find((radio) => isOptionSelected(value, radio.value, this.compareWith) && !radio.disabled);

if (!first && !checked) {
return;
Expand Down Expand Up @@ -219,7 +219,8 @@ export class RadioGroup implements ComponentInterface {
if (selectedRadio && !selectedRadio.disabled) {
const currentValue = this.value;
const newValue = selectedRadio.value;
if (newValue !== currentValue) {
const isSelected = isOptionSelected(currentValue, newValue, this.compareWith);
if (!isSelected) {
this.value = newValue;
this.emitValueChange(ev);
} else if (this.allowEmptySelection) {
Expand Down Expand Up @@ -275,7 +276,8 @@ export class RadioGroup implements ComponentInterface {
// space bar on top of a selected radio
if ([' '].includes(ev.key)) {
const previousValue = this.value;
this.value = this.allowEmptySelection && this.value !== undefined ? undefined : current.value;
const isSelected = isOptionSelected(this.value, current.value, this.compareWith);
this.value = this.allowEmptySelection && isSelected ? undefined : current.value;
if (previousValue !== this.value || this.allowEmptySelection) {
/**
* Value change should only be emitted if the value is different,
Expand Down
39 changes: 39 additions & 0 deletions core/src/components/radio-group/test/radio-group.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,43 @@ describe('ion-radio-group', () => {
expect(radios[2].getAttribute('aria-checked')).toBe('true');
expect(radioGroup.value).toEqual(3);
});

it('should allow deselection with compareWith when allowEmptySelection is true', async () => {
const page = await newSpecPage({
components: [Radio, RadioGroup],
template: () => (
<ion-radio-group value={{ id: 1 }} compareWith="id" allowEmptySelection={true}>
<ion-radio value={{ id: 1 }}>Option #1</ion-radio>
<ion-radio value={{ id: 2 }}>Option #2</ion-radio>
</ion-radio-group>
),
});

const radioGroup = page.body.querySelector('ion-radio-group')!;
const radios = document.querySelectorAll('ion-radio')!;

expect(radios[0].getAttribute('aria-checked')).toBe('true');

await radios[0].click();
await page.waitForChanges();

expect(radioGroup.value).toBeUndefined();
expect(radios[0].getAttribute('aria-checked')).toBe('false');
});

it('should handle null values safely when compareWith is a property name', async () => {
const page = await newSpecPage({
components: [Radio, RadioGroup],
template: () => (
<ion-radio-group value={null} compareWith="id">
<ion-radio value={{ id: 1 }}>Option #1</ion-radio>
<ion-radio value={{ id: 2 }}>Option #2</ion-radio>
</ion-radio-group>
),
});

const radios = document.querySelectorAll('ion-radio')!;
expect(radios[0].getAttribute('aria-checked')).toBe('false');
expect(radios[1].getAttribute('aria-checked')).toBe('false');
});
});
4 changes: 2 additions & 2 deletions core/src/utils/forms/compare-with-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const compareOptions = (
if (typeof compareWith === 'function') {
return compareWith(currentValue, compareValue);
} else if (typeof compareWith === 'string') {
return currentValue[compareWith] === compareValue[compareWith];
return currentValue?.[compareWith] !== undefined && currentValue?.[compareWith] === compareValue?.[compareWith];
} else {
return Array.isArray(compareValue) ? compareValue.includes(currentValue) : currentValue === compareValue;
}
Expand All @@ -33,7 +33,7 @@ export const isOptionSelected = (
compareValue: any,
compareWith?: string | CompareFn | null
) => {
if (currentValue === undefined) {
if (currentValue === undefined || currentValue === null) {
return false;
}
if (Array.isArray(currentValue)) {
Expand Down
Loading