Merge pull request #397 from ing-bank/fix/input-amount-a11y
fix(input-amount): a11y currency
This commit is contained in:
commit
fb5c2a350f
6 changed files with 81 additions and 7 deletions
|
|
@ -1,3 +1,5 @@
|
||||||
|
const isIE11 = /Trident/.test(window.navigator.userAgent);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @desc Let the order of adding ids to aria element by DOM order, so that the screen reader
|
* @desc Let the order of adding ids to aria element by DOM order, so that the screen reader
|
||||||
* respects visual order when reading:
|
* respects visual order when reading:
|
||||||
|
|
@ -5,16 +7,16 @@
|
||||||
* @param {array} descriptionElements - holds references to description or label elements whose
|
* @param {array} descriptionElements - holds references to description or label elements whose
|
||||||
* id should be returned
|
* id should be returned
|
||||||
* @returns {array} sorted set of elements based on dom order
|
* @returns {array} sorted set of elements based on dom order
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
export function getAriaElementsInRightDomOrder(descriptionElements, { reverse } = {}) {
|
export function getAriaElementsInRightDomOrder(descriptionElements, { reverse } = {}) {
|
||||||
const putPrecedingSiblingsAndLocalParentsFirst = (a, b) => {
|
const putPrecedingSiblingsAndLocalParentsFirst = (a, b) => {
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition
|
// https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition
|
||||||
const pos = a.compareDocumentPosition(b);
|
const pos = a.compareDocumentPosition(b);
|
||||||
|
// Unfortunately, for IE, we have to switch the order (?)
|
||||||
if (pos === Node.DOCUMENT_POSITION_PRECEDING || pos === Node.DOCUMENT_POSITION_CONTAINED_BY) {
|
if (pos === Node.DOCUMENT_POSITION_PRECEDING || pos === Node.DOCUMENT_POSITION_CONTAINED_BY) {
|
||||||
return 1;
|
return isIE11 ? -1 : 1;
|
||||||
}
|
}
|
||||||
return -1;
|
return isIE11 ? 1 : -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
const descriptionEls = descriptionElements.filter(el => el); // filter out null references
|
const descriptionEls = descriptionElements.filter(el => el); // filter out null references
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { css } from '@lion/core';
|
import { css } from '@lion/core';
|
||||||
import { LocalizeMixin } from '@lion/localize';
|
import { LocalizeMixin, getCurrencyName } from '@lion/localize';
|
||||||
import { LionInput } from '@lion/input';
|
import { LionInput } from '@lion/input';
|
||||||
import { FieldCustomMixin } from '@lion/field';
|
import { FieldCustomMixin } from '@lion/field';
|
||||||
import { IsNumber } from '@lion/validate';
|
import { IsNumber } from '@lion/validate';
|
||||||
|
|
@ -34,6 +34,9 @@ export class LionInputAmount extends FieldCustomMixin(LocalizeMixin(LionInput))
|
||||||
after: () => {
|
after: () => {
|
||||||
if (this.currency) {
|
if (this.currency) {
|
||||||
const el = document.createElement('span');
|
const el = document.createElement('span');
|
||||||
|
// The data-label attribute will make sure that FormControl adds this to
|
||||||
|
// input[aria-labelledby]
|
||||||
|
el.setAttribute('data-label', '');
|
||||||
el.textContent = this.currency;
|
el.textContent = this.currency;
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
@ -54,14 +57,27 @@ export class LionInputAmount extends FieldCustomMixin(LocalizeMixin(LionInput))
|
||||||
// eslint-disable-next-line wc/guard-super-call
|
// eslint-disable-next-line wc/guard-super-call
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this.type = 'text';
|
this.type = 'text';
|
||||||
|
|
||||||
|
if (this.currency) {
|
||||||
|
this.__setCurrencyDisplayLabel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__setCurrencyDisplayLabel() {
|
||||||
|
this._currencyDisplayNode.setAttribute('aria-label', getCurrencyName(this.currency));
|
||||||
|
}
|
||||||
|
|
||||||
|
get _currencyDisplayNode() {
|
||||||
|
return Array.from(this.children).find(child => child.slot === 'after');
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCurrencyChanged({ currency }) {
|
_onCurrencyChanged({ currency }) {
|
||||||
if (this._isPrivateSlot('after')) {
|
if (this._isPrivateSlot('after')) {
|
||||||
Array.from(this.children).find(child => child.slot === 'after').textContent = currency;
|
this._currencyDisplayNode.textContent = currency;
|
||||||
}
|
}
|
||||||
this.formatOptions.currency = currency;
|
this.formatOptions.currency = currency;
|
||||||
this._calculateValues();
|
this._calculateValues();
|
||||||
|
this.__setCurrencyDisplayLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
|
|
|
||||||
|
|
@ -69,9 +69,9 @@ describe('<lion-input-amount>', () => {
|
||||||
expect(el._inputNode.type).to.equal('text');
|
expect(el._inputNode.type).to.equal('text');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows no currency', async () => {
|
it('shows no currency by default', async () => {
|
||||||
const el = await fixture(`<lion-input-amount></lion-input-amount>`);
|
const el = await fixture(`<lion-input-amount></lion-input-amount>`);
|
||||||
expect(Array.from(el.children).find(child => child.slot === 'suffix')).to.be.undefined;
|
expect(Array.from(el.children).find(child => child.slot === 'after')).to.be.undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('displays currency if provided', async () => {
|
it('displays currency if provided', async () => {
|
||||||
|
|
@ -99,4 +99,23 @@ describe('<lion-input-amount>', () => {
|
||||||
'my-currency',
|
'my-currency',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Accessibility', () => {
|
||||||
|
it('adds currency id to aria-labelledby of input', async () => {
|
||||||
|
const el = await fixture(`<lion-input-amount currency="EUR"></lion-input-amount>`);
|
||||||
|
expect(el._currencyDisplayNode.getAttribute('data-label')).to.be.not.null;
|
||||||
|
expect(el._inputNode.getAttribute('aria-labelledby')).to.contain(el._currencyDisplayNode.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds an aria-label to currency slot', async () => {
|
||||||
|
const el = await fixture(`<lion-input-amount currency="EUR"></lion-input-amount>`);
|
||||||
|
expect(el._currencyDisplayNode.getAttribute('aria-label')).to.equal('euros');
|
||||||
|
el.currency = 'USD';
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el._currencyDisplayNode.getAttribute('aria-label')).to.equal('US dollars');
|
||||||
|
el.currency = 'PHP';
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el._currencyDisplayNode.getAttribute('aria-label')).to.equal('Philippine pisos');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ export { normalizeDateTime } from './src/date/normalizeDateTime.js';
|
||||||
export { parseDate } from './src/date/parseDate.js';
|
export { parseDate } from './src/date/parseDate.js';
|
||||||
export { formatNumber } from './src/number/formatNumber.js';
|
export { formatNumber } from './src/number/formatNumber.js';
|
||||||
export { formatNumberToParts } from './src/number/formatNumberToParts.js';
|
export { formatNumberToParts } from './src/number/formatNumberToParts.js';
|
||||||
|
export { getCurrencyName } from './src/number/getCurrencyName.js';
|
||||||
export { getDecimalSeparator } from './src/number/getDecimalSeparator.js';
|
export { getDecimalSeparator } from './src/number/getDecimalSeparator.js';
|
||||||
export { getFractionDigits } from './src/number/getFractionDigits.js';
|
export { getFractionDigits } from './src/number/getFractionDigits.js';
|
||||||
export { getGroupSeparator } from './src/number/getGroupSeparator.js';
|
export { getGroupSeparator } from './src/number/getGroupSeparator.js';
|
||||||
|
|
|
||||||
22
packages/localize/src/number/getCurrencyName.js
Normal file
22
packages/localize/src/number/getCurrencyName.js
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { formatNumberToParts } from './formatNumberToParts.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on number, returns currency name like 'US dollar'
|
||||||
|
*
|
||||||
|
* @param {string} currencyIso iso code like USD
|
||||||
|
* @param {Object} options Intl options are available extended by roundMode
|
||||||
|
* @returns {string} currency name like 'US dollar'
|
||||||
|
*/
|
||||||
|
export function getCurrencyName(currencyIso, options) {
|
||||||
|
const parts = formatNumberToParts(1, {
|
||||||
|
...options,
|
||||||
|
style: 'currency',
|
||||||
|
currency: currencyIso,
|
||||||
|
currencyDisplay: 'name',
|
||||||
|
});
|
||||||
|
const currencyName = parts
|
||||||
|
.filter(p => p.type === 'currency')
|
||||||
|
.map(o => o.value)
|
||||||
|
.join(' ');
|
||||||
|
return currencyName;
|
||||||
|
}
|
||||||
14
packages/localize/test/number/getCurrencyName.test.js
Normal file
14
packages/localize/test/number/getCurrencyName.test.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { expect } from '@open-wc/testing';
|
||||||
|
import { localizeTearDown } from '../../test-helpers.js';
|
||||||
|
import { getCurrencyName } from '../../src/number/getCurrencyName.js';
|
||||||
|
|
||||||
|
describe('getCurrencyName', () => {
|
||||||
|
afterEach(localizeTearDown);
|
||||||
|
|
||||||
|
it('returns the right name for currency and locale combination', async () => {
|
||||||
|
expect(getCurrencyName('USD', { locale: 'en-GB' })).to.equal('US dollars');
|
||||||
|
expect(getCurrencyName('USD', { locale: 'nl-NL' })).to.equal('Amerikaanse dollar');
|
||||||
|
expect(getCurrencyName('EUR', { locale: 'en-GB' })).to.equal('euros');
|
||||||
|
expect(getCurrencyName('EUR', { locale: 'nl-NL' })).to.equal('euro');
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue