Revert "Merge branch 'radio-group' of https://github.com/Trendy/shoelace into next"

This reverts commit e5cbee2770, reversing
changes made to 3897446cb7.
pull/597/head
Cory LaViska 2021-11-23 11:05:04 -05:00
rodzic e5cbee2770
commit a15ccfee1b
5 zmienionych plików z 58 dodań i 250 usunięć

Wyświetl plik

@ -42,62 +42,11 @@ You can show a fieldset and legend that wraps the radio group using the `fieldse
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlRadioGroup label="Select an option" fieldset value="1">
<SlRadioGroup label="Select an option" fieldset>
<SlRadio value="1" checked>Option 1</SlRadio>
<SlRadio value="2">Option 2</SlRadio>
<SlRadio value="3">Option 3</SlRadio>
</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 class="required-radio-group" 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 class="required-button">Validate Group</sl-button>
<sl-button class="required-reset-button">Reset Group</sl-button>
<script>
const button = document.querySelector('sl-button.required-button');
const resetButton = document.querySelector('sl-button.required-reset-button');
const group = document.querySelector('sl-radio-group.required-radio-group');
button.addEventListener('click', ()=> group.reportValidity());
resetButton.addEventListener('click', () => { group.value = ''})
</script>
```
```jsx react
import { SlRadio, SlRadioGroup } from '@shoelace-style/shoelace/dist/react';
const validateGroup = () => {
const group = document.querySelector('sl-radio-group.required-radio-group');
group.reportValidity();
}
const resetGroup = () => {
const group = document.querySelector('sl-radio-group.required-radio-group');
group.value = "";
}
const App = () => (
<>
<SlRadioGroup label="Select an option" fieldset required>
<SlRadio value="1" checked>Option 1</SlRadio>
<SlRadio value="2">Option 2</SlRadio>
<SlRadio value="3">Option 3</SlRadio>
</SlRadioGroup>
<br />
<sl-button class="required-button" onClick={()=> validateGroup()}>Validate Group</sl-button>
<sl-button class="required-button" onClick={()=> resetGroup()}>Reset Group</sl-button>
</>
);
```
[component-metadata:sl-radio-group]

Wyświetl plik

@ -1,88 +0,0 @@
import { expect, fixture, html, oneEvent } from '@open-wc/testing';
import { sendKeys } from '@web/test-runner-commands';
import '../../../dist/shoelace.js';
import type SlRadio from '../radio/radio';
import type SlRadioGroup from './radio-group';
describe('<sl-radio-group>', () => {
it('should toggle selected radio when toggled via keyboard - arrow right key', async () => {
const radioGroup = await fixture<SlRadioGroup>(html`
<sl-radio-group>
<sl-radio id="radio-1" checked></sl-radio>
<sl-radio id="radio-2"></sl-radio>
</sl-radio-group>
`);
const radio1: SlRadio = radioGroup.querySelector('sl-radio#radio-1');
const radio2: SlRadio = radioGroup.querySelector('sl-radio#radio-2');
expect(radio2.checked).to.be.false;
expect(radio1.checked).to.be.true;
radio1.focus();
await sendKeys({ press: 'ArrowRight' });
expect(radio2.checked).to.be.true;
expect(radio1.checked).to.be.false;
});
it('should toggle selected radio when toggled via keyboard - arrow down key', async () => {
const radioGroup = await fixture<SlRadioGroup>(html`
<sl-radio-group>
<sl-radio id="radio-1" checked></sl-radio>
<sl-radio id="radio-2"></sl-radio>
</sl-radio-group>
`);
const radio1: SlRadio = radioGroup.querySelector('sl-radio#radio-1');
const radio2: SlRadio = radioGroup.querySelector('sl-radio#radio-2');
expect(radio2.checked).to.be.false;
expect(radio1.checked).to.be.true;
radio1.focus();
await sendKeys({ press: 'ArrowDown' });
expect(radio2.checked).to.be.true;
expect(radio1.checked).to.be.false;
});
it('should toggle selected radio when toggled via keyboard - arrow left key', async () => {
const radioGroup = await fixture<SlRadioGroup>(html`
<sl-radio-group>
<sl-radio id="radio-1"></sl-radio>
<sl-radio id="radio-2" checked></sl-radio>
</sl-radio-group>
`);
const radio1: SlRadio = radioGroup.querySelector('sl-radio#radio-1');
const radio2: SlRadio = radioGroup.querySelector('sl-radio#radio-2');
expect(radio2.checked).to.be.true;
expect(radio1.checked).to.be.false;
radio1.focus();
await sendKeys({ press: 'ArrowLeft' });
expect(radio2.checked).to.be.false;
expect(radio1.checked).to.be.true;
});
it('should toggle selected radio when toggled via keyboard - arrow up key', async () => {
const radioGroup = await fixture<SlRadioGroup>(html`
<sl-radio-group>
<sl-radio id="radio-1"></sl-radio>
<sl-radio id="radio-2" checked></sl-radio>
</sl-radio-group>
`);
const radio1: SlRadio = radioGroup.querySelector('sl-radio#radio-1');
const radio2: SlRadio = radioGroup.querySelector('sl-radio#radio-2');
expect(radio2.checked).to.be.true;
expect(radio1.checked).to.be.false;
radio1.focus();
await sendKeys({ press: 'ArrowUp' });
expect(radio2.checked).to.be.false;
expect(radio1.checked).to.be.true;
});
});

Wyświetl plik

@ -17,63 +17,15 @@ import styles from './radio-group.styles';
@customElement('sl-radio-group')
export default class SlRadioGroup extends LitElement {
static styles = styles;
private _value: string = '';
@query('slot:not([name])') defaultSlot: HTMLSlotElement;
/** The radio group label. Required for proper accessibility. Alternatively, you can use the label slot. */
@property() label = '';
/** The current value of the radio group. */
@property()
get value() {
if (!this._value) return this.getCurrentValue();
return this._value;
}
set value(newValue) {
const index = this.getAllRadios().findIndex(el => el.value === newValue);
const oldValue = this._value;
if (index > -1) {
this.checkRadioByIndex(index);
this._value = newValue;
this.requestUpdate('value', oldValue);
} else {
this._value = '';
this.deselectAll();
}
}
/** 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;
connectedCallback() {
this.addEventListener('sl-change', this.syncRadioButtons);
}
disconnectedCallback() {
this.removeEventListener('sl-change', this.syncRadioButtons);
}
syncRadioButtons(event: CustomEvent) {
const currentRadio = event.target;
const radios = this.getAllRadios().filter(el => !el.disabled && el !== currentRadio);
radios.forEach(el => {
el.checked = false;
});
}
getCurrentValue() {
const valRadio = this.getAllRadios().filter(el => el.checked);
this._value = valRadio.length === 1 ? valRadio[0].value : '';
return this._value;
}
handleFocusIn() {
// When tabbing into the fieldset, make sure it lands on the checked radio
requestAnimationFrame(() => {
@ -87,63 +39,6 @@ export default class SlRadioGroup extends LitElement {
});
}
getAllRadios(): SlRadio[] {
return [...this.querySelectorAll('sl-radio')];
}
checkRadioByIndex(index: number): SlRadio[] {
const radios = this.deselectAll();
radios[index].focus();
radios[index].checked = true;
this._value = radios[index].value;
return radios;
}
deselectAll(): SlRadio[] {
return this.getAllRadios().map(radio => {
radio.checked = false;
return radio;
});
}
handleKeyDown(event: KeyboardEvent) {
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
const radios = this.getAllRadios().filter(radio => !radio.disabled);
const currentIndex = radios.findIndex(el => el.checked);
const incr = ['ArrowUp', 'ArrowLeft'].includes(event.key) ? -1 : 1;
let index = currentIndex + incr;
if (index < 0) index = radios.length - 1;
if (index > radios.length - 1) index = 0;
this.checkRadioByIndex(index);
event.preventDefault();
}
}
reportValidity() {
const radios = [...(this.defaultSlot.assignedElements({ flatten: true }) as SlRadio[])];
let isChecked = true;
if (this.required && radios.length > 0) {
isChecked = radios.some(el => el.checked);
if (!isChecked) {
// This is hacky...
radios[0].required = true;
setTimeout(() => {
radios[0].reportValidity();
}, 0);
}
}
return isChecked;
}
render() {
return html`
<fieldset
@ -154,7 +49,6 @@ export default class SlRadioGroup extends LitElement {
})}
role="radiogroup"
@focusin=${this.handleFocusIn}
@keydown=${this.handleKeyDown}
>
<legend part="label" class="radio-group__label">
<slot name="label">${this.label}</slot>

Wyświetl plik

@ -37,6 +37,23 @@ describe('<sl-radio>', () => {
expect(el.checked).to.be.true;
});
it('should fire sl-change when toggled via keyboard - arrow key', async () => {
const radioGroup = await fixture<SlRadioGroup>(html`
<sl-radio-group>
<sl-radio id="radio-1"></sl-radio>
<sl-radio id="radio-2"></sl-radio>
</sl-radio-group>
`);
const radio1: SlRadio = radioGroup.querySelector('sl-radio#radio-1');
const radio2: SlRadio = radioGroup.querySelector('sl-radio#radio-2');
const input1 = radio1.shadowRoot?.querySelector('input');
input1.focus();
setTimeout(() => sendKeys({ press: 'ArrowRight' }));
const event = await oneEvent(radio2, 'sl-change');
expect(event.target).to.equal(radio2);
expect(radio2.checked).to.be.true;
});
it('should not fire sl-change when checked is set by javascript', async () => {
const el = await fixture<SlRadio>(html` <sl-radio></sl-radio> `);
el.addEventListener('sl-change', () => expect.fail('event fired'));

Wyświetl plik

@ -47,9 +47,6 @@ 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.
@ -82,11 +79,33 @@ export default class SlRadio extends LitElement {
this.invalid = !this.input.checkValidity();
}
getAllRadios() {
const radioGroup = this.closest('sl-radio-group');
// Radios must be part of a radio group
if (!radioGroup) {
return [this];
}
return [...radioGroup.querySelectorAll('sl-radio')].filter((radio: this) => radio.name === this.name) as this[];
}
getSiblingRadios() {
return this.getAllRadios().filter(radio => radio !== this) as this[];
}
handleBlur() {
this.hasFocus = false;
emit(this, 'sl-blur');
}
@watch('checked', { waitUntilFirstUpdate: true })
handleCheckedChange() {
if (this.checked) {
this.getSiblingRadios().map(radio => (radio.checked = false));
}
}
handleClick() {
this.checked = true;
emit(this, 'sl-change');
@ -106,6 +125,23 @@ export default class SlRadio extends LitElement {
emit(this, 'sl-focus');
}
handleKeyDown(event: KeyboardEvent) {
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
const radios = this.getAllRadios().filter(radio => !radio.disabled);
const incr = ['ArrowUp', 'ArrowLeft'].includes(event.key) ? -1 : 1;
let index = radios.indexOf(this) + incr;
if (index < 0) index = radios.length - 1;
if (index > radios.length - 1) index = 0;
this.getAllRadios().map(radio => (radio.checked = false));
radios[index].focus();
radios[index].checked = true;
emit(radios[index], 'sl-change');
event.preventDefault();
}
}
render() {
return html`
<label
@ -117,6 +153,7 @@ export default class SlRadio extends LitElement {
'radio--focused': this.hasFocus
})}
for=${this.inputId}
@keydown=${this.handleKeyDown}
>
<input
id=${this.inputId}
@ -124,7 +161,6 @@ 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'}