fix: fix a11y for audio/video controls in dialog (#2031)
This commit is contained in:
parent
3a91ad75b8
commit
ad9609738b
58
src/routes/_components/MediaControlsFix.html
Normal file
58
src/routes/_components/MediaControlsFix.html
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<!--
|
||||||
|
Nasty hack for video/audio with controls in dialogs. Audio/video controls in a dialog
|
||||||
|
can't work probably with tab focusing per aria-practices guidelines, so we add
|
||||||
|
an extra focusable element after the video/audio element that just loops focus back around
|
||||||
|
to the beginning of the dialog.
|
||||||
|
Things that are impossible to fix, however:
|
||||||
|
- Shift-Tab cycling to the last element inside the video/audio shadow
|
||||||
|
- Pressing Esc while inside the video/audio shadow closes the dialog
|
||||||
|
See: https://github.com/w3c/aria-practices/issues/1772
|
||||||
|
-->
|
||||||
|
<div class="media-controls-fix"
|
||||||
|
on:focus="onFocus()"
|
||||||
|
on:documentKeydown="onDocumentKeydown(event)"
|
||||||
|
aria-hidden="true"
|
||||||
|
tabindex="0"
|
||||||
|
></div>
|
||||||
|
<style>
|
||||||
|
.media-controls-fix {
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
import { FOCUSABLE_ELEMENTS_QUERY } from '../_thirdparty/a11y-dialog/a11y-dialog'
|
||||||
|
import { documentKeydown } from '../_utils/events'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: () => ({
|
||||||
|
tabKeyHadShift: undefined
|
||||||
|
}),
|
||||||
|
events: {
|
||||||
|
documentKeydown
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onDocumentKeydown (event) {
|
||||||
|
if (event.key === 'Tab') {
|
||||||
|
this.set({ tabKeyHadShift: !!event.shiftKey })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFocus () {
|
||||||
|
const { tabKeyHadShift } = this.get()
|
||||||
|
const modal = document.querySelector('.modal-dialog-contents')
|
||||||
|
if (tabKeyHadShift) { // user typed Shift-Tab
|
||||||
|
// focus the last element in the modal dialog which is not this element.
|
||||||
|
// This _should_ be the last element inside of the video/audio controls, but it's not possible
|
||||||
|
// to target these shadow elements, so just target the whole video/audio controls.
|
||||||
|
const elements = modal.querySelectorAll(FOCUSABLE_ELEMENTS_QUERY)
|
||||||
|
elements[elements.length - 2].focus()
|
||||||
|
} else { // user typed Tab
|
||||||
|
// focus the first element in the modal dialog, which should be the X (close) button
|
||||||
|
modal.querySelector(FOCUSABLE_ELEMENTS_QUERY).focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -9,6 +9,7 @@
|
||||||
height={intrinsicHeight}
|
height={intrinsicHeight}
|
||||||
ref:player
|
ref:player
|
||||||
/>
|
/>
|
||||||
|
<MediaControlsFix />
|
||||||
{:elseif type === 'audio'}
|
{:elseif type === 'audio'}
|
||||||
<div class="audio-player-container">
|
<div class="audio-player-container">
|
||||||
<audio
|
<audio
|
||||||
|
@ -19,6 +20,7 @@
|
||||||
ref:player
|
ref:player
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<MediaControlsFix />
|
||||||
{:elseif type === 'gifv'}
|
{:elseif type === 'gifv'}
|
||||||
<video
|
<video
|
||||||
class="media-fit"
|
class="media-fit"
|
||||||
|
@ -73,6 +75,7 @@
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
import { get } from '../../../_utils/lodash-lite'
|
import { get } from '../../../_utils/lodash-lite'
|
||||||
|
import MediaControlsFix from '../../MediaControlsFix.html'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -91,6 +94,9 @@
|
||||||
if (player && !player.paused) {
|
if (player && !player.paused) {
|
||||||
player.pause()
|
player.pause()
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
MediaControlsFix
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// you can at least tab to the video/audio and use other controls, like space bar and left/right)
|
// you can at least tab to the video/audio and use other controls, like space bar and left/right)
|
||||||
// Original: https://unpkg.com/a11y-dialog@4.0.1/a11y-dialog.js
|
// Original: https://unpkg.com/a11y-dialog@4.0.1/a11y-dialog.js
|
||||||
|
|
||||||
const FOCUSABLE_ELEMENTS_QUERY = 'a[href], area[href], input, select, textarea, ' +
|
export const FOCUSABLE_ELEMENTS_QUERY = 'a[href], area[href], input, select, textarea, ' +
|
||||||
'button, iframe, object, embed, [contenteditable], [tabindex], ' +
|
'button, iframe, object, embed, [contenteditable], [tabindex], ' +
|
||||||
'video[controls], audio[controls], summary'
|
'video[controls], audio[controls], summary'
|
||||||
const TAB_KEY = 9
|
const TAB_KEY = 9
|
||||||
|
|
|
@ -43,3 +43,12 @@ export function resize (node, callback) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function documentKeydown (node, callback) {
|
||||||
|
document.addEventListener('keydown', callback)
|
||||||
|
return {
|
||||||
|
destroy () {
|
||||||
|
document.removeEventListener('keydown', callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue