Skip to content

Commit

Permalink
fix(clock): a11y, tick, show-seconds should not pronounce all the time (
Browse files Browse the repository at this point in the history
  • Loading branch information
Theeraphat-Sorasetsakul authored May 31, 2022
1 parent fc0f76a commit b4e51ac
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 39 deletions.
12 changes: 11 additions & 1 deletion packages/elements/src/clock/__test__/clock.interactive.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,19 @@ describe('clock/Interactive', () => {
});

describe('Accessibility', () => {
it('Should have role="spinbutton" and focusable', async () => {
it('Should have role="spinbutton", be focusable, and aria attributes', async () => {
expect(el.getAttribute('role')).to.be.equal('spinbutton');
expect(el.getAttribute('tabindex')).to.be.equal('0');
expect(el.getAttribute('aria-valuenow')).to.be.equal(`${el.displayTime}`);
expect(el.getAttribute('aria-valuetext')).to.be.equal('Time: 00:00');
});
it('Should remove attributes when interactive attribute has been change', async () => {
el.interactive = false;
await elementUpdated(el);
expect(el.getAttribute('role')).to.be.equal(null);
expect(el.getAttribute('tabindex')).to.be.equal(null);
expect(el.getAttribute('aria-valuenow')).to.be.equal(null);
expect(el.getAttribute('aria-valuetext')).to.be.equal(null);
});
it('Should increase hour value and update aria-valuetext when Arrow Up is pressed on hour segment', async () => {
await onTapstart(hoursSegment, el);
Expand Down
95 changes: 57 additions & 38 deletions packages/elements/src/clock/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,33 +256,11 @@ export class Clock extends ResponsiveElement {
@property({ type: Boolean, reflect: true })
public analogue = false;

private _interactive = false;

/**
* Enable interactive mode. Allowing the user to offset the value.
* When interactive mode, clock will be focusable and role=spinbutton.
* When non interactive mode, clock is unable to focus and has no role.
* @param value Set interactive mode
*/
@property({ type: Boolean })
public set interactive (value: boolean) {
const oldValue = this.interactive;
if (oldValue !== value) {
this._interactive = value;
if (this._interactive) {
this.tabIndex = 0;
this.setAttribute('role', 'spinbutton');
}
else {
this.tabIndex = -1;
this.removeAttribute('role');
}
this.requestUpdate('interactive', oldValue);
}
}
public get interactive (): boolean {
return this._interactive;
}
public interactive = false;

/**
* Getter for hours part.
Expand Down Expand Up @@ -410,6 +388,14 @@ export class Clock extends ResponsiveElement {
return this.displayHours24 < HOURS_OF_NOON;
}

/**
* Returns `true` if display minutes has changed
* @returns Result
*/
private get isDisplayMinutesChange (): boolean {
return this.displayTime % SECONDS_IN_MINUTE === 0;
}

/**
* Configures the tick manager to either start or stop ticking,
* depending on the state of the element.
Expand Down Expand Up @@ -579,16 +565,20 @@ export class Clock extends ResponsiveElement {

/**
* Set aria-valuenow to display value and aria-valuetext to translated format
* @param updateAriaValueText condition to update aria-valueText
* @returns {void}
*/
private async updateAriaValue () {
const value = await this.tPromise('TIME', {
value: parse(this.displayValue),
amPm: this.amPm,
showSeconds: this.showSeconds
});
private async updateAriaValue (updateAriaValueText = true) {
this.setAttribute('aria-valuenow', `${this.displayTime}`);
this.setAttribute('aria-valuetext', value);

if (updateAriaValueText) {
const value = await this.tPromise('TIME', {
value: parse(this.displayValue),
amPm: this.amPm,
showSeconds: this.showSeconds
});
this.setAttribute('aria-valuetext', value);
}
}

/**
Expand Down Expand Up @@ -699,6 +689,25 @@ export class Clock extends ResponsiveElement {
this.renderRoot.addEventListener('tapstart', (event) => this.onTapStart(event as TapEvent));
}

/**
* Handles interactive by update role, tabindex, and aria attribute
* @returns {void}
*/
private interactiveChanged (): void {
if (this.interactive) {
const tabIndex = (this.tabIndex >= 0) ? this.tabIndex.toString() : '0';
this.setAttribute('role', 'spinbutton');
this.setAttribute('tabindex', tabIndex);
void this.updateAriaValue();
}
else {
this.removeAttribute('role');
this.removeAttribute('tabindex');
this.removeAttribute('aria-valuenow');
this.removeAttribute('aria-valuetext');
}
}

/**
* Called before update() to compute values needed during the update.
* @param changedProperties Properties that has changed
Expand All @@ -707,14 +716,24 @@ export class Clock extends ResponsiveElement {
protected willUpdate (changedProperties: PropertyValues): void {
super.willUpdate(changedProperties);

if (this.interactive && (!this.hasUpdated
|| changedProperties.has('sessionTicks')
|| changedProperties.has('offset')
|| changedProperties.has('value')
|| changedProperties.has('showSeconds')
|| changedProperties.has('amPm')
|| changedProperties.has(TranslatePropertyKey))) {
void this.updateAriaValue();
if (changedProperties.has('interactive')) {
this.interactiveChanged();
}

if (this.interactive) {
if (!this.hasUpdated
|| changedProperties.has('offset')
|| changedProperties.has('value')
|| changedProperties.has('showSeconds')
|| changedProperties.has('amPm')
|| changedProperties.has(TranslatePropertyKey)) {
void this.updateAriaValue();
}

// Avoid announce every second that could interrupt the screen reader when the user takes an action.
if (changedProperties.has('sessionTicks')) {
void this.updateAriaValue(this.isDisplayMinutesChange);
}
}
}

Expand Down

0 comments on commit b4e51ac

Please sign in to comment.