mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 04:09:12 +00:00
backport SL-2192
This commit is contained in:
@@ -20,6 +20,7 @@ During the alpha period, things might break! We take breaking changes very serio
|
||||
- Added support for <kbd>Enter</kbd> to `<wa-split-panel>` 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 `<wa-select>`
|
||||
- Added support for vertical button groups
|
||||
- Added the `focus()` method to `<wa-radio-group>`
|
||||
- Fixed a bug in `<wa-rating>` when using `precision`
|
||||
- Fixed a bug in `<wa-rating>` that allowed tabbing into the rating when readonly
|
||||
- Fixed a bug in `<wa-relative-time>` where the title attribute would show with redundant info
|
||||
|
||||
@@ -297,6 +297,102 @@ describe('<wa-radio-group>', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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<HTMLLabelElement>('#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<WaRadioGroup>(html`
|
||||
<wa-radio-group>
|
||||
<wa-radio id="radio-0" value="0" disabled></wa-radio>
|
||||
<wa-radio id="radio-1" value="1" disabled></wa-radio>
|
||||
<wa-radio id="radio-2" value="2" disabled></wa-radio>
|
||||
<wa-radio id="radio-3" value="3" disabled></wa-radio>
|
||||
</wa-radio-group>
|
||||
`);
|
||||
|
||||
const validFocusHandler = sinon.spy();
|
||||
|
||||
Array.from(el.querySelectorAll<WaRadio>('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<WaRadioGroup>(html`
|
||||
<wa-radio-group>
|
||||
<wa-radio id="radio-0" value="0" disabled></wa-radio>
|
||||
<wa-radio id="radio-1" value="1"></wa-radio>
|
||||
<wa-radio id="radio-2" value="2"></wa-radio>
|
||||
<wa-radio id="radio-3" value="3"></wa-radio>
|
||||
</wa-radio-group>
|
||||
`);
|
||||
|
||||
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<WaRadioGroup>(html`
|
||||
<wa-radio-group value="2">
|
||||
<wa-radio id="radio-0" value="0" disabled></wa-radio>
|
||||
<wa-radio id="radio-1" value="1"></wa-radio>
|
||||
<wa-radio id="radio-2" value="2" checked></wa-radio>
|
||||
<wa-radio id="radio-3" value="3"></wa-radio>
|
||||
</wa-radio-group>
|
||||
`);
|
||||
|
||||
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<WaRadioGroup>(html`
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user