UNPKG

preact-spatial-navigation

Version:

A powerful Preact library for TV-style spatial navigation with LRUD algorithm, virtualized lists/grids, and smart TV support

319 lines (295 loc) 9.16 kB
import type { JSX } from 'preact'; import type { ComponentChildren } from 'preact'; /** * Direction for spatial navigation (from @bam.tech/lrud) */ export type Direction = 'left' | 'right' | 'up' | 'down'; /** * Orientation for layouts */ export type Orientation = 'vertical' | 'horizontal'; /** * Node orientation (alias for compatibility) */ export type NodeOrientation = Orientation; /** * Ref methods for SpatialNavigationNode */ export interface SpatialNavigationNodeRef { focus: () => void; } /** * Parameters passed to children render function in SpatialNavigationNode (focusable) */ export interface FocusableNodeState { /** Whether this node is currently focused */ isFocused: boolean; /** Whether this node is active (has a focused child) */ isActive: boolean; /** Whether the root navigator is active */ isRootActive: boolean; } /** * Parameters passed to children render function in SpatialNavigationNode (non-focusable) */ export interface NonFocusableNodeState { /** Whether this node is active (has a focused child) */ isActive: boolean; /** Whether the root navigator is active */ isRootActive: boolean; } /** * Props for SpatialNavigationRoot */ export interface SpatialNavigationRootProps { /** Determines if the spatial navigation is active */ isActive?: boolean; /** Called when reaching a border of the navigator */ onDirectionHandledWithoutMovement?: (direction: Direction) => void; /** Children elements */ children: ComponentChildren; } /** * Props for SpatialNavigationNode (focusable variant) */ export interface SpatialNavigationNodeFocusableProps { /** This node is focusable */ isFocusable: true; /** Children - render function receiving focus state */ children: (props: FocusableNodeState) => JSX.Element; /** Callback when node gains focus */ onFocus?: () => void; /** Callback when node loses focus */ onBlur?: () => void; /** Callback when node is selected (Enter key) */ onSelect?: () => void; /** Callback when node is selected with long press */ onLongSelect?: () => void; /** Callback when node becomes active (child is focused) */ onActive?: () => void; /** Callback when node becomes inactive */ onInactive?: () => void; /** Orientation of the node */ orientation?: NodeOrientation; /** Whether child lists should behave like a grid */ alignInGrid?: boolean; /** Index range for grid alignment */ indexRange?: { start: number; end: number }; /** Additional offset for scroll */ additionalOffset?: number; } /** * Props for SpatialNavigationNode (non-focusable variant) */ export interface SpatialNavigationNodeNonFocusableProps { /** This node is not focusable (container only) */ isFocusable?: false; /** Children - can be render function or elements */ children: ComponentChildren | ((props: NonFocusableNodeState) => JSX.Element); /** Callback when node becomes active (child is focused) */ onActive?: () => void; /** Callback when node becomes inactive */ onInactive?: () => void; /** Orientation of the node */ orientation?: NodeOrientation; /** Whether child lists should behave like a grid */ alignInGrid?: boolean; /** Index range for grid alignment */ indexRange?: { start: number; end: number }; /** Additional offset for scroll */ additionalOffset?: number; } /** * Props for SpatialNavigationNode (union of focusable and non-focusable) */ export type SpatialNavigationNodeProps = | SpatialNavigationNodeFocusableProps | SpatialNavigationNodeNonFocusableProps; /** * Props for SpatialNavigationView */ export interface SpatialNavigationViewProps { /** Direction of the layout */ direction: 'horizontal' | 'vertical'; /** Whether child lists should behave like a grid */ alignInGrid?: boolean; /** Style for the view */ style?: JSX.CSSProperties; /** Children elements */ children: ComponentChildren; } /** * Props for SpatialNavigationFocusableView */ export interface SpatialNavigationFocusableViewProps { /** Callback when node gains focus */ onFocus?: () => void; /** Callback when node loses focus */ onBlur?: () => void; /** Callback when node is selected (Enter key) */ onSelect?: () => void; /** Callback when node is selected with long press */ onLongSelect?: () => void; /** Callback when node becomes active (child is focused) */ onActive?: () => void; /** Callback when node becomes inactive */ onInactive?: () => void; /** Orientation of the node */ orientation?: NodeOrientation; /** Whether child lists should behave like a grid */ alignInGrid?: boolean; /** Index range for grid alignment */ indexRange?: { start: number; end: number }; /** Additional offset for scroll */ additionalOffset?: number; /** Style for the view */ style?: JSX.CSSProperties; /** Props to pass to the inner div */ viewProps?: JSX.HTMLAttributes<HTMLDivElement>; /** Children - can be render function or element */ children: JSX.Element | ((props: FocusableNodeState) => JSX.Element); } /** * Props for SpatialNavigationScrollView */ export interface SpatialNavigationScrollViewProps { /** Whether scrolling is horizontal */ horizontal?: boolean; /** Offset from start edge in pixels */ offsetFromStart?: number; /** Style for the scroll view */ style?: JSX.CSSProperties; /** Children elements */ children: ComponentChildren; /** Arrow for ascending scroll (web TV cursor) */ ascendingArrow?: JSX.Element; /** Style for ascending arrow container */ ascendingArrowContainerStyle?: JSX.CSSProperties; /** Arrow for descending scroll (web TV cursor) */ descendingArrow?: JSX.Element; /** Style for descending arrow container */ descendingArrowContainerStyle?: JSX.CSSProperties; /** Scroll speed in pixels per 10ms on pointer hover */ pointerScrollSpeed?: number; /** Use native scroll instead of CSS scroll */ useNativeScroll?: boolean; } /** * Spatial Navigation context value (legacy - for backward compatibility) */ export interface SpatialNavigationContextValue { /** Whether the navigation system is initialized */ initialized: boolean; /** Currently focused section ID */ currentSectionId: string | null; /** Register a new section */ registerSection: (config: any) => string; /** Unregister a section */ unregisterSection: (sectionId: string) => void; /** Set focus to a specific section */ focusSection: (sectionId: string) => void; /** Pause navigation */ pause: () => void; /** Resume navigation */ resume: () => void; /** Whether the root is currently active */ rootActive: boolean; } // Legacy types (for backward compatibility - deprecated) /** * @deprecated Use the new render props API with SpatialNavigationNode */ export interface FocusableConfig { onFocus?: (event?: FocusEvent) => void; onBlur?: (event?: FocusEvent) => void; onEnterPress?: () => void; disabled?: boolean; autoFocus?: boolean; focusedClassName?: string; trackingId?: string; } /** * @deprecated Use the new render props API with SpatialNavigationNode */ export interface UseFocusableReturn { ref: (element: HTMLElement | null) => void; focused: boolean; focus: () => void; blur: () => void; } /** * @deprecated Legacy Grid component props - use SpatialNavigationView with alignInGrid instead */ export interface GridProps { children: ComponentChildren; columns?: number; gap?: string; className?: string; style?: JSX.CSSProperties; id?: string; enterTo?: '' | 'last-focused' | 'default-element'; restrict?: 'self-first' | 'self-only' | 'none'; disabled?: boolean; rememberLastFocus?: boolean; defaultElement?: string; } /** * @deprecated Legacy List component props - use SpatialNavigationView instead */ export interface ListProps { children: ComponentChildren; orientation?: 'vertical' | 'horizontal'; gap?: string; className?: string; style?: JSX.CSSProperties; id?: string; enterTo?: '' | 'last-focused' | 'default-element'; restrict?: 'self-first' | 'self-only' | 'none'; disabled?: boolean; rememberLastFocus?: boolean; defaultElement?: string; } /** * @deprecated Legacy configuration */ export interface SectionConfig { id?: string; defaultElement?: string; enterTo?: '' | 'last-focused' | 'default-element'; restrict?: 'self-first' | 'self-only' | 'none'; disabled?: boolean; } /** * @deprecated Legacy Grid configuration */ export interface GridConfig extends SectionConfig { columns?: number; gap?: string; rememberLastFocus?: boolean; } /** * @deprecated Legacy List configuration */ export interface ListConfig extends SectionConfig { orientation?: 'vertical' | 'horizontal'; gap?: string; rememberLastFocus?: boolean; } /** * @deprecated Use SpatialNavigationRootProps */ export interface SpatialNavigationProviderProps { children: ComponentChildren; config?: Record<string, any>; } /** * Focus event detail (legacy) * @deprecated */ export interface FocusDetail { sectionId?: string; direction?: Direction; previousElement?: HTMLElement; nextElement?: HTMLElement; native?: boolean; }