fix: fix a11y for audio/video controls in dialog (#2031)

This commit is contained in:
Nolan Lawson 2021-04-11 09:58:32 -07:00 committed by GitHub
parent 3a91ad75b8
commit ad9609738b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 74 additions and 1 deletions

View 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>

View file

@ -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>

View file

@ -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

View file

@ -43,3 +43,12 @@ export function resize (node, callback) {
} }
} }
} }
export function documentKeydown (node, callback) {
document.addEventListener('keydown', callback)
return {
destroy () {
document.removeEventListener('keydown', callback)
}
}
}