diff --git a/CHANGELOG.md b/CHANGELOG.md
index 93c220b2b..fcd062a12 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Dark Themes
- Slider component
- Range Slider component
+- Support `required` property in Radio component.
### Changed
diff --git a/src/components/radio-group/radio-group.spec.ts b/src/components/radio-group/radio-group.spec.ts
index f0d4142d1..8baebde4f 100644
--- a/src/components/radio-group/radio-group.spec.ts
+++ b/src/components/radio-group/radio-group.spec.ts
@@ -112,6 +112,17 @@ describe('Radio Group Component', () => {
expect(radio3).to.be.calledWith('igcFocus');
expect(radio3).to.be.calledWith('igcChange');
});
+
+ it('should set required attribute correctly', async () => {
+ expect(Array.from(radios).every((r) => r.required)).to.be.false;
+ expect(radios[0].required).to.be.true;
+
+ radios[1].checked = true;
+ await elementUpdated(group);
+
+ expect(radios[0].required).to.be.false;
+ expect(radios[1].required).to.be.true;
+ });
});
const values = ['orange', 'apple', 'mango'];
@@ -119,7 +130,9 @@ describe('Radio Group Component', () => {
template = html`
${values.map(
(value) =>
- html`${value}`
+ html`${value}`
)}
`
) => {
diff --git a/src/components/radio-group/radio-group.ts b/src/components/radio-group/radio-group.ts
index 311c429a2..cd97b3c2f 100644
--- a/src/components/radio-group/radio-group.ts
+++ b/src/components/radio-group/radio-group.ts
@@ -8,12 +8,11 @@ export default class IgcRadioGroupComponent extends LitElement {
public static override styles = styles;
- @queryAssignedElements({ flatten: true, selector: 'igc-radio' })
- private _slottedRadios!: Array;
-
- private get radios() {
- return this._slottedRadios.filter((radio) => !radio.disabled);
- }
+ @queryAssignedElements({
+ flatten: true,
+ selector: 'igc-radio:not([disabled])',
+ })
+ private radios!: Array;
private get isLTR(): boolean {
const styles = window.getComputedStyle(this);
@@ -23,11 +22,28 @@ export default class IgcRadioGroupComponent extends LitElement {
constructor() {
super();
this.addEventListener('keydown', this.handleKeydown);
+ this.addEventListener('igcChange', this.updateRequiredState);
}
@property({ reflect: true })
public alignment: 'vertical' | 'horizontal' = 'vertical';
+ private updateRequiredState() {
+ const hasRequired = this.radios.some((r) => r.required);
+
+ if (hasRequired) {
+ this.radios.forEach((r) => (r.required = false));
+
+ const hasChecked = this.radios.some((r) => r.checked);
+
+ if (hasChecked) {
+ this.radios.filter((r) => r.checked)[0].required = true;
+ } else {
+ this.radios[0].required = true;
+ }
+ }
+ }
+
private handleKeydown = (event: KeyboardEvent) => {
const { key } = event;
@@ -61,7 +77,7 @@ export default class IgcRadioGroupComponent extends LitElement {
};
protected override render() {
- return html``;
+ return html``;
}
}
diff --git a/src/components/radio/radio.spec.ts b/src/components/radio/radio.spec.ts
index 33c76117b..01b21ded9 100644
--- a/src/components/radio/radio.spec.ts
+++ b/src/components/radio/radio.spec.ts
@@ -46,6 +46,8 @@ describe('Radio Component', () => {
expect(radio.invalid).to.equal(false);
expect(radio.disabled).to.equal(false);
expect(input.disabled).to.equal(false);
+ expect(radio.required).to.equal(false);
+ expect(input.required).to.equal(false);
expect(radio.labelPosition).to.equal('after');
});
@@ -158,6 +160,19 @@ describe('Radio Component', () => {
);
});
+ it('sets the required property successfully', async () => {
+ radio.required = true;
+ expect(radio.required).to.be.true;
+ await elementUpdated(radio);
+ expect(input).dom.to.equal(``, DIFF_OPTIONS);
+
+ radio.required = false;
+ expect(radio.required).to.be.false;
+ await elementUpdated(radio);
+
+ expect(input).dom.to.equal(``, DIFF_OPTIONS);
+ });
+
it('should emit focus/blur events when methods are called', () => {
const eventSpy = sinon.spy(radio, 'emitEvent');
radio.focus();
diff --git a/src/components/radio/radio.ts b/src/components/radio/radio.ts
index 48ebe4746..9399a24a5 100644
--- a/src/components/radio/radio.ts
+++ b/src/components/radio/radio.ts
@@ -54,6 +54,10 @@ export default class IgcRadioComponent extends EventEmitterMixin<
@property()
public value!: string;
+ /** Makes the control a required field. */
+ @property({ type: Boolean, reflect: true })
+ public required = false;
+
/** The checked state of the control. */
@property({ type: Boolean })
@blazorTwoWayBind('igcChange', 'detail')
@@ -133,16 +137,25 @@ export default class IgcRadioComponent extends EventEmitterMixin<
this.input.focus();
this._tabIndex = 0;
this.emitEvent('igcChange', { detail: this.checked });
+ } else {
+ if (this.required) {
+ this.required = false;
+ this.getAllInGroup()[0].required = true;
+ }
}
}
protected getSiblings() {
+ return this.getAllInGroup().filter(
+ (radio) => radio.name === this.name && radio !== this
+ );
+ }
+
+ protected getAllInGroup() {
const group = this.closest('igc-radio-group');
if (!group) return [];
- return Array.from(
- group.querySelectorAll('igc-radio')
- ).filter((radio) => radio.name === this.name && radio !== this);
+ return Array.from(group.querySelectorAll('igc-radio'));
}
protected override render() {
@@ -157,6 +170,7 @@ export default class IgcRadioComponent extends EventEmitterMixin<
type="radio"
name="${ifDefined(this.name)}"
value="${ifDefined(this.value)}"
+ .required="${this.required}"
.disabled="${this.disabled}"
.checked="${live(this.checked)}"
tabindex=${this._tabIndex}
diff --git a/stories/form.stories.ts b/stories/form.stories.ts
index 35523f5e0..e20076b63 100644
--- a/stories/form.stories.ts
+++ b/stories/form.stories.ts
@@ -43,7 +43,10 @@ The cat was playing
in the garden.Gender:
${radios.map(
- (v) => html`${v} `
+ (v) =>
+ html`${v}`
)}
diff --git a/stories/radio.stories.ts b/stories/radio.stories.ts
index 6a80b09b9..60dd81876 100644
--- a/stories/radio.stories.ts
+++ b/stories/radio.stories.ts
@@ -17,6 +17,12 @@ const metadata = {
description: 'The value attribute of the control.',
control: 'text',
},
+ required: {
+ type: 'boolean',
+ description: 'Makes the control a required field.',
+ control: 'boolean',
+ defaultValue: false,
+ },
checked: {
type: 'boolean',
description: 'The checked state of the control.',
@@ -55,6 +61,7 @@ export default metadata;
interface ArgTypes {
name: string;
value: string;
+ required: boolean;
checked: boolean;
disabled: boolean;
invalid: boolean;
@@ -64,13 +71,14 @@ interface ArgTypes {
// endregion
const Template: Story = (
- { labelPosition, checked, disabled }: ArgTypes,
+ { labelPosition, checked, disabled, required }: ArgTypes,
{ globals: { direction } }: Context
) => html`
Label