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;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
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 = '';
|
@property() label = '';
|
||||||
|
|
||||||
/** Shows the fieldset and legend that surrounds the radio group. */
|
/** 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() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
@ -89,7 +92,7 @@ export default class SlRadioGroup extends LitElement {
|
||||||
const radios = this.getAllRadios();
|
const radios = this.getAllRadios();
|
||||||
const checkedRadio = radios.find(radio => radio.checked);
|
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 => {
|
radios.forEach(radio => {
|
||||||
radio.setAttribute('role', 'radio');
|
radio.setAttribute('role', 'radio');
|
||||||
|
@ -113,7 +116,8 @@ export default class SlRadioGroup extends LitElement {
|
||||||
part="base"
|
part="base"
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
'radio-group': true,
|
'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">
|
<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. */
|
/** Checks for validity and shows the browser's validation message if the control is invalid. */
|
||||||
reportValidity() {
|
reportValidity(): boolean {
|
||||||
return this.input.reportValidity();
|
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. */
|
/** Sets a custom validation message. If `message` is not empty, the field will be considered invalid. */
|
||||||
|
|
Ładowanie…
Reference in New Issue