reflect fieldset and add required

pull/800/head
Cory LaViska 2022-06-21 09:21:33 -04:00
rodzic 07af6f2741
commit 7d22e18bfb
4 zmienionych plików z 104 dodań i 5 usunięć

Wyświetl plik

@ -44,4 +44,9 @@ export default css`
overflow: hidden;
white-space: nowrap;
}
.radio-group--required .radio-group__label::after {
content: var(--sl-input-required-content);
margin-inline-start: -2px;
}
`;

Wyświetl plik

@ -0,0 +1,61 @@
import { expect, fixture, html } from '@open-wc/testing';
import type SlRadio from '../radio/radio';
import type SlRadioGroup from './radio-group';
describe('<sl-radio-group>', () => {
describe('validation tests', () => {
it(`should be valid when required and one radio is checked`, async () => {
const el = await fixture<SlRadioGroup>(html`
<sl-radio-group label="Select an option" required>
<sl-radio name="option" value="1" checked>Option 1</sl-radio>
<sl-radio name="option" value="2">Option 2</sl-radio>
<sl-radio name="option" value="3">Option 3</sl-radio>
</sl-radio-group>
`);
const radio = el.querySelector<SlRadio>('sl-radio')!;
expect(radio.reportValidity()).to.be.true;
});
it(`should be invalid when required and no radios are checked`, async () => {
const el = await fixture<SlRadioGroup>(html`
<sl-radio-group label="Select an option" required>
<sl-radio name="option" value="1">Option 1</sl-radio>
<sl-radio name="option" value="2">Option 2</sl-radio>
<sl-radio name="option" value="3">Option 3</sl-radio>
</sl-radio-group>
`);
const radio = el.querySelector<SlRadio>('sl-radio')!;
expect(radio.reportValidity()).to.be.false;
});
it(`should be valid when required and a different radio is checked`, async () => {
const el = await fixture<SlRadioGroup>(html`
<sl-radio-group label="Select an option" required>
<sl-radio name="option" value="1">Option 1</sl-radio>
<sl-radio name="option" value="2">Option 2</sl-radio>
<sl-radio name="option" value="3" checked>Option 3</sl-radio>
</sl-radio-group>
`);
const radio = el.querySelectorAll('sl-radio')![2];
expect(radio.reportValidity()).to.be.true;
});
it(`should be invalid when custom validity is set`, async () => {
const el = await fixture<SlRadioGroup>(html`
<sl-radio-group label="Select an option">
<sl-radio name="option" value="1">Option 1</sl-radio>
<sl-radio name="option" value="2">Option 2</sl-radio>
<sl-radio name="option" value="3">Option 3</sl-radio>
</sl-radio-group>
`);
const radio = el.querySelector<SlRadio>('sl-radio')!;
radio.setCustomValidity('Error');
expect(radio.reportValidity()).to.be.false;
});
});
});

Wyświetl plik

@ -33,7 +33,10 @@ export default class SlRadioGroup extends LitElement {
@property() label = '';
/** Shows the fieldset and legend that surrounds the radio group. */
@property({ type: Boolean, attribute: 'fieldset' }) fieldset = false;
@property({ type: Boolean, attribute: 'fieldset', reflect: true }) fieldset = false;
/** Ensures a child radio is checked before allowing the containing form to submit. */
@property({ type: Boolean, reflect: true }) required = false;
connectedCallback() {
super.connectedCallback();
@ -89,7 +92,7 @@ export default class SlRadioGroup extends LitElement {
const radios = this.getAllRadios();
const checkedRadio = radios.find(radio => radio.checked);
this.hasButtonGroup = !!radios.find(radio => radio.tagName.toLowerCase() === 'sl-radio-button');
this.hasButtonGroup = radios.some(radio => radio.tagName.toLowerCase() === 'sl-radio-button');
radios.forEach(radio => {
radio.setAttribute('role', 'radio');
@ -113,7 +116,8 @@ export default class SlRadioGroup extends LitElement {
part="base"
class=${classMap({
'radio-group': true,
'radio-group--has-fieldset': this.fieldset
'radio-group--has-fieldset': this.fieldset,
'radio-group--required': this.required
})}
>
<legend part="label" class="radio-group__label">

Wyświetl plik

@ -74,8 +74,37 @@ export default class SlRadio extends LitElement {
}
/** Checks for validity and shows the browser's validation message if the control is invalid. */
reportValidity() {
return this.input.reportValidity();
reportValidity(): boolean {
const group = this.closest('sl-radio-group');
const allRadios = group?.getAllRadios().filter(radio => !radio.disabled);
const isRequired = group?.required;
const isChecked = allRadios?.some(radio => radio.checked);
const internalRadio = (radio: SlRadio): HTMLInputElement =>
radio.shadowRoot!.querySelector<HTMLInputElement>('input[type="radio"]')!;
// If no radio group or radios are found, skip validation
if (!group || !allRadios) {
return true;
}
// If the radio group is required but no radios are checked, mark the first internal radio required and report it
if (isRequired && !isChecked) {
const radio = internalRadio(allRadios[0]);
radio.required = true;
return radio.reportValidity();
}
// Reset the required state of all internal radios so we can accurately report custom validation messages
allRadios.forEach(radio => (internalRadio(radio).required = false));
// Report custom validation errors
for (const radio of allRadios) {
if (!internalRadio(radio).reportValidity()) {
return false;
}
}
return true;
}
/** Sets a custom validation message. If `message` is not empty, the field will be considered invalid. */