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-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-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-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 | ✔️ |
|
||||
|
|
|
|||
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-email/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/select/stories/index.stories.js';
|
||||
import '../packages/fieldset/stories/index.stories.js';
|
||||
|
|
|
|||
Loading…
Reference in a new issue