feat(input-range): create input-range component
This commit is contained in:
parent
a0c9f0bd3e
commit
d81e5ea547
9 changed files with 419 additions and 0 deletions
|
|
@ -53,6 +53,7 @@ The accessibility column indicates whether the functionality is accessible in it
|
||||||
| [input-datepicker](./packages/input-datepicker) | [](https://www.npmjs.com/package/@lion/input-datepicker) | Input element for dates with a datepicker | ✔️ |
|
| [input-datepicker](./packages/input-datepicker) | [](https://www.npmjs.com/package/@lion/input-datepicker) | Input element for dates with a datepicker | ✔️ |
|
||||||
| [input-email](./packages/input-email) | [](https://www.npmjs.com/package/@lion/input-email) | Input element for e-mails | [#169][i169] |
|
| [input-email](./packages/input-email) | [](https://www.npmjs.com/package/@lion/input-email) | Input element for e-mails | [#169][i169] |
|
||||||
| [input-iban](./packages/input-iban) | [](https://www.npmjs.com/package/@lion/input-iban) | Input element for IBANs | [#169][i169] |
|
| [input-iban](./packages/input-iban) | [](https://www.npmjs.com/package/@lion/input-iban) | Input element for IBANs | [#169][i169] |
|
||||||
|
| [input-range](./packages/input-range) | [](https://www.npmjs.com/package/@lion/input-range) | Input element for a range of values | ✔️ |
|
||||||
| [radio](./packages/radio) | [](https://www.npmjs.com/package/@lion/radio) | Radio from element | ✔️ |
|
| [radio](./packages/radio) | [](https://www.npmjs.com/package/@lion/radio) | Radio from element | ✔️ |
|
||||||
| [radio-group](./packages/radio-group) | [](https://www.npmjs.com/package/@lion/radio-group) | Group of radios | ✔️ |
|
| [radio-group](./packages/radio-group) | [](https://www.npmjs.com/package/@lion/radio-group) | Group of radios | ✔️ |
|
||||||
| [select](./packages/select) | [](https://www.npmjs.com/package/@lion/select) | Simple native dropdown element | ✔️ |
|
| [select](./packages/select) | [](https://www.npmjs.com/package/@lion/select) | Simple native dropdown element | ✔️ |
|
||||||
|
|
|
||||||
32
packages/input-range/README.md
Normal file
32
packages/input-range/README.md
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Input range
|
||||||
|
|
||||||
|
[//]: # 'AUTO INSERT HEADER PREPUBLISH'
|
||||||
|
|
||||||
|
`lion-input-range` component is based on the native range input.
|
||||||
|
Its purpose is to provide a way for users to select one value from a range of values.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Based on [lion-input](../input).
|
||||||
|
- Shows `modelValue` and `unit` above the range input.
|
||||||
|
- Shows `min` and `max` value after the range input.
|
||||||
|
- Can hide the `min` and `max` value via `no-min-max-labels`.
|
||||||
|
- Makes use of [formatNumber](../localize/docs/number.md) for formatting and parsing.
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm i --save @lion/input-range
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
import '@lion/input-range/lion-input-range.js';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```html
|
||||||
|
<lion-input-range min="200" max="500" .modelValue="${300}" label="Input range"></lion-input-range>
|
||||||
|
```
|
||||||
1
packages/input-range/index.js
Normal file
1
packages/input-range/index.js
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export { LionInputRange } from './src/LionInputRange.js';
|
||||||
3
packages/input-range/lion-input-range.js
Normal file
3
packages/input-range/lion-input-range.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { LionInputRange } from './src/LionInputRange.js';
|
||||||
|
|
||||||
|
customElements.define('lion-input-range', LionInputRange);
|
||||||
43
packages/input-range/package.json
Normal file
43
packages/input-range/package.json
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"name": "@lion/input-range",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "Provide a way for users to select one value from a range of values",
|
||||||
|
"author": "ing-bank",
|
||||||
|
"homepage": "https://github.com/ing-bank/lion/",
|
||||||
|
"license": "MIT",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/ing-bank/lion.git",
|
||||||
|
"directory": "packages/input-range"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"prepublishOnly": "../../scripts/npm-prepublish.js"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"lion",
|
||||||
|
"web-components",
|
||||||
|
"input-range",
|
||||||
|
"slider"
|
||||||
|
],
|
||||||
|
"main": "index.js",
|
||||||
|
"module": "index.js",
|
||||||
|
"files": [
|
||||||
|
"src",
|
||||||
|
"stories",
|
||||||
|
"test",
|
||||||
|
"*.js"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@lion/core": "0.3.0",
|
||||||
|
"@lion/field": "0.6.8",
|
||||||
|
"@lion/input": "0.4.0",
|
||||||
|
"@lion/localize": "0.7.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@open-wc/demoing-storybook": "^0.2.0",
|
||||||
|
"@open-wc/testing": "^2.3.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
129
packages/input-range/src/LionInputRange.js
Normal file
129
packages/input-range/src/LionInputRange.js
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
|
import { LocalizeMixin, formatNumber } from '@lion/localize';
|
||||||
|
import { FieldCustomMixin } from '@lion/field';
|
||||||
|
import { LionInput } from '@lion/input';
|
||||||
|
import { html, css, unsafeCSS } from '@lion/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LionInputRange: extension of lion-input
|
||||||
|
*
|
||||||
|
* @customElement `lion-input-range`
|
||||||
|
* @extends LionInput
|
||||||
|
*/
|
||||||
|
export class LionInputRange extends FieldCustomMixin(LocalizeMixin(LionInput)) {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
min: Number,
|
||||||
|
max: Number,
|
||||||
|
unit: String,
|
||||||
|
step: {
|
||||||
|
type: Number,
|
||||||
|
reflect: true,
|
||||||
|
},
|
||||||
|
noMinMaxLabels: {
|
||||||
|
type: Boolean,
|
||||||
|
attribute: 'no-min-max-labels',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static rangeStyles(scope) {
|
||||||
|
return css`
|
||||||
|
/* Custom input range styling comes here, be aware that this won't work for polyfilled browsers */
|
||||||
|
.${scope} .form-control {
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
if (super.connectedCallback) super.connectedCallback();
|
||||||
|
this.type = 'range';
|
||||||
|
/* eslint-disable-next-line wc/no-self-class */
|
||||||
|
this.classList.add(this.scopedClass);
|
||||||
|
|
||||||
|
this.__setupStyleTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
if (super.disconnectedCallback) super.disconnectedCallback();
|
||||||
|
this.__teardownStyleTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.parser = modelValue => parseFloat(modelValue);
|
||||||
|
this.scopedClass = `${this.localName}-${Math.floor(Math.random() * 10000)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
updated(changedProperties) {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
|
||||||
|
if (changedProperties.has('min')) {
|
||||||
|
this._inputNode.min = this.min;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changedProperties.has('max')) {
|
||||||
|
this._inputNode.max = this.max;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changedProperties.has('step')) {
|
||||||
|
this._inputNode.step = this.step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inputGroupTemplate() {
|
||||||
|
return html`
|
||||||
|
<div>
|
||||||
|
<span class="input-range__value">${formatNumber(this.formattedValue)}</span>
|
||||||
|
<span class="input-range__unit">${this.unit}</span>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
${this.inputGroupBeforeTemplate()}
|
||||||
|
<div class="input-group__container">
|
||||||
|
${this.inputGroupPrefixTemplate()} ${this.inputGroupInputTemplate()}
|
||||||
|
${this.inputGroupSuffixTemplate()}
|
||||||
|
</div>
|
||||||
|
${this.inputGroupAfterTemplate()}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputGroupInputTemplate() {
|
||||||
|
return html`
|
||||||
|
<div class="input-group__input">
|
||||||
|
<slot name="input"></slot>
|
||||||
|
${!this.noMinMaxLabels
|
||||||
|
? html`
|
||||||
|
<div class="input-range__limits">
|
||||||
|
<span>${formatNumber(this.min)}</span>
|
||||||
|
<span>${formatNumber(this.max)}</span>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ''}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
__setupStyleTag() {
|
||||||
|
this.__styleTag = document.createElement('style');
|
||||||
|
this.__styleTag.innerHTML = this.constructor.rangeStyles(unsafeCSS(this.scopedClass));
|
||||||
|
this.insertBefore(this.__styleTag, this.childNodes[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
__teardownStyleTag() {
|
||||||
|
this.removeChild(this.__styleTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
111
packages/input-range/stories/index.stories.js
Normal file
111
packages/input-range/stories/index.stories.js
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
import { storiesOf, html } from '@open-wc/demoing-storybook';
|
||||||
|
import { css } from '@lion/core';
|
||||||
|
|
||||||
|
import '../lion-input-range.js';
|
||||||
|
|
||||||
|
const rangeDemoStyle = css`
|
||||||
|
.demo-range {
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
storiesOf('Forms | Input Range', module)
|
||||||
|
.add(
|
||||||
|
'Default',
|
||||||
|
() => html`
|
||||||
|
<style>
|
||||||
|
${rangeDemoStyle}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<lion-input-range
|
||||||
|
class="demo-range"
|
||||||
|
min="200"
|
||||||
|
max="500"
|
||||||
|
label="Input range"
|
||||||
|
></lion-input-range>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
'Help text',
|
||||||
|
() => html`
|
||||||
|
<style>
|
||||||
|
${rangeDemoStyle}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<lion-input-range
|
||||||
|
class="demo-range"
|
||||||
|
min="200"
|
||||||
|
max="500"
|
||||||
|
.modelValue="${300}"
|
||||||
|
label="Input range"
|
||||||
|
help-text="A help text can show additional hints"
|
||||||
|
></lion-input-range>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
'Units',
|
||||||
|
() => html`
|
||||||
|
<style>
|
||||||
|
${rangeDemoStyle}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<lion-input-range
|
||||||
|
class="demo-range"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
.modelValue="${50}"
|
||||||
|
unit="%"
|
||||||
|
label="Percentage"
|
||||||
|
></lion-input-range>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
'With steps',
|
||||||
|
() => html`
|
||||||
|
<style>
|
||||||
|
${rangeDemoStyle}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<lion-input-range
|
||||||
|
class="demo-range"
|
||||||
|
min="200"
|
||||||
|
max="500"
|
||||||
|
step="50"
|
||||||
|
.modelValue="${300}"
|
||||||
|
label="Input range"
|
||||||
|
help-text="This slider uses increments of 50"
|
||||||
|
></lion-input-range>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
'No min max labels',
|
||||||
|
() => html`
|
||||||
|
<style>
|
||||||
|
${rangeDemoStyle}
|
||||||
|
</style>
|
||||||
|
<lion-input-range
|
||||||
|
class="demo-range"
|
||||||
|
label="Input range"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
no-min-max-labels
|
||||||
|
></lion-input-range>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
'Disabled',
|
||||||
|
() => html`
|
||||||
|
<style>
|
||||||
|
${rangeDemoStyle}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<lion-input-range
|
||||||
|
class="demo-range"
|
||||||
|
min="200"
|
||||||
|
max="500"
|
||||||
|
.modelValue="${300}"
|
||||||
|
disabled
|
||||||
|
label="Input range"
|
||||||
|
></lion-input-range>
|
||||||
|
`,
|
||||||
|
);
|
||||||
98
packages/input-range/test/lion-input-range.test.js
Normal file
98
packages/input-range/test/lion-input-range.test.js
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
import { expect, fixture, nextFrame, html } from '@open-wc/testing';
|
||||||
|
|
||||||
|
import '../lion-input-range.js';
|
||||||
|
|
||||||
|
describe('<lion-input-range>', () => {
|
||||||
|
it('has a type = range', async () => {
|
||||||
|
const el = await fixture(`<lion-input-range></lion-input-range>`);
|
||||||
|
expect(el._inputNode.type).to.equal('range');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('contain the scoped css class for the slotted input style', async () => {
|
||||||
|
const el = await fixture(`
|
||||||
|
<lion-input-range></lion-input-range>
|
||||||
|
`);
|
||||||
|
expect(el.classList.contains(el.scopedClass)).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds a style tag as the first child which contains a class selector to the element', async () => {
|
||||||
|
const el = await fixture(`
|
||||||
|
<lion-input-range></lion-input-range>
|
||||||
|
`);
|
||||||
|
expect(el.children[0].tagName).to.equal('STYLE');
|
||||||
|
expect(el.children[0].innerHTML).to.contain(el.scopedClass);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does cleanup of the style tag when moving or deleting the el', async () => {
|
||||||
|
const wrapper = await fixture(`
|
||||||
|
<div></div>
|
||||||
|
`);
|
||||||
|
const wrapper2 = await fixture(`
|
||||||
|
<div></div>
|
||||||
|
`);
|
||||||
|
const el = document.createElement('lion-input-range');
|
||||||
|
wrapper.appendChild(el);
|
||||||
|
wrapper2.appendChild(el);
|
||||||
|
|
||||||
|
expect(el.children[1].tagName).to.not.equal('STYLE');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays the modelValue and unit', async () => {
|
||||||
|
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('%');
|
||||||
|
});
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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';
|
||||||
|
await nextFrame();
|
||||||
|
expect(el._inputNode.min).to.equal(el.min);
|
||||||
|
expect(el._inputNode.max).to.equal(el.max);
|
||||||
|
});
|
||||||
|
|
||||||
|
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(`
|
||||||
|
<div class="input-group__input">
|
||||||
|
<slot name="input"></slot>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parser method should return a value parsed into a number format', async () => {
|
||||||
|
const el = await fixture(html`
|
||||||
|
<lion-input-range min="100" max="200" .modelValue=${150}></lion-input-range>
|
||||||
|
`);
|
||||||
|
expect(el.modelValue).to.equal(150);
|
||||||
|
el._inputNode.value = '130';
|
||||||
|
el._inputNode.dispatchEvent(new Event('input'));
|
||||||
|
expect(el.modelValue).to.equal(130);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is accessible', async () => {
|
||||||
|
const el = await fixture(`<lion-input-range label="range"></lion-input-range>`);
|
||||||
|
await expect(el).to.be.accessible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is accessible when disabled', async () => {
|
||||||
|
const el = await fixture(`<lion-input-range label="range" disabled></lion-input-range>`);
|
||||||
|
await expect(el).to.be.accessible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -8,6 +8,7 @@ import '../packages/input-date/stories/index.stories.js';
|
||||||
import '../packages/input-datepicker/stories/index.stories.js';
|
import '../packages/input-datepicker/stories/index.stories.js';
|
||||||
import '../packages/input-email/stories/index.stories.js';
|
import '../packages/input-email/stories/index.stories.js';
|
||||||
import '../packages/input-iban/stories/index.stories.js';
|
import '../packages/input-iban/stories/index.stories.js';
|
||||||
|
import '../packages/input-range/stories/index.stories.js';
|
||||||
import '../packages/option/stories/index.stories.js';
|
import '../packages/option/stories/index.stories.js';
|
||||||
import '../packages/select/stories/index.stories.js';
|
import '../packages/select/stories/index.stories.js';
|
||||||
import '../packages/fieldset/stories/index.stories.js';
|
import '../packages/fieldset/stories/index.stories.js';
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue