Skip to content

Commit 33a113a

Browse files
authored
fix(vimeo): a11y keyboard and video placeholder alt text (#803)
1 parent 4ed9c62 commit 33a113a

2 files changed

Lines changed: 32 additions & 5 deletions

File tree

packages/script/src/runtime/components/ScriptVimeoPlayer.vue

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -253,16 +253,43 @@ watch(status, (status) => {
253253
}
254254
})
255255
256+
// Keyboard accessibility: dispatch the configured trigger event on Enter/Space
257+
// so users navigating via keyboard can activate the placeholder.
258+
const keyboardTriggerable = computed(() => {
259+
if (ready.value)
260+
return false
261+
const triggers = Array.isArray(props.trigger) ? props.trigger : [props.trigger]
262+
return triggers.some(t => typeof t === 'string' && !['immediate', 'onNuxtReady', 'visibility', 'visible'].includes(t))
263+
})
264+
265+
function onPlaceholderKeydown(e: KeyboardEvent) {
266+
if (e.key !== 'Enter' && e.key !== ' ')
267+
return
268+
e.preventDefault()
269+
if (!rootEl.value)
270+
return
271+
const triggers = (Array.isArray(props.trigger) ? props.trigger : [props.trigger]).filter(Boolean) as string[]
272+
for (const t of triggers) {
273+
if (typeof t !== 'string')
274+
continue
275+
if (['immediate', 'onNuxtReady', 'visibility', 'visible'].includes(t))
276+
continue
277+
rootEl.value.dispatchEvent(new Event(t, { bubbles: false }))
278+
}
279+
}
280+
256281
const rootAttrs = computed(() => {
282+
const interactive = keyboardTriggerable.value
257283
return defu(props.rootAttrs, {
258284
'aria-busy': status.value === 'loading',
259285
'aria-label': status.value === 'awaitingLoad'
260-
? 'Vimeo Player - Placeholder'
286+
? (interactive ? 'Play video' : 'Vimeo Player - Placeholder')
261287
: status.value === 'loading'
262288
? 'Vimeo Player - Loading'
263289
: 'Vimeo Player - Loaded',
264290
'aria-live': 'polite',
265-
'role': 'application',
291+
'role': interactive ? 'button' : 'application',
292+
'tabindex': interactive ? 0 : undefined,
266293
'style': {
267294
'--vimeo-ratio': props.ratio,
268295
'maxWidth': '100%',
@@ -279,7 +306,7 @@ const rootAttrs = computed(() => {
279306
const placeholderAttrs = computed(() => {
280307
return defu(props.placeholderAttrs, {
281308
src: placeholder.value,
282-
alt: '',
309+
alt: 'Play video',
283310
loading: props.aboveTheFold ? 'eager' : 'lazy',
284311
// @ts-expect-error untyped
285312
fetchpriority: props.aboveTheFold ? 'high' : undefined,
@@ -296,7 +323,7 @@ onBeforeUnmount(() => player?.unload())
296323
</script>
297324

298325
<template>
299-
<div ref="rootEl" v-bind="rootAttrs">
326+
<div ref="rootEl" v-bind="rootAttrs" @keydown="keyboardTriggerable ? onPlaceholderKeydown($event) : undefined">
300327
<div v-show="ready" ref="elVimeo" class="vimeo-player" />
301328
<slot v-if="!ready" v-bind="payload" :placeholder="placeholder" name="placeholder">
302329
<img v-if="placeholder" v-bind="placeholderAttrs">

packages/script/src/runtime/components/ScriptYouTubePlayer.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ if (import.meta.server) {
245245
const placeholderAttrs = computed(() => {
246246
return defu(props.placeholderAttrs, {
247247
src: isFallbackPlaceHolder.value ? fallbackPlaceHolder.value : placeholder.value,
248-
alt: '',
248+
alt: 'Play video',
249249
loading: props.aboveTheFold ? 'eager' : 'lazy',
250250
// @ts-expect-error untyped
251251
fetchpriority: props.aboveTheFold ? 'high' : undefined,

0 commit comments

Comments
 (0)