feat(input-stepper): add aria-valuemin, aria-valuemax and an option t… (#2423)
* feat(input-stepper): add aria-valuemin, aria-valuemax and an option to set aria-valuetext * chore: change valueText into an Object * chore: remove reflect of valueText * Update packages/ui/components/input-stepper/src/LionInputStepper.js Co-authored-by: Oleksii Kadurin <ovkadurin@gmail.com> * Update packages/ui/components/input-stepper/src/LionInputStepper.js Co-authored-by: Oleksii Kadurin <ovkadurin@gmail.com> * chore: rename valueText to valueTextMapping --------- Co-authored-by: Oleksii Kadurin <ovkadurin@gmail.com>
This commit is contained in:
parent
b975615c8b
commit
5dc2205d7d
4 changed files with 146 additions and 4 deletions
5
.changeset/sweet-cows-happen.md
Normal file
5
.changeset/sweet-cows-happen.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@lion/ui': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
[input-stepper] add aria-valuemin, aria-valuemax and an option to set aria-valuetext
|
||||||
|
|
@ -49,6 +49,36 @@ Use `min` and `max` attribute to specify a range.
|
||||||
></lion-input-stepper>
|
></lion-input-stepper>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Value text
|
||||||
|
|
||||||
|
Use the `.valueTextMapping` property to override the value with a text.
|
||||||
|
|
||||||
|
```js preview-story
|
||||||
|
export const valueTextMapping = () => {
|
||||||
|
const values = {
|
||||||
|
1: 'first',
|
||||||
|
2: 'second',
|
||||||
|
3: 'third',
|
||||||
|
4: 'fourth',
|
||||||
|
5: 'fifth',
|
||||||
|
6: 'sixth',
|
||||||
|
7: 'seventh',
|
||||||
|
8: 'eighth',
|
||||||
|
9: 'ninth',
|
||||||
|
10: 'tenth',
|
||||||
|
};
|
||||||
|
return html`
|
||||||
|
<lion-input-stepper
|
||||||
|
label="Order"
|
||||||
|
min="1"
|
||||||
|
max="10"
|
||||||
|
name="value"
|
||||||
|
.valueTextMapping="${values}"
|
||||||
|
></lion-input-stepper>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
### Formatting
|
### Formatting
|
||||||
|
|
||||||
Just like with the `input-amount` you can add the `formatOptions` to format the numbers to your preferences, to a different locale or adjust the amount of fractions.
|
Just like with the `input-amount` you can add the `formatOptions` to format the numbers to your preferences, to a different locale or adjust the amount of fractions.
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,10 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
||||||
type: Number,
|
type: Number,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
},
|
},
|
||||||
|
valueTextMapping: {
|
||||||
|
type: Object,
|
||||||
|
attribute: 'value-text',
|
||||||
|
},
|
||||||
step: {
|
step: {
|
||||||
type: Number,
|
type: Number,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
|
|
@ -66,6 +70,11 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
||||||
this.formatter = formatNumber;
|
this.formatter = formatNumber;
|
||||||
this.min = Infinity;
|
this.min = Infinity;
|
||||||
this.max = Infinity;
|
this.max = Infinity;
|
||||||
|
/**
|
||||||
|
* The aria-valuetext attribute defines the human-readable text alternative of aria-valuenow.
|
||||||
|
* @type {{[key: number]: string}}
|
||||||
|
*/
|
||||||
|
this.valueTextMapping = {};
|
||||||
this.step = 1;
|
this.step = 1;
|
||||||
this.values = {
|
this.values = {
|
||||||
max: this.max,
|
max: this.max,
|
||||||
|
|
@ -110,15 +119,29 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
||||||
if (changedProperties.has('min')) {
|
if (changedProperties.has('min')) {
|
||||||
this._inputNode.min = `${this.min}`;
|
this._inputNode.min = `${this.min}`;
|
||||||
this.values.min = this.min;
|
this.values.min = this.min;
|
||||||
|
if (this.min !== Infinity) {
|
||||||
|
this.setAttribute('aria-valuemin', `${this.min}`);
|
||||||
|
} else {
|
||||||
|
this.removeAttribute('aria-valuemin');
|
||||||
|
}
|
||||||
this.__toggleSpinnerButtonsState();
|
this.__toggleSpinnerButtonsState();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changedProperties.has('max')) {
|
if (changedProperties.has('max')) {
|
||||||
this._inputNode.max = `${this.max}`;
|
this._inputNode.max = `${this.max}`;
|
||||||
this.values.max = this.max;
|
this.values.max = this.max;
|
||||||
|
if (this.max !== Infinity) {
|
||||||
|
this.setAttribute('aria-valuemax', `${this.max}`);
|
||||||
|
} else {
|
||||||
|
this.removeAttribute('aria-valuemax');
|
||||||
|
}
|
||||||
this.__toggleSpinnerButtonsState();
|
this.__toggleSpinnerButtonsState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changedProperties.has('valueTextMapping')) {
|
||||||
|
this._updateAriaAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
if (changedProperties.has('step')) {
|
if (changedProperties.has('step')) {
|
||||||
this._inputNode.step = `${this.step}`;
|
this._inputNode.step = `${this.step}`;
|
||||||
this.values.step = this.step;
|
this.values.step = this.step;
|
||||||
|
|
@ -193,8 +216,8 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
||||||
*/
|
*/
|
||||||
__toggleSpinnerButtonsState() {
|
__toggleSpinnerButtonsState() {
|
||||||
const { min, max } = this.values;
|
const { min, max } = this.values;
|
||||||
const decrementButton = this.__getSlot('prefix');
|
const decrementButton = /** @type {HTMLButtonElement} */ (this.__getSlot('prefix'));
|
||||||
const incrementButton = this.__getSlot('suffix');
|
const incrementButton = /** @type {HTMLButtonElement} */ (this.__getSlot('suffix'));
|
||||||
const disableIncrementor = this.currentValue >= max && max !== Infinity;
|
const disableIncrementor = this.currentValue >= max && max !== Infinity;
|
||||||
const disableDecrementor = this.currentValue <= min && min !== Infinity;
|
const disableDecrementor = this.currentValue <= min && min !== Infinity;
|
||||||
if (
|
if (
|
||||||
|
|
@ -205,7 +228,32 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
||||||
}
|
}
|
||||||
decrementButton[disableDecrementor ? 'setAttribute' : 'removeAttribute']('disabled', 'true');
|
decrementButton[disableDecrementor ? 'setAttribute' : 'removeAttribute']('disabled', 'true');
|
||||||
incrementButton[disableIncrementor ? 'setAttribute' : 'removeAttribute']('disabled', 'true');
|
incrementButton[disableIncrementor ? 'setAttribute' : 'removeAttribute']('disabled', 'true');
|
||||||
this.setAttribute('aria-valuenow', `${this.currentValue}`);
|
|
||||||
|
this._updateAriaAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
_updateAriaAttributes() {
|
||||||
|
const displayValue = this._inputNode.value;
|
||||||
|
if (displayValue) {
|
||||||
|
this.setAttribute('aria-valuenow', `${displayValue}`);
|
||||||
|
if (
|
||||||
|
Object.keys(this.valueTextMapping).length !== 0 &&
|
||||||
|
Object.keys(this.valueTextMapping).find(key => Number(key) === this.currentValue)
|
||||||
|
) {
|
||||||
|
this.setAttribute('aria-valuetext', `${this.valueTextMapping[this.currentValue]}`);
|
||||||
|
} else {
|
||||||
|
// VoiceOver announces percentages once the valuemin or valuemax are used.
|
||||||
|
// This can be fixed by setting valuetext to the same value as valuenow
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuenow
|
||||||
|
this.setAttribute('aria-valuetext', `${displayValue}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.removeAttribute('aria-valuenow');
|
||||||
|
this.removeAttribute('aria-valuetext');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -280,9 +280,68 @@ describe('<lion-input-stepper>', () => {
|
||||||
|
|
||||||
it('updates aria-valuenow when stepper is changed', async () => {
|
it('updates aria-valuenow when stepper is changed', async () => {
|
||||||
const el = await fixture(defaultInputStepper);
|
const el = await fixture(defaultInputStepper);
|
||||||
|
el.modelValue = 1;
|
||||||
|
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.hasAttribute('aria-valuenow')).to.be.true;
|
||||||
|
expect(el.getAttribute('aria-valuenow')).to.equal('1');
|
||||||
|
|
||||||
|
el.modelValue = '';
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.hasAttribute('aria-valuenow')).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates aria-valuetext when stepper is changed', async () => {
|
||||||
|
// VoiceOver announces percentages once the valuemin or valuemax are used.
|
||||||
|
// This can be fixed by setting valuetext to the same value as valuenow
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-valuenow
|
||||||
|
const el = await fixture(defaultInputStepper);
|
||||||
|
el.modelValue = 1;
|
||||||
|
await el.updateComplete;
|
||||||
|
|
||||||
|
expect(el.hasAttribute('aria-valuetext')).to.be.true;
|
||||||
|
expect(el.getAttribute('aria-valuetext')).to.equal('1');
|
||||||
|
|
||||||
|
el.modelValue = '';
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.hasAttribute('aria-valuetext')).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can give aria-valuetext to override default value as a human-readable text alternative', async () => {
|
||||||
|
const values = {
|
||||||
|
1: 'first',
|
||||||
|
2: 'second',
|
||||||
|
3: 'third',
|
||||||
|
};
|
||||||
|
const el = await fixture(html`
|
||||||
|
<lion-input-stepper min="1" max="3" .valueTextMapping="${values}"></lion-input-stepper>
|
||||||
|
`);
|
||||||
|
el.modelValue = 1;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.hasAttribute('aria-valuetext')).to.be.true;
|
||||||
|
expect(el.getAttribute('aria-valuetext')).to.equal('first');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates aria-valuemin when stepper is changed', async () => {
|
||||||
|
const el = await fixture(inputStepperWithAttrs);
|
||||||
const incrementButton = el.querySelector('[slot=suffix]');
|
const incrementButton = el.querySelector('[slot=suffix]');
|
||||||
incrementButton?.dispatchEvent(new Event('click'));
|
incrementButton?.dispatchEvent(new Event('click'));
|
||||||
expect(el).to.have.attribute('aria-valuenow', '1');
|
expect(el).to.have.attribute('aria-valuemin', '100');
|
||||||
|
|
||||||
|
el.min = 0;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el).to.have.attribute('aria-valuemin', '0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates aria-valuemax when stepper is changed', async () => {
|
||||||
|
const el = await fixture(inputStepperWithAttrs);
|
||||||
|
const incrementButton = el.querySelector('[slot=suffix]');
|
||||||
|
incrementButton?.dispatchEvent(new Event('click'));
|
||||||
|
expect(el).to.have.attribute('aria-valuemax', '200');
|
||||||
|
|
||||||
|
el.max = 1000;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el).to.have.attribute('aria-valuemax', '1000');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue