diff --git a/docs/resources/changelog.md b/docs/resources/changelog.md index aaf63b96..b171cd94 100644 --- a/docs/resources/changelog.md +++ b/docs/resources/changelog.md @@ -12,6 +12,7 @@ _During the beta period, these restrictions may be relaxed in the event of a mis - Added `--indicator-width` custom property to `` [#837](https://github.com/shoelace-style/shoelace/issues/837) - Added Swedish translation [#838](https://github.com/shoelace-style/shoelace/pull/838) +- Added support for `step="any"` for `` [#839](https://github.com/shoelace-style/shoelace/issues/839) - Changed the type of component styles from `CSSResult` to `CSSResultGroup` [#828](https://github.com/shoelace-style/shoelace/issues/828) - Fixed a bug in `` where using Left and Right would select the wrong color - Fixed a bug in `` where the divider was on the wrong side when using `placement="end"` diff --git a/src/components/input/input.test.ts b/src/components/input/input.test.ts index 868d0c39..79db3efc 100644 --- a/src/components/input/input.test.ts +++ b/src/components/input/input.test.ts @@ -188,4 +188,25 @@ describe('', () => { expect(form.reportValidity()).to.be.false; }); }); + + describe('when type="number"', () => { + it('should be valid when the value is within the boundary of a step', async () => { + const el = await fixture(html` `); + expect(el.invalid).to.be.false; + }); + + it('should be invalid when the value is not within the boundary of a step', async () => { + const el = await fixture(html` `); + expect(el.invalid).to.be.true; + }); + + it('should update validity when step changes', async () => { + const el = await fixture(html` `); + expect(el.invalid).to.be.false; + + el.step = 1; + await el.updateComplete; + expect(el.invalid).to.be.true; + }); + }); }); diff --git a/src/components/input/input.ts b/src/components/input/input.ts index 1aba3c0c..9d7e17a1 100644 --- a/src/components/input/input.ts +++ b/src/components/input/input.ts @@ -125,8 +125,11 @@ export default class SlInput extends LitElement { /** The input's maximum value. */ @property() max: number | string; - /** The input's step attribute. */ - @property({ type: Number }) step: number; + /** + * Specifies the granularity that the value must adhere to, or the special value `any` which means no stepping is + * implied, allowing any numeric value. + */ + @property() step: number | 'any'; /** A pattern to validate input against. */ @property() pattern: string; @@ -272,6 +275,14 @@ export default class SlInput extends LitElement { this.invalid = !this.input.checkValidity(); } + @watch('step', { waitUntilFirstUpdate: true }) + handleStepChange() { + // If step changes, the value may become invalid so we need to recheck after the update. We set the new step + // imperatively so we don't have to wait for the next render to report the updated validity. + this.input.step = String(this.step); + this.invalid = !this.input.checkValidity(); + } + handleFocus() { this.hasFocus = true; emit(this, 'sl-focus'); @@ -384,7 +395,7 @@ export default class SlInput extends LitElement { maxlength=${ifDefined(this.maxlength)} min=${ifDefined(this.min)} max=${ifDefined(this.max)} - step=${ifDefined(this.step)} + step=${ifDefined(this.step as number)} .value=${live(this.value)} autocapitalize=${ifDefined(this.type === 'password' ? 'off' : this.autocapitalize)} autocomplete=${ifDefined(this.type === 'password' ? 'off' : this.autocomplete)}