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>
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
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,
|
||||
reflect: true,
|
||||
},
|
||||
valueTextMapping: {
|
||||
type: Object,
|
||||
attribute: 'value-text',
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
reflect: true,
|
||||
|
|
@ -66,6 +70,11 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|||
this.formatter = formatNumber;
|
||||
this.min = 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.values = {
|
||||
max: this.max,
|
||||
|
|
@ -110,15 +119,29 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|||
if (changedProperties.has('min')) {
|
||||
this._inputNode.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();
|
||||
}
|
||||
|
||||
if (changedProperties.has('max')) {
|
||||
this._inputNode.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();
|
||||
}
|
||||
|
||||
if (changedProperties.has('valueTextMapping')) {
|
||||
this._updateAriaAttributes();
|
||||
}
|
||||
|
||||
if (changedProperties.has('step')) {
|
||||
this._inputNode.step = `${this.step}`;
|
||||
this.values.step = this.step;
|
||||
|
|
@ -193,8 +216,8 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|||
*/
|
||||
__toggleSpinnerButtonsState() {
|
||||
const { min, max } = this.values;
|
||||
const decrementButton = this.__getSlot('prefix');
|
||||
const incrementButton = this.__getSlot('suffix');
|
||||
const decrementButton = /** @type {HTMLButtonElement} */ (this.__getSlot('prefix'));
|
||||
const incrementButton = /** @type {HTMLButtonElement} */ (this.__getSlot('suffix'));
|
||||
const disableIncrementor = this.currentValue >= max && max !== Infinity;
|
||||
const disableDecrementor = this.currentValue <= min && min !== Infinity;
|
||||
if (
|
||||
|
|
@ -205,7 +228,32 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
|||
}
|
||||
decrementButton[disableDecrementor ? '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 () => {
|
||||
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]');
|
||||
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