import * as React from 'react'; import { DEFAULT_ANIMATION_SHORTCUTS, buildAriaKeyShortcuts, buildShortcutHelpRows, createAnimationShortcutHandler, createAnimationStatusAnnouncement, type AnimationAccessibilityCommand, type AnimationShortcutActions, type AnimationShortcutHandlerOptions, type AnimationStatusAnnouncementInput, type KeyboardShortcutEventLike, type ShortcutDefinition, type ShortcutDisplayPlatform, } from '../../animations/animationAccessibility'; export interface AnimationShortcutHelpPanelProps { shortcuts?: readonly ShortcutDefinition[] | undefined; open?: boolean | undefined; id?: string | undefined; title?: string | undefined; description?: string | undefined; platform?: ShortcutDisplayPlatform | undefined; className?: string | undefined; } export interface AnimationLiveRegionProps { announcement: string; politeness?: 'polite' | 'assertive' | 'off' | undefined; atomic?: boolean | undefined; visuallyHidden?: boolean | undefined; className?: string | undefined; } export interface AnimationStatusLiveRegionProps extends Omit { state: AnimationStatusAnnouncementInput; } export interface AnimationKeyboardScopeProps { actions: AnimationShortcutActions; shortcuts?: readonly ShortcutDefinition[] | undefined; enabled?: AnimationShortcutHandlerOptions['enabled']; allowInEditable?: boolean | undefined; stopPropagation?: boolean | undefined; onCommand?: AnimationShortcutHandlerOptions['onCommand']; children?: React.ReactNode; className?: string | undefined; ariaLabel?: string | undefined; describedBy?: string | undefined; tabIndex?: number | undefined; } const DEFAULT_HELP_DESCRIPTION = 'Keyboard shortcuts operate only while the animation controls or viewer region have focus, and never override text fields or sliders.'; export function AnimationShortcutHelpPanel({ shortcuts = DEFAULT_ANIMATION_SHORTCUTS, open = true, id = 'animation-shortcut-help', title = 'Animation keyboard shortcuts', description = DEFAULT_HELP_DESCRIPTION, platform = 'generic', className, }: AnimationShortcutHelpPanelProps): JSX.Element | null { const rows = React.useMemo( () => buildShortcutHelpRows(shortcuts, platform), [platform, shortcuts], ); if (!open) { return null; } return (

{title}

{description ?

{description}

: null}
{rows.map((row) => (
{row.chord}
{groupLabel(row.group)} {row.description}
))}
); } export function AnimationLiveRegion({ announcement, politeness = 'polite', atomic = true, visuallyHidden = true, className, }: AnimationLiveRegionProps): JSX.Element { const role = politeness === 'assertive' ? 'alert' : politeness === 'polite' ? 'status' : undefined; return (
{announcement}
); } export function AnimationStatusLiveRegion({ state, ...liveRegionProps }: AnimationStatusLiveRegionProps): JSX.Element { const announcement = React.useMemo(() => createAnimationStatusAnnouncement(state), [state]); return ; } export function useAnimationShortcutHandler( actions: AnimationShortcutActions, options?: AnimationShortcutHandlerOptions, ): ( event: KeyboardShortcutEventLike | React.KeyboardEvent, ) => AnimationAccessibilityCommand | undefined { const actionsRef = React.useRef(actions); const optionsRef = React.useRef(options); React.useEffect(() => { actionsRef.current = actions; }, [actions]); React.useEffect(() => { optionsRef.current = options; }, [options]); return React.useCallback( (event: KeyboardShortcutEventLike | React.KeyboardEvent) => createAnimationShortcutHandler(actionsRef.current, optionsRef.current)( event as KeyboardShortcutEventLike, ), [], ); } export function AnimationKeyboardScope({ actions, shortcuts = DEFAULT_ANIMATION_SHORTCUTS, enabled, allowInEditable, stopPropagation, onCommand, children, className, ariaLabel = 'Interactive animation controls', describedBy, tabIndex = 0, }: AnimationKeyboardScopeProps): JSX.Element { const options = React.useMemo( () => ({ shortcuts, enabled, allowInEditable, stopPropagation, onCommand, }), [allowInEditable, enabled, onCommand, shortcuts, stopPropagation], ); const handleShortcut = useAnimationShortcutHandler(actions, options); const ariaKeyShortcuts = React.useMemo(() => buildAriaKeyShortcuts(shortcuts), [shortcuts]); return (
{ handleShortcut(event); }} data-animation-keyboard-scope="" > {children}
); } function groupLabel(group: ShortcutDefinition['group']): string { switch (group) { case 'playback': return 'Playback'; case 'stepping': return 'Step / seek'; case 'speed': return 'Speed'; case 'tour': return 'Guided tour'; case 'view': return 'View'; case 'help': return 'Help'; } } function cx(...tokens: Array): string { return tokens.filter(Boolean).join(' '); }