From 693f7afa5440c8ccb8ca59c9915a913d9fb0517f Mon Sep 17 00:00:00 2001
From: Arathy-s <ask6295@gmail.com>
Date: Thu, 21 Nov 2024 13:05:05 +0530
Subject: [PATCH 01/14] chore: added radio button component

---
 elements/package.json                   |  1 +
 elements/pf-radio/README.md             | 11 ++++++
 elements/pf-radio/demo/pf-radio.html    | 12 +++++++
 elements/pf-radio/docs/pf-radio.md      | 17 +++++++++
 elements/pf-radio/pf-radio.css          |  3 ++
 elements/pf-radio/pf-radio.ts           | 46 +++++++++++++++++++++++++
 elements/pf-radio/test/pf-radio.e2e.ts  | 25 ++++++++++++++
 elements/pf-radio/test/pf-radio.spec.ts | 21 +++++++++++
 8 files changed, 136 insertions(+)
 create mode 100644 elements/pf-radio/README.md
 create mode 100644 elements/pf-radio/demo/pf-radio.html
 create mode 100644 elements/pf-radio/docs/pf-radio.md
 create mode 100644 elements/pf-radio/pf-radio.css
 create mode 100644 elements/pf-radio/pf-radio.ts
 create mode 100644 elements/pf-radio/test/pf-radio.e2e.ts
 create mode 100644 elements/pf-radio/test/pf-radio.spec.ts

diff --git a/elements/package.json b/elements/package.json
index 4b159d1d9c..3d3b40a2fd 100644
--- a/elements/package.json
+++ b/elements/package.json
@@ -36,6 +36,7 @@
     "./pf-jump-links/pf-jump-links-list.js": "./pf-jump-links/pf-jump-links-list.js",
     "./pf-jump-links/pf-jump-links.js": "./pf-jump-links/pf-jump-links.js",
     "./pf-label/pf-label.js": "./pf-label/pf-label.js",
+    "./pf-radio/pf-radio.js": "./pf-radio/pf-radio.js",
     "./pf-select/pf-select.js": "./pf-select/pf-select.js",
     "./pf-select/pf-listbox.js": "./pf-select/pf-listbox.js",
     "./pf-select/pf-option-group.js": "./pf-select/pf-option-group.js",
diff --git a/elements/pf-radio/README.md b/elements/pf-radio/README.md
new file mode 100644
index 0000000000..42617ae474
--- /dev/null
+++ b/elements/pf-radio/README.md
@@ -0,0 +1,11 @@
+# Radio
+Add a description of the component here.
+
+## Usage
+Describe how best to use this web component along with best practices.
+
+```html
+<pf-radio>
+
+</pf-radio>
+```
diff --git a/elements/pf-radio/demo/pf-radio.html b/elements/pf-radio/demo/pf-radio.html
new file mode 100644
index 0000000000..6d16656326
--- /dev/null
+++ b/elements/pf-radio/demo/pf-radio.html
@@ -0,0 +1,12 @@
+<pf-radio></pf-radio>
+
+<script type="module">
+  import '@patternfly/elements/pf-radio/pf-radio.js';
+</script>
+
+<style>
+pf-radio {
+  /* insert demo styles */
+}
+</style>
+
diff --git a/elements/pf-radio/docs/pf-radio.md b/elements/pf-radio/docs/pf-radio.md
new file mode 100644
index 0000000000..c2bd3a1b21
--- /dev/null
+++ b/elements/pf-radio/docs/pf-radio.md
@@ -0,0 +1,17 @@
+{% renderOverview %}
+  <pf-radio></pf-radio>
+{% endrenderOverview %}
+
+{% band header="Usage" %}{% endband %}
+
+{% renderSlots %}{% endrenderSlots %}
+
+{% renderAttributes %}{% endrenderAttributes %}
+
+{% renderMethods %}{% endrenderMethods %}
+
+{% renderEvents %}{% endrenderEvents %}
+
+{% renderCssCustomProperties %}{% endrenderCssCustomProperties %}
+
+{% renderCssParts %}{% endrenderCssParts %}
diff --git a/elements/pf-radio/pf-radio.css b/elements/pf-radio/pf-radio.css
new file mode 100644
index 0000000000..5d4e87f30f
--- /dev/null
+++ b/elements/pf-radio/pf-radio.css
@@ -0,0 +1,3 @@
+:host {
+  display: block;
+}
diff --git a/elements/pf-radio/pf-radio.ts b/elements/pf-radio/pf-radio.ts
new file mode 100644
index 0000000000..2fee69a3b9
--- /dev/null
+++ b/elements/pf-radio/pf-radio.ts
@@ -0,0 +1,46 @@
+import { LitElement, html, type TemplateResult } from 'lit';
+import { customElement } from 'lit/decorators/custom-element.js';
+
+import styles from './pf-radio.css';
+import { property } from 'lit/decorators/property.js';
+
+/**
+ * Radio
+ * @slot - Place element content here
+ */
+@customElement('pf-radio')
+export class PfRadio extends LitElement {
+  static readonly styles: CSSStyleSheet[] = [styles];
+  @property() checked = false;
+  @property({ reflect: true }) name = 'radio-test';
+  @property({ reflect: true }) label?: string;
+  // #input:any
+
+  constructor() {
+    super();
+  }
+
+  connectedCallback(): void {
+    super.connectedCallback();
+
+    const root = this.getRootNode();
+    if (root instanceof Document || root instanceof ShadowRoot) {
+      const group = root.querySelectorAll(`pf-radio`);
+      // console.log("------------- the group is", group);
+    }
+  }
+
+
+  render(): TemplateResult<1> {
+    return html`
+      <label for=input>${this.label}</label>
+      <input id=input class="pf-radio" .name=${this.name} type="radio" .checked="${this.checked}">
+    `;
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'pf-radio': PfRadio;
+  }
+}
diff --git a/elements/pf-radio/test/pf-radio.e2e.ts b/elements/pf-radio/test/pf-radio.e2e.ts
new file mode 100644
index 0000000000..da6108b886
--- /dev/null
+++ b/elements/pf-radio/test/pf-radio.e2e.ts
@@ -0,0 +1,25 @@
+import { test } from '@playwright/test';
+import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js';
+import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js';
+
+const tagName = 'pf-radio';
+
+test.describe(tagName, () => {
+  test('snapshot', async ({ page }) => {
+    const componentPage = new PfeDemoPage(page, tagName);
+    await componentPage.navigate();
+    await componentPage.snapshot();
+  });
+
+  test('ssr', async ({ browser }) => {
+    const fixture = new SSRPage({
+      tagName,
+      browser,
+      demoDir: new URL('../demo/', import.meta.url),
+      importSpecifiers: [
+        `@patternfly/elements/${tagName}/${tagName}.js`,
+      ],
+    });
+    await fixture.snapshots();
+  });
+});
diff --git a/elements/pf-radio/test/pf-radio.spec.ts b/elements/pf-radio/test/pf-radio.spec.ts
new file mode 100644
index 0000000000..a5e7aa9b8b
--- /dev/null
+++ b/elements/pf-radio/test/pf-radio.spec.ts
@@ -0,0 +1,21 @@
+import { expect, html } from '@open-wc/testing';
+import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js';
+import { PfRadio } from '@patternfly/elements/pf-radio/pf-radio.js';
+
+describe('<pf-radio>', function() {
+  describe('simply instantiating', function() {
+    let element: PfRadio;
+    it('imperatively instantiates', function() {
+      expect(document.createElement('pf-radio')).to.be.an.instanceof(PfRadio);
+    });
+
+    it('should upgrade', async function() {
+      element = await createFixture<PfRadio>(html`<pf-radio></pf-radio>`);
+      const klass = customElements.get('pf-radio');
+      expect(element)
+          .to.be.an.instanceOf(klass)
+          .and
+          .to.be.an.instanceOf(PfRadio);
+    });
+  });
+});

From b5689778960277da4a2fccedd376b142cefee273 Mon Sep 17 00:00:00 2001
From: Arathy-s <ask6295@gmail.com>
Date: Fri, 22 Nov 2024 18:13:19 +0530
Subject: [PATCH 02/14] chore: radio button click functionality implemented

---
 elements/pf-radio/pf-radio.ts | 47 ++++++++++++++++++++++++++++-------
 1 file changed, 38 insertions(+), 9 deletions(-)

diff --git a/elements/pf-radio/pf-radio.ts b/elements/pf-radio/pf-radio.ts
index 2fee69a3b9..0290b3b8d7 100644
--- a/elements/pf-radio/pf-radio.ts
+++ b/elements/pf-radio/pf-radio.ts
@@ -11,10 +11,25 @@ import { property } from 'lit/decorators/property.js';
 @customElement('pf-radio')
 export class PfRadio extends LitElement {
   static readonly styles: CSSStyleSheet[] = [styles];
-  @property() checked = false;
+  static formAssociated = true;
+  static shadowRootOptions: ShadowRootInit = {
+    ...LitElement.shadowRootOptions,
+    delegatesFocus: true,
+  };
+
+  @property({
+    type: Boolean,
+    // attribute: 'inline-filter',
+    converter: {
+      fromAttribute: value => value === 'true',
+    },
+    reflect: true,
+  })
+  checked = false;
+
   @property({ reflect: true }) name = 'radio-test';
   @property({ reflect: true }) label?: string;
-  // #input:any
+  @property({ reflect: true }) value = '';
 
   constructor() {
     super();
@@ -22,19 +37,33 @@ export class PfRadio extends LitElement {
 
   connectedCallback(): void {
     super.connectedCallback();
+  }
 
-    const root = this.getRootNode();
-    if (root instanceof Document || root instanceof ShadowRoot) {
-      const group = root.querySelectorAll(`pf-radio`);
-      // console.log("------------- the group is", group);
+  #onRadioButtonClick(event: Event) {
+    if (!this.checked) {
+      const root: Node = this.getRootNode();
+      let radioGroup: NodeListOf<PfRadio>;
+      if (root instanceof Document || root instanceof ShadowRoot) {
+        radioGroup = root.querySelectorAll('pf-radio');
+        radioGroup.forEach(radio => {
+          const element: HTMLElement = radio as HTMLElement;
+          element?.removeAttribute('checked');
+        });
+        this.checked = true;
+      }
     }
   }
 
-
   render(): TemplateResult<1> {
     return html`
-      <label for=input>${this.label}</label>
-      <input id=input class="pf-radio" .name=${this.name} type="radio" .checked="${this.checked}">
+      <label for='input'>${this.label}</label>
+      <input
+        @click=${(e: Event) => this.#onRadioButtonClick(e)}
+        id='input'
+        .name=${this.name}
+        type='radio'
+        .checked='${this.checked}'
+      />
     `;
   }
 }

From dec283f8ae2f3d4d0ede8c5b1749fb5262c9d579 Mon Sep 17 00:00:00 2001
From: Arathy-s <ask6295@gmail.com>
Date: Sun, 24 Nov 2024 00:44:15 +0530
Subject: [PATCH 03/14] chore: keyboard event handling added

---
 elements/pf-radio/pf-radio.ts | 118 +++++++++++++++++++++++++++++++++-
 1 file changed, 115 insertions(+), 3 deletions(-)

diff --git a/elements/pf-radio/pf-radio.ts b/elements/pf-radio/pf-radio.ts
index 0290b3b8d7..9aefcac40e 100644
--- a/elements/pf-radio/pf-radio.ts
+++ b/elements/pf-radio/pf-radio.ts
@@ -30,6 +30,7 @@ export class PfRadio extends LitElement {
   @property({ reflect: true }) name = 'radio-test';
   @property({ reflect: true }) label?: string;
   @property({ reflect: true }) value = '';
+  @property({ reflect: true }) id = '';
 
   constructor() {
     super();
@@ -37,9 +38,11 @@ export class PfRadio extends LitElement {
 
   connectedCallback(): void {
     super.connectedCallback();
+    this.addEventListener('keydown', this.#onKeydown);
+    document.addEventListener('keydown', this.#onKeyPress);
   }
 
-  #onRadioButtonClick(event: Event) {
+  #onRadioButtonClick() {
     if (!this.checked) {
       const root: Node = this.getRootNode();
       let radioGroup: NodeListOf<PfRadio>;
@@ -54,12 +57,121 @@ export class PfRadio extends LitElement {
     }
   }
 
+  #onKeyPress = (event: KeyboardEvent) => {
+    const root: Node = this.getRootNode();
+    let radioGroup: NodeListOf<PfRadio>;
+    if (root instanceof Document || root instanceof ShadowRoot) {
+      radioGroup = root.querySelectorAll('pf-radio');
+      if (!event.shiftKey && event.key === 'Tab') {
+        radioGroup.forEach((radio, index) => {
+          const input = radio.shadowRoot?.querySelector('input') as HTMLInputElement;
+          // input.tabIndex = -1;
+          // if(radio.id === this.shadowRoot?.activeElement?.id){
+          //   //input.tabIndex = -1;
+          //   //event.preventDefault();
+          //   //root.focusOut()
+          // }else{
+          //   //input.tabIndex = 0;
+          // }
+          if (radio.checked === true) {
+            input.tabIndex = 0;
+          } else if (index === 0) {
+            input.tabIndex = 0;
+          } else {
+            input.tabIndex = -1;
+          }
+        });
+      }
+
+      if (event.shiftKey && event.key === 'Tab') {
+        radioGroup.forEach((radio, index) => {
+          const input = radio.shadowRoot?.querySelector('input') as HTMLInputElement;
+          // input.tabIndex = 0;
+          // input.tabIndex = 0;
+          // if(radio.id === this.shadowRoot?.activeElement?.id){
+          //   input.tabIndex = 0;
+          //   //event.preventDefault();
+          //   //root.focusOut()
+          // }else{
+          //   //input.tabIndex = 0;
+          // }
+          if (radio.checked === true) {
+            input.tabIndex = 0;
+            input.focus();
+          } else if (index === (radioGroup.length - 1)) {
+            input.tabIndex = 0;
+          } else {
+            input.tabIndex = -1;
+          }
+        });
+      }
+    }
+  };
+
+  #onKeydown = (event: KeyboardEvent) => {
+    if (event.key === 'ArrowDown'
+      || event.key === 'ArrowRight'
+      || event.key === 'ArrowUp'
+      || event.key === 'ArrowLeft') {
+      const root: Node = this.getRootNode();
+      let radioGroup: NodeListOf<PfRadio>;
+      if (root instanceof Document || root instanceof ShadowRoot) {
+        radioGroup = root.querySelectorAll('pf-radio');
+        radioGroup.forEach((radio, index) => {
+          const element: HTMLElement = radio as HTMLElement;
+          element?.removeAttribute('checked');
+          this.checked = false;
+          if (radioGroup[index] === event.target ) {
+            if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
+              if ((radioGroup.length - 1) === index) {
+                radioGroup[0].focus();
+                radioGroup[0].checked = true;
+              } else {
+                radioGroup[index + 1].focus();
+                radioGroup[index + 1].checked = true;
+              }
+            } else if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {
+              if (index === 0) {
+                radioGroup[radioGroup.length - 1].focus();
+                radioGroup[radioGroup.length - 1].checked = true;
+              } else {
+                radioGroup[index - 1].focus();
+                radioGroup[index - 1].checked = true;
+              }
+            }
+          }
+        });
+      }
+    }
+  };
+
+
+  // #onKeydown1 = (event: KeyboardEvent) => {
+  //   switch (event.key) {
+  //     case "ArrowDown":
+  //       //this.#onArrowDown(event);
+  //       // Do something for "down arrow" key press.
+  //       break;
+  //     case "ArrowUp":
+  //       // Do something for "up arrow" key press.
+  //       break;
+  //     case "ArrowLeft":
+  //       // Do something for "left arrow" key press.
+  //       break;
+  //     case "ArrowRight":
+  //       // Do something for "right arrow" key press.
+  //       break;
+  //       default:
+  //         return; // Quit when this doesn't handle the key event.
+  //   }
+  // };
+
   render(): TemplateResult<1> {
     return html`
       <label for='input'>${this.label}</label>
       <input
-        @click=${(e: Event) => this.#onRadioButtonClick(e)}
-        id='input'
+        @click=${this.#onRadioButtonClick}
+        id=${this.id}
         .name=${this.name}
         type='radio'
         .checked='${this.checked}'

From 457cb367ed8fa0fee8d82abdea6d39268b58503a Mon Sep 17 00:00:00 2001
From: Arathy-s <ask6295@gmail.com>
Date: Tue, 26 Nov 2024 14:58:13 +0530
Subject: [PATCH 04/14] chore: tab navigation implemented

---
 elements/pf-radio/pf-radio.ts | 90 ++++++++++++++++++-----------------
 1 file changed, 46 insertions(+), 44 deletions(-)

diff --git a/elements/pf-radio/pf-radio.ts b/elements/pf-radio/pf-radio.ts
index 9aefcac40e..9f287ba3aa 100644
--- a/elements/pf-radio/pf-radio.ts
+++ b/elements/pf-radio/pf-radio.ts
@@ -19,18 +19,28 @@ export class PfRadio extends LitElement {
 
   @property({
     type: Boolean,
-    // attribute: 'inline-filter',
+    attribute: 'checked',
     converter: {
       fromAttribute: value => value === 'true',
     },
     reflect: true,
-  })
-  checked = false;
+  }) checked = false;
+
+  @property({
+    type: Boolean,
+    attribute: 'disabled',
+    converter: {
+      fromAttribute: value => value === 'true',
+    },
+    reflect: true,
+  }) disabled = false;
+
+  @property({ attribute: 'name', reflect: true }) name = 'radio-test';
+  @property({ attribute: 'label', reflect: true }) label?: string;
+  @property({ attribute: 'value', reflect: true }) value = '';
+  @property({ attribute: 'id', reflect: true }) id = '';
+  @property({ attribute: 'tabindex', reflect: true }) tabIndex = -1;
 
-  @property({ reflect: true }) name = 'radio-test';
-  @property({ reflect: true }) label?: string;
-  @property({ reflect: true }) value = '';
-  @property({ reflect: true }) id = '';
 
   constructor() {
     super();
@@ -51,8 +61,10 @@ export class PfRadio extends LitElement {
         radioGroup.forEach(radio => {
           const element: HTMLElement = radio as HTMLElement;
           element?.removeAttribute('checked');
+          element.tabIndex = -1;
         });
         this.checked = true;
+        this.tabIndex = 0;
       }
     }
   }
@@ -60,50 +72,38 @@ export class PfRadio extends LitElement {
   #onKeyPress = (event: KeyboardEvent) => {
     const root: Node = this.getRootNode();
     let radioGroup: NodeListOf<PfRadio>;
+    let isRadioChecked = false;
     if (root instanceof Document || root instanceof ShadowRoot) {
       radioGroup = root.querySelectorAll('pf-radio');
-      if (!event.shiftKey && event.key === 'Tab') {
+      if (event.key === 'Tab') {
         radioGroup.forEach((radio, index) => {
-          const input = radio.shadowRoot?.querySelector('input') as HTMLInputElement;
-          // input.tabIndex = -1;
-          // if(radio.id === this.shadowRoot?.activeElement?.id){
-          //   //input.tabIndex = -1;
-          //   //event.preventDefault();
-          //   //root.focusOut()
-          // }else{
-          //   //input.tabIndex = 0;
-          // }
+          radio.tabIndex = -1;
           if (radio.checked === true) {
-            input.tabIndex = 0;
-          } else if (index === 0) {
-            input.tabIndex = 0;
-          } else {
-            input.tabIndex = -1;
+            radio.tabIndex = 0;
+            isRadioChecked = true;
           }
         });
-      }
-
-      if (event.shiftKey && event.key === 'Tab') {
-        radioGroup.forEach((radio, index) => {
-          const input = radio.shadowRoot?.querySelector('input') as HTMLInputElement;
-          // input.tabIndex = 0;
-          // input.tabIndex = 0;
-          // if(radio.id === this.shadowRoot?.activeElement?.id){
-          //   input.tabIndex = 0;
-          //   //event.preventDefault();
-          //   //root.focusOut()
-          // }else{
-          //   //input.tabIndex = 0;
-          // }
-          if (radio.checked === true) {
-            input.tabIndex = 0;
-            input.focus();
-          } else if (index === (radioGroup.length - 1)) {
-            input.tabIndex = 0;
-          } else {
-            input.tabIndex = -1;
+        if (!isRadioChecked) {
+          if (event.key === 'Tab') {
+            radioGroup.forEach((radio, index) => {
+              radio.tabIndex = -1;
+              if ( event.shiftKey ) {
+                if (index === (radioGroup.length - 1)) {
+                  radio.tabIndex = 0;
+                } else {
+                  radio.tabIndex = -1;
+                }
+              }
+              if (!event.shiftKey) {
+                if (index === 0) {
+                  radio.tabIndex = 0;
+                } else {
+                  radio.tabIndex = -1;
+                }
+              }
+            });
           }
-        });
+        }
       }
     }
   };
@@ -121,6 +121,7 @@ export class PfRadio extends LitElement {
           const element: HTMLElement = radio as HTMLElement;
           element?.removeAttribute('checked');
           this.checked = false;
+          radio.tabIndex = 0;
           if (radioGroup[index] === event.target ) {
             if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
               if ((radioGroup.length - 1) === index) {
@@ -174,6 +175,7 @@ export class PfRadio extends LitElement {
         id=${this.id}
         .name=${this.name}
         type='radio'
+        tabindex=${this.tabIndex}
         .checked='${this.checked}'
       />
     `;

From 76b553dc8dff871e8ef3a6b643e9d38457b7cfee Mon Sep 17 00:00:00 2001
From: Arathy-s <ask6295@gmail.com>
Date: Tue, 26 Nov 2024 14:59:37 +0530
Subject: [PATCH 05/14] chore: associated form label added

---
 elements/pf-radio/pf-radio.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/elements/pf-radio/pf-radio.ts b/elements/pf-radio/pf-radio.ts
index 9f287ba3aa..0d4463ec0e 100644
--- a/elements/pf-radio/pf-radio.ts
+++ b/elements/pf-radio/pf-radio.ts
@@ -169,7 +169,7 @@ export class PfRadio extends LitElement {
 
   render(): TemplateResult<1> {
     return html`
-      <label for='input'>${this.label}</label>
+      <label for=${this.id}>${this.label}</label>
       <input
         @click=${this.#onRadioButtonClick}
         id=${this.id}

From f0d269d93a0144213a03173ba5df9d924f8f7a58 Mon Sep 17 00:00:00 2001
From: Arathy-s <ask6295@gmail.com>
Date: Tue, 26 Nov 2024 20:57:56 +0530
Subject: [PATCH 06/14] chore: code cleanup

---
 elements/pf-radio/pf-radio.ts | 118 +++++++++++-----------------------
 1 file changed, 38 insertions(+), 80 deletions(-)

diff --git a/elements/pf-radio/pf-radio.ts b/elements/pf-radio/pf-radio.ts
index 0d4463ec0e..71beb68bed 100644
--- a/elements/pf-radio/pf-radio.ts
+++ b/elements/pf-radio/pf-radio.ts
@@ -24,7 +24,8 @@ export class PfRadio extends LitElement {
       fromAttribute: value => value === 'true',
     },
     reflect: true,
-  }) checked = false;
+  })
+  checked = false;
 
   @property({
     type: Boolean,
@@ -33,15 +34,15 @@ export class PfRadio extends LitElement {
       fromAttribute: value => value === 'true',
     },
     reflect: true,
-  }) disabled = false;
+  })
+  disabled = false;
 
-  @property({ attribute: 'name', reflect: true }) name = 'radio-test';
+  @property({ attribute: 'name', reflect: true }) name = '';
   @property({ attribute: 'label', reflect: true }) label?: string;
   @property({ attribute: 'value', reflect: true }) value = '';
   @property({ attribute: 'id', reflect: true }) id = '';
   @property({ attribute: 'tabindex', reflect: true }) tabIndex = -1;
 
-
   constructor() {
     super();
   }
@@ -58,7 +59,7 @@ export class PfRadio extends LitElement {
       let radioGroup: NodeListOf<PfRadio>;
       if (root instanceof Document || root instanceof ShadowRoot) {
         radioGroup = root.querySelectorAll('pf-radio');
-        radioGroup.forEach(radio => {
+        radioGroup.forEach((radio: PfRadio) => {
           const element: HTMLElement = radio as HTMLElement;
           element?.removeAttribute('checked');
           element.tabIndex = -1;
@@ -69,104 +70,61 @@ export class PfRadio extends LitElement {
     }
   }
 
+  // Function to handle tab key navigation
   #onKeyPress = (event: KeyboardEvent) => {
     const root: Node = this.getRootNode();
-    let radioGroup: NodeListOf<PfRadio>;
-    let isRadioChecked = false;
     if (root instanceof Document || root instanceof ShadowRoot) {
-      radioGroup = root.querySelectorAll('pf-radio');
+      const radioGroup: NodeListOf<PfRadio> = root.querySelectorAll('pf-radio');
+      const isRadioChecked: boolean = Array.from(radioGroup).some(
+        (radio: PfRadio) => radio.checked
+      );
       if (event.key === 'Tab') {
-        radioGroup.forEach((radio, index) => {
-          radio.tabIndex = -1;
-          if (radio.checked === true) {
-            radio.tabIndex = 0;
-            isRadioChecked = true;
-          }
+        radioGroup.forEach((radio: PfRadio) => {
+          radio.tabIndex = radio.checked ? 0 : -1;
         });
         if (!isRadioChecked) {
-          if (event.key === 'Tab') {
-            radioGroup.forEach((radio, index) => {
-              radio.tabIndex = -1;
-              if ( event.shiftKey ) {
-                if (index === (radioGroup.length - 1)) {
-                  radio.tabIndex = 0;
-                } else {
-                  radio.tabIndex = -1;
-                }
+          radioGroup.forEach((radio: PfRadio, index: number) => {
+            radio.tabIndex = -1;
+            if (event.shiftKey) {
+              if (index === radioGroup.length - 1) {
+                radio.tabIndex = 0;
               }
-              if (!event.shiftKey) {
-                if (index === 0) {
-                  radio.tabIndex = 0;
-                } else {
-                  radio.tabIndex = -1;
-                }
-              }
-            });
-          }
+            } else if (index === 0) {
+              radio.tabIndex = 0;
+            }
+          });
         }
       }
     }
   };
 
+  // Function to handle keyboard navigation
   #onKeydown = (event: KeyboardEvent) => {
-    if (event.key === 'ArrowDown'
-      || event.key === 'ArrowRight'
-      || event.key === 'ArrowUp'
-      || event.key === 'ArrowLeft') {
+    const arrowKeys: string[] = ['ArrowDown', 'ArrowRight', 'ArrowUp', 'ArrowLeft'];
+    if (arrowKeys.includes(event.key)) {
       const root: Node = this.getRootNode();
-      let radioGroup: NodeListOf<PfRadio>;
       if (root instanceof Document || root instanceof ShadowRoot) {
-        radioGroup = root.querySelectorAll('pf-radio');
-        radioGroup.forEach((radio, index) => {
-          const element: HTMLElement = radio as HTMLElement;
-          element?.removeAttribute('checked');
+        const radioGroup: NodeListOf<PfRadio> = root.querySelectorAll('pf-radio');
+        radioGroup.forEach((radio: PfRadio, index: number) => {
           this.checked = false;
-          radio.tabIndex = 0;
-          if (radioGroup[index] === event.target ) {
-            if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
-              if ((radioGroup.length - 1) === index) {
-                radioGroup[0].focus();
-                radioGroup[0].checked = true;
-              } else {
-                radioGroup[index + 1].focus();
-                radioGroup[index + 1].checked = true;
-              }
-            } else if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {
-              if (index === 0) {
-                radioGroup[radioGroup.length - 1].focus();
-                radioGroup[radioGroup.length - 1].checked = true;
-              } else {
-                radioGroup[index - 1].focus();
-                radioGroup[index - 1].checked = true;
-              }
+          this.tabIndex = 0;
+
+          if (radio === event.target) {
+            const isArrowDownOrRight: boolean = ['ArrowDown', 'ArrowRight'].includes(event.key);
+            const isArrowUpOrLeft: boolean = ['ArrowUp', 'ArrowLeft'].includes(event.key);
+            const direction: 1 | 0 | -1 = isArrowDownOrRight ? 1 : isArrowUpOrLeft ? -1 : 0;
+            if (direction === 0) {
+              return;
             }
+            const nextIndex: number = (index + direction + radioGroup.length) % radioGroup.length;
+            radioGroup[nextIndex].focus();
+            radioGroup[nextIndex].checked = true;
           }
         });
       }
     }
   };
 
-
-  // #onKeydown1 = (event: KeyboardEvent) => {
-  //   switch (event.key) {
-  //     case "ArrowDown":
-  //       //this.#onArrowDown(event);
-  //       // Do something for "down arrow" key press.
-  //       break;
-  //     case "ArrowUp":
-  //       // Do something for "up arrow" key press.
-  //       break;
-  //     case "ArrowLeft":
-  //       // Do something for "left arrow" key press.
-  //       break;
-  //     case "ArrowRight":
-  //       // Do something for "right arrow" key press.
-  //       break;
-  //       default:
-  //         return; // Quit when this doesn't handle the key event.
-  //   }
-  // };
-
   render(): TemplateResult<1> {
     return html`
       <label for=${this.id}>${this.label}</label>

From 23b7d0b5bf9c6dc0b06435f6b83af59004673dab Mon Sep 17 00:00:00 2001
From: Arathy-s <ask6295@gmail.com>
Date: Wed, 27 Nov 2024 12:27:35 +0530
Subject: [PATCH 07/14] chore: demo.html updated

---
 elements/pf-radio/demo/pf-radio.html | 30 +++++++++++++++++++++++++---
 elements/pf-radio/pf-radio.ts        |  5 ++---
 2 files changed, 29 insertions(+), 6 deletions(-)

diff --git a/elements/pf-radio/demo/pf-radio.html b/elements/pf-radio/demo/pf-radio.html
index 6d16656326..5bab10e169 100644
--- a/elements/pf-radio/demo/pf-radio.html
+++ b/elements/pf-radio/demo/pf-radio.html
@@ -1,12 +1,36 @@
-<pf-radio></pf-radio>
+<section class='container'>
+  <p> Select a title </p>
+  <div class='radio-group'>
+    <pf-radio id="title-mr" label="Mr" name="title" checked=${false}></pf-radio>
+    <pf-radio id="title-miss" label="Miss" name="title" checked=${false}></pf-radio>
+    <pf-radio id="title-mrs" label="Mrs" name="title" checked=${false}></pf-radio>
+    <pf-radio id="title-ms" label="Ms" name="title" checked=${false}></pf-radio>
+    <pf-radio id="title-dr" label="Dr" name="title" checked=${false}></pf-radio>
+    <pf-radio id="title-other" label="Other" name="title" checked=${false}></pf-radio>
+  </div>
+  <pf-button> Submit</pf-button>
+</section>
 
 <script type="module">
   import '@patternfly/elements/pf-radio/pf-radio.js';
+  import '@patternfly/elements/pf-button/pf-button.js';
 </script>
 
 <style>
-pf-radio {
-  /* insert demo styles */
+.container{
+  padding: 3rem;
+}
+.container p {
+  font-size: 1.5rem;
+  margin-block-end: 0.5rem;
+}
+.radio-group{
+  display: flex;
+  justify-content: flex-start;
+  padding-bottom: 1rem;
+}
+.radio-group pf-radio{
+  padding-right: 1rem;
 }
 </style>
 
diff --git a/elements/pf-radio/pf-radio.ts b/elements/pf-radio/pf-radio.ts
index 71beb68bed..ca585437e8 100644
--- a/elements/pf-radio/pf-radio.ts
+++ b/elements/pf-radio/pf-radio.ts
@@ -1,6 +1,5 @@
 import { LitElement, html, type TemplateResult } from 'lit';
 import { customElement } from 'lit/decorators/custom-element.js';
-
 import styles from './pf-radio.css';
 import { property } from 'lit/decorators/property.js';
 
@@ -127,15 +126,15 @@ export class PfRadio extends LitElement {
 
   render(): TemplateResult<1> {
     return html`
-      <label for=${this.id}>${this.label}</label>
       <input
         @click=${this.#onRadioButtonClick}
         id=${this.id}
         .name=${this.name}
         type='radio'
         tabindex=${this.tabIndex}
-        .checked='${this.checked}'
+        .checked=${this.checked}
       />
+      <label for=${this.id}>${this.label}</label>
     `;
   }
 }

From d14f49c86bbd81ef992ecbdc7d3c4c6ac0a3b09c Mon Sep 17 00:00:00 2001
From: Arathy-s <ask6295@gmail.com>
Date: Wed, 27 Nov 2024 13:00:47 +0530
Subject: [PATCH 08/14] chore: radio check event added

---
 elements/pf-radio/demo/pf-radio.html | 12 ++++++------
 elements/pf-radio/pf-radio.ts        | 11 ++++++++++-
 2 files changed, 16 insertions(+), 7 deletions(-)

diff --git a/elements/pf-radio/demo/pf-radio.html b/elements/pf-radio/demo/pf-radio.html
index 5bab10e169..758dc6b5b2 100644
--- a/elements/pf-radio/demo/pf-radio.html
+++ b/elements/pf-radio/demo/pf-radio.html
@@ -1,12 +1,12 @@
 <section class='container'>
   <p> Select a title </p>
   <div class='radio-group'>
-    <pf-radio id="title-mr" label="Mr" name="title" checked=${false}></pf-radio>
-    <pf-radio id="title-miss" label="Miss" name="title" checked=${false}></pf-radio>
-    <pf-radio id="title-mrs" label="Mrs" name="title" checked=${false}></pf-radio>
-    <pf-radio id="title-ms" label="Ms" name="title" checked=${false}></pf-radio>
-    <pf-radio id="title-dr" label="Dr" name="title" checked=${false}></pf-radio>
-    <pf-radio id="title-other" label="Other" name="title" checked=${false}></pf-radio>
+    <pf-radio id="title-mr" value='mr' label="Mr" name="title" checked=${false}></pf-radio>
+    <pf-radio id="title-miss" value='miss' label="Miss" name="title" checked=${false}></pf-radio>
+    <pf-radio id="title-mrs" value='mrs' label="Mrs" name="title" checked=${false}></pf-radio>
+    <pf-radio id="title-ms" value='ms' label="Ms" name="title" checked=${false}></pf-radio>
+    <pf-radio id="title-dr" value='dr' label="Dr" name="title" checked=${false}></pf-radio>
+    <pf-radio id="title-other" value='other' label="Other" name="title" checked=${false}></pf-radio>
   </div>
   <pf-button> Submit</pf-button>
 </section>
diff --git a/elements/pf-radio/pf-radio.ts b/elements/pf-radio/pf-radio.ts
index ca585437e8..7947de93f2 100644
--- a/elements/pf-radio/pf-radio.ts
+++ b/elements/pf-radio/pf-radio.ts
@@ -3,6 +3,12 @@ import { customElement } from 'lit/decorators/custom-element.js';
 import styles from './pf-radio.css';
 import { property } from 'lit/decorators/property.js';
 
+export class PfRadioChangeEvent extends Event {
+  constructor(public event: Event, public value: string) {
+    super('change', { bubbles: true });
+  }
+}
+
 /**
  * Radio
  * @slot - Place element content here
@@ -52,7 +58,7 @@ export class PfRadio extends LitElement {
     document.addEventListener('keydown', this.#onKeyPress);
   }
 
-  #onRadioButtonClick() {
+  #onRadioButtonClick(event: Event) {
     if (!this.checked) {
       const root: Node = this.getRootNode();
       let radioGroup: NodeListOf<PfRadio>;
@@ -65,6 +71,7 @@ export class PfRadio extends LitElement {
         });
         this.checked = true;
         this.tabIndex = 0;
+        this.dispatchEvent(new PfRadioChangeEvent(event, this.value));
       }
     }
   }
@@ -118,6 +125,7 @@ export class PfRadio extends LitElement {
             const nextIndex: number = (index + direction + radioGroup.length) % radioGroup.length;
             radioGroup[nextIndex].focus();
             radioGroup[nextIndex].checked = true;
+            this.dispatchEvent(new PfRadioChangeEvent(event, radioGroup[nextIndex].value));
           }
         });
       }
@@ -131,6 +139,7 @@ export class PfRadio extends LitElement {
         id=${this.id}
         .name=${this.name}
         type='radio'
+        value=${this.value}
         tabindex=${this.tabIndex}
         .checked=${this.checked}
       />

From 95be15a5afed6310269039a9132a9f28f4bf8716 Mon Sep 17 00:00:00 2001
From: Benny Powers <web@bennypowers.com>
Date: Wed, 27 Nov 2024 16:18:51 +0200
Subject: [PATCH 09/14] docs(radio): clean up demo

---
 elements/pf-radio/demo/pf-radio.html | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/elements/pf-radio/demo/pf-radio.html b/elements/pf-radio/demo/pf-radio.html
index 758dc6b5b2..a37f63f17f 100644
--- a/elements/pf-radio/demo/pf-radio.html
+++ b/elements/pf-radio/demo/pf-radio.html
@@ -1,12 +1,12 @@
-<section class='container'>
+<section class="container">
   <p> Select a title </p>
-  <div class='radio-group'>
-    <pf-radio id="title-mr" value='mr' label="Mr" name="title" checked=${false}></pf-radio>
-    <pf-radio id="title-miss" value='miss' label="Miss" name="title" checked=${false}></pf-radio>
-    <pf-radio id="title-mrs" value='mrs' label="Mrs" name="title" checked=${false}></pf-radio>
-    <pf-radio id="title-ms" value='ms' label="Ms" name="title" checked=${false}></pf-radio>
-    <pf-radio id="title-dr" value='dr' label="Dr" name="title" checked=${false}></pf-radio>
-    <pf-radio id="title-other" value='other' label="Other" name="title" checked=${false}></pf-radio>
+  <div class="radio-group">
+    <pf-radio id="title-mr" value="mr" label="Mr" name="title"></pf-radio>
+    <pf-radio id="title-miss" value="miss" label="Miss" name="title"></pf-radio>
+    <pf-radio id="title-mrs" value="mrs" label="Mrs" name="title"></pf-radio>
+    <pf-radio id="title-ms" value="ms" label="Ms" name="title"></pf-radio>
+    <pf-radio id="title-dr" value="dr" label="Dr" name="title"></pf-radio>
+    <pf-radio id="title-other" value="other" label="Other" name="title"></pf-radio>
   </div>
   <pf-button> Submit</pf-button>
 </section>

From e79a3f7a3a8b7bac2b5a37c1f31a095f010c76cd Mon Sep 17 00:00:00 2001
From: Benny Powers <web@bennypowers.com>
Date: Wed, 27 Nov 2024 16:19:02 +0200
Subject: [PATCH 10/14] docs(radio): multiple groups

---
 elements/pf-radio/demo/multiple-groups.html | 42 +++++++++++++++++++++
 1 file changed, 42 insertions(+)
 create mode 100644 elements/pf-radio/demo/multiple-groups.html

diff --git a/elements/pf-radio/demo/multiple-groups.html b/elements/pf-radio/demo/multiple-groups.html
new file mode 100644
index 0000000000..c86f235b84
--- /dev/null
+++ b/elements/pf-radio/demo/multiple-groups.html
@@ -0,0 +1,42 @@
+<section class="container">
+  <div class="radio-group">
+    <p>Salutation</p>
+    <pf-radio value="mr"    name="title" label="Mr"></pf-radio>
+    <pf-radio value="miss"  name="title" label="Miss"></pf-radio>
+    <pf-radio value="mrs"   name="title" label="Mrs"></pf-radio>
+    <pf-radio value="ms"    name="title" label="Ms"></pf-radio>
+    <pf-radio value="dr"    name="title" label="Dr"></pf-radio>
+    <pf-radio value="other" name="title" label="Other"></pf-radio>
+  </div>
+  <div class="radio-group">
+    <p>Score</p>
+    <pf-radio value="a" name="score" label="A"></pf-radio>
+    <pf-radio value="b" name="score" label="B"></pf-radio>
+    <pf-radio value="c" name="score" label="C"></pf-radio>
+  </div>
+  <pf-button> Submit</pf-button>
+</section>
+
+<script type="module">
+  import '@patternfly/elements/pf-radio/pf-radio.js';
+  import '@patternfly/elements/pf-button/pf-button.js';
+</script>
+
+<style>
+.container{
+  padding: 3rem;
+}
+.container p {
+  font-size: 1.5rem;
+  margin-block-end: 0.5rem;
+}
+.radio-group{
+  display: flex;
+  justify-content: flex-start;
+  padding-bottom: 1rem;
+}
+.radio-group pf-radio{
+  padding-right: 1rem;
+}
+</style>
+

From 82275a1738f1e52afdf037b5f07703d6365ff5ac Mon Sep 17 00:00:00 2001
From: Benny Powers <web@bennypowers.com>
Date: Wed, 27 Nov 2024 16:19:39 +0200
Subject: [PATCH 11/14] style(radio): refactor tabindex code

---
 elements/pf-radio/pf-radio.ts | 143 +++++++++++++++++++---------------
 1 file changed, 82 insertions(+), 61 deletions(-)

diff --git a/elements/pf-radio/pf-radio.ts b/elements/pf-radio/pf-radio.ts
index 7947de93f2..ba4bd43ac3 100644
--- a/elements/pf-radio/pf-radio.ts
+++ b/elements/pf-radio/pf-radio.ts
@@ -1,7 +1,10 @@
 import { LitElement, html, type TemplateResult } from 'lit';
 import { customElement } from 'lit/decorators/custom-element.js';
-import styles from './pf-radio.css';
 import { property } from 'lit/decorators/property.js';
+import { observes } from '@patternfly/pfe-core/decorators/observes.js';
+import { state } from 'lit/decorators/state.js';
+
+import styles from './pf-radio.css';
 
 export class PfRadioChangeEvent extends Event {
   constructor(public event: Event, public value: string) {
@@ -16,46 +19,89 @@ export class PfRadioChangeEvent extends Event {
 @customElement('pf-radio')
 export class PfRadio extends LitElement {
   static readonly styles: CSSStyleSheet[] = [styles];
+
   static formAssociated = true;
+
   static shadowRootOptions: ShadowRootInit = {
     ...LitElement.shadowRootOptions,
     delegatesFocus: true,
   };
 
-  @property({
-    type: Boolean,
-    attribute: 'checked',
-    converter: {
-      fromAttribute: value => value === 'true',
-    },
-    reflect: true,
-  })
+  @property({ type: Boolean, reflect: true })
   checked = false;
 
-  @property({
-    type: Boolean,
-    attribute: 'disabled',
-    converter: {
-      fromAttribute: value => value === 'true',
-    },
-    reflect: true,
-  })
+  @property({ type: Boolean, reflect: true })
   disabled = false;
 
-  @property({ attribute: 'name', reflect: true }) name = '';
-  @property({ attribute: 'label', reflect: true }) label?: string;
-  @property({ attribute: 'value', reflect: true }) value = '';
-  @property({ attribute: 'id', reflect: true }) id = '';
-  @property({ attribute: 'tabindex', reflect: true }) tabIndex = -1;
+  @property({ reflect: true }) name = '';
+
+  @property({ reflect: true }) label?: string;
+
+  @property({ reflect: true }) value = '';
+
+  @state() private focusable = false;
 
-  constructor() {
-    super();
+  /** Radio groups: instances.get(groupName).forEach(pfRadio => { ... }) */
+  private static instances = new Map<string, Set<PfRadio>>();
+
+  private static selected = new Map<string, PfRadio>;
+
+  static {
+    globalThis.addEventListener('keydown', e => {
+      switch (e.key) {
+        case 'Tab':
+          this.instances.forEach((radioSet, groupName) => {
+            const selected = this.selected.get(groupName);
+            [...radioSet].forEach((radio, i, radios) => {
+              // the radio group has a selected element
+              // it should be the only focusable member of the group
+              if (selected) {
+                radio.focusable = radio === selected;
+              // when Shift-tabbing into a group, only the last member should be selected
+              } else if (e.shiftKey) {
+                radio.focusable = radio === radios.at(-1);
+              // otherwise, the first member must be focusable
+              } else {
+                radio.focusable = i === 0;
+              }
+            });
+          });
+          break;
+      }
+    });
   }
 
   connectedCallback(): void {
     super.connectedCallback();
     this.addEventListener('keydown', this.#onKeydown);
-    document.addEventListener('keydown', this.#onKeyPress);
+  }
+
+  @observes('checked')
+  protected checkedChanged(): void {
+    if (this.checked) {
+      PfRadio.selected.set(this.name, this);
+    }
+  }
+
+  @observes('name')
+  protected nameChanged(oldName: string): void {
+    // reset the map of groupname to selected radio button
+    if (PfRadio.selected.get(oldName) === this) {
+      PfRadio.selected.delete(oldName);
+      PfRadio.selected.set(this.name, this);
+    }
+    if (typeof oldName === 'string') {
+      PfRadio.instances.get(oldName)?.delete(this);
+    }
+    if (!PfRadio.instances.has(this.name)) {
+      PfRadio.instances.set(this.name, new Set());
+    }
+    PfRadio.instances.get(this.name)?.add(this);
+  }
+
+  disconnectedCallback(): void {
+    PfRadio.instances.get(this.name)?.delete(this);
+    super.disconnectedCallback();
   }
 
   #onRadioButtonClick(event: Event) {
@@ -66,44 +112,17 @@ export class PfRadio extends LitElement {
         radioGroup = root.querySelectorAll('pf-radio');
         radioGroup.forEach((radio: PfRadio) => {
           const element: HTMLElement = radio as HTMLElement;
+          // avoid removeAttribute: set checked property instead
+          // even better: listen for `change` on the shadow input,
+          // and recalculate state from there.
           element?.removeAttribute('checked');
-          element.tabIndex = -1;
         });
         this.checked = true;
-        this.tabIndex = 0;
         this.dispatchEvent(new PfRadioChangeEvent(event, this.value));
       }
     }
   }
 
-  // Function to handle tab key navigation
-  #onKeyPress = (event: KeyboardEvent) => {
-    const root: Node = this.getRootNode();
-    if (root instanceof Document || root instanceof ShadowRoot) {
-      const radioGroup: NodeListOf<PfRadio> = root.querySelectorAll('pf-radio');
-      const isRadioChecked: boolean = Array.from(radioGroup).some(
-        (radio: PfRadio) => radio.checked
-      );
-      if (event.key === 'Tab') {
-        radioGroup.forEach((radio: PfRadio) => {
-          radio.tabIndex = radio.checked ? 0 : -1;
-        });
-        if (!isRadioChecked) {
-          radioGroup.forEach((radio: PfRadio, index: number) => {
-            radio.tabIndex = -1;
-            if (event.shiftKey) {
-              if (index === radioGroup.length - 1) {
-                radio.tabIndex = 0;
-              }
-            } else if (index === 0) {
-              radio.tabIndex = 0;
-            }
-          });
-        }
-      }
-    }
-  };
-
   // Function to handle keyboard navigation
   #onKeydown = (event: KeyboardEvent) => {
     const arrowKeys: string[] = ['ArrowDown', 'ArrowRight', 'ArrowUp', 'ArrowLeft'];
@@ -113,7 +132,6 @@ export class PfRadio extends LitElement {
         const radioGroup: NodeListOf<PfRadio> = root.querySelectorAll('pf-radio');
         radioGroup.forEach((radio: PfRadio, index: number) => {
           this.checked = false;
-          this.tabIndex = 0;
 
           if (radio === event.target) {
             const isArrowDownOrRight: boolean = ['ArrowDown', 'ArrowRight'].includes(event.key);
@@ -125,6 +143,9 @@ export class PfRadio extends LitElement {
             const nextIndex: number = (index + direction + radioGroup.length) % radioGroup.length;
             radioGroup[nextIndex].focus();
             radioGroup[nextIndex].checked = true;
+            // TODO: move this to an @observes
+            // consider the api of this event.
+            // do we add the group to it? do we fire from every element on every change?
             this.dispatchEvent(new PfRadioChangeEvent(event, radioGroup[nextIndex].value));
           }
         });
@@ -135,15 +156,15 @@ export class PfRadio extends LitElement {
   render(): TemplateResult<1> {
     return html`
       <input
+        id="radio"
+        type="radio"
         @click=${this.#onRadioButtonClick}
-        id=${this.id}
         .name=${this.name}
-        type='radio'
         value=${this.value}
-        tabindex=${this.tabIndex}
+        tabindex=${this.focusable ? 0 : -1}
         .checked=${this.checked}
-      />
-      <label for=${this.id}>${this.label}</label>
+      >
+      <label for="radio">${this.label}</label>
     `;
   }
 }

From 1dc97121194c84d0874da6ad0d26beb9bbefb55b Mon Sep 17 00:00:00 2001
From: Arathy-s <ask6295@gmail.com>
Date: Wed, 4 Dec 2024 19:10:56 +0530
Subject: [PATCH 12/14] chore: implemented pf-radio for multiple groups and
 inside a shadowroot scenarios

---
 elements/pf-radio/demo/multiple-groups.html |  96 +++++++++-
 elements/pf-radio/pf-radio.ts               | 199 ++++++++++++--------
 2 files changed, 210 insertions(+), 85 deletions(-)

diff --git a/elements/pf-radio/demo/multiple-groups.html b/elements/pf-radio/demo/multiple-groups.html
index c86f235b84..6cbde72e0e 100644
--- a/elements/pf-radio/demo/multiple-groups.html
+++ b/elements/pf-radio/demo/multiple-groups.html
@@ -1,25 +1,67 @@
 <section class="container">
+  <p>Basic <code>&lt;pf-radio&gt;</code> group</p>
   <div class="radio-group">
-    <p>Salutation</p>
+    <p>Salutation: </p>
     <pf-radio value="mr"    name="title" label="Mr"></pf-radio>
     <pf-radio value="miss"  name="title" label="Miss"></pf-radio>
     <pf-radio value="mrs"   name="title" label="Mrs"></pf-radio>
     <pf-radio value="ms"    name="title" label="Ms"></pf-radio>
-    <pf-radio value="dr"    name="title" label="Dr"></pf-radio>
-    <pf-radio value="other" name="title" label="Other"></pf-radio>
   </div>
-  <div class="radio-group">
-    <p>Score</p>
-    <pf-radio value="a" name="score" label="A"></pf-radio>
-    <pf-radio value="b" name="score" label="B"></pf-radio>
-    <pf-radio value="c" name="score" label="C"></pf-radio>
+
+  <p><code>&lt;pf-radio&gt;</code> group with different name inside same parent</p>
+  <div class="radio-group-container">
+    <div class="radio-group">
+      <p>Salutation: </p>
+      <pf-radio value="mr"    name="title" label="Mr"></pf-radio>
+      <pf-radio value="miss"  name="title" label="Miss"></pf-radio>
+      <pf-radio value="mrs"   name="title" label="Mrs"></pf-radio>
+      <pf-radio value="ms"    name="title" label="Ms"></pf-radio>
+      <pf-radio value="dr"    name="title" label="Dr"></pf-radio>
+      <pf-radio value="other" name="title" label="Other"></pf-radio>
+      
+      <div class='spacing'></div>
+
+      <p>Score: </p>
+      <pf-radio value="a" name="score" label="A"></pf-radio>
+      <pf-radio value="b" name="score" label="B"></pf-radio>
+      <pf-radio value="c" name="score" label="C"></pf-radio>
+    </div>
+  </div>
+
+  <p><code>&lt;pf-radio&gt;</code> group with same name inside different parent</p>
+  <div class="radio-group-container">
+    <div class="radio-group">
+      <p>Score: </p>
+      <pf-radio value="a" name="score" label="A"></pf-radio>
+      <pf-radio value="b" name="score" label="B"></pf-radio>
+      <pf-radio value="c" name="score" label="C"></pf-radio>
+    </div>
+
+    <div class="radio-group">
+      <p>Score: </p>
+      <pf-radio value="a" name="score" label="A"></pf-radio>
+      <pf-radio value="b" name="score" label="B"></pf-radio>
+      <pf-radio value="c" name="score" label="C"></pf-radio>
+    </div>
   </div>
+
+  <p><code>&lt;pf-radio&gt;</code> group inside pf-card component (component inside shadowroot)</p>
+  <pf-card>
+    <div class="radio-group">
+      <p>Score: </p>
+      <pf-radio value="a" name="score" label="A"></pf-radio>
+      <pf-radio value="b" name="score" label="B"></pf-radio>
+      <pf-radio value="c" name="score" label="C"></pf-radio>
+      <pf-radio value="d" name="score" label="D"></pf-radio>
+    </div>
+  </pf-card>
   <pf-button> Submit</pf-button>
 </section>
 
 <script type="module">
   import '@patternfly/elements/pf-radio/pf-radio.js';
   import '@patternfly/elements/pf-button/pf-button.js';
+  import '@patternfly/elements/pf-card/pf-card.js'
 </script>
 
 <style>
@@ -27,16 +69,50 @@
   padding: 3rem;
 }
 .container p {
-  font-size: 1.5rem;
-  margin-block-end: 0.5rem;
+  font-size: 1.3rem;
+  margin-block-end: 1rem;
+}
+
+.container .radio-group p {
+  font-size: 1.1rem;
+  margin-block-end: 0rem;
+  margin-block-start: 0rem;
+  color: var(--rh-color-red-60, #a60000);
+  padding-right: 0.5rem;
+}
+
+.radio-group-container {
+  display: flex;
+  justify-content: flex-start;
+  flex-direction: row;
 }
+
 .radio-group{
   display: flex;
   justify-content: flex-start;
   padding-bottom: 1rem;
+  padding-right: 2rem;
+  flex-direction: row;
+  align-items: baseline;
 }
 .radio-group pf-radio{
   padding-right: 1rem;
 }
+pf-card{
+  margin-bottom: 2rem;
+  width: fit-content;
+}
+
+pf-card .radio-group{
+  padding-top: 2rem;
+  padding-bottom: 0.5rem;
+}
+.spacing{
+  margin-right: 2rem;
+}
+
+code{
+  font-size: 1.2rem;
+}
 </style>
 
diff --git a/elements/pf-radio/pf-radio.ts b/elements/pf-radio/pf-radio.ts
index ba4bd43ac3..2cca7fa2a8 100644
--- a/elements/pf-radio/pf-radio.ts
+++ b/elements/pf-radio/pf-radio.ts
@@ -1,7 +1,7 @@
 import { LitElement, html, type TemplateResult } from 'lit';
 import { customElement } from 'lit/decorators/custom-element.js';
 import { property } from 'lit/decorators/property.js';
-import { observes } from '@patternfly/pfe-core/decorators/observes.js';
+// import { observes } from '@patternfly/pfe-core/decorators/observes.js';
 import { state } from 'lit/decorators/state.js';
 
 import styles from './pf-radio.css';
@@ -43,27 +43,34 @@ export class PfRadio extends LitElement {
 
   /** Radio groups: instances.get(groupName).forEach(pfRadio => { ... }) */
   private static instances = new Map<string, Set<PfRadio>>();
+  private static radioInstances = new Map<Node, Map<string, Set<PfRadio>>>();
 
-  private static selected = new Map<string, PfRadio>;
+  private static selected = new Map<Node, Map<string, PfRadio>>();
 
   static {
     globalThis.addEventListener('keydown', e => {
       switch (e.key) {
         case 'Tab':
-          this.instances.forEach((radioSet, groupName) => {
-            const selected = this.selected.get(groupName);
-            [...radioSet].forEach((radio, i, radios) => {
-              // the radio group has a selected element
-              // it should be the only focusable member of the group
-              if (selected) {
-                radio.focusable = radio === selected;
-              // when Shift-tabbing into a group, only the last member should be selected
-              } else if (e.shiftKey) {
-                radio.focusable = radio === radios.at(-1);
-              // otherwise, the first member must be focusable
-              } else {
-                radio.focusable = i === 0;
-              }
+          this.radioInstances.forEach((radioGroup, parentNode) => {
+            radioGroup.forEach((radioSet, groupName) => {
+              const selectedNode = this.selected.get(parentNode);
+              const selected = selectedNode?.get(groupName);
+              [...radioSet].forEach((radio, i, radios) => {
+                // the radio group has a selected element
+                // it should be the only focusable member of the group
+                radio.focusable = false;
+                if (groupName === radio.name) {
+                  if (selected) {
+                    radio.focusable = radio === selected;
+                    // when Shift-tabbing into a group, only the last member should be selected
+                  } else if (e.shiftKey) {
+                    radio.focusable = radio === radios.at(-1);
+                    // otherwise, the first member must be focusable
+                  } else {
+                    radio.focusable = i === 0;
+                  }
+                }
+              });
             });
           });
           break;
@@ -74,52 +81,86 @@ export class PfRadio extends LitElement {
   connectedCallback(): void {
     super.connectedCallback();
     this.addEventListener('keydown', this.#onKeydown);
-  }
 
-  @observes('checked')
-  protected checkedChanged(): void {
-    if (this.checked) {
-      PfRadio.selected.set(this.name, this);
+    // Function to group radios based on parent node and name
+    const root: Node = this.getRootNode();
+    let radioGroup: NodeListOf<PfRadio>;
+    if (root instanceof Document || root instanceof ShadowRoot) {
+      radioGroup = root.querySelectorAll('pf-radio');
+      // let radioGroupArray: any[] = [];
+      radioGroup.forEach((radio: PfRadio) => {
+        if (radio.parentNode === this.parentNode && radio.name === this.name) {
+          // radioGroupArray.push(radio);
+          let map = PfRadio.radioInstances.get(this.parentNode as HTMLElement);
+          if (!map) {
+            map = new Map<string, Set<PfRadio>>();
+            PfRadio.radioInstances.set(this.parentNode as HTMLElement, map);
+          }
+          let set = map.get(this.name);
+          if (!set) {
+            set = new Set<PfRadio>();
+            map.set(this.name, set);
+          }
+          set.add(radio);
+        }
+      });
     }
   }
 
-  @observes('name')
-  protected nameChanged(oldName: string): void {
-    // reset the map of groupname to selected radio button
-    if (PfRadio.selected.get(oldName) === this) {
-      PfRadio.selected.delete(oldName);
-      PfRadio.selected.set(this.name, this);
-    }
-    if (typeof oldName === 'string') {
-      PfRadio.instances.get(oldName)?.delete(this);
-    }
-    if (!PfRadio.instances.has(this.name)) {
-      PfRadio.instances.set(this.name, new Set());
-    }
-    PfRadio.instances.get(this.name)?.add(this);
-  }
+  // @observes('checked')
+  // protected checkedChanged(): void {
+  //   if (this.checked) {
+  //     PfRadio.selected.set(this.name, this);
+  //   }
+  // }
+
+  // @observes('name')
+  // protected nameChanged(oldName: string): void {
+  //   // reset the map of groupname to selected radio button
+  //   if (PfRadio.selected.get(oldName) === this) {
+  //     PfRadio.selected.delete(oldName);
+  //     PfRadio.selected.set(this.name, this);
+  //   }
+  //   if (typeof oldName === 'string') {
+  //     PfRadio.instances.get(oldName)?.delete(this);
+  //   }
+  //   if (!PfRadio.instances.has(this.name)) {
+  //     PfRadio.instances.set(this.name, new Set());
+  //   }
+  //   PfRadio.instances.get(this.name)?.add(this);
+  // }
 
   disconnectedCallback(): void {
     PfRadio.instances.get(this.name)?.delete(this);
     super.disconnectedCallback();
   }
 
-  #onRadioButtonClick(event: Event) {
+  #onChange(event: Event) {
     if (!this.checked) {
-      const root: Node = this.getRootNode();
-      let radioGroup: NodeListOf<PfRadio>;
-      if (root instanceof Document || root instanceof ShadowRoot) {
-        radioGroup = root.querySelectorAll('pf-radio');
-        radioGroup.forEach((radio: PfRadio) => {
-          const element: HTMLElement = radio as HTMLElement;
-          // avoid removeAttribute: set checked property instead
-          // even better: listen for `change` on the shadow input,
-          // and recalculate state from there.
-          element?.removeAttribute('checked');
-        });
-        this.checked = true;
-        this.dispatchEvent(new PfRadioChangeEvent(event, this.value));
-      }
+      PfRadio.radioInstances.forEach((radioGroup, parentNode) => {
+        if (parentNode === this.parentNode) {
+          radioGroup.forEach((radioSet, groupName) => {
+            if (groupName === this.name) {
+              [...radioSet].forEach(radio => {
+                radio.checked = false;
+              });
+              this.checked = true;
+              this.dispatchEvent(new PfRadioChangeEvent(event, this.value));
+              this.#updateSelected(this.parentNode as HTMLElement, this, this.name);
+            }
+          });
+        }
+      });
+    }
+  }
+
+  #updateSelected(parentNode: ParentNode, radio: PfRadio, name: string) {
+    if (!PfRadio.selected.has(parentNode)) {
+      PfRadio.selected.set(parentNode, new Map<string, PfRadio>());
+    }
+    const nodeMap = PfRadio.selected.get(parentNode);
+    if (nodeMap) {
+      PfRadio.selected.get(parentNode)?.set(name, radio);
     }
   }
 
@@ -127,42 +168,50 @@ export class PfRadio extends LitElement {
   #onKeydown = (event: KeyboardEvent) => {
     const arrowKeys: string[] = ['ArrowDown', 'ArrowRight', 'ArrowUp', 'ArrowLeft'];
     if (arrowKeys.includes(event.key)) {
-      const root: Node = this.getRootNode();
-      if (root instanceof Document || root instanceof ShadowRoot) {
-        const radioGroup: NodeListOf<PfRadio> = root.querySelectorAll('pf-radio');
-        radioGroup.forEach((radio: PfRadio, index: number) => {
-          this.checked = false;
-
-          if (radio === event.target) {
-            const isArrowDownOrRight: boolean = ['ArrowDown', 'ArrowRight'].includes(event.key);
-            const isArrowUpOrLeft: boolean = ['ArrowUp', 'ArrowLeft'].includes(event.key);
-            const direction: 1 | 0 | -1 = isArrowDownOrRight ? 1 : isArrowUpOrLeft ? -1 : 0;
-            if (direction === 0) {
-              return;
+      PfRadio.radioInstances.forEach((radioGroup, parentNode) => {
+        if (parentNode === this.parentNode) {
+          radioGroup.forEach((radioSet: Set<PfRadio>, groupName: string) => {
+            if (groupName === this.name) {
+              this.checked = false;
+              [...radioSet].forEach((radio: PfRadio, index: number, radios: PfRadio[]) => {
+                if (radio === event.target) {
+                  const isArrowDownOrRight: boolean =
+                    ['ArrowDown', 'ArrowRight'].includes(event.key);
+                  const isArrowUpOrLeft: boolean = ['ArrowUp', 'ArrowLeft'].includes(event.key);
+                  const direction: 1 | 0 | -1 = isArrowDownOrRight ? 1 : isArrowUpOrLeft ? -1 : 0;
+                  if (direction === 0) {
+                    return;
+                  }
+                  const nextIndex: number = (index + direction + radios.length) % radios.length;
+                  radios[nextIndex].focus();
+                  radios[nextIndex].checked = true;
+                  // TODO: move this to an @observes
+                  // consider the api of this event.
+                  // do we add the group to it? do we fire from every element on every change?
+                  this.dispatchEvent(new PfRadioChangeEvent(event, radios[nextIndex].value));
+                  this.#updateSelected(this.parentNode as HTMLElement,
+                                       radios[nextIndex], radios[nextIndex].name);
+                }
+              });
             }
-            const nextIndex: number = (index + direction + radioGroup.length) % radioGroup.length;
-            radioGroup[nextIndex].focus();
-            radioGroup[nextIndex].checked = true;
-            // TODO: move this to an @observes
-            // consider the api of this event.
-            // do we add the group to it? do we fire from every element on every change?
-            this.dispatchEvent(new PfRadioChangeEvent(event, radioGroup[nextIndex].value));
-          }
-        });
-      }
+          });
+        }
+      });
     }
   };
 
+
+  // Add a pf component and check if there is any change with the values.
   render(): TemplateResult<1> {
     return html`
       <input
         id="radio"
         type="radio"
-        @click=${this.#onRadioButtonClick}
+        @change=${this.#onChange}
         .name=${this.name}
         value=${this.value}
         tabindex=${this.focusable ? 0 : -1}
-        .checked=${this.checked}
+        .checked=${this.checked} 
       >
       <label for="radio">${this.label}</label>
     `;

From 405c52e6216b26c8118559ad411649241209f22a Mon Sep 17 00:00:00 2001
From: Arathy-s <ask6295@gmail.com>
Date: Wed, 4 Dec 2024 19:54:41 +0530
Subject: [PATCH 13/14] chore: code cleanup

---
 elements/pf-radio/pf-radio.ts | 42 ++++++++++++++++++-----------------
 1 file changed, 22 insertions(+), 20 deletions(-)

diff --git a/elements/pf-radio/pf-radio.ts b/elements/pf-radio/pf-radio.ts
index 2cca7fa2a8..4030937eaa 100644
--- a/elements/pf-radio/pf-radio.ts
+++ b/elements/pf-radio/pf-radio.ts
@@ -44,18 +44,17 @@ export class PfRadio extends LitElement {
   /** Radio groups: instances.get(groupName).forEach(pfRadio => { ... }) */
   private static instances = new Map<string, Set<PfRadio>>();
   private static radioInstances = new Map<Node, Map<string, Set<PfRadio>>>();
-
   private static selected = new Map<Node, Map<string, PfRadio>>();
 
   static {
-    globalThis.addEventListener('keydown', e => {
+    globalThis.addEventListener('keydown', (e: KeyboardEvent) => {
       switch (e.key) {
         case 'Tab':
           this.radioInstances.forEach((radioGroup, parentNode) => {
             radioGroup.forEach((radioSet, groupName) => {
               const selectedNode = this.selected.get(parentNode);
               const selected = selectedNode?.get(groupName);
-              [...radioSet].forEach((radio, i, radios) => {
+              [...radioSet].forEach((radio: PfRadio, i: number, radios: PfRadio[]) => {
                 // the radio group has a selected element
                 // it should be the only focusable member of the group
                 radio.focusable = false;
@@ -87,21 +86,19 @@ export class PfRadio extends LitElement {
     let radioGroup: NodeListOf<PfRadio>;
     if (root instanceof Document || root instanceof ShadowRoot) {
       radioGroup = root.querySelectorAll('pf-radio');
-      // let radioGroupArray: any[] = [];
       radioGroup.forEach((radio: PfRadio) => {
-        if (radio.parentNode === this.parentNode && radio.name === this.name) {
-          // radioGroupArray.push(radio);
-          let map = PfRadio.radioInstances.get(this.parentNode as HTMLElement);
-          if (!map) {
-            map = new Map<string, Set<PfRadio>>();
-            PfRadio.radioInstances.set(this.parentNode as HTMLElement, map);
+        if (radio.parentNode && radio.parentNode === this.parentNode && radio.name === this.name) {
+          let radioGroupMap = PfRadio.radioInstances.get(radio.parentNode);
+          if (!radioGroupMap) {
+            radioGroupMap = new Map<string, Set<PfRadio>>();
+            PfRadio.radioInstances.set(radio.parentNode, radioGroupMap);
           }
-          let set = map.get(this.name);
-          if (!set) {
-            set = new Set<PfRadio>();
-            map.set(this.name, set);
+          let radioSet: Set<PfRadio> | undefined = radioGroupMap.get(this.name);
+          if (!radioSet) {
+            radioSet = new Set<PfRadio>();
+            radioGroupMap.set(this.name, radioSet);
           }
-          set.add(radio);
+          radioSet.add(radio);
         }
       });
     }
@@ -132,6 +129,12 @@ export class PfRadio extends LitElement {
 
   disconnectedCallback(): void {
     PfRadio.instances.get(this.name)?.delete(this);
+    if (this.parentNode) {
+      const parentNode = PfRadio.radioInstances.get(this.parentNode);
+      if (parentNode) {
+        PfRadio.radioInstances.delete(this.parentNode);
+      }
+    }
     super.disconnectedCallback();
   }
 
@@ -140,13 +143,13 @@ export class PfRadio extends LitElement {
       PfRadio.radioInstances.forEach((radioGroup, parentNode) => {
         if (parentNode === this.parentNode) {
           radioGroup.forEach((radioSet, groupName) => {
-            if (groupName === this.name) {
+            if (this.parentNode && groupName === this.name) {
               [...radioSet].forEach(radio => {
                 radio.checked = false;
               });
               this.checked = true;
               this.dispatchEvent(new PfRadioChangeEvent(event, this.value));
-              this.#updateSelected(this.parentNode as HTMLElement, this, this.name);
+              this.#updateSelected(this.parentNode, this, this.name);
             }
           });
         }
@@ -174,7 +177,7 @@ export class PfRadio extends LitElement {
             if (groupName === this.name) {
               this.checked = false;
               [...radioSet].forEach((radio: PfRadio, index: number, radios: PfRadio[]) => {
-                if (radio === event.target) {
+                if (this.parentNode && radio === event.target) {
                   const isArrowDownOrRight: boolean =
                     ['ArrowDown', 'ArrowRight'].includes(event.key);
                   const isArrowUpOrLeft: boolean = ['ArrowUp', 'ArrowLeft'].includes(event.key);
@@ -189,8 +192,7 @@ export class PfRadio extends LitElement {
                   // consider the api of this event.
                   // do we add the group to it? do we fire from every element on every change?
                   this.dispatchEvent(new PfRadioChangeEvent(event, radios[nextIndex].value));
-                  this.#updateSelected(this.parentNode as HTMLElement,
-                                       radios[nextIndex], radios[nextIndex].name);
+                  this.#updateSelected(this.parentNode, radios[nextIndex], radios[nextIndex].name);
                 }
               });
             }

From 3ed8f18653877645e8ec7b712a65d489bdc67f38 Mon Sep 17 00:00:00 2001
From: ArathyKumar <ask6295@gmail.com>
Date: Mon, 9 Dec 2024 19:57:34 +0530
Subject: [PATCH 14/14] chore: code cleanup

---
 elements/pf-radio/pf-radio.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/elements/pf-radio/pf-radio.ts b/elements/pf-radio/pf-radio.ts
index 4030937eaa..96deacbbea 100644
--- a/elements/pf-radio/pf-radio.ts
+++ b/elements/pf-radio/pf-radio.ts
@@ -144,7 +144,7 @@ export class PfRadio extends LitElement {
         if (parentNode === this.parentNode) {
           radioGroup.forEach((radioSet, groupName) => {
             if (this.parentNode && groupName === this.name) {
-              [...radioSet].forEach(radio => {
+              [...radioSet].forEach((radio: PfRadio) => {
                 radio.checked = false;
               });
               this.checked = true;