fix(a11y): leverage native :focus-visible in CSS

This commit is contained in:
Sam Potts 2023-03-11 21:15:32 +11:00
parent e17d0220c0
commit 0202e8efb0
22 changed files with 39 additions and 136 deletions

View File

@ -197,11 +197,10 @@ Here's a list of the properties and what they are used for:
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `--plyr-color-main` | The primary UI color. | ![#f03c15](https://place-hold.it/15/00b3ff/000000?text=+) `#00b3ff` |
| `--plyr-video-background` | The background color of video and poster wrappers for using alpha channel videos and poster images. | `rgba(0, 0, 0, 1)` |
| `--plyr-tab-focus-color` | The color used for the dotted outline when an element is `:focus-visible` (equivalent) keyboard focus. | `--plyr-color-main` |
| `--plyr-focus-visible-color` | The color used for the focus styles when an element is `:focus-visible` (keyboard focused). | `--plyr-color-main` |
| `--plyr-badge-background` | The background color for badges in the menu. | ![#4a5464](https://place-hold.it/15/4a5464/000000?text=+) `#4a5464` |
| `--plyr-badge-text-color` | The text color for badges. | ![#ffffff](https://place-hold.it/15/ffffff/000000?text=+) `#ffffff` |
| `--plyr-badge-border-radius` | The border radius used for badges. | `2px` |
| `--plyr-tab-focus-color` | The color used to highlight tab (keyboard) focus. | `--plyr-color-main` |
| `--plyr-captions-background` | The color for the background of captions. | `rgba(0, 0, 0, 0.8)` |
| `--plyr-captions-text-color` | The color used for the captions text. | ![#ffffff](https://place-hold.it/15/ffffff/000000?text=+) `#ffffff` |
| `--plyr-control-icon-size` | The size of the icons used in the controls. | `18px` |

View File

@ -4,7 +4,6 @@
// Please see README.md in the root or github.com/sampotts/plyr
// ==========================================================================
import './tab-focus';
import 'custom-event-polyfill';
import 'url-polyfill';
@ -13,7 +12,6 @@ import Shr from 'shr-buttons';
import Plyr from '../../../src/js/plyr';
import sources from './sources';
import toggleClass from './toggle-class';
(() => {
const production = 'plyr.io';
@ -108,10 +106,10 @@ import toggleClass from './toggle-class';
function render(type) {
// Remove active classes
Array.from(buttons).forEach((button) => toggleClass(button.parentElement, 'active', false));
Array.from(buttons).forEach((button) => button.parentElement.classList.toggle('active', false));
// Set active on parent
toggleClass(document.querySelector(`[data-source="${type}"]`), 'active', true);
document.querySelector(`[data-source="${type}"]`).classList.toggle('active', true);
// Show cite
Array.from(document.querySelectorAll('.plyr__cite')).forEach((cite) => {

View File

@ -1,31 +0,0 @@
// Setup tab focus
const container = document.getElementById('container');
const tabClassName = 'tab-focus';
// Remove class on blur
document.addEventListener('focusout', (event) => {
if (!event.target.classList || container.contains(event.target)) {
return;
}
event.target.classList.remove(tabClassName);
});
// Add classname to tabbed elements
document.addEventListener('keydown', (event) => {
if (event.key !== 'Tab') {
return;
}
// Delay the adding of classname until the focus has changed
// This event fires before the focusin event
setTimeout(() => {
const focused = document.activeElement;
if (!focused || !focused.classList || container.contains(focused)) {
return;
}
focused.classList.add(tabClassName);
}, 10);
});

View File

@ -1,5 +0,0 @@
// Toggle class on an element
const toggleClass = (element, className = '', toggle = false) =>
element && element.classList[toggle ? 'add' : 'remove'](className);
export default toggleClass;

View File

@ -44,8 +44,8 @@
outline: 0;
}
&.tab-focus {
@include tab-focus;
&:focus-visible {
@include focus-visible($color-button-background);
}
&:active {

View File

@ -38,8 +38,8 @@ a {
}
}
&.tab-focus {
@include tab-focus;
&:focus-visible {
@include focus-visible($color-link);
}
&.no-border::after {

View File

@ -58,8 +58,8 @@ aside {
a {
color: $color-twitter;
&.tab-focus {
@include tab-focus($color-twitter);
&:focus-visible {
@include focus-visible($color-twitter);
}
}
}

View File

@ -25,9 +25,9 @@
// Nicer focus styles
// ---------------------------------------
@mixin tab-focus($color: $tab-focus-default-color) {
box-shadow: 0 0 0 3px rgba($color, 0.35);
outline: 0;
@mixin focus-visible($color: $focus-default-color) {
outline: 2px dashed $color;
outline-offset: 2px;
}
// Use rems for font sizing

View File

@ -39,4 +39,4 @@ $color-button-count-background: #fff;
$color-button-count-text: $color-gray-600;
// Focus
$tab-focus-default-color: #fff;
$focus-default-color: $color-brand-primary;

View File

@ -0,0 +1,4 @@
*:focus-visible {
outline: 2px dotted $color-brand-primary;
outline-offset: 2px;
}

View File

@ -379,7 +379,6 @@ const defaults = {
supported: 'plyr--airplay-supported',
active: 'plyr--airplay-active',
},
tabFocus: 'plyr__tab-focus',
previewThumbnails: {
// Tooltip thumbs
thumbContainer: 'plyr__preview-thumb',

8
src/js/controls.js vendored
View File

@ -1106,7 +1106,7 @@ const controls = {
},
// Focus the first menu item in a given (or visible) menu
focusFirstMenuItem(pane, tabFocus = false) {
focusFirstMenuItem(pane, focusVisible = false) {
if (this.elements.settings.popup.hidden) {
return;
}
@ -1119,7 +1119,7 @@ const controls = {
const firstItem = target.querySelector('[role^="menuitem"]');
setFocus.call(this, firstItem, tabFocus);
setFocus.call(this, firstItem, focusVisible);
},
// Show/hide menu
@ -1196,7 +1196,7 @@ const controls = {
},
// Show a panel in the menu
showMenuPanel(type = '', tabFocus = false) {
showMenuPanel(type = '', focusVisible = false) {
const target = this.elements.container.querySelector(`#plyr-settings-${this.id}-${type}`);
// Nothing to show, bail
@ -1247,7 +1247,7 @@ const controls = {
toggleHidden(target, false);
// Focus the first item
controls.focusFirstMenuItem.call(this, target, tabFocus);
controls.focusFirstMenuItem.call(this, target, focusVisible);
},
// Set the download URL

View File

@ -21,7 +21,6 @@ class Listeners {
this.handleKey = this.handleKey.bind(this);
this.toggleMenu = this.toggleMenu.bind(this);
this.setTabFocus = this.setTabFocus.bind(this);
this.firstTouch = this.firstTouch.bind(this);
}
@ -194,57 +193,6 @@ class Listeners {
toggleClass(elements.container, player.config.classNames.isTouch, true);
};
setTabFocus = (event) => {
const { player } = this;
const { elements } = player;
const { key, type, timeStamp } = event;
clearTimeout(this.focusTimer);
// Ignore any key other than tab
if (type === 'keydown' && key !== 'Tab') {
return;
}
// Store reference to event timeStamp
if (type === 'keydown') {
this.lastKeyDown = timeStamp;
}
// Remove current classes
const removeCurrent = () => {
const className = player.config.classNames.tabFocus;
const current = getElements.call(player, `.${className}`);
toggleClass(current, className, false);
};
// Determine if a key was pressed to trigger this event
const wasKeyDown = timeStamp - this.lastKeyDown <= 20;
// Ignore focus events if a key was pressed prior
if (type === 'focus' && !wasKeyDown) {
return;
}
// Remove all current
removeCurrent();
// Delay the adding of classname until the focus has changed
// This event fires before the focusin event
if (type !== 'focusout') {
this.focusTimer = setTimeout(() => {
const focused = document.activeElement;
// Ignore if current focus element isn't inside the player
if (!elements.container.contains(focused)) {
return;
}
toggleClass(document.activeElement, player.config.classNames.tabFocus, true);
}, 10);
}
};
// Global window & document listeners
global = (toggle = true) => {
const { player } = this;
@ -259,9 +207,6 @@ class Listeners {
// Detect touch by events
once.call(player, document.body, 'touchstart', this.firstTouch);
// Tab focus detection
toggleListener.call(player, document.body, 'keydown focus blur focusout', this.setTabFocus, toggle, false, true);
};
// Container listeners

View File

@ -268,16 +268,11 @@ export function getElement(selector) {
}
// Set focus and tab focus class
export function setFocus(element = null, tabFocus = false) {
export function setFocus(element = null, focusVisible = false) {
if (!is.element(element)) {
return;
}
// Set regular focus
element.focus({ preventScroll: true });
// If we want to mimic keyboard focus via tab
if (tabFocus) {
toggleClass(element, this.config.classNames.tabFocus);
}
element.focus({ preventScroll: true, focusVisible });
}

View File

@ -28,8 +28,8 @@
}
// Tab focus
&.plyr__tab-focus {
@include plyr-tab-focus;
&:focus-visible {
@include plyr-focus-visible;
}
}

View File

@ -100,7 +100,7 @@
right: calc((#{$plyr-control-padding} * 1.5) - #{$plyr-menu-item-arrow-size});
}
&.plyr__tab-focus::after,
&:focus-visible::after,
&:hover::after {
border-left-color: currentColor;
}
@ -132,7 +132,7 @@
top: 100%;
}
&.plyr__tab-focus::after,
&:focus-visible::after,
&:hover::after {
border-right-color: currentColor;
}
@ -181,7 +181,7 @@
}
}
&.plyr__tab-focus::before,
&:focus-visible::before,
&:hover::before {
background: rgba($plyr-color-gray-900, 0.1);
}

View File

@ -83,17 +83,17 @@
outline: 0;
}
&.plyr__tab-focus {
&:focus-visible {
&::-webkit-slider-runnable-track {
@include plyr-tab-focus;
@include plyr-focus-visible;
}
&::-moz-range-track {
@include plyr-tab-focus;
@include plyr-focus-visible;
}
&::-ms-track {
@include plyr-tab-focus;
@include plyr-focus-visible;
}
}
}

View File

@ -42,7 +42,7 @@
// Displaying
.plyr .plyr__control:hover .plyr__tooltip,
.plyr .plyr__control.plyr__tab-focus .plyr__tooltip,
.plyr .plyr__control:focus-visible .plyr__tooltip,
.plyr__tooltip--visible {
opacity: 1;
transform: translate(-50%, 0) scale(1);
@ -82,7 +82,7 @@
.plyr__controls > .plyr__control:first-child + .plyr__control,
.plyr__controls > .plyr__control:last-child {
&:hover .plyr__tooltip,
&.plyr__tab-focus .plyr__tooltip,
&:focus-visible .plyr__tooltip,
.plyr__tooltip--visible {
transform: translate(0, 0) scale(1);
}

View File

@ -4,8 +4,8 @@
// Nicer focus styles
// ---------------------------------------
@mixin plyr-tab-focus($color: $plyr-tab-focus-color) {
outline: $color dotted 3px;
@mixin plyr-focus-visible($color: $plyr-focus-visible-color) {
outline: 2px dashed $color;
outline-offset: 2px;
}

View File

@ -2,4 +2,4 @@
// Cosmetic
// ==========================================================================
$plyr-tab-focus-color: var(--plyr-tab-focus-color, var(--plyr-color-main, $plyr-color-main)) !default;
$plyr-focus-visible-color: var(--plyr-focus-visible-color, var(--plyr-color-main, $plyr-color-main)) !default;

View File

@ -17,7 +17,7 @@
// Control elements
.plyr--audio .plyr__control {
&.plyr__tab-focus,
&:focus-visible,
&:hover,
&[aria-expanded='true'] {
background: $plyr-audio-control-background-hover;

View File

@ -87,8 +87,7 @@ $embed-padding: (math.div(100, 16) * 9);
// Control elements
.plyr--video .plyr__control {
// Hover and tab focus
&.plyr__tab-focus,
&:focus-visible,
&:hover,
&[aria-expanded='true'] {
background: $plyr-video-control-background-hover;