feat(packages): add hotkey bindings to preset skins (#1264)

This commit is contained in:
rahim
2026-04-07 19:46:44 -07:00
committed by GitHub
parent 2d702ba1dd
commit 9912a8e759
11 changed files with 137 additions and 4 deletions
+16 -3
View File
@@ -3,7 +3,7 @@
import type { Media, MediaContainer } from '@videojs/core/dom';
import type { UnknownState, UnknownStore } from '@videojs/store';
import { useStore } from '@videojs/store/react';
import type { Dispatch, HTMLAttributes, ReactNode, SetStateAction } from 'react';
import type { Dispatch, HTMLAttributes, ReactNode, PointerEvent as ReactPointerEvent, SetStateAction } from 'react';
import { createContext, forwardRef, useContext, useEffect, useRef } from 'react';
import { useComposedRefs } from '../utils/use-composed-refs';
@@ -103,7 +103,10 @@ export interface ContainerProps extends HTMLAttributes<HTMLDivElement> {
children?: ReactNode;
}
export const Container = forwardRef<HTMLDivElement, ContainerProps>(function Container({ children, ...props }, ref) {
export const Container = forwardRef<HTMLDivElement, ContainerProps>(function Container(
{ children, tabIndex = 0, ...props },
ref
) {
const setContainer = useContainerAttach();
const internalRef = useRef<HTMLDivElement>(null);
const composedRef = useComposedRefs(ref, internalRef);
@@ -113,8 +116,18 @@ export const Container = forwardRef<HTMLDivElement, ContainerProps>(function Con
return () => setContainer?.(null);
}, [setContainer]);
const handlePointerUp = (event: ReactPointerEvent<HTMLDivElement>) => {
props.onPointerUp?.(event);
const el = internalRef.current;
if (!el) return;
// If nothing inside has focus, grab it so keyboard events reach hotkey listeners.
if (!el.contains(document.activeElement) || document.activeElement === document.body) {
el.focus({ preventScroll: true });
}
};
return (
<div ref={composedRef} {...props}>
<div ref={composedRef} tabIndex={tabIndex} {...props} onPointerUp={handlePointerUp}>
{children}
</div>
);
+17
View File
@@ -11,6 +11,7 @@ import { cn } from '@videojs/utils/style';
import { type ComponentProps, forwardRef, type ReactNode } from 'react';
import { Container, usePlayer } from '@/player/context';
import { ErrorDialog } from '@/ui/error-dialog';
import { MediaHotkey } from '@/ui/hotkey/media-hotkey';
import { MuteButton } from '@/ui/mute-button';
import { PlayButton } from '@/ui/play-button';
import { PlaybackRateButton } from '@/ui/playback-rate-button';
@@ -155,6 +156,22 @@ export function AudioSkin(props: AudioSkinProps): ReactNode {
</div>
</Tooltip.Provider>
</div>
{/* Hotkeys */}
<MediaHotkey keys="Space" action="togglePaused" />
<MediaHotkey keys="k" action="togglePaused" />
<MediaHotkey keys="m" action="toggleMuted" />
<MediaHotkey keys="ArrowRight" action="seekStep" value={5} />
<MediaHotkey keys="ArrowLeft" action="seekStep" value={-5} />
<MediaHotkey keys="l" action="seekStep" value={10} />
<MediaHotkey keys="j" action="seekStep" value={-10} />
<MediaHotkey keys="ArrowUp" action="volumeStep" value={0.05} />
<MediaHotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
<MediaHotkey keys="0-9" action="seekToPercent" />
<MediaHotkey keys="Home" action="seekToPercent" value={0} />
<MediaHotkey keys="End" action="seekToPercent" value={100} />
<MediaHotkey keys="Shift+>" action="speedUp" />
<MediaHotkey keys="Shift+<" action="speedDown" />
</Container>
);
}
+20
View File
@@ -23,6 +23,7 @@ import { CaptionsButton } from '@/ui/captions-button';
import { Controls } from '@/ui/controls';
import { ErrorDialog } from '@/ui/error-dialog';
import { FullscreenButton } from '@/ui/fullscreen-button';
import { MediaHotkey } from '@/ui/hotkey/media-hotkey';
import { MuteButton } from '@/ui/mute-button';
import { PiPButton } from '@/ui/pip-button';
import { PlayButton } from '@/ui/play-button';
@@ -229,6 +230,25 @@ export function VideoSkin(props: VideoSkinProps): ReactNode {
</Controls.Root>
<div className="media-overlay" />
{/* Hotkeys */}
<MediaHotkey keys="Space" action="togglePaused" />
<MediaHotkey keys="k" action="togglePaused" />
<MediaHotkey keys="m" action="toggleMuted" />
<MediaHotkey keys="f" action="toggleFullscreen" />
<MediaHotkey keys="c" action="toggleSubtitles" />
<MediaHotkey keys="i" action="togglePiP" />
<MediaHotkey keys="ArrowRight" action="seekStep" value={5} />
<MediaHotkey keys="ArrowLeft" action="seekStep" value={-5} />
<MediaHotkey keys="l" action="seekStep" value={10} />
<MediaHotkey keys="j" action="seekStep" value={-10} />
<MediaHotkey keys="ArrowUp" action="volumeStep" value={0.05} />
<MediaHotkey keys="ArrowDown" action="volumeStep" value={-0.05} />
<MediaHotkey keys="0-9" action="seekToPercent" />
<MediaHotkey keys="Home" action="seekToPercent" value={0} />
<MediaHotkey keys="End" action="seekToPercent" value={100} />
<MediaHotkey keys="Shift+>" action="speedUp" />
<MediaHotkey keys="Shift+<" action="speedDown" />
</Container>
);
}