From c73b59cea428bf1b59bf04c13704cd79bcce8703 Mon Sep 17 00:00:00 2001 From: Elizabeth Mitchell Date: Thu, 25 Aug 2022 09:18:55 -0700 Subject: [PATCH] feat(text-field): add min, max, and step PiperOrigin-RevId: 470006182 --- textfield/lib/text-field.ts | 56 ++++++++++++++++++++++++ textfield/lib/text-field_test.ts | 75 ++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+) diff --git a/textfield/lib/text-field.ts b/textfield/lib/text-field.ts index 48b8885416..24d2995c37 100644 --- a/textfield/lib/text-field.ts +++ b/textfield/lib/text-field.ts @@ -145,6 +145,12 @@ export abstract class TextField extends LitElement { } // properties + /** + * Defines the greatest value in the range of permitted values. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#max + */ + @property({type: String}) max = ''; /** * The maximum number of characters a user can enter into the text field. Set * to -1 for none. @@ -152,6 +158,12 @@ export abstract class TextField extends LitElement { * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#maxlength */ @property({type: Number}) maxLength = -1; + /** + * Defines the most negative value in the range of permitted values. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#min + */ + @property({type: String}) min = ''; /** * The minimum number of characters a user can enter into the text field. Set * to -1 for none. @@ -193,6 +205,14 @@ export abstract class TextField extends LitElement { this.getInput().selectionStart = value; } + /** + * Returns or sets the element's step attribute, which works with min and max + * to limit the increments at which a numeric or date-time value can be set. + * + * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#step + */ + @property({type: String}) step = ''; + // TODO(b/237284412): replace with exported types @property({type: String, reflect: true}) type: 'email'|'number'|'password'|'search'|'tel'|'text'|'url'|'color'|'date'| @@ -379,6 +399,34 @@ export abstract class TextField extends LitElement { this.getInput().setSelectionRange(start, end, direction); } + /** + * Decrements the value of a numeric type text field by `step` or `n` `step` + * number of times. + * + * https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/stepDown + * + * @param stepDecrement The number of steps to decrement, defaults to 1. + */ + stepDown(stepDecrement?: number) { + const input = this.getInput(); + input.stepDown(stepDecrement); + this.value = input.value; + } + + /** + * Increments the value of a numeric type text field by `step` or `n` `step` + * number of times. + * + * https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/stepUp + * + * @param stepIncrement The number of steps to increment, defaults to 1. + */ + stepUp(stepIncrement?: number) { + const input = this.getInput(); + input.stepUp(stepIncrement); + this.value = input.value; + } + /** * Reset the text field to its default value. */ @@ -465,10 +513,15 @@ export abstract class TextField extends LitElement { const ariaExpandedValue = this.ariaExpanded || undefined; const ariaLabelValue = this.ariaLabel || this.label || undefined; const ariaLabelledByValue = this.ariaLabelledBy || undefined; + const maxValue = this.max || undefined; const maxLengthValue = this.maxLength > -1 ? this.maxLength : undefined; + const minValue = this.min || undefined; const minLengthValue = this.minLength > -1 ? this.minLength : undefined; const roleValue = this.role || undefined; + const stepValue = this.step || undefined; + // TODO(b/243805848): remove `as unknown as number` once lit analyzer is + // fixed return html` { .toBeFalse(); }); }); + + describe('min, max, and step', () => { + it('should set attribute on input', async () => { + const {testElement, input} = await setupTest(); + testElement.type = 'number'; + testElement.min = '2'; + testElement.max = '5'; + testElement.step = '1'; + await env.waitForStability(); + + expect(input.getAttribute('min')).withContext('min').toEqual('2'); + expect(input.getAttribute('max')).withContext('max').toEqual('5'); + expect(input.getAttribute('step')).withContext('step').toEqual('1'); + }); + + it('should not set attribute if value is empty', async () => { + const {testElement, input} = await setupTest(); + testElement.type = 'number'; + testElement.min = '2'; + testElement.max = '5'; + testElement.step = '1'; + await env.waitForStability(); + + expect(input.hasAttribute('min')) + .withContext('should have min') + .toBeTrue(); + expect(input.hasAttribute('max')) + .withContext('should have max') + .toBeTrue(); + expect(input.hasAttribute('step')) + .withContext('should have step') + .toBeTrue(); + + testElement.min = ''; + testElement.max = ''; + testElement.step = ''; + await env.waitForStability(); + + expect(input.hasAttribute('min')) + .withContext('should not have min') + .toBeFalse(); + expect(input.hasAttribute('max')) + .withContext('should not have max') + .toBeFalse(); + expect(input.hasAttribute('step')) + .withContext('should not have step') + .toBeFalse(); + }); + }); + }); + + describe('stepUp()', () => { + it('should increment the value by `step`', async () => { + const {testElement} = await setupTest(); + testElement.type = 'number'; + testElement.valueAsNumber = 10; + testElement.step = '5'; + + testElement.stepUp(); + + expect(testElement.valueAsNumber).toEqual(15); + }); + }); + + describe('stepDown()', () => { + it('should decrement the value by `step`', async () => { + const {testElement} = await setupTest(); + testElement.type = 'number'; + testElement.valueAsNumber = 10; + testElement.step = '5'; + + testElement.stepDown(); + + expect(testElement.valueAsNumber).toEqual(5); + }); }); // TODO(b/235238545): Add shared FormController tests.