From 541e75e5f9c1316fca407df4d07edf64735a062b Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 19 Sep 2024 14:08:40 +0200 Subject: [PATCH 1/7] Extract enable/disable helpers for Button --- src/components/Button.ts | 30 ++++++++++++++++++++++-------- src/components/LinkButton.ts | 30 ++++++++++++++++++++++-------- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/components/Button.ts b/src/components/Button.ts index 2bf07d0c..18c1ce12 100644 --- a/src/components/Button.ts +++ b/src/components/Button.ts @@ -66,6 +66,9 @@ export class Button extends HTMLElement { // Let the screen reader user know that the text of the button may change this.setAttribute(Attribute.ARIA_LIVE, 'polite'); } + if (!this.hasAttribute(Attribute.DISABLED)) { + this._enable(); + } this.addEventListener('click', this._onClick); } @@ -90,16 +93,10 @@ export class Button extends HTMLElement { attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { if (attrName === Attribute.DISABLED && newValue !== oldValue) { const hasValue = newValue != null; - this.setAttribute('aria-disabled', hasValue ? 'true' : 'false'); - // The `tabindex` attribute does not provide a way to fully remove focusability from an element. - // Elements with `tabindex=-1` can still be focused with a mouse or by calling `focus()`. - // To make sure an element is disabled and not focusable, remove the `tabindex` attribute. if (hasValue) { - this.removeAttribute('tabindex'); - // If the focus is currently on this element, unfocus it by calling the `HTMLElement.blur()` method. - this.blur(); + this._disable(); } else { - this.setAttribute('tabindex', '0'); + this._enable(); } } if (Button.observedAttributes.indexOf(attrName as Attribute) >= 0) { @@ -107,6 +104,23 @@ export class Button extends HTMLElement { } } + private _enable(): void { + this.setAttribute('aria-disabled', 'false'); + this.setAttribute('tabindex', '0'); + } + + private _disable(): void { + this.setAttribute('aria-disabled', 'true'); + + // The `tabindex` attribute does not provide a way to fully remove focusability from an element. + // Elements with `tabindex=-1` can still be focused with a mouse or by calling `focus()`. + // To make sure an element is disabled and not focusable, remove the `tabindex` attribute. + this.removeAttribute('tabindex'); + + // If the focus is currently on this element, unfocus it by calling the `HTMLElement.blur()` method. + this.blur(); + } + private readonly _onClick = () => { if (this.disabled) { return; diff --git a/src/components/LinkButton.ts b/src/components/LinkButton.ts index 71dac58e..da7fb935 100644 --- a/src/components/LinkButton.ts +++ b/src/components/LinkButton.ts @@ -59,6 +59,9 @@ export class LinkButton extends HTMLElement { // Let the screen reader user know that the text of the button may change this.setAttribute(Attribute.ARIA_LIVE, 'polite'); } + if (!this.hasAttribute(Attribute.DISABLED)) { + this._enable(); + } this._linkEl.addEventListener('keydown', this._onKeyDown); this._linkEl.addEventListener('click', this._onClick); @@ -90,16 +93,10 @@ export class LinkButton extends HTMLElement { attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { if (attrName === Attribute.DISABLED && newValue !== oldValue) { const hasValue = newValue != null; - this.setAttribute('aria-disabled', hasValue ? 'true' : 'false'); - // The `tabindex` attribute does not provide a way to fully remove focusability from an element. - // Elements with `tabindex=-1` can still be focused with a mouse or by calling `focus()`. - // To make sure an element is disabled and not focusable, remove the `tabindex` attribute. if (hasValue) { - this.removeAttribute('tabindex'); - // If the focus is currently on this element, unfocus it by calling the `HTMLElement.blur()` method. - this.blur(); + this._disable(); } else { - this.setAttribute('tabindex', '0'); + this._enable(); } } if (Button.observedAttributes.indexOf(attrName as Attribute) >= 0) { @@ -107,6 +104,23 @@ export class LinkButton extends HTMLElement { } } + private _enable(): void { + this.setAttribute('aria-disabled', 'false'); + this.setAttribute('tabindex', '0'); + } + + private _disable(): void { + this.setAttribute('aria-disabled', 'true'); + + // The `tabindex` attribute does not provide a way to fully remove focusability from an element. + // Elements with `tabindex=-1` can still be focused with a mouse or by calling `focus()`. + // To make sure an element is disabled and not focusable, remove the `tabindex` attribute. + this.removeAttribute('tabindex'); + + // If the focus is currently on this element, unfocus it by calling the `HTMLElement.blur()` method. + this.blur(); + } + private readonly _onKeyDown = (event: KeyboardEvent) => { // Don't handle modifier shortcuts typically used by assistive technology. if (event.altKey) return; From f396051425a27b5f981cb4d10fa9e44d2287c3b4 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 19 Sep 2024 14:10:33 +0200 Subject: [PATCH 2/7] Remove click listener when disabled --- src/components/Button.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/Button.ts b/src/components/Button.ts index 18c1ce12..6be8ff5d 100644 --- a/src/components/Button.ts +++ b/src/components/Button.ts @@ -69,8 +69,6 @@ export class Button extends HTMLElement { if (!this.hasAttribute(Attribute.DISABLED)) { this._enable(); } - - this.addEventListener('click', this._onClick); } disconnectedCallback(): void { @@ -105,11 +103,14 @@ export class Button extends HTMLElement { } private _enable(): void { + this.removeEventListener('click', this._onClick); + this.addEventListener('click', this._onClick); this.setAttribute('aria-disabled', 'false'); this.setAttribute('tabindex', '0'); } private _disable(): void { + this.removeEventListener('click', this._onClick); this.setAttribute('aria-disabled', 'true'); // The `tabindex` attribute does not provide a way to fully remove focusability from an element. @@ -122,9 +123,6 @@ export class Button extends HTMLElement { } private readonly _onClick = () => { - if (this.disabled) { - return; - } this.handleClick(); }; From 918d0d6d306bf916b58c352947bdc7e43360eb98 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 19 Sep 2024 14:12:53 +0200 Subject: [PATCH 3/7] Handle Enter and Space key presses for Button --- src/components/Button.ts | 25 +++++++++++++++++++++++++ src/util/KeyCode.ts | 5 +++++ 2 files changed, 30 insertions(+) diff --git a/src/components/Button.ts b/src/components/Button.ts index 6be8ff5d..4eacce90 100644 --- a/src/components/Button.ts +++ b/src/components/Button.ts @@ -3,6 +3,7 @@ import buttonCss from './Button.css'; import { Attribute } from '../util/Attribute'; import { toggleAttribute } from '../util/CommonUtils'; import { createTemplate } from '../util/TemplateUtils'; +import { isActivationKey } from '../util/KeyCode'; export interface ButtonOptions { template: HTMLTemplateElement; @@ -73,6 +74,8 @@ export class Button extends HTMLElement { disconnectedCallback(): void { this.removeEventListener('click', this._onClick); + this.removeEventListener('keydown', this._onKeyDown); + this.removeEventListener('keyup', this._onKeyUp); } /** @@ -104,13 +107,20 @@ export class Button extends HTMLElement { private _enable(): void { this.removeEventListener('click', this._onClick); + this.removeEventListener('keydown', this._onKeyDown); + this.removeEventListener('keyup', this._onKeyUp); this.addEventListener('click', this._onClick); + this.addEventListener('keydown', this._onKeyDown); + this.setAttribute('aria-disabled', 'false'); this.setAttribute('tabindex', '0'); } private _disable(): void { this.removeEventListener('click', this._onClick); + this.removeEventListener('keydown', this._onKeyDown); + this.removeEventListener('keyup', this._onKeyUp); + this.setAttribute('aria-disabled', 'true'); // The `tabindex` attribute does not provide a way to fully remove focusability from an element. @@ -126,6 +136,21 @@ export class Button extends HTMLElement { this.handleClick(); }; + protected readonly _onKeyDown = (e: KeyboardEvent) => { + if (isActivationKey(e.keyCode) && !e.metaKey && !e.altKey) { + this.addEventListener('keyup', this._onKeyUp); + } else { + this.removeEventListener('keyup', this._onKeyUp); + } + }; + + protected readonly _onKeyUp = (e: KeyboardEvent) => { + this.removeEventListener('keyup', this._onKeyUp); + if (isActivationKey(e.keyCode)) { + this.handleClick(); + } + }; + /** * Handle a button click. * diff --git a/src/util/KeyCode.ts b/src/util/KeyCode.ts index 11d76bb9..f468a801 100644 --- a/src/util/KeyCode.ts +++ b/src/util/KeyCode.ts @@ -17,6 +17,7 @@ export enum KeyCode { export type ArrowKeyCode = KeyCode.LEFT | KeyCode.UP | KeyCode.RIGHT | KeyCode.DOWN; export type BackKeyCode = KeyCode.BACK_TIZEN | KeyCode.ESCAPE | KeyCode.BACK_SAMSUNG | KeyCode.BACK_WEBOS; +export type ActivationKeyCode = KeyCode.ENTER | KeyCode.SPACE; export function isArrowKey(keyCode: number): keyCode is ArrowKeyCode { return KeyCode.LEFT <= keyCode && keyCode <= KeyCode.DOWN; @@ -25,3 +26,7 @@ export function isArrowKey(keyCode: number): keyCode is ArrowKeyCode { export function isBackKey(keyCode: number): keyCode is BackKeyCode { return keyCode === KeyCode.BACK_TIZEN || keyCode === KeyCode.ESCAPE || keyCode === KeyCode.BACK_SAMSUNG || keyCode === KeyCode.BACK_WEBOS; } + +export function isActivationKey(keyCode: number): keyCode is ActivationKeyCode { + return keyCode === KeyCode.ENTER || keyCode === KeyCode.SPACE; +} From a7e50d2bb3cd980d70ab3a646f36cc1eb4ad6b42 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 19 Sep 2024 14:21:17 +0200 Subject: [PATCH 4/7] Ignore meta key for LinkButton --- src/components/LinkButton.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LinkButton.ts b/src/components/LinkButton.ts index da7fb935..1696e468 100644 --- a/src/components/LinkButton.ts +++ b/src/components/LinkButton.ts @@ -123,7 +123,7 @@ export class LinkButton extends HTMLElement { private readonly _onKeyDown = (event: KeyboardEvent) => { // Don't handle modifier shortcuts typically used by assistive technology. - if (event.altKey) return; + if (event.metaKey || event.altKey) return; switch (event.keyCode) { // Enter is already handled by the browser. From 98942f9b30f5a67903b005c80ef599d2598fcd07 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 19 Sep 2024 14:23:57 +0200 Subject: [PATCH 5/7] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c3f5d1f..396506fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ sidebar_custom_props: { 'icon': '📰' } > - 🏠 Internal > - 💅 Polish +## Unreleased + +- 🐛 Fixed Enter and Space keys not working to activate buttons in the UI. + ## v1.9.0 (2024-09-06) - 🚀 Added support for THEOplayer 8.0. From bb99bd6d26fe56e8b267d20fc1fc31d8cfbaca6b Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 19 Sep 2024 14:45:44 +0200 Subject: [PATCH 6/7] Improve a11y for TimeDisplay --- src/components/TimeDisplay.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/TimeDisplay.ts b/src/components/TimeDisplay.ts index e7467551..7f2ec6db 100644 --- a/src/components/TimeDisplay.ts +++ b/src/components/TimeDisplay.ts @@ -52,6 +52,12 @@ export class TimeDisplay extends StateReceiverMixin(HTMLElement, ['player', 'str connectedCallback(): void { shadyCss.styleElement(this); + if (!this.hasAttribute('role')) { + this.setAttribute('role', 'progressbar'); + } + if (!this.hasAttribute(Attribute.ARIA_LABEL)) { + this.setAttribute(Attribute.ARIA_LABEL, 'playback time'); + } if (!this.hasAttribute(Attribute.ARIA_LIVE)) { // Tell screen readers not to automatically read the time as it changes this.setAttribute(Attribute.ARIA_LIVE, 'off'); From 29bad73cbbde1d692699a3af7464de5ce47c9f4b Mon Sep 17 00:00:00 2001 From: "theoplayer-bot[bot]" <873105+theoplayer-bot[bot]@users.noreply.github.com> Date: Fri, 27 Sep 2024 13:34:56 +0000 Subject: [PATCH 7/7] 1.9.1 --- CHANGELOG.md | 2 +- package-lock.json | 12 ++++++------ package.json | 2 +- react/CHANGELOG.md | 4 ++++ react/package.json | 4 ++-- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 396506fe..2fa73506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ sidebar_custom_props: { 'icon': '📰' } > - 🏠 Internal > - 💅 Polish -## Unreleased +## v1.9.1 (2024-09-27) - 🐛 Fixed Enter and Space keys not working to activate buttons in the UI. diff --git a/package-lock.json b/package-lock.json index c6ae974b..53f99d6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@theoplayer/web-ui", - "version": "1.9.0", + "version": "1.9.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@theoplayer/web-ui", - "version": "1.9.0", + "version": "1.9.1", "license": "MIT", "workspaces": [ ".", @@ -6465,11 +6465,11 @@ }, "react": { "name": "@theoplayer/react-ui", - "version": "1.9.0", + "version": "1.9.1", "license": "MIT", "dependencies": { "@lit/react": "^1.0.3", - "@theoplayer/web-ui": "^1.9.0" + "@theoplayer/web-ui": "^1.9.1" }, "devDependencies": { "@rollup/plugin-json": "^6.1.0", @@ -7104,7 +7104,7 @@ "@swc/cli": "^0.1.62", "@swc/core": "^1.3.89", "@swc/helpers": "^0.5.2", - "@theoplayer/web-ui": "^1.9.0", + "@theoplayer/web-ui": "^1.9.1", "@types/react": "^18.2.48", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -7760,7 +7760,7 @@ "@swc/cli": "^0.1.62", "@swc/core": "^1.3.89", "@swc/helpers": "^0.5.2", - "@theoplayer/web-ui": "^1.9.0", + "@theoplayer/web-ui": "^1.9.1", "@types/react": "^18.2.48", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/package.json b/package.json index 32771c21..e0c2d1ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@theoplayer/web-ui", - "version": "1.9.0", + "version": "1.9.1", "description": "UI component library for the THEOplayer Web SDK", "main": "dist/THEOplayerUI.js", "module": "dist/THEOplayerUI.mjs", diff --git a/react/CHANGELOG.md b/react/CHANGELOG.md index 89b7fc53..2092deae 100644 --- a/react/CHANGELOG.md +++ b/react/CHANGELOG.md @@ -15,6 +15,10 @@ sidebar_custom_props: { 'icon': '📰' } > - 🏠 Internal > - 💅 Polish +## v1.9.1 (2024-09-27) + +- 🏠 See changes to [Open Video UI for Web v1.9.1](https://github.com/THEOplayer/web-ui/blob/v1.9.1/CHANGELOG.md) + ## v1.9.0 (2024-09-06) - 🚀 Added support for THEOplayer 8.0. diff --git a/react/package.json b/react/package.json index 7f7adcf6..76c2ef43 100644 --- a/react/package.json +++ b/react/package.json @@ -1,6 +1,6 @@ { "name": "@theoplayer/react-ui", - "version": "1.9.0", + "version": "1.9.1", "description": "React component library for the THEOplayer Web SDK", "main": "dist/THEOplayerReactUI.js", "module": "dist/THEOplayerReactUI.mjs", @@ -52,7 +52,7 @@ }, "dependencies": { "@lit/react": "^1.0.3", - "@theoplayer/web-ui": "^1.9.0" + "@theoplayer/web-ui": "^1.9.1" }, "peerDependencies": { "@types/react": "^16.3.0 || ^17 || ^18",