diff --git a/docs/resources/changelog.md b/docs/resources/changelog.md index e2ea2dd2..9ff8a73d 100644 --- a/docs/resources/changelog.md +++ b/docs/resources/changelog.md @@ -11,7 +11,9 @@ _During the beta period, these restrictions may be relaxed in the event of a mis - Added `role="status"` to `` - Fixed broken spinner animation in Safari [#633](https://github.com/shoelace-style/shoelace/issues/633) - Fixed an a11y bug in `` where `aria-describedby` referenced an id in the shadow root +- Fixed a bug in `` where tabbing didn't work properly in Firefox [#596](https://github.com/shoelace-style/shoelace/issues/596) - Improved `` track color when used on various backgrounds +- Improved a11y in `` so VoiceOver announces radios properly in a radio group - Refactored internal id usage in ``, ``, ``, and `` - Removed `position: relative` from the common component stylesheet diff --git a/src/components/radio/radio.ts b/src/components/radio/radio.ts index 79690c20..673511fa 100644 --- a/src/components/radio/radio.ts +++ b/src/components/radio/radio.ts @@ -7,8 +7,6 @@ import { emit } from '../../internal/event'; import { watch } from '../../internal/watch'; import styles from './radio.styles'; -let id = 0; - /** * @since 2.0 * @status stable @@ -30,9 +28,6 @@ export default class SlRadio extends LitElement { @query('input[type="radio"]') input: HTMLInputElement; - private inputId = `radio-${++id}`; - private labelId = `radio-label-${id}`; - @state() private hasFocus = false; /** The radio's name attribute. */ @@ -53,6 +48,23 @@ export default class SlRadio extends LitElement { */ @property({ type: Boolean, reflect: true }) invalid = false; + firstUpdated() { + const radios = this.getAllRadios(); + const checkedRadio = radios.find(radio => radio.checked); + + radios.map(radio => { + if (radio.input) { + radio.input.tabIndex = -1; + } + }); + + if (checkedRadio) { + checkedRadio.input.tabIndex = 0; + } else if (radios.length) { + radios[0].input.tabIndex = 0; + } + } + /** Simulates a click on the radio. */ click() { this.input.click(); @@ -102,7 +114,12 @@ export default class SlRadio extends LitElement { @watch('checked', { waitUntilFirstUpdate: true }) handleCheckedChange() { if (this.checked) { - this.getSiblingRadios().map(radio => (radio.checked = false)); + this.input.tabIndex = 0; + + this.getSiblingRadios().map(radio => { + radio.input.tabIndex = -1; + radio.checked = false; + }); } } @@ -133,9 +150,15 @@ export default class SlRadio extends LitElement { if (index < 0) index = radios.length - 1; if (index > radios.length - 1) index = 0; - this.getAllRadios().map(radio => (radio.checked = false)); + this.getAllRadios().map(radio => { + radio.checked = false; + radio.input.tabIndex = -1; + }); + radios[index].focus(); radios[index].checked = true; + radios[index].input.tabIndex = 0; + emit(radios[index], 'sl-change'); event.preventDefault(); @@ -143,6 +166,10 @@ export default class SlRadio extends LitElement { } render() { + this.setAttribute('role', 'radio'); + this.setAttribute('aria-checked', this.checked ? 'true' : 'false'); + this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false'); + return html`