From 9c8bdc01701479bf207e4bd3692f6dfc92ca1914 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Wed, 27 May 2026 15:23:47 +1000 Subject: [PATCH] fix(youtube-player): make placeholder keyboard-accessible Adds tabindex, role=button, aria-label and Enter/Space key handling on the ScriptYouTubePlayer placeholder so keyboard users can load the video without a pointer. The keydown handler dispatches the configured trigger event on the root element so it flows through the existing useScriptTriggerElement listener. Closes #797 --- .../components/ScriptYouTubePlayer.vue | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/script/src/runtime/components/ScriptYouTubePlayer.vue b/packages/script/src/runtime/components/ScriptYouTubePlayer.vue index c5ff0671..1170bdb7 100644 --- a/packages/script/src/runtime/components/ScriptYouTubePlayer.vue +++ b/packages/script/src/runtime/components/ScriptYouTubePlayer.vue @@ -197,16 +197,41 @@ defineExpose({ player, }) +// Keyboard accessibility: dispatch the configured trigger event on Enter/Space +// so users navigating via keyboard can activate the placeholder (fixes #797). +const keyboardTriggerable = computed(() => { + if (isTriggered.value) + return false + const triggers = Array.isArray(props.trigger) ? props.trigger : [props.trigger] + return triggers.some(t => typeof t === 'string' && !['immediate', 'onNuxtReady', 'visibility', 'visible'].includes(t)) +}) + +function onPlaceholderKeydown(e: KeyboardEvent) { + if (e.key !== 'Enter' && e.key !== ' ') + return + e.preventDefault() + if (!rootEl.value) + return + const triggers = (Array.isArray(props.trigger) ? props.trigger : [props.trigger]).filter(Boolean) as string[] + for (const t of triggers) { + if (['immediate', 'onNuxtReady', 'visibility', 'visible'].includes(t)) + continue + rootEl.value.dispatchEvent(new Event(t, { bubbles: false })) + } +} + const rootAttrs = computed(() => { + const interactive = keyboardTriggerable.value return defu(props.rootAttrs, { 'aria-busy': status.value === 'loading', 'aria-label': status.value === 'awaitingLoad' - ? 'YouTube Player - Placeholder' + ? (interactive ? 'Play video' : 'YouTube Player - Placeholder') : status.value === 'loading' ? 'YouTube Player - Loading' : 'YouTube Player - Loaded', 'aria-live': 'polite', - 'role': 'application', + 'role': interactive ? 'button' : 'application', + 'tabindex': interactive ? 0 : undefined, 'style': { cursor: 'pointer', position: 'relative', @@ -265,7 +290,7 @@ const placeholderAttrs = computed(() => {