From a706e69be67cd82c8edd3a284ffa8b5a46b20e88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=BCnyamin=20Eskiocak?= <37407974+bunesk@users.noreply.github.com> Date: Mon, 21 Nov 2022 20:41:09 +0300 Subject: [PATCH] Added stepUp, stepDown and showPicker functions (#1013) * added stepUp and stepDown to sl-range * step function & default props tests for sl-range * stepUp, stepDown & showPicker functions for input * step functions & default props tests for sl-input * made name and placeholder default to empty string * updated changelog Co-authored-by: Cory LaViska --- docs/resources/changelog.md | 2 + src/components/input/input.test.ts | 84 +++++++++++++++++++++++++++++ src/components/input/input.ts | 31 ++++++++++- src/components/range/range.test.ts | 57 +++++++++++++++++++- src/components/range/range.ts | 18 +++++++ src/components/textarea/textarea.ts | 4 +- src/declaration.d.ts | 4 ++ 7 files changed, 195 insertions(+), 5 deletions(-) diff --git a/docs/resources/changelog.md b/docs/resources/changelog.md index b088a673..bbed2948 100644 --- a/docs/resources/changelog.md +++ b/docs/resources/changelog.md @@ -32,6 +32,8 @@ New versions of Shoelace are released as-needed and generally occur when a criti - Added `--sl-input-required-content-color` custom property to all form controls [#948](https://github.com/shoelace-style/shoelace/pull/948) - Added the ability to cancel `sl-show` and `sl-hide` events in `` [#993](https://github.com/shoelace-style/shoelace/issues/993) - Added `focus()` and `blur()` methods to `` +- Added `stepUp()` and `stepDown()` methods to `` and `` [#1013](https://github.com/shoelace-style/shoelace/pull/1013) +- Added `showPicker()` method to `` [#1013](https://github.com/shoelace-style/shoelace/pull/1013) - Added the `handle-icon` part to `` - Added `caret`, `check`, `grip-vertical`, `indeterminate`, and `radio` icons to the system library and removed `check-lg` [#985](https://github.com/shoelace-style/shoelace/issues/985) - Added the `loading` attribute to `` to allow lazy loading of image avatars [#1006](https://github.com/shoelace-style/shoelace/pull/1006) diff --git a/src/components/input/input.test.ts b/src/components/input/input.test.ts index ce59b74c..8d2ff773 100644 --- a/src/components/input/input.test.ts +++ b/src/components/input/input.test.ts @@ -10,6 +10,43 @@ describe('', () => { await expect(el).to.be.accessible(); }); + it('default properties', async () => { + const el = await fixture(html` `); + + expect(el.type).to.equal('text'); + expect(el.size).to.equal('medium'); + expect(el.name).to.equal(''); + expect(el.value).to.equal(''); + expect(el.defaultValue).to.equal(''); + expect(el.filled).to.be.false; + expect(el.pill).to.be.false; + expect(el.label).to.equal(''); + expect(el.helpText).to.equal(''); + expect(el.clearable).to.be.false; + expect(el.passwordToggle).to.be.false; + expect(el.passwordVisible).to.be.false; + expect(el.noSpinButtons).to.be.false; + expect(el.placeholder).to.equal(''); + expect(el.disabled).to.be.false; + expect(el.readonly).to.be.false; + expect(el.minlength).to.be.undefined; + expect(el.maxlength).to.be.undefined; + expect(el.min).to.be.undefined; + expect(el.max).to.be.undefined; + expect(el.step).to.be.undefined; + expect(el.pattern).to.be.undefined; + expect(el.required).to.be.false; + expect(el.autocapitalize).to.be.undefined; + expect(el.autocorrect).to.be.undefined; + expect(el.autocomplete).to.be.undefined; + expect(el.autofocus).to.be.undefined; + expect(el.enterkeyhint).to.be.undefined; + expect(el.spellcheck).to.be.undefined; + expect(el.inputmode).to.be.undefined; + expect(el.valueAsDate).to.be.null; + expect(isNaN(el.valueAsNumber)).to.be.true; + }); + it('should be disabled with the disabled attribute', async () => { const el = await fixture(html` `); const input = el.shadowRoot!.querySelector('[part~="input"]')!; @@ -208,5 +245,52 @@ describe('', () => { await el.updateComplete; expect(el.invalid).to.be.true; }); + + it('should increment by step if stepUp() is called', async () => { + const el = await fixture(html` `); + + el.stepUp(); + await el.updateComplete; + expect(el.value).to.equal('4'); + }); + + it('should decrement by step if stepDown() is called', async () => { + const el = await fixture(html` `); + + el.stepDown(); + await el.updateComplete; + expect(el.value).to.equal('0'); + }); + + it('should fire sl-input and sl-change if stepUp() is called', async () => { + const el = await fixture(html` `); + + const inputHandler = sinon.spy(); + const changeHandler = sinon.spy(); + el.addEventListener('sl-input', inputHandler); + el.addEventListener('sl-change', changeHandler); + + el.stepUp(); + + await waitUntil(() => inputHandler.calledOnce); + await waitUntil(() => changeHandler.calledOnce); + expect(inputHandler).to.have.been.calledOnce; + expect(changeHandler).to.have.been.calledOnce; + }); + + it('should fire sl-input and sl-change if stepDown() is called', async () => { + const el = await fixture(html` `); + + const inputHandler = sinon.spy(); + const changeHandler = sinon.spy(); + el.addEventListener('sl-input', inputHandler); + el.addEventListener('sl-change', changeHandler); + + el.stepUp(); + + await waitUntil(() => inputHandler.calledOnce); + await waitUntil(() => changeHandler.calledOnce); + expect(changeHandler).to.have.been.calledOnce; + }); }); }); diff --git a/src/components/input/input.ts b/src/components/input/input.ts index a2d0cd16..d7f6a325 100644 --- a/src/components/input/input.ts +++ b/src/components/input/input.ts @@ -88,7 +88,7 @@ export default class SlInput extends ShoelaceElement implements ShoelaceFormCont @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium'; /** The input's name attribute. */ - @property() name: string; + @property() name = ''; /** The input's value attribute. */ @property() value = ''; @@ -121,7 +121,7 @@ export default class SlInput extends ShoelaceElement implements ShoelaceFormCont @property({ attribute: 'no-spin-buttons', type: Boolean }) noSpinButtons = false; /** The input's placeholder text. */ - @property() placeholder: string; + @property() placeholder = ''; /** Disables the input. */ @property({ type: Boolean, reflect: true }) disabled = false; @@ -247,6 +247,33 @@ export default class SlInput extends ShoelaceElement implements ShoelaceFormCont } } + /** Displays the browser picker for an input element (only works if the browser supports it for the input type). */ + showPicker() { + if ('showPicker' in HTMLInputElement.prototype) { + this.input.showPicker(); + } + } + + /** Increments the value of a numeric input type by the value of the step attribute. */ + stepUp() { + this.input.stepUp(); + if (this.value !== this.input.value) { + this.value = this.input.value; + this.emit('sl-input'); + this.emit('sl-change'); + } + } + + /** Decrements the value of a numeric input type by the value of the step attribute. */ + stepDown() { + this.input.stepDown(); + if (this.value !== this.input.value) { + this.value = this.input.value; + this.emit('sl-input'); + this.emit('sl-change'); + } + } + /** Checks for validity but does not show the browser's validation message. */ checkValidity() { return this.input.checkValidity(); diff --git a/src/components/range/range.test.ts b/src/components/range/range.test.ts index 02a4cd9b..b737b1b6 100644 --- a/src/components/range/range.test.ts +++ b/src/components/range/range.test.ts @@ -1,4 +1,5 @@ -import { expect, fixture, html, oneEvent } from '@open-wc/testing'; +import { expect, fixture, html, oneEvent, waitUntil } from '@open-wc/testing'; +import sinon from 'sinon'; import { serialize } from '../../utilities/form'; import type SlRange from './range'; @@ -8,6 +9,22 @@ describe('', () => { await expect(el).to.be.accessible(); }); + it('default properties', async () => { + const el = await fixture(html` `); + + expect(el.name).to.equal(''); + expect(el.value).to.equal(0); + expect(el.label).to.equal(''); + expect(el.helpText).to.equal(''); + expect(el.disabled).to.be.false; + expect(el.invalid).to.be.false; + expect(el.min).to.equal(0); + expect(el.max).to.equal(100); + expect(el.step).to.equal(1); + expect(el.tooltip).to.equal('top'); + expect(el.defaultValue).to.equal(0); + }); + it('should be disabled with the disabled attribute', async () => { const el = await fixture(html` `); const input = el.shadowRoot!.querySelector('[part~="input"]')!; @@ -15,6 +32,44 @@ describe('', () => { expect(input.disabled).to.be.true; }); + describe('step', () => { + it('should increment by step if stepUp() is called', async () => { + const el = await fixture(html` `); + + el.stepUp(); + await el.updateComplete; + expect(el.value).to.equal(4); + }); + + it('should decrement by step if stepDown() is called', async () => { + const el = await fixture(html` `); + + el.stepDown(); + await el.updateComplete; + expect(el.value).to.equal(0); + }); + + it('should fire sl-change if stepUp() is called', async () => { + const el = await fixture(html` `); + + const changeHandler = sinon.spy(); + el.addEventListener('sl-change', changeHandler); + el.stepUp(); + await waitUntil(() => changeHandler.calledOnce); + expect(changeHandler).to.have.been.calledOnce; + }); + + it('should fire sl-change if stepDown() is called', async () => { + const el = await fixture(html` `); + + const changeHandler = sinon.spy(); + el.addEventListener('sl-change', changeHandler); + el.stepUp(); + await waitUntil(() => changeHandler.calledOnce); + expect(changeHandler).to.have.been.calledOnce; + }); + }); + describe('when serializing', () => { it('should serialize its name and value with FormData', async () => { const form = await fixture(html`
`); diff --git a/src/components/range/range.ts b/src/components/range/range.ts index 1702dd84..0133843e 100644 --- a/src/components/range/range.ts +++ b/src/components/range/range.ts @@ -123,6 +123,24 @@ export default class SlRange extends ShoelaceElement implements ShoelaceFormCont this.input.blur(); } + /** Increments the value of the input by the value of the step attribute. */ + stepUp() { + this.input.stepUp(); + if (this.value !== Number(this.input.value)) { + this.value = Number(this.input.value); + this.emit('sl-change'); + } + } + + /** Decrements the value of the input by the value of the step attribute. */ + stepDown() { + this.input.stepDown(); + if (this.value !== Number(this.input.value)) { + this.value = Number(this.input.value); + this.emit('sl-change'); + } + } + /** Checks for validity but does not show the browser's validation message. */ checkValidity() { return this.input.checkValidity(); diff --git a/src/components/textarea/textarea.ts b/src/components/textarea/textarea.ts index 32f57a3f..0b85af49 100644 --- a/src/components/textarea/textarea.ts +++ b/src/components/textarea/textarea.ts @@ -51,7 +51,7 @@ export default class SlTextarea extends ShoelaceElement implements ShoelaceFormC @property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium'; /** The textarea's name attribute. */ - @property() name: string; + @property() name = ''; /** The textarea's value attribute. */ @property() value = ''; @@ -66,7 +66,7 @@ export default class SlTextarea extends ShoelaceElement implements ShoelaceFormC @property({ attribute: 'help-text' }) helpText = ''; /** The textarea's placeholder text. */ - @property() placeholder: string; + @property() placeholder = ''; /** The number of rows to display by default. */ @property({ type: Number }) rows = 4; diff --git a/src/declaration.d.ts b/src/declaration.d.ts index d16b948d..2b906527 100644 --- a/src/declaration.d.ts +++ b/src/declaration.d.ts @@ -10,3 +10,7 @@ declare namespace Chai { accessible: (options?: Object) => PromiseLike; } } + +interface HTMLInputElement { + showPicker: () => void; +}