diff --git a/.changeset/stale-moons-wait.md b/.changeset/stale-moons-wait.md new file mode 100644 index 000000000..7c5dbd784 --- /dev/null +++ b/.changeset/stale-moons-wait.md @@ -0,0 +1,5 @@ +--- +'@lion/input-stepper': minor +--- + +Release initial version of an input stepper diff --git a/README.md b/README.md index 1a7ac9363..664bf80c7 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ The accessibility column indicates whether the functionality is accessible in it | [input-email](https://lion-web-components.netlify.app/?path=/docs/forms-input-email--main) | [![input-email](https://img.shields.io/npm/v/@lion/input-email.svg)](https://www.npmjs.com/package/@lion/input-email) | Input element for e-mails | ✔️ | | [input-iban](https://lion-web-components.netlify.app/?path=/docs/forms-input-iban--main) | [![input-iban](https://img.shields.io/npm/v/@lion/input-iban.svg)](https://www.npmjs.com/package/@lion/input-iban) | Input element for IBANs | ✔️ | | [input-range](https://lion-web-components.netlify.app/?path=/docs/forms-input-range--main) | [![input-range](https://img.shields.io/npm/v/@lion/input-range.svg)](https://www.npmjs.com/package/@lion/input-range) | Input element for a range of values | ✔️ | +| [input-stepper](https://lion-web-components.netlify.app/?path=/docs/forms-input-stepper--main) | [![input-stepper](https://img.shields.io/npm/v/@lion/input-stepper.svg)](https://www.npmjs.com/package/@lion/input-stepper) | Input stepper element for the predefined range | ✔️ | | [radio-group](https://lion-web-components.netlify.app/?path=/docs/forms-radio-group--main) | [![radio-group](https://img.shields.io/npm/v/@lion/radio-group.svg)](https://www.npmjs.com/package/@lion/radio-group) | Group of radios | ✔️ | | [select](https://lion-web-components.netlify.app/?path=/docs/forms-select--main) | [![select](https://img.shields.io/npm/v/@lion/select.svg)](https://www.npmjs.com/package/@lion/select) | Simple native dropdown element | ✔️ | | [select-rich](https://lion-web-components.netlify.app/?path=/docs/forms-select-rich--main) | [![select-rich](https://img.shields.io/npm/v/@lion/select-rich.svg)](https://www.npmjs.com/package/@lion/select-rich) | 'rich' version of the native dropdown element | [#243][i243] | diff --git a/packages/form-integrations/README.md b/packages/form-integrations/README.md index 3b127641f..761ca532d 100644 --- a/packages/form-integrations/README.md +++ b/packages/form-integrations/README.md @@ -20,21 +20,22 @@ For a more in depth description look into the [Form System Overview](?path=/docs ## Packages -| Package | Version | Description | -| -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | -| [checkbox-group](?path=/docs/forms-checkbox-group--main) | [![checkbox-group](https://img.shields.io/npm/v/@lion/checkbox-group.svg)](https://www.npmjs.com/package/@lion/checkbox-group) | Group of checkboxes | -| [form-core](?path=/docs/forms-system-overview--page) | [![form-core](https://img.shields.io/npm/v/@lion/form-core.svg)](https://www.npmjs.com/package/@lion/form-core) | Core functionality for all controls | -| [form-integrations](?path=/docs/forms-features-overview--main) | [![form-integrations](https://img.shields.io/npm/v/@lion/form-integrations.svg)](https://www.npmjs.com/package/@lion/form-integrations) | Shows form elements in an integrated way | -| [fieldset](?path=/docs/forms-fieldset-overview--main) | [![fieldset](https://img.shields.io/npm/v/@lion/fieldset.svg)](https://www.npmjs.com/package/@lion/fieldset) | Group for form inputs | -| [form](?path=/docs/forms-form-overview--main) | [![form](https://img.shields.io/npm/v/@lion/form.svg)](https://www.npmjs.com/package/@lion/form) | Wrapper for multiple form elements | -| [input](?path=/docs/forms-input--main) | [![input](https://img.shields.io/npm/v/@lion/input.svg)](https://www.npmjs.com/package/@lion/input) | Input element for strings | -| [input-amount](?path=/docs/forms-input-amount--main) | [![input-amount](https://img.shields.io/npm/v/@lion/input-amount.svg)](https://www.npmjs.com/package/@lion/input-amount) | Input element for amounts | -| [input-date](?path=/docs/forms-input-date--main) | [![input-date](https://img.shields.io/npm/v/@lion/input-date.svg)](https://www.npmjs.com/package/@lion/input-date) | Input element for dates | -| [input-datepicker](?path=/docs/forms-input-datepicker--main) | [![input-datepicker](https://img.shields.io/npm/v/@lion/input-datepicker.svg)](https://www.npmjs.com/package/@lion/input-datepicker) | Input element for dates with a datepicker | -| [input-email](?path=/docs/forms-input-email--main) | [![input-email](https://img.shields.io/npm/v/@lion/input-email.svg)](https://www.npmjs.com/package/@lion/input-email) | Input element for e-mails | -| [input-iban](?path=/docs/forms-input-iban--main) | [![input-iban](https://img.shields.io/npm/v/@lion/input-iban.svg)](https://www.npmjs.com/package/@lion/input-iban) | Input element for IBANs | -| [input-range](?path=/docs/forms-input-range--main) | [![input-range](https://img.shields.io/npm/v/@lion/input-range.svg)](https://www.npmjs.com/package/@lion/input-range) | Input element for a range of values | -| [radio-group](?path=/docs/forms-radio-group--main) | [![radio-group](https://img.shields.io/npm/v/@lion/radio-group.svg)](https://www.npmjs.com/package/@lion/radio-group) | Group of radios | -| [select](?path=/docs/forms-select--main) | [![select](https://img.shields.io/npm/v/@lion/select.svg)](https://www.npmjs.com/package/@lion/select) | Simple native dropdown element | -| [select-rich](?path=/docs/forms-select-rich--main) | [![select-rich](https://img.shields.io/npm/v/@lion/select-rich.svg)](https://www.npmjs.com/package/@lion/select-rich) | 'rich' version of the native dropdown element | -| [textarea](?path=/docs/forms-textarea--main) | [![textarea](https://img.shields.io/npm/v/@lion/textarea.svg)](https://www.npmjs.com/package/@lion/textarea) | Multiline text input | +| Package | Version | Description | +| -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | +| [checkbox-group](?path=/docs/forms-checkbox-group--main) | [![checkbox-group](https://img.shields.io/npm/v/@lion/checkbox-group.svg)](https://www.npmjs.com/package/@lion/checkbox-group) | Group of checkboxes | +| [form-core](?path=/docs/forms-system-overview--page) | [![form-core](https://img.shields.io/npm/v/@lion/form-core.svg)](https://www.npmjs.com/package/@lion/form-core) | Core functionality for all controls | +| [form-integrations](?path=/docs/forms-features-overview--main) | [![form-integrations](https://img.shields.io/npm/v/@lion/form-integrations.svg)](https://www.npmjs.com/package/@lion/form-integrations) | Shows form elements in an integrated way | +| [fieldset](?path=/docs/forms-fieldset-overview--main) | [![fieldset](https://img.shields.io/npm/v/@lion/fieldset.svg)](https://www.npmjs.com/package/@lion/fieldset) | Group for form inputs | +| [form](?path=/docs/forms-form-overview--main) | [![form](https://img.shields.io/npm/v/@lion/form.svg)](https://www.npmjs.com/package/@lion/form) | Wrapper for multiple form elements | +| [input](?path=/docs/forms-input--main) | [![input](https://img.shields.io/npm/v/@lion/input.svg)](https://www.npmjs.com/package/@lion/input) | Input element for strings | +| [input-amount](?path=/docs/forms-input-amount--main) | [![input-amount](https://img.shields.io/npm/v/@lion/input-amount.svg)](https://www.npmjs.com/package/@lion/input-amount) | Input element for amounts | +| [input-date](?path=/docs/forms-input-date--main) | [![input-date](https://img.shields.io/npm/v/@lion/input-date.svg)](https://www.npmjs.com/package/@lion/input-date) | Input element for dates | +| [input-datepicker](?path=/docs/forms-input-datepicker--main) | [![input-datepicker](https://img.shields.io/npm/v/@lion/input-datepicker.svg)](https://www.npmjs.com/package/@lion/input-datepicker) | Input element for dates with a datepicker | +| [input-email](?path=/docs/forms-input-email--main) | [![input-email](https://img.shields.io/npm/v/@lion/input-email.svg)](https://www.npmjs.com/package/@lion/input-email) | Input element for e-mails | +| [input-iban](?path=/docs/forms-input-iban--main) | [![input-iban](https://img.shields.io/npm/v/@lion/input-iban.svg)](https://www.npmjs.com/package/@lion/input-iban) | Input element for IBANs | +| [input-range](?path=/docs/forms-input-range--main) | [![input-range](https://img.shields.io/npm/v/@lion/input-range.svg)](https://www.npmjs.com/package/@lion/input-range) | Input element for a range of values | +| [input-stepper](?path=/docs/forms-input-stepper--main) | [![input-stepper](https://img.shields.io/npm/v/@lion/input-stepper.svg)](https://www.npmjs.com/package/@lion/input-stepper) | Input stepper element for the predefined range | +| [radio-group](?path=/docs/forms-radio-group--main) | [![radio-group](https://img.shields.io/npm/v/@lion/radio-group.svg)](https://www.npmjs.com/package/@lion/radio-group) | Group of radios | +| [select](?path=/docs/forms-select--main) | [![select](https://img.shields.io/npm/v/@lion/select.svg)](https://www.npmjs.com/package/@lion/select) | Simple native dropdown element | +| [select-rich](?path=/docs/forms-select-rich--main) | [![select-rich](https://img.shields.io/npm/v/@lion/select-rich.svg)](https://www.npmjs.com/package/@lion/select-rich) | 'rich' version of the native dropdown element | +| [textarea](?path=/docs/forms-textarea--main) | [![textarea](https://img.shields.io/npm/v/@lion/textarea.svg)](https://www.npmjs.com/package/@lion/textarea) | Multiline text input | diff --git a/packages/form-integrations/docs/15-features-overview.md b/packages/form-integrations/docs/15-features-overview.md index 78428ebd8..327cc1372 100644 --- a/packages/form-integrations/docs/15-features-overview.md +++ b/packages/form-integrations/docs/15-features-overview.md @@ -17,6 +17,7 @@ import '@lion/input-datepicker/lion-input-datepicker.js'; import '@lion/input-email/lion-input-email.js'; import '@lion/input-iban/lion-input-iban.js'; import '@lion/input-range/lion-input-range.js'; +import '@lion/input-stepper/lion-input-stepper.js'; import '@lion/input/lion-input.js'; import '@lion/radio-group/lion-radio-group.js'; import '@lion/radio-group/lion-radio.js'; @@ -123,6 +124,12 @@ export const main = () => { > + + +
+ Max. 5 guests +
+
Submit diff --git a/packages/input-stepper/CHANGELOG.md b/packages/input-stepper/CHANGELOG.md new file mode 100644 index 000000000..420e6f23d --- /dev/null +++ b/packages/input-stepper/CHANGELOG.md @@ -0,0 +1 @@ +# Change Log diff --git a/packages/input-stepper/README.md b/packages/input-stepper/README.md new file mode 100644 index 000000000..2244e6e94 --- /dev/null +++ b/packages/input-stepper/README.md @@ -0,0 +1,136 @@ +# Input Stepper + +`lion-input-stepper` enables the user to increase and decrease a numeric value by predefined range. It is a combination of two buttons and a number input field with an optional slot `after` to suffix the extra information. + +```js script +import { html } from 'lit-html'; +import '@lion/form/lion-form.js'; +import './lion-input-stepper.js'; + +export default { + title: 'Forms/Input Stepper', +}; +``` + +```js preview-story +export const main = () => html` + + +
Max. 5 guests
+
+`; +``` + +## Features + +- Based on [lion-input](?path=/docs/forms-input--main#input). +- Set `min` and `max` value to define the range. +- Set `step` value in integer or decimal to increase and decrease the value. +- Use `ArrowUp` or `ArrowDown` to update the value. + +## How to use + +### Installation + +```bash +npm i --save @lion/input-stepper +``` + +```js +import { LionInputStepper } from '@lion/input-stepper'; +// or +import '@lion/input-stepper/lion-input-stepper.js'; +``` + +### Usage + +```html + + +
Max. 5 guests
+
+``` + +### Examples + +#### Default with no specification + +When no range or step is defined, it can go infinite with default step value as `1`. You can also specify prefix content using `after` slot. + +```js preview-story +export const defaultMode = () => html` + + +
In Billion Years
+
+`; +``` + +#### Step and Value + +Use `step` attribute to specify the incrementor or decrementor difference and `value` to set the default value. + +```js preview-story +export const steps = () => html` +

Min: 100, Value: 200, Step: 100

+ +`; +``` + +#### Range + +Use `min` and `max` attribute to specify range. + +```js preview-story +export const range = () => html` +

Min: 200, Max: 500, Step: 100

+ +`; +``` + +#### Validation + +Only numbers are allowed in the field. + +```js preview-story +export const validation = () => html` +

+ Min: 100, Max: 500, Step: 100, + Value: Test +

+ +`; +``` + +#### Form + +```js preview-story +export const usingForm = () => html` + { + console.log(e.target.serializedValue); + const code = document.getElementById('code'); + code.style = 'background-color:#DEDEDE;padding:12px;'; + code.innerHTML = `${JSON.stringify(e.target.serializedValue, null, 4)}`; + }} + > +
+ +
+ + +
Max. 5 guests
+
+
+ Submit + ev.currentTarget.parentElement.parentElement.resetGroup()} + >Reset +
+
+

+`;
+```
diff --git a/packages/input-stepper/index.js b/packages/input-stepper/index.js
new file mode 100644
index 000000000..03a25adf7
--- /dev/null
+++ b/packages/input-stepper/index.js
@@ -0,0 +1 @@
+export { LionInputStepper } from './src/LionInputStepper.js';
diff --git a/packages/input-stepper/lion-input-stepper.js b/packages/input-stepper/lion-input-stepper.js
new file mode 100644
index 000000000..7b7273792
--- /dev/null
+++ b/packages/input-stepper/lion-input-stepper.js
@@ -0,0 +1,3 @@
+import { LionInputStepper } from './src/LionInputStepper.js';
+
+customElements.define('lion-input-stepper', LionInputStepper);
diff --git a/packages/input-stepper/package.json b/packages/input-stepper/package.json
new file mode 100644
index 000000000..dd67fa90d
--- /dev/null
+++ b/packages/input-stepper/package.json
@@ -0,0 +1,48 @@
+{
+  "name": "@lion/input-stepper",
+  "version": "0.0.0",
+  "description": "This component enables the user to increase and decrease a numeric value by predefined range.",
+  "license": "MIT",
+  "author": "ing-bank",
+  "homepage": "https://github.com/ing-bank/lion/",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/ing-bank/lion.git",
+    "directory": "packages/input-stepper"
+  },
+  "main": "index.js",
+  "module": "index.js",
+  "files": [
+    "*.d.ts",
+    "*.js",
+    "docs",
+    "src",
+    "test",
+    "test-helpers",
+    "translations",
+    "types"
+  ],
+  "scripts": {
+    "prepublishOnly": "../../scripts/npm-prepublish.js",
+    "start": "cd ../../ && yarn dev-server --open packages/input-stepper/README.md",
+    "test": "cd ../../ && yarn test:browser --grep \"packages/input-stepper/test/**/*.test.js\"",
+    "test:watch": "cd ../../ && yarn test:browser:watch --grep \"packages/input-stepper/test/**/*.test.js\""
+  },
+  "sideEffects": [
+    "lion-input-stepper.js"
+  ],
+  "dependencies": {
+    "@lion/core": "0.12.0",
+    "@lion/form-core": "0.6.1",
+    "@lion/input": "0.9.2"
+  },
+  "keywords": [
+    "input",
+    "input-stepper",
+    "lion",
+    "web-components"
+  ],
+  "publishConfig": {
+    "access": "public"
+  }
+}
diff --git a/packages/input-stepper/src/LionInputStepper.js b/packages/input-stepper/src/LionInputStepper.js
new file mode 100644
index 000000000..b902946b1
--- /dev/null
+++ b/packages/input-stepper/src/LionInputStepper.js
@@ -0,0 +1,238 @@
+import { html, css } from '@lion/core';
+import { LionInput } from '@lion/input';
+import { IsNumber, MinNumber, MaxNumber } from '@lion/form-core';
+
+/**
+ * `LionInputStepper` is a class for custom input-stepper element (`` web component).
+ *
+ * @customElement lion-input-stepper
+ * @extends LitElement
+ */
+export class LionInputStepper extends LionInput {
+  static get styles() {
+    return [
+      super.styles,
+      css`
+        .input-group__container > .input-group__input ::slotted(.form-control) {
+          text-align: center;
+        }
+      `,
+    ];
+  }
+
+  static get properties() {
+    return {
+      min: Number,
+      max: Number,
+      step: Number,
+      modelValue: Number,
+      __disableIncrementor: Boolean,
+      __disableDecrementor: Boolean,
+    };
+  }
+
+  get currentValue() {
+    return parseFloat(this.value || 0);
+  }
+
+  constructor() {
+    super();
+    this.parser = modelValue => parseFloat(modelValue);
+    this.__disableIncrementor = false;
+    this.__disableDecrementor = false;
+  }
+
+  connectedCallback() {
+    if (super.connectedCallback) {
+      super.connectedCallback();
+    }
+    this.role = 'spinbutton';
+    this.addEventListener('keydown', this.__keyDownHandler);
+    this._inputNode.setAttribute('inputmode', 'decimal');
+    this._inputNode.setAttribute('autocomplete', 'off');
+    this.step = this.hasAttribute('step') ? this.step : 1;
+    this.__setAriaLabelsAndValidator();
+    this.__toggleSpinnerButtonsState();
+  }
+
+  disconnectedCallback() {
+    if (super.disconnectedCallback) {
+      super.disconnectedCallback();
+    }
+    this.removeEventListener('keydown', this.__keyDownHandler);
+  }
+
+  /**
+   * Update native input values
+   * @param {Object} changedProps - changed props
+   */
+  updated(changedProps) {
+    super.updated(changedProps);
+
+    if (changedProps.has('min')) {
+      this._inputNode.min = this.min;
+    }
+
+    if (changedProps.has('max')) {
+      this._inputNode.max = this.max;
+    }
+
+    if (changedProps.has('step')) {
+      this._inputNode.step = this.step;
+    }
+  }
+
+  /**
+   * Set aria labels and apply validators
+   * @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)
+      .map(([key, val]) => {
+        if (val !== Infinity) {
+          this.setAttribute(key, val);
+          return key === 'aria-valuemax' ? new MaxNumber(val) : new MinNumber(val);
+        }
+        return null;
+      })
+      .filter(validator => validator);
+    validators = [new IsNumber(), ...validators];
+    this.defaultValidators.push(...validators);
+  }
+
+  /**
+   * Update values on keyboard arrow up and down event
+   * @param {KeyboardEvent} e - keyboard event
+   * @private
+   */
+  __keyDownHandler(e) {
+    if (e.key === 'ArrowUp') {
+      this.__increment();
+    }
+
+    if (e.key === 'ArrowDown') {
+      this.__decrement();
+    }
+  }
+
+  /**
+   * Toggle disabled state for the buttons
+   * @private
+   */
+  __toggleSpinnerButtonsState() {
+    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.dispatchEvent(
+      new CustomEvent('user-input-changed', {
+        bubbles: true,
+      }),
+    );
+  }
+
+  /**
+   * Increment the value based on given step or default step value is 1
+   * @private
+   */
+  __increment() {
+    const { step, max } = this.values;
+    const newValue = this.currentValue + step;
+    if (newValue <= max || max === Infinity) {
+      this.value = newValue;
+      this.__toggleSpinnerButtonsState();
+    }
+  }
+
+  /**
+   * Decrement the value based on given step or default step value is 1
+   * @private
+   */
+  __decrement() {
+    const { step, min } = this.values;
+    const newValue = this.currentValue - step;
+    if (newValue >= min || min === Infinity) {
+      this.value = newValue;
+      this.__toggleSpinnerButtonsState();
+    }
+  }
+
+  /**
+   * Toggle +/- buttons on change
+   * @override
+   */
+  _onChange() {
+    super._onChange();
+    this.__toggleSpinnerButtonsState();
+  }
+
+  /**
+   * Override after template to none
+   * @override
+   */
+  // eslint-disable-next-line class-methods-use-this
+  _inputGroupAfterTemplate() {
+    return html``;
+  }
+
+  /**
+   * Override before template to none
+   * @override
+   */
+  // eslint-disable-next-line class-methods-use-this
+  _inputGroupBeforeTemplate() {
+    return html``;
+  }
+
+  /**
+   * Override prefix template for the increment button
+   * @override
+   */
+  // eslint-disable-next-line class-methods-use-this
+  _inputGroupPrefixTemplate() {
+    return html`
+      
+    `;
+  }
+
+  /**
+   * Override suffix template for the decrement button and add after slot
+   * @override
+   */
+  // eslint-disable-next-line class-methods-use-this
+  _inputGroupSuffixTemplate() {
+    return html`
+      
+      
+    `;
+  }
+}
diff --git a/packages/input-stepper/test/lion-input-stepper.test.js b/packages/input-stepper/test/lion-input-stepper.test.js
new file mode 100644
index 000000000..568c6bbc0
--- /dev/null
+++ b/packages/input-stepper/test/lion-input-stepper.test.js
@@ -0,0 +1,68 @@
+import { expect, fixture, nextFrame, html } from '@open-wc/testing';
+import '../lion-input-stepper.js';
+
+const defaultInputStepper = html`
+  
+`;
+const inputStepperWithAttrs = html``;
+
+describe('', () => {
+  describe('Stepper', () => {
+    it('has a type text', async () => {
+      const el = await fixture(defaultInputStepper);
+      expect(el._inputNode.type).to.equal('text');
+    });
+  });
+
+  describe('User interaction', () => {
+    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'));
+      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'));
+      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';
+      await nextFrame();
+      expect(el._inputNode.min).to.equal(el.min);
+      expect(el._inputNode.max).to.equal(el.max);
+    });
+  });
+
+  describe('Accessibility', () => {
+    it('is a11y AXE accessible', async () => {
+      const el = await fixture(defaultInputStepper);
+      await expect(el).to.be.accessible();
+    });
+
+    it('is accessible when disabled', async () => {
+      const el = await fixture(``);
+      await expect(el).to.be.accessible();
+    });
+
+    it('adds aria-valuemax and aria-valuemin when min and max are provided', async () => {
+      const el = await fixture(inputStepperWithAttrs);
+      expect(el).to.have.attribute('aria-valuemax', '200');
+      expect(el).to.have.attribute('aria-valuemin', '100');
+    });
+
+    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'));
+      expect(el).to.have.attribute('aria-valuenow', '1');
+    });
+  });
+});