feat: add types for input components
This commit is contained in:
parent
35efc9c49e
commit
cfa2daf674
15 changed files with 252 additions and 119 deletions
9
.changeset/gentle-trees-beg.md
Normal file
9
.changeset/gentle-trees-beg.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
'@lion/input-date': minor
|
||||
'@lion/input-email': minor
|
||||
'@lion/input-iban': minor
|
||||
'@lion/input-range': minor
|
||||
'@lion/input-stepper': minor
|
||||
---
|
||||
|
||||
Added types for all other input components except for datepicker.
|
||||
|
|
@ -2,8 +2,12 @@ import { IsDate } from '@lion/form-core';
|
|||
import { LionInput } from '@lion/input';
|
||||
import { formatDate, LocalizeMixin, parseDate } from '@lion/localize';
|
||||
|
||||
/**
|
||||
* @param {Date|number} date
|
||||
*/
|
||||
function isValidDate(date) {
|
||||
// to make sure it is a valid date we use isNaN and not Number.isNaN
|
||||
// @ts-ignore dirty hack, you're not supposed to pass Date instances to isNaN
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
return date instanceof Date && !isNaN(date);
|
||||
}
|
||||
|
|
@ -13,8 +17,8 @@ function isValidDate(date) {
|
|||
* on locale.
|
||||
*
|
||||
* @customElement lion-input-date
|
||||
* @extends {LionInput}
|
||||
*/
|
||||
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/40110
|
||||
export class LionInputDate extends LocalizeMixin(LionInput) {
|
||||
static get properties() {
|
||||
return {
|
||||
|
|
@ -24,15 +28,19 @@ export class LionInputDate extends LocalizeMixin(LionInput) {
|
|||
|
||||
constructor() {
|
||||
super();
|
||||
this.parser = (value, options) => (value === '' ? undefined : parseDate(value, options));
|
||||
/**
|
||||
* @param {string} value
|
||||
*/
|
||||
this.parser = value => (value === '' ? undefined : parseDate(value));
|
||||
this.formatter = formatDate;
|
||||
this.defaultValidators.push(new IsDate());
|
||||
}
|
||||
|
||||
/** @param {import('lit-element').PropertyValues } changedProperties */
|
||||
updated(changedProperties) {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has('locale')) {
|
||||
this._calculateValues();
|
||||
this._calculateValues({ source: null });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -42,6 +50,9 @@ export class LionInputDate extends LocalizeMixin(LionInput) {
|
|||
this.type = 'text';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Date} modelValue
|
||||
*/
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
serializer(modelValue) {
|
||||
if (!isValidDate(modelValue)) {
|
||||
|
|
@ -50,9 +61,12 @@ export class LionInputDate extends LocalizeMixin(LionInput) {
|
|||
// modelValue is localized, so we take the timezone offset in milliseconds and subtract it
|
||||
// before converting it to ISO string.
|
||||
const offset = modelValue.getTimezoneOffset() * 60000;
|
||||
return new Date(modelValue - offset).toISOString().slice(0, 10);
|
||||
return new Date(modelValue.getTime() - offset).toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} serializedValue
|
||||
*/
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
deserializer(serializedValue) {
|
||||
return new Date(serializedValue);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ const tagString = 'lion-input-date';
|
|||
describe('<lion-input-date> integrations', () => {
|
||||
runInteractionStateMixinSuite({
|
||||
tagString,
|
||||
suffix: tagString,
|
||||
allowedModelValueTypes: [Date],
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,15 @@ import { html } from '@lion/core';
|
|||
import { localize } from '@lion/localize';
|
||||
import { localizeTearDown } from '@lion/localize/test-helpers.js';
|
||||
import { MaxDate } from '@lion/form-core';
|
||||
import { expect, fixture } from '@open-wc/testing';
|
||||
import { expect, fixture as _fixture } from '@open-wc/testing';
|
||||
import '../lion-input-date.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../src/LionInputDate').LionInputDate} LionInputDate
|
||||
* @typedef {import('lit-html').TemplateResult} TemplateResult
|
||||
*/
|
||||
const fixture = /** @type {(arg: TemplateResult) => Promise<LionInputDate>} */ (_fixture);
|
||||
|
||||
describe('<lion-input-date>', () => {
|
||||
beforeEach(() => {
|
||||
localizeTearDown();
|
||||
|
|
@ -24,16 +30,16 @@ describe('<lion-input-date>', () => {
|
|||
const el = await fixture(html`<lion-input-date></lion-input-date>`);
|
||||
el.modelValue = '2005/11/10';
|
||||
expect(el.hasFeedbackFor).to.include('error');
|
||||
expect(el.validationStates).to.have.a.property('error');
|
||||
expect(el.validationStates.error).to.have.a.property('IsDate');
|
||||
expect(el.validationStates).to.have.property('error');
|
||||
expect(el.validationStates.error).to.have.property('IsDate');
|
||||
|
||||
el.modelValue = new Date('2005/11/10');
|
||||
expect(el.hasFeedbackFor).not.to.include('error');
|
||||
expect(el.validationStates).to.have.a.property('error');
|
||||
expect(el.validationStates.error).not.to.have.a.property('IsDate');
|
||||
expect(el.validationStates).to.have.property('error');
|
||||
expect(el.validationStates.error).not.to.have.property('IsDate');
|
||||
});
|
||||
|
||||
it("does not throw on invalid dates like new Date('foo')", async () => {
|
||||
it("does not throw on invalid dates like new Date('20.10.'), which could happen while the user types", async () => {
|
||||
const el = await fixture(html`<lion-input-date></lion-input-date>`);
|
||||
expect(() => {
|
||||
el.modelValue = new Date('foo');
|
||||
|
|
@ -48,13 +54,13 @@ describe('<lion-input-date>', () => {
|
|||
></lion-input-date>
|
||||
`);
|
||||
expect(el.hasFeedbackFor).to.include('error');
|
||||
expect(el.validationStates).to.have.a.property('error');
|
||||
expect(el.validationStates.error).to.have.a.property('MaxDate');
|
||||
expect(el.validationStates).to.have.property('error');
|
||||
expect(el.validationStates.error).to.have.property('MaxDate');
|
||||
|
||||
el.modelValue = new Date('2017/06/14');
|
||||
expect(el.hasFeedbackFor).not.to.include('error');
|
||||
expect(el.validationStates).to.have.a.property('error');
|
||||
expect(el.validationStates.error).not.to.have.a.property('MaxDate');
|
||||
expect(el.validationStates).to.have.property('error');
|
||||
expect(el.validationStates.error).not.to.have.property('MaxDate');
|
||||
});
|
||||
|
||||
it('uses formatOptions.locale', async () => {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import { LocalizeMixin } from '@lion/localize';
|
|||
* LionInputEmail: extension of lion-input
|
||||
*
|
||||
* @customElement lion-input-email
|
||||
* @extends {LionInput}
|
||||
*/
|
||||
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/40110
|
||||
export class LionInputEmail extends LocalizeMixin(LionInput) {
|
||||
constructor() {
|
||||
super();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
import { expect, fixture } from '@open-wc/testing';
|
||||
import { expect, fixture as _fixture } from '@open-wc/testing';
|
||||
|
||||
import '../lion-input-email.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../src/LionInputEmail').LionInputEmail} LionInputEmail
|
||||
* @typedef {import('lit-html').TemplateResult} TemplateResult
|
||||
*/
|
||||
const fixture = /** @type {(arg: TemplateResult|string) => Promise<LionInputEmail>} */ (_fixture);
|
||||
|
||||
describe('<lion-input-email>', () => {
|
||||
it('has a type = text', async () => {
|
||||
const el = await fixture(`<lion-input-email></lion-input-email>`);
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import { IsIBAN } from './validators.js';
|
|||
|
||||
/**
|
||||
* `LionInputIban` is a class for an IBAN custom form element (`<lion-input-iban>`).
|
||||
*
|
||||
* @extends {LionInput}
|
||||
* @customElement lion-input-iban
|
||||
*/
|
||||
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/40110
|
||||
export class LionInputIban extends LocalizeMixin(LionInput) {
|
||||
constructor() {
|
||||
super();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { isValidIBAN } from 'ibantools';
|
|||
* Parses an IBAN trimming spaces and making uppercase.
|
||||
*
|
||||
* @param {string} viewValue value to be parsed
|
||||
* @return {string} parsed value
|
||||
* @return {string|undefined} parsed value
|
||||
*/
|
||||
export function parseIBAN(viewValue) {
|
||||
const trimmedValue = viewValue.replace(/\s/g, '').toUpperCase();
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const loadTranslations = async () => {
|
|||
}
|
||||
await localize.loadNamespace(
|
||||
{
|
||||
'lion-validate+iban': locale => {
|
||||
'lion-validate+iban': /** @param {string} locale */ locale => {
|
||||
switch (locale) {
|
||||
case 'bg-BG':
|
||||
return import('../translations/bg-BG.js');
|
||||
|
|
@ -86,7 +86,7 @@ const loadTranslations = async () => {
|
|||
}
|
||||
},
|
||||
},
|
||||
{ locale: localize.localize },
|
||||
{ locale: localize.locale },
|
||||
);
|
||||
loaded = true;
|
||||
};
|
||||
|
|
@ -96,11 +96,22 @@ export class IsIBAN extends Validator {
|
|||
return 'IsIBAN';
|
||||
}
|
||||
|
||||
/** @param {string} value */
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
execute(value) {
|
||||
return !isValidIBAN(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} [data]
|
||||
* @param {*} [data.modelValue]
|
||||
* @param {string} [data.fieldName]
|
||||
* @param {*} [data.params]
|
||||
* @param {string} [data.type]
|
||||
* @param {Object.<string,?>} [data.config]
|
||||
* @param {string} [data.name]
|
||||
* @returns {Promise<string|Node>}
|
||||
*/
|
||||
static async getMessage(data) {
|
||||
await loadTranslations();
|
||||
return localize.msg('lion-validate+iban:error.IsIBAN', data);
|
||||
|
|
@ -112,6 +123,10 @@ export class IsCountryIBAN extends IsIBAN {
|
|||
return 'IsCountryIBAN';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?} [value]
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
execute(value) {
|
||||
const notIBAN = super.execute(value);
|
||||
if (value.slice(0, 2) !== this.param) {
|
||||
|
|
@ -123,6 +138,16 @@ export class IsCountryIBAN extends IsIBAN {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} [data]
|
||||
* @param {*} [data.modelValue]
|
||||
* @param {string} [data.fieldName]
|
||||
* @param {*} [data.params]
|
||||
* @param {string} [data.type]
|
||||
* @param {Object.<string,?>} [data.config]
|
||||
* @param {string} [data.name]
|
||||
* @returns {Promise<string|Node>}
|
||||
*/
|
||||
static async getMessage(data) {
|
||||
await loadTranslations();
|
||||
return localize.msg('lion-validate+iban:error.IsCountryIBAN', data);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { expect, fixture } from '@open-wc/testing';
|
||||
import { expect, fixture as _fixture } from '@open-wc/testing';
|
||||
import { html } from '@lion/core';
|
||||
|
||||
import { IsCountryIBAN } from '../src/validators.js';
|
||||
|
|
@ -7,6 +7,12 @@ import { parseIBAN } from '../src/parsers.js';
|
|||
|
||||
import '../lion-input-iban.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../src/LionInputIban').LionInputIban} LionInputIban
|
||||
* @typedef {import('lit-html').TemplateResult} TemplateResult
|
||||
*/
|
||||
const fixture = /** @type {(arg: TemplateResult|string) => Promise<LionInputIban>} */ (_fixture);
|
||||
|
||||
describe('<lion-input-iban>', () => {
|
||||
it('uses formatIBAN for formatting', async () => {
|
||||
const el = await fixture(`<lion-input-iban></lion-input-iban>`);
|
||||
|
|
@ -27,12 +33,12 @@ describe('<lion-input-iban>', () => {
|
|||
const el = await fixture(`<lion-input-iban></lion-input-iban>`);
|
||||
el.modelValue = 'FOO';
|
||||
expect(el.hasFeedbackFor).to.include('error');
|
||||
expect(el.validationStates).to.have.a.property('error');
|
||||
expect(el.validationStates.error).to.have.a.property('IsIBAN');
|
||||
expect(el.validationStates).to.have.property('error');
|
||||
expect(el.validationStates.error).to.have.property('IsIBAN');
|
||||
el.modelValue = 'DE89370400440532013000';
|
||||
expect(el.hasFeedbackFor).not.to.include('error');
|
||||
expect(el.validationStates).to.have.a.property('error');
|
||||
expect(el.validationStates.error).not.to.have.a.property('IsIBAN');
|
||||
expect(el.validationStates).to.have.property('error');
|
||||
expect(el.validationStates.error).not.to.have.property('IsIBAN');
|
||||
});
|
||||
|
||||
it('can apply validator "IsCountryIBAN" to restrict countries', async () => {
|
||||
|
|
@ -41,17 +47,17 @@ describe('<lion-input-iban>', () => {
|
|||
`);
|
||||
el.modelValue = 'DE89370400440532013000';
|
||||
expect(el.hasFeedbackFor).to.include('error');
|
||||
expect(el.validationStates).to.have.a.property('error');
|
||||
expect(el.validationStates.error).to.have.a.property('IsCountryIBAN');
|
||||
expect(el.validationStates).to.have.property('error');
|
||||
expect(el.validationStates.error).to.have.property('IsCountryIBAN');
|
||||
el.modelValue = 'NL17INGB0002822608';
|
||||
expect(el.hasFeedbackFor).not.to.include('error');
|
||||
expect(el.validationStates).to.have.a.property('error');
|
||||
expect(el.validationStates.error).not.to.have.a.property('IsCountryIBAN');
|
||||
expect(el.validationStates).to.have.property('error');
|
||||
expect(el.validationStates.error).not.to.have.property('IsCountryIBAN');
|
||||
el.modelValue = 'FOO';
|
||||
expect(el.hasFeedbackFor).to.include('error');
|
||||
expect(el.validationStates).to.have.a.property('error');
|
||||
expect(el.validationStates.error).to.have.a.property('IsIBAN');
|
||||
expect(el.validationStates.error).to.have.a.property('IsCountryIBAN');
|
||||
expect(el.validationStates).to.have.property('error');
|
||||
expect(el.validationStates.error).to.have.property('IsIBAN');
|
||||
expect(el.validationStates.error).to.have.property('IsCountryIBAN');
|
||||
});
|
||||
|
||||
it('is accessible', async () => {
|
||||
|
|
|
|||
|
|
@ -3,18 +3,31 @@ import { css, html, unsafeCSS } from '@lion/core';
|
|||
import { LionInput } from '@lion/input';
|
||||
import { formatNumber, LocalizeMixin } from '@lion/localize';
|
||||
|
||||
/**
|
||||
* @typedef {import('lit-element').CSSResult} CSSResult
|
||||
*/
|
||||
|
||||
/**
|
||||
* LionInputRange: extension of lion-input.
|
||||
*
|
||||
* @customElement `lion-input-range`
|
||||
* @extends LionInput
|
||||
*/
|
||||
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/40110 + false positive for incompatible static get properties. Lit-element merges super properties already for you.
|
||||
export class LionInputRange extends LocalizeMixin(LionInput) {
|
||||
static get properties() {
|
||||
return {
|
||||
min: Number,
|
||||
max: Number,
|
||||
unit: String,
|
||||
min: {
|
||||
type: Number,
|
||||
reflect: true,
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
reflect: true,
|
||||
},
|
||||
unit: {
|
||||
type: String,
|
||||
reflect: true,
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
reflect: true,
|
||||
|
|
@ -26,6 +39,9 @@ export class LionInputRange extends LocalizeMixin(LionInput) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CSSResult} scope
|
||||
*/
|
||||
static rangeStyles(scope) {
|
||||
return css`
|
||||
/* Custom input range styling comes here, be aware that this won't work for polyfilled browsers */
|
||||
|
|
@ -37,9 +53,24 @@ export class LionInputRange extends LocalizeMixin(LionInput) {
|
|||
`;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (super.connectedCallback) super.connectedCallback();
|
||||
constructor() {
|
||||
super();
|
||||
this.min = Infinity;
|
||||
this.max = Infinity;
|
||||
this.step = 1;
|
||||
this.unit = '';
|
||||
this.type = 'range';
|
||||
this.noMinMaxLabels = false;
|
||||
/**
|
||||
* @param {string} modelValue
|
||||
*/
|
||||
this.parser = modelValue => parseFloat(modelValue);
|
||||
this.scopedClass = `${this.localName}-${Math.floor(Math.random() * 10000)}`;
|
||||
this.__styleTag = document.createElement('style');
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
/* eslint-disable-next-line wc/no-self-class */
|
||||
this.classList.add(this.scopedClass);
|
||||
|
||||
|
|
@ -47,38 +78,34 @@ export class LionInputRange extends LocalizeMixin(LionInput) {
|
|||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (super.disconnectedCallback) super.disconnectedCallback();
|
||||
super.disconnectedCallback();
|
||||
this.__teardownStyleTag();
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.parser = modelValue => parseFloat(modelValue);
|
||||
this.scopedClass = `${this.localName}-${Math.floor(Math.random() * 10000)}`;
|
||||
}
|
||||
|
||||
/** @param {import('lit-element').PropertyValues } changedProperties */
|
||||
updated(changedProperties) {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (changedProperties.has('min')) {
|
||||
this._inputNode.min = this.min;
|
||||
this._inputNode.min = `${this.min}`;
|
||||
}
|
||||
|
||||
if (changedProperties.has('max')) {
|
||||
this._inputNode.max = this.max;
|
||||
this._inputNode.max = `${this.max}`;
|
||||
}
|
||||
|
||||
if (changedProperties.has('step')) {
|
||||
this._inputNode.step = this.step;
|
||||
this._inputNode.step = `${this.step}`;
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {import('lit-element').PropertyValues } changedProperties */
|
||||
firstUpdated(changedProperties) {
|
||||
super.firstUpdated(changedProperties);
|
||||
if (changedProperties.has('modelValue')) {
|
||||
// TODO: find out why this hack is needed to display the initial modelValue
|
||||
this.updateComplete.then(() => {
|
||||
this._inputNode.value = this.modelValue;
|
||||
this._inputNode.value = `${this.modelValue}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -86,7 +113,7 @@ export class LionInputRange extends LocalizeMixin(LionInput) {
|
|||
_inputGroupTemplate() {
|
||||
return html`
|
||||
<div>
|
||||
<span class="input-range__value">${formatNumber(this.formattedValue)}</span>
|
||||
<span class="input-range__value">${formatNumber(parseFloat(this.formattedValue))}</span>
|
||||
<span class="input-range__unit">${this.unit}</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
|
|
@ -117,8 +144,9 @@ export class LionInputRange extends LocalizeMixin(LionInput) {
|
|||
}
|
||||
|
||||
__setupStyleTag() {
|
||||
this.__styleTag = document.createElement('style');
|
||||
this.__styleTag.textContent = this.constructor.rangeStyles(unsafeCSS(this.scopedClass));
|
||||
this.__styleTag.textContent = /** @type {typeof LionInputRange} */ (this.constructor)
|
||||
.rangeStyles(unsafeCSS(this.scopedClass))
|
||||
.toString();
|
||||
this.insertBefore(this.__styleTag, this.childNodes[0]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
import { expect, fixture, nextFrame, html } from '@open-wc/testing';
|
||||
import { expect, fixture as _fixture, nextFrame, html } from '@open-wc/testing';
|
||||
|
||||
import '../lion-input-range.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../src/LionInputRange').LionInputRange} LionInputRange
|
||||
* @typedef {import('lit-html').TemplateResult} TemplateResult
|
||||
*/
|
||||
const fixture = /** @type {(arg: TemplateResult|string) => Promise<LionInputRange>} */ (_fixture);
|
||||
|
||||
describe('<lion-input-range>', () => {
|
||||
it('has a type = range', async () => {
|
||||
const el = await fixture(`<lion-input-range></lion-input-range>`);
|
||||
|
|
@ -41,35 +47,41 @@ describe('<lion-input-range>', () => {
|
|||
const el = await fixture(html`
|
||||
<lion-input-range .modelValue=${75} unit="${`%`}"></lion-input-range>
|
||||
`);
|
||||
expect(el.shadowRoot.querySelector('.input-range__value').innerText).to.equal('75');
|
||||
expect(el.shadowRoot.querySelector('.input-range__unit').innerText).to.equal('%');
|
||||
expect(
|
||||
/** @type {HTMLElement} */ (el.shadowRoot?.querySelector('.input-range__value')).innerText,
|
||||
).to.equal('75');
|
||||
expect(
|
||||
/** @type {HTMLElement} */ (el.shadowRoot?.querySelector('.input-range__unit')).innerText,
|
||||
).to.equal('%');
|
||||
});
|
||||
|
||||
it('displays 2 tick labels (min and max values) by default', async () => {
|
||||
const el = await fixture(`<lion-input-range min="100" max="200"></lion-input-range>`);
|
||||
expect(el.shadowRoot.querySelectorAll('.input-range__limits span').length).to.equal(2);
|
||||
expect(el.shadowRoot.querySelectorAll('.input-range__limits span')[0].innerText).to.equal(
|
||||
el.min,
|
||||
);
|
||||
expect(el.shadowRoot.querySelectorAll('.input-range__limits span')[1].innerText).to.equal(
|
||||
el.max,
|
||||
);
|
||||
expect(el.shadowRoot?.querySelectorAll('.input-range__limits span').length).to.equal(2);
|
||||
expect(
|
||||
/** @type {HTMLElement} */ (el.shadowRoot?.querySelectorAll('.input-range__limits span')[0])
|
||||
.innerText,
|
||||
).to.equal(el.min.toString());
|
||||
expect(
|
||||
/** @type {HTMLElement} */ (el.shadowRoot?.querySelectorAll('.input-range__limits span')[1])
|
||||
.innerText,
|
||||
).to.equal(el.max.toString());
|
||||
});
|
||||
|
||||
it('update min and max attributes when min and max property change', async () => {
|
||||
const el = await fixture(`<lion-input-range min="100" max="200"></lion-input-range>`);
|
||||
el.min = '120';
|
||||
el.max = '220';
|
||||
el.min = 120;
|
||||
el.max = 220;
|
||||
await nextFrame(); // sync to native element takes some time
|
||||
expect(el._inputNode.min).to.equal(el.min);
|
||||
expect(el._inputNode.max).to.equal(el.max);
|
||||
expect(el._inputNode.min).to.equal(el.min.toString());
|
||||
expect(el._inputNode.max).to.equal(el.max.toString());
|
||||
});
|
||||
|
||||
it('can hide the tick labels', async () => {
|
||||
const el = await fixture(
|
||||
`<lion-input-range min="100" max="200" no-min-max-labels></lion-input-range>`,
|
||||
);
|
||||
expect(el.shadowRoot.querySelectorAll('.input-group__input')[0]).dom.to.equal(`
|
||||
expect(el.shadowRoot?.querySelectorAll('.input-group__input')[0]).dom.to.equal(`
|
||||
<div class="input-group__input">
|
||||
<slot name="input"></slot>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import { IsNumber, MinNumber, MaxNumber } from '@lion/form-core';
|
|||
* `LionInputStepper` is a class for custom input-stepper element (`<lion-input-stepper>` web component).
|
||||
*
|
||||
* @customElement lion-input-stepper
|
||||
* @extends LitElement
|
||||
*/
|
||||
// @ts-expect-error false positive for incompatible static get properties. Lit-element merges super properties already for you.
|
||||
export class LionInputStepper extends LionInput {
|
||||
static get styles() {
|
||||
return [
|
||||
|
|
@ -22,30 +22,53 @@ export class LionInputStepper extends LionInput {
|
|||
|
||||
static get properties() {
|
||||
return {
|
||||
min: Number,
|
||||
max: Number,
|
||||
step: Number,
|
||||
modelValue: Number,
|
||||
__disableIncrementor: Boolean,
|
||||
__disableDecrementor: Boolean,
|
||||
min: {
|
||||
type: Number,
|
||||
reflect: true,
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
reflect: true,
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
reflect: true,
|
||||
},
|
||||
__disableIncrementor: { attribute: false },
|
||||
__disableDecrementor: { attribute: false },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get currentValue() {
|
||||
return parseFloat(this.value || 0);
|
||||
return parseFloat(this.value) || 0;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
/** @param {string} modelValue */
|
||||
this.parser = modelValue => parseFloat(modelValue);
|
||||
this.__disableIncrementor = false;
|
||||
this.__disableDecrementor = false;
|
||||
this.min = Infinity;
|
||||
this.max = Infinity;
|
||||
this.step = 1;
|
||||
this.values = {
|
||||
max: this.max,
|
||||
min: this.min,
|
||||
step: this.step,
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (super.connectedCallback) {
|
||||
super.connectedCallback();
|
||||
}
|
||||
super.connectedCallback();
|
||||
this.values = {
|
||||
max: this.max,
|
||||
min: this.min,
|
||||
step: this.step,
|
||||
};
|
||||
this.role = 'spinbutton';
|
||||
this.addEventListener('keydown', this.__keyDownHandler);
|
||||
this._inputNode.setAttribute('inputmode', 'decimal');
|
||||
|
|
@ -56,29 +79,27 @@ export class LionInputStepper extends LionInput {
|
|||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (super.disconnectedCallback) {
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
super.disconnectedCallback();
|
||||
this.removeEventListener('keydown', this.__keyDownHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update native input values
|
||||
* @param {Object} changedProps - changed props
|
||||
*/
|
||||
updated(changedProps) {
|
||||
super.updated(changedProps);
|
||||
/** @param {import('lit-element').PropertyValues } changedProperties */
|
||||
updated(changedProperties) {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (changedProps.has('min')) {
|
||||
this._inputNode.min = this.min;
|
||||
if (changedProperties.has('min')) {
|
||||
this._inputNode.min = `${this.min}`;
|
||||
this.values.min = this.min;
|
||||
}
|
||||
|
||||
if (changedProps.has('max')) {
|
||||
this._inputNode.max = this.max;
|
||||
if (changedProperties.has('max')) {
|
||||
this._inputNode.max = `${this.max}`;
|
||||
this.values.max = this.max;
|
||||
}
|
||||
|
||||
if (changedProps.has('step')) {
|
||||
this._inputNode.step = this.step;
|
||||
if (changedProperties.has('step')) {
|
||||
this._inputNode.step = `${this.step}`;
|
||||
this.values.step = this.step;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,27 +108,23 @@ export class LionInputStepper extends LionInput {
|
|||
* @private
|
||||
*/
|
||||
__setAriaLabelsAndValidator() {
|
||||
this.values = {
|
||||
max: parseFloat(this.max || Infinity),
|
||||
min: parseFloat(this.min || Infinity),
|
||||
step: parseFloat(this.step),
|
||||
};
|
||||
|
||||
const ariaAttributes = {
|
||||
'aria-valuemax': this.values.max,
|
||||
'aria-valuemin': this.values.min,
|
||||
};
|
||||
|
||||
let validators = Object.entries(ariaAttributes)
|
||||
const minMaxValidators = /** @type {(MaxNumber | MinNumber)[]} */ (Object.entries(
|
||||
ariaAttributes,
|
||||
)
|
||||
.map(([key, val]) => {
|
||||
if (val !== Infinity) {
|
||||
this.setAttribute(key, val);
|
||||
this.setAttribute(key, `${val}`);
|
||||
return key === 'aria-valuemax' ? new MaxNumber(val) : new MinNumber(val);
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(validator => validator);
|
||||
validators = [new IsNumber(), ...validators];
|
||||
.filter(validator => validator !== null));
|
||||
const validators = [new IsNumber(), ...minMaxValidators];
|
||||
this.defaultValidators.push(...validators);
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +151,7 @@ export class LionInputStepper extends LionInput {
|
|||
const { min, max } = this.values;
|
||||
this.__disableIncrementor = this.currentValue >= max && max !== Infinity;
|
||||
this.__disableDecrementor = this.currentValue <= min && min !== Infinity;
|
||||
this.setAttribute('aria-valuenow', this.currentValue);
|
||||
this.setAttribute('aria-valuenow', `${this.currentValue}`);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('user-input-changed', {
|
||||
bubbles: true,
|
||||
|
|
@ -150,7 +167,7 @@ export class LionInputStepper extends LionInput {
|
|||
const { step, max } = this.values;
|
||||
const newValue = this.currentValue + step;
|
||||
if (newValue <= max || max === Infinity) {
|
||||
this.value = newValue;
|
||||
this.value = `${newValue}`;
|
||||
this.__toggleSpinnerButtonsState();
|
||||
}
|
||||
}
|
||||
|
|
@ -163,7 +180,7 @@ export class LionInputStepper extends LionInput {
|
|||
const { step, min } = this.values;
|
||||
const newValue = this.currentValue - step;
|
||||
if (newValue >= min || min === Infinity) {
|
||||
this.value = newValue;
|
||||
this.value = `${newValue}`;
|
||||
this.__toggleSpinnerButtonsState();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import { expect, fixture, nextFrame, html } from '@open-wc/testing';
|
||||
import { expect, fixture as _fixture, nextFrame, html } from '@open-wc/testing';
|
||||
import '../lion-input-stepper.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('../src/LionInputStepper').LionInputStepper} LionInputStepper
|
||||
* @typedef {import('lit-html').TemplateResult} TemplateResult
|
||||
*/
|
||||
const fixture = /** @type {(arg: TemplateResult|string) => Promise<LionInputStepper>} */ (_fixture);
|
||||
|
||||
const defaultInputStepper = html`
|
||||
<lion-input-stepper name="year" label="Years"></lion-input-stepper>
|
||||
`;
|
||||
|
|
@ -18,26 +24,26 @@ describe('<lion-input-stepper>', () => {
|
|||
it('should increment the value to 1 on + button click', async () => {
|
||||
const el = await fixture(defaultInputStepper);
|
||||
expect(el.value).to.equal('');
|
||||
const incrementButton = el.shadowRoot.querySelector('[name=increment]');
|
||||
incrementButton.dispatchEvent(new Event('click'));
|
||||
const incrementButton = el.shadowRoot?.querySelector('[name=increment]');
|
||||
incrementButton?.dispatchEvent(new Event('click'));
|
||||
expect(el.value).to.equal('1');
|
||||
});
|
||||
|
||||
it('should decrement the value to -1 on - button click', async () => {
|
||||
const el = await fixture(defaultInputStepper);
|
||||
expect(el.value).to.equal('');
|
||||
const incrementButton = el.shadowRoot.querySelector('[name=decrement]');
|
||||
incrementButton.dispatchEvent(new Event('click'));
|
||||
const incrementButton = el.shadowRoot?.querySelector('[name=decrement]');
|
||||
incrementButton?.dispatchEvent(new Event('click'));
|
||||
expect(el.value).to.equal('-1');
|
||||
});
|
||||
|
||||
it('should update min and max attributes when min and max property change', async () => {
|
||||
const el = await fixture(inputStepperWithAttrs);
|
||||
el.min = '100';
|
||||
el.max = '200';
|
||||
el.min = 100;
|
||||
el.max = 200;
|
||||
await nextFrame();
|
||||
expect(el._inputNode.min).to.equal(el.min);
|
||||
expect(el._inputNode.max).to.equal(el.max);
|
||||
expect(el._inputNode.min).to.equal(el.min.toString());
|
||||
expect(el._inputNode.max).to.equal(el.max.toString());
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -60,8 +66,8 @@ describe('<lion-input-stepper>', () => {
|
|||
|
||||
it('updates aria-valuenow when stepper is changed', async () => {
|
||||
const el = await fixture(inputStepperWithAttrs);
|
||||
const incrementButton = el.shadowRoot.querySelector('[name=increment]');
|
||||
incrementButton.dispatchEvent(new Event('click'));
|
||||
const incrementButton = el.shadowRoot?.querySelector('[name=increment]');
|
||||
incrementButton?.dispatchEvent(new Event('click'));
|
||||
expect(el).to.have.attribute('aria-valuenow', '1');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -26,6 +26,11 @@
|
|||
"packages/form-core/**/*.js",
|
||||
"packages/input/**/*.js",
|
||||
"packages/input-amount/**/*.js",
|
||||
"packages/input-date/**/*.js",
|
||||
"packages/input-email/**/*.js",
|
||||
"packages/input-iban/**/*.js",
|
||||
"packages/input-range/**/*.js",
|
||||
"packages/input-stepper/**/*.js",
|
||||
"packages/listbox/src/*.js",
|
||||
"packages/localize/**/*.js",
|
||||
"packages/overlays/**/*.js",
|
||||
|
|
|
|||
Loading…
Reference in a new issue