diff --git a/docs/docs/resources/changelog.md b/docs/docs/resources/changelog.md
index f4662d9d8..39d6be02f 100644
--- a/docs/docs/resources/changelog.md
+++ b/docs/docs/resources/changelog.md
@@ -20,6 +20,7 @@ During the alpha period, things might break! We take breaking changes very serio
- Added support for Enter to `` to align with ARIA APG's [window splitter pattern](https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/)
- Added more resilient support for lazy loaded options in ``
- Added support for vertical button groups
+- Added the `focus()` method to ``
- Fixed a bug in `` when using `precision`
- Fixed a bug in `` that allowed tabbing into the rating when readonly
- Fixed a bug in `` where the title attribute would show with redundant info
diff --git a/src/components/radio-group/radio-group.test.ts b/src/components/radio-group/radio-group.test.ts
index 1f5ef9ad6..1bbba970e 100644
--- a/src/components/radio-group/radio-group.test.ts
+++ b/src/components/radio-group/radio-group.test.ts
@@ -297,6 +297,102 @@ describe('', () => {
});
});
+ describe('when handling focus', () => {
+ const doAction = async (instance: WaRadioGroup, type: string) => {
+ if (type === 'focus') {
+ instance.focus();
+ await instance.updateComplete;
+ return;
+ }
+
+ const label = instance.shadowRoot!.querySelector('#label')!;
+ label.click();
+ await instance.updateComplete;
+ };
+
+ // Tests for focus and label actions with radio buttons
+ ['focus', 'label'].forEach(actionType => {
+ describe(`when using ${actionType}`, () => {
+ it('should do nothing if all elements are disabled', async () => {
+ const el = await fixture(html`
+
+
+
+
+
+
+ `);
+
+ const validFocusHandler = sinon.spy();
+
+ Array.from(el.querySelectorAll('wa-radio')).forEach(radio =>
+ radio.addEventListener('wa-focus', validFocusHandler)
+ );
+
+ expect(validFocusHandler).to.not.have.been.called;
+ await doAction(el, actionType);
+ expect(validFocusHandler).to.not.have.been.called;
+ });
+
+ it('should focus the first radio that is enabled when the group receives focus', async () => {
+ const el = await fixture(html`
+
+
+
+
+
+
+ `);
+
+ const invalidFocusHandler = sinon.spy();
+ const validFocusHandler = sinon.spy();
+
+ const disabledRadio = el.querySelector('#radio-0')!;
+ const validRadio = el.querySelector('#radio-1')!;
+
+ disabledRadio.addEventListener('wa-focus', invalidFocusHandler);
+ validRadio.addEventListener('wa-focus', validFocusHandler);
+
+ expect(invalidFocusHandler).to.not.have.been.called;
+ expect(validFocusHandler).to.not.have.been.called;
+
+ await doAction(el, actionType);
+
+ expect(invalidFocusHandler).to.not.have.been.called;
+ expect(validFocusHandler).to.have.been.called;
+ });
+
+ it('should focus the currently enabled radio when the group receives focus', async () => {
+ const el = await fixture(html`
+
+
+
+
+
+
+ `);
+
+ const invalidFocusHandler = sinon.spy();
+ const validFocusHandler = sinon.spy();
+
+ const disabledRadio = el.querySelector('#radio-0')!;
+ const validRadio = el.querySelector('#radio-2')!;
+
+ disabledRadio.addEventListener('wa-focus', invalidFocusHandler);
+ validRadio.addEventListener('wa-focus', validFocusHandler);
+
+ expect(invalidFocusHandler).to.not.have.been.called;
+ expect(validFocusHandler).to.not.have.been.called;
+
+ await doAction(el, actionType);
+
+ expect(invalidFocusHandler).to.not.have.been.called;
+ expect(validFocusHandler).to.have.been.called;
+ });
+ });
+ });
+ });
+
describe('when the value changes', () => {
it('should emit wa-change when toggled with the arrow keys', async () => {
const radioGroup = await fixture(html`
diff --git a/src/components/radio-group/radio-group.ts b/src/components/radio-group/radio-group.ts
index 5e4fe3668..7dc136b96 100644
--- a/src/components/radio-group/radio-group.ts
+++ b/src/components/radio-group/radio-group.ts
@@ -170,14 +170,7 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
}
private handleLabelClick() {
- const radios = this.getAllRadios();
- const checked = radios.find(radio => radio.checked);
- const radioToFocus = checked || radios[0];
-
- // Move focus to the checked radio (or the first one if none are checked) when clicking the label
- if (radioToFocus) {
- radioToFocus.focus();
- }
+ this.focus();
}
private async syncRadioElements() {
@@ -305,6 +298,19 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
event.preventDefault();
}
+ /** Sets focus on the radio group. */
+ public focus(options?: FocusOptions) {
+ const radios = this.getAllRadios();
+ const checked = radios.find(radio => radio.checked);
+ const firstEnabledRadio = radios.find(radio => !radio.disabled);
+ const radioToFocus = checked || firstEnabledRadio;
+
+ // Call focus for the checked radio. If no radio is checked, focus the first one that isn't disabled.
+ if (radioToFocus) {
+ radioToFocus.focus(options);
+ }
+ }
+
render() {
const hasLabelSlot = this.hasUpdated ? this.hasSlotController.test('label') : this.withLabel;
const hasHelpTextSlot = this.hasUpdated ? this.hasSlotController.test('help-text') : this.withHelpText;