kopia lustrzana https://github.com/shoelace-style/shoelace
reflect fieldset and add required
rodzic
07af6f2741
commit
7d22e18bfb
|
@ -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;
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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">
|
||||
|
|
|
@ -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. */
|
||||
|
|
Ładowanie…
Reference in New Issue