diff --git a/docs/resources/changelog.md b/docs/resources/changelog.md index 4672a567..20fb793d 100644 --- a/docs/resources/changelog.md +++ b/docs/resources/changelog.md @@ -13,13 +13,16 @@ _During the beta period, these restrictions may be relaxed in the event of a mis - Added `form`, `formaction`, `formmethod`, `formnovalidate`, and `formtarget` attributes to `` [#699](https://github.com/shoelace-style/shoelace/issues/699) - Added Prettier and ESLint to markdown files - Added background color and border to `` +- Added more tests for ``, ``, and `` - Fixed a bug that prevented forms from submitting when pressing Enter inside of an `` [#700](https://github.com/shoelace-style/shoelace/issues/700) - Fixed a bug in `` that prevented the `valueAsDate` and `valueAsNumber` properties from working when set before the component was initialized - Improved `autofocus` behavior in Safari for `` and `` [#693](https://github.com/shoelace-style/shoelace/issues/693) - Improved type to select logic in `` so it supports Backspace and gives users more time before resetting - Improved checkmark size and positioning in `` +- Improved accessibility in form controls that have help text so they're announced correctly in various screen readers - Removed feature detection for `focus({ preventScroll })` since it no longer works in Safari - Removed the `--sl-tooltip-arrow-start-end-offset` design token +- Removed the `pattern` attribute from `` as it was documented incorrectly and never supported - Replaced Popper positioning dependency with Floating UI in `` and `` ## 2.0.0-beta.70 diff --git a/src/components/avatar/avatar.test.ts b/src/components/avatar/avatar.test.ts index 6517c2f9..d0090dca 100644 --- a/src/components/avatar/avatar.test.ts +++ b/src/components/avatar/avatar.test.ts @@ -9,7 +9,7 @@ describe('', () => { el = await fixture(html` `); }); - it('passes accessibility test', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); @@ -27,7 +27,7 @@ describe('', () => { el = await fixture(html``); }); - it('passes accessibility test', async () => { + it('should pass accessibility tests', async () => { /** * The image element itself is ancillary, because it's parent container contains the * aria-label which dictates what "sl-avatar" is. This also implies that label text will @@ -57,7 +57,7 @@ describe('', () => { el = await fixture(html``); }); - it('passes accessibility test', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); @@ -74,7 +74,7 @@ describe('', () => { el = await fixture(html``); }); - it('passes accessibility test', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); @@ -92,7 +92,7 @@ describe('', () => { el = await fixture(html`random content`); }); - it('passes accessibility test', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); diff --git a/src/components/badge/badge.test.ts b/src/components/badge/badge.test.ts index 97495ab4..b50a1737 100644 --- a/src/components/badge/badge.test.ts +++ b/src/components/badge/badge.test.ts @@ -9,7 +9,7 @@ describe('', () => { el = await fixture(html` Badge `); }); - it('should render a component that passes accessibility test, with a role of status on the base part.', async () => { + it('should pass accessibility tests with a role of status on the base part.', async () => { await expect(el).to.be.accessible(); const part = el.shadowRoot!.querySelector('[part="base"]')!; @@ -31,7 +31,7 @@ describe('', () => { el = await fixture(html` Badge `); }); - it('should render a component that passes accessibility test', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); @@ -46,7 +46,7 @@ describe('', () => { el = await fixture(html` Badge `); }); - it('should render a component that passes accessibility test', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); @@ -62,7 +62,7 @@ describe('', () => { el = await fixture(html`Badge`); }); - it('should render a component that passes accessibility test', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); diff --git a/src/components/breadcrumb-item/breadcrumb-item.test.ts b/src/components/breadcrumb-item/breadcrumb-item.test.ts index 2782d870..1d9e7d36 100644 --- a/src/components/breadcrumb-item/breadcrumb-item.test.ts +++ b/src/components/breadcrumb-item/breadcrumb-item.test.ts @@ -9,7 +9,7 @@ describe('', () => { el = await fixture(html` Home `); }); - it('should render a component that passes accessibility test', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); @@ -33,7 +33,7 @@ describe('', () => { `); }); - it('should render a component that passes accessibility test', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); @@ -50,7 +50,7 @@ describe('', () => { `); }); - it('should render a component that passes accessibility test', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); @@ -80,7 +80,7 @@ describe('', () => { `); }); - it('should render a component that passes accessibility test', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); @@ -112,7 +112,7 @@ describe('', () => { `); }); - it('should render a component that passes accessibility test', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); @@ -139,7 +139,7 @@ describe('', () => { `); }); - it('should render a component that passes accessibility test', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); diff --git a/src/components/breadcrumb/breadcrumb.test.ts b/src/components/breadcrumb/breadcrumb.test.ts index b22589eb..1756859c 100644 --- a/src/components/breadcrumb/breadcrumb.test.ts +++ b/src/components/breadcrumb/breadcrumb.test.ts @@ -16,7 +16,7 @@ describe('', () => { `); }); - it('should render a component that passes accessibility test', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); @@ -43,7 +43,7 @@ describe('', () => { `); }); - it('should render a component that passes accessibility test', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); @@ -75,7 +75,7 @@ describe('', () => { `); }); - it('should render a component that passes accessibility test', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); }); @@ -95,7 +95,7 @@ describe('', () => { `); }); - it('should render a component that passes accessibility test', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); }); diff --git a/src/components/card/card.test.ts b/src/components/card/card.test.ts index ad770800..f347086c 100644 --- a/src/components/card/card.test.ts +++ b/src/components/card/card.test.ts @@ -11,7 +11,7 @@ describe('', () => { ); }); - it('should render a component that passes accessibility test.', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); @@ -35,7 +35,7 @@ describe('', () => { ); }); - it('should render a component that passes accessibility test.', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); @@ -72,7 +72,7 @@ describe('', () => { ); }); - it('should render a component that passes accessibility test.', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); @@ -112,7 +112,7 @@ describe('', () => { ); }); - it('should render a component that passes accessibility test.', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); diff --git a/src/components/checkbox/checkbox.ts b/src/components/checkbox/checkbox.ts index 6e77cd42..12475112 100644 --- a/src/components/checkbox/checkbox.ts +++ b/src/components/checkbox/checkbox.ts @@ -3,7 +3,6 @@ import { customElement, property, query, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { live } from 'lit/directives/live.js'; -import { autoIncrement } from '~/internal/auto-increment'; import { emit } from '~/internal/event'; import { FormSubmitController } from '~/internal/form-control'; import { watch } from '~/internal/watch'; @@ -35,9 +34,6 @@ export default class SlCheckbox extends LitElement { private readonly formSubmitController = new FormSubmitController(this, { value: (control: SlCheckbox) => (control.checked ? control.value : undefined) }); - private readonly attrId = autoIncrement(); - private readonly inputId = `checkbox-${this.attrId}`; - private readonly labelId = `checkbox-label-${this.attrId}`; @state() private hasFocus = false; @@ -132,10 +128,8 @@ export default class SlCheckbox extends LitElement { 'checkbox--focused': this.hasFocus, 'checkbox--indeterminate': this.indeterminate })} - for=${this.inputId} > - + diff --git a/src/components/image-comparer/image-comparer.ts b/src/components/image-comparer/image-comparer.ts index dc56178d..414a3f7c 100644 --- a/src/components/image-comparer/image-comparer.ts +++ b/src/components/image-comparer/image-comparer.ts @@ -2,7 +2,6 @@ import { html, LitElement } from 'lit'; import { customElement, property, query } from 'lit/decorators.js'; import { styleMap } from 'lit/directives/style-map.js'; import '~/components/icon/icon'; -import { autoIncrement } from '~/internal/auto-increment'; import { drag } from '~/internal/drag'; import { emit } from '~/internal/event'; import { clamp } from '~/internal/math'; @@ -37,9 +36,6 @@ export default class SlImageComparer extends LitElement { @query('.image-comparer') base: HTMLElement; @query('.image-comparer__handle') handle: HTMLElement; - private readonly attrId = autoIncrement(); - private readonly containerId = `comparer-container-${this.attrId}`; - /** The position of the divider as a percentage. */ @property({ type: Number, reflect: true }) position = 50; @@ -85,7 +81,7 @@ export default class SlImageComparer extends LitElement { render() { return html` -
+
@@ -114,7 +110,7 @@ export default class SlImageComparer extends LitElement { aria-valuenow=${this.position} aria-valuemin="0" aria-valuemax="100" - aria-controls=${this.containerId} + aria-controls="image-comparer" tabindex="0" > diff --git a/src/components/input/input.test.ts b/src/components/input/input.test.ts index 23fb8d90..8985c99c 100644 --- a/src/components/input/input.test.ts +++ b/src/components/input/input.test.ts @@ -1,9 +1,16 @@ import { expect, fixture, html, waitUntil } from '@open-wc/testing'; import { sendKeys } from '@web/test-runner-commands'; import sinon from 'sinon'; +// eslint-disable-next-line no-restricted-imports +import { serialize } from '../../utilities/form'; import type SlInput from './input'; describe('', () => { + it('should pass accessibility tests', async () => { + const el = await fixture(html` `); + await expect(el).to.be.accessible(); + }); + it('should be disabled with the disabled attribute', async () => { const el = await fixture(html` `); const input = el.shadowRoot!.querySelector('[part="input"]')!; @@ -11,27 +18,6 @@ describe('', () => { expect(input.disabled).to.be.true; }); - it('should be valid by default', async () => { - const el = await fixture(html` `); - - expect(el.invalid).to.be.false; - }); - - it('should be invalid when required and empty', async () => { - const el = await fixture(html` `); - - expect(el.invalid).to.be.true; - }); - - it('should be invalid when required and after removing disabled', async () => { - const el = await fixture(html` `); - - el.disabled = false; - await el.updateComplete; - - expect(el.invalid).to.be.true; - }); - it('should submit the form when pressing enter in a form without a submit button', async () => { const form = await fixture(html`
`); const input = form.querySelector('sl-input')!; @@ -64,4 +50,56 @@ describe('', () => { expect(el.value).to.equal(num.toString()); }); + + it('should focus the input when clicking on the label', async () => { + const el = await fixture(html` `); + const label = el.shadowRoot!.querySelector('[part="label"]')!; + const submitHandler = sinon.spy(); + + el.addEventListener('sl-focus', submitHandler); + (label as HTMLLabelElement).click(); + await waitUntil(() => submitHandler.calledOnce); + + expect(submitHandler).to.have.been.calledOnce; + }); + + // + // Constraint validation tests + // + + it('should be valid by default', async () => { + const el = await fixture(html` `); + expect(el.invalid).to.be.false; + }); + + it('should be invalid when required and empty', async () => { + const el = await fixture(html` `); + expect(el.reportValidity()).to.be.false; + expect(el.invalid).to.be.true; + }); + + it('should be invalid when the pattern does not match', async () => { + const el = await fixture(html` `); + expect(el.invalid).to.be.true; + expect(el.reportValidity()).to.be.false; + }); + + it('should be invalid when required and disabled is removed', async () => { + const el = await fixture(html` `); + el.disabled = false; + await el.updateComplete; + expect(el.invalid).to.be.true; + }); + + it('should serialize its name and value with FormData', async () => { + const form = await fixture(html`
`); + const formData = new FormData(form); + expect(formData.get('a')).to.equal('1'); + }); + + it('should serialize its name and value with JSON', async () => { + const form = await fixture(html`
`); + const json = serialize(form); + expect(json.a).to.equal('1'); + }); }); diff --git a/src/components/input/input.ts b/src/components/input/input.ts index 8cac1fcd..9b123587 100644 --- a/src/components/input/input.ts +++ b/src/components/input/input.ts @@ -4,9 +4,8 @@ import { classMap } from 'lit/directives/class-map.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { live } from 'lit/directives/live.js'; import '~/components/icon/icon'; -import { autoIncrement } from '~/internal/auto-increment'; import { emit } from '~/internal/event'; -import { FormSubmitController, getLabelledBy, renderFormControl } from '~/internal/form-control'; +import { FormSubmitController } from '~/internal/form-control'; import { HasSlotController } from '~/internal/slot'; import { watch } from '~/internal/watch'; import styles from './input.styles'; @@ -49,10 +48,6 @@ export default class SlInput extends LitElement { private readonly formSubmitController = new FormSubmitController(this); private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label'); - private readonly attrId = autoIncrement(); - private readonly inputId = `input-${this.attrId}`; - private readonly helpTextId = `input-help-text-${this.attrId}`; - private readonly labelId = `input-label-${this.attrId}`; @state() private hasFocus = false; @state() private isPasswordVisible = false; @@ -283,131 +278,138 @@ export default class SlInput extends LitElement { render() { const hasLabelSlot = this.hasSlotController.test('label'); const hasHelpTextSlot = this.hasSlotController.test('help-text'); + const hasLabel = this.label ? true : !!hasLabelSlot; + const hasHelpText = this.helpText ? true : !!hasHelpTextSlot; - // NOTE - always bind value after min/max, otherwise it will be clamped - return renderFormControl( - { - inputId: this.inputId, - label: this.label, - labelId: this.labelId, - hasLabelSlot, - helpTextId: this.helpTextId, - helpText: this.helpText, - hasHelpTextSlot, - size: this.size - }, - html` -
+ - // Sizes - 'input--small': this.size === 'small', - 'input--medium': this.size === 'medium', - 'input--large': this.size === 'large', +
+
- - - + // Sizes + 'input--small': this.size === 'small', + 'input--medium': this.size === 'medium', + 'input--large': this.size === 'large', - + // States + 'input--pill': this.pill, + 'input--standard': !this.filled, + 'input--filled': this.filled, + 'input--disabled': this.disabled, + 'input--focused': this.hasFocus, + 'input--empty': this.value.length === 0, + 'input--invalid': this.invalid + })} + > + + + - ${this.clearable && this.value.length > 0 - ? html` - - ` - : ''} - ${this.togglePassword - ? html` - - ` - : ''} + - - - + ${this.clearable && this.value.length > 0 + ? html` + + ` + : ''} + ${this.togglePassword + ? html` + + ` + : ''} + + + + +
- ` - ); + +
+ ${this.helpText} +
+
+ `; } } diff --git a/src/components/progress-bar/progress-bar.test.ts b/src/components/progress-bar/progress-bar.test.ts index e91a456c..9344664e 100644 --- a/src/components/progress-bar/progress-bar.test.ts +++ b/src/components/progress-bar/progress-bar.test.ts @@ -9,7 +9,7 @@ describe('', () => { el = await fixture(html``); }); - it('should render a component that passes accessibility test.', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); }); @@ -26,7 +26,7 @@ describe('', () => { indicator = el.shadowRoot!.querySelector('[part="indicator"]')!; }); - it('should render a component that passes accessibility test.', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); @@ -49,7 +49,7 @@ describe('', () => { base = el.shadowRoot!.querySelector('[part="base"]')!; }); - it('should render a component that passes accessibility test.', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); @@ -65,7 +65,7 @@ describe('', () => { ); }); - it('should render a component that passes accessibility test.', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); }); @@ -80,7 +80,7 @@ describe('', () => { ); }); - it('should render a component that passes accessibility test.', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); }); diff --git a/src/components/progress-ring/progress-ring.test.ts b/src/components/progress-ring/progress-ring.test.ts index 722180cd..068cec85 100644 --- a/src/components/progress-ring/progress-ring.test.ts +++ b/src/components/progress-ring/progress-ring.test.ts @@ -9,7 +9,7 @@ describe('', () => { el = await fixture(html``); }); - it('should render a component that passes accessibility test.', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); }); @@ -24,7 +24,7 @@ describe('', () => { base = el.shadowRoot!.querySelector('[part="base"]')!; }); - it('should render a component that passes accessibility test.', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); @@ -44,7 +44,7 @@ describe('', () => { ); }); - it('should render a component that passes accessibility test.', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); }); @@ -59,7 +59,7 @@ describe('', () => { ); }); - it('should render a component that passes accessibility test.', async () => { + it('should pass accessibility tests', async () => { await expect(el).to.be.accessible(); }); }); diff --git a/src/components/radio/radio.ts b/src/components/radio/radio.ts index 2a51da4e..c0f3bc16 100644 --- a/src/components/radio/radio.ts +++ b/src/components/radio/radio.ts @@ -104,7 +104,7 @@ export default class SlRadio extends LitElement { return [this]; } - return [...radioGroup.querySelectorAll('sl-radio')].filter((radio: this) => radio.name === this.name) as this[]; + return [...radioGroup.querySelectorAll('sl-radio')].filter((radio: this) => radio.name === this.name); } getSiblingRadios() { diff --git a/src/components/range/range.ts b/src/components/range/range.ts index dddeb50d..64f584ae 100644 --- a/src/components/range/range.ts +++ b/src/components/range/range.ts @@ -3,9 +3,8 @@ import { customElement, property, query, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { live } from 'lit/directives/live.js'; -import { autoIncrement } from '~/internal/auto-increment'; import { emit } from '~/internal/event'; -import { FormSubmitController, getLabelledBy, renderFormControl } from '~/internal/form-control'; +import { FormSubmitController } from '~/internal/form-control'; import { HasSlotController } from '~/internal/slot'; import { watch } from '~/internal/watch'; import styles from './range.styles'; @@ -41,10 +40,6 @@ export default class SlRange extends LitElement { // @ts-expect-error -- Controller is currently unused private readonly formSubmitController = new FormSubmitController(this); private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label'); - private readonly attrId = autoIncrement(); - private readonly inputId = `input-${this.attrId}`; - private readonly helpTextId = `input-help-text-${this.attrId}`; - private readonly labelId = `input-label-${this.attrId}`; private resizeObserver: ResizeObserver; @state() private hasFocus = false; @@ -204,69 +199,76 @@ export default class SlRange extends LitElement { render() { const hasLabelSlot = this.hasSlotController.test('label'); const hasHelpTextSlot = this.hasSlotController.test('help-text'); + const hasLabel = this.label ? true : !!hasLabelSlot; + const hasHelpText = this.helpText ? true : !!hasHelpTextSlot; // NOTE - always bind value after min/max, otherwise it will be clamped - return renderFormControl( - { - inputId: this.inputId, - label: this.label, - labelId: this.labelId, - hasLabelSlot, - helpTextId: this.helpTextId, - helpText: this.helpText, - hasHelpTextSlot, - size: 'medium' - }, - html` -
- - ${this.tooltip !== 'none' && !this.disabled - ? html` - - ${typeof this.tooltipFormatter === 'function' ? this.tooltipFormatter(this.value) : this.value} - - ` - : ''} + return html` +
+ + +
+
+ + ${this.tooltip !== 'none' && !this.disabled + ? html` + + ${typeof this.tooltipFormatter === 'function' ? this.tooltipFormatter(this.value) : this.value} + + ` + : ''} +
- ` - ); + +
+ ${this.helpText} +
+
+ `; } } diff --git a/src/components/select/select.test.ts b/src/components/select/select.test.ts index ea28ad77..376a97ec 100644 --- a/src/components/select/select.test.ts +++ b/src/components/select/select.test.ts @@ -3,6 +3,47 @@ import sinon from 'sinon'; import type SlSelect from './select'; describe('', () => { + it('should pass accessibility tests', async () => { + const el = await fixture(html` + + Option 1 + Option 2 + Option 3 + + `); + await expect(el).to.be.accessible(); + }); + + it('should be disabled with the disabled attribute', async () => { + const el = await fixture(html` + + Option 1 + Option 2 + Option 3 + + `); + expect(el.dropdown.disabled).to.be.true; + expect(el.control.tabIndex).to.equal(-1); + }); + + it('should focus the select when clicking on the label', async () => { + const el = await fixture(html` + + Option 1 + Option 2 + Option 3 + + `); + const label = el.shadowRoot!.querySelector('[part="label"]')!; + const submitHandler = sinon.spy(); + + el.addEventListener('sl-focus', submitHandler); + (label as HTMLLabelElement).click(); + await waitUntil(() => submitHandler.calledOnce); + + expect(submitHandler).to.have.been.calledOnce; + }); + it('should emit sl-change when the value changes', async () => { const el = await fixture(html` @@ -21,7 +62,7 @@ describe('', () => { }); it('should open the menu when any letter key is pressed with sl-select is on focus', async () => { - const el = await fixture(html` + const el = await fixture(html` Option 1 Option 2 @@ -37,7 +78,7 @@ describe('', () => { }); it('should not open the menu when ctrl + R is pressed with sl-select is on focus', async () => { - const el = await fixture(html` + const el = await fixture(html` Option 1 Option 2 @@ -51,4 +92,24 @@ describe('', () => { await aTimeout(100); expect(control.getAttribute('aria-expanded')).to.equal('false'); }); + + // + // Constraint validation tests + // + + it('should focus on the custom control when constraint validation occurs', async () => { + const el = await fixture(html` +
+ + Option 1 + Option 2 + Option 3 + +
+ `); + const select = el.querySelector('sl-select')!; + el.requestSubmit(); + + expect(select.shadowRoot!.activeElement).to.equal(select.control); + }); }); diff --git a/src/components/select/select.ts b/src/components/select/select.ts index 5bac66bc..d786847e 100644 --- a/src/components/select/select.ts +++ b/src/components/select/select.ts @@ -1,7 +1,6 @@ import { html, LitElement } from 'lit'; import { customElement, property, query, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; -import { ifDefined } from 'lit/directives/if-defined.js'; import '~/components/dropdown/dropdown'; import type SlDropdown from '~/components/dropdown/dropdown'; import '~/components/icon-button/icon-button'; @@ -11,9 +10,8 @@ import type SlMenuItem from '~/components/menu-item/menu-item'; import type SlMenu from '~/components/menu/menu'; import type { MenuSelectEventDetail } from '~/components/menu/menu'; import '~/components/tag/tag'; -import { autoIncrement } from '~/internal/auto-increment'; import { emit } from '~/internal/event'; -import { FormSubmitController, getLabelledBy, renderFormControl } from '~/internal/form-control'; +import { FormSubmitController } from '~/internal/form-control'; import { getTextContent, HasSlotController } from '~/internal/slot'; import { watch } from '~/internal/watch'; import styles from './select.styles'; @@ -70,11 +68,6 @@ export default class SlSelect extends LitElement { // @ts-expect-error -- Controller is currently unused private readonly formSubmitController = new FormSubmitController(this); private readonly hasSlotController = new HasSlotController(this, 'help-text', 'label'); - private readonly attrId = autoIncrement(); - private readonly inputId = `select-${this.attrId}`; - private readonly helpTextId = `select-help-text-${this.attrId}`; - private readonly labelId = `select-label-${this.attrId}`; - private readonly menuId = `select-menu-${this.attrId}`; private resizeObserver: ResizeObserver; @state() private hasFocus = false; @@ -176,7 +169,7 @@ export default class SlSelect extends LitElement { } getItems() { - return [...this.querySelectorAll('sl-menu-item')] as SlMenuItem[]; + return [...this.querySelectorAll('sl-menu-item')]; } getValueAsArray() { @@ -440,119 +433,134 @@ export default class SlSelect extends LitElement { const hasLabelSlot = this.hasSlotController.test('label'); const hasHelpTextSlot = this.hasSlotController.test('help-text'); const hasSelection = this.multiple ? this.value.length > 0 : this.value !== ''; + const hasLabel = this.label ? true : !!hasLabelSlot; + const hasHelpText = this.helpText ? true : !!hasHelpTextSlot; - return renderFormControl( - { - inputId: this.inputId, - label: this.label, - labelId: this.labelId, - hasLabelSlot, - helpTextId: this.helpTextId, - helpText: this.helpText, - hasHelpTextSlot, - size: this.size, - onLabelClick: () => this.handleLabelClick() - }, - html` - 0, - 'select--placeholder-visible': this.displayLabel === '', - 'select--small': this.size === 'small', - 'select--medium': this.size === 'medium', - 'select--large': this.size === 'large', - 'select--pill': this.pill, - 'select--invalid': this.invalid - })} - @sl-show=${this.handleMenuShow} - @sl-hide=${this.handleMenuHide} + return html` +
+