From 0c6060eae799d65892ea51945e9c91ffa0710fe3 Mon Sep 17 00:00:00 2001
From: Jeremiah Hoyet <jerhoyet@gmail.com>
Date: Mon, 22 Nov 2021 12:33:39 -0500
Subject: [PATCH] Add required attribute to radio group and radio

---
 docs/components/radio-group.md            | 44 +++++++++++++++++++++++
 src/components/radio-group/radio-group.ts | 23 ++++++++++++
 src/components/radio/radio.ts             |  4 +++
 3 files changed, 71 insertions(+)

diff --git a/docs/components/radio-group.md b/docs/components/radio-group.md
index 0f784721..e879eb85 100644
--- a/docs/components/radio-group.md
+++ b/docs/components/radio-group.md
@@ -49,4 +49,48 @@ const App = () => (
   </SlRadioGroup>
 );
 ```
+
+### Using the required attribute
+
+Adding a `required` attribute to `sl-radio-group` will require at least one option to be selected.
+
+```html preview
+<sl-radio-group id="radio-group1" label="Select an option" fieldset required>
+  <sl-radio value="1" name="foo">Option 1</sl-radio>
+  <sl-radio value="2" name="foo">Option 2</sl-radio>
+  <sl-radio value="3" name="foo">Option 3</sl-radio>
+</sl-radio-group>
+
+<br />
+
+<sl-button id="group1">Validate Group</sl-button>
+
+<script>
+  const button = document.getElementById('group1');
+  const group = document.getElementById('radio-group1');
+
+  button.addEventListener('click', ()=> group.reportValidity())
+</script>
+```
+
+Alternatively, if any of the `sl-radio` elements has a `required` attribute, `sl-radio-group` mimics the behaviour of the browser and will require at least one selection from the group.
+
+```html preview
+<sl-radio-group id="radio-group2" label="Select an option" fieldset>
+  <sl-radio value="1" name="foo" required>Option 1</sl-radio>
+  <sl-radio value="2" name="foo">Option 2</sl-radio>
+  <sl-radio value="3" name="foo">Option 3</sl-radio>
+</sl-radio-group>
+
+<br />
+
+<sl-button id="group2">Validate Group</sl-button>
+
+<script>
+  const button = document.getElementById('group2');
+  const group = document.getElementById('radio-group2');
+
+  button.addEventListener('click', ()=> group.reportValidity())
+</script>
+```
 [component-metadata:sl-radio-group]
diff --git a/src/components/radio-group/radio-group.ts b/src/components/radio-group/radio-group.ts
index f306ac82..a444471d 100644
--- a/src/components/radio-group/radio-group.ts
+++ b/src/components/radio-group/radio-group.ts
@@ -26,6 +26,9 @@ export default class SlRadioGroup extends LitElement {
   /** Shows the fieldset and legend that surrounds the radio group. */
   @property({ type: Boolean, attribute: 'fieldset' }) fieldset = false;
 
+  /** Indicates that a selection is required. */
+  @property({ type: Boolean, reflect: true }) required = false;
+
   handleFocusIn() {
     // When tabbing into the fieldset, make sure it lands on the checked radio
     requestAnimationFrame(() => {
@@ -39,6 +42,26 @@ export default class SlRadioGroup extends LitElement {
     });
   }
 
+  reportValidity() {
+    const radios = [...(this.defaultSlot.assignedElements({ flatten: true }) as SlRadio[])];
+    let isChecked = true;
+
+    // Set required to true if any of the radio elements are required
+    this.required = this.required || radios.some(el => el.required);
+
+    if (this.required && radios.length > 0) {
+      isChecked = radios.some(el => el.checked);
+
+      if (!isChecked) {
+        radios[0].required = true;
+        // Trigger validity message on first input
+        radios[0].reportValidity();
+      }
+    }
+
+    return isChecked;
+  }
+
   render() {
     return html`
       <fieldset
diff --git a/src/components/radio/radio.ts b/src/components/radio/radio.ts
index 79690c20..916d200d 100644
--- a/src/components/radio/radio.ts
+++ b/src/components/radio/radio.ts
@@ -47,6 +47,9 @@ export default class SlRadio extends LitElement {
   /** Draws the radio in a checked state. */
   @property({ type: Boolean, reflect: true }) checked = false;
 
+  /** Indicates that a selection is required. */
+  @property({ type: Boolean, reflect: true }) required = false;
+
   /**
    * This will be true when the control is in an invalid state. Validity in range inputs is determined by the message
    * provided by the `setCustomValidity` method.
@@ -161,6 +164,7 @@ export default class SlRadio extends LitElement {
           type="radio"
           name=${ifDefined(this.name)}
           value=${ifDefined(this.value)}
+          ?required=${this.required}
           .checked=${live(this.checked)}
           .disabled=${this.disabled}
           aria-checked=${this.checked ? 'true' : 'false'}