UNPKG

@zag-js/splitter

Version:

Core logic for the splitter widget implemented as a state machine

263 lines (260 loc) • 7.91 kB
import { Machine, EventObject, Service } from '@zag-js/core'; import { PropTypes, RequiredBy, DirectionProperty, CommonProperties } from '@zag-js/types'; import { SplitterRegistry } from './utils/registry.js'; type ResizeEvent = PointerEvent | KeyboardEvent; type PanelId = string; type ResizeTriggerId = `${PanelId}:${PanelId}` | `${PanelId}:` | `:${PanelId}`; type PanelSize = number | string; type PanelResizeBehavior = "preserve-relative-size" | "preserve-pixel-size"; interface PanelData { /** * The id of the panel. */ id: PanelId; /** * The order of the panel. useful of you intend to conditionally render the panel. */ order?: number | undefined; /** * The minimum size of the panel. */ minSize?: PanelSize | undefined; /** * The maximum size of the panel. */ maxSize?: PanelSize | undefined; /** * Whether the panel is collapsible. */ collapsible?: boolean | undefined; /** * How the panel should behave when the parent group is resized. */ resizeBehavior?: PanelResizeBehavior | undefined; /** * The size of the panel when collapsed. */ collapsedSize?: PanelSize | undefined; } type NormalizedPanelData = Omit<PanelData, "minSize" | "maxSize" | "collapsedSize"> & { minSize?: number | undefined; maxSize?: number | undefined; collapsedSize?: number | undefined; }; interface CursorState { isAtMin: boolean; isAtMax: boolean; } interface ResizeDetails { size: number[]; resizeTriggerId: string | null; layout: string; expandToSizes: Record<string, number>; } interface ResizeEndDetails { size: number[]; resizeTriggerId: string | null; } interface ExpandCollapseDetails { panelId: string; size: number; } type ElementIds = Partial<{ root: string; resizeTrigger: (id: string) => string; label: (id: string) => string; panel: (id: string | number) => string; }>; interface SplitterProps extends DirectionProperty, CommonProperties { /** * The orientation of the splitter. Can be `horizontal` or `vertical` * @default "horizontal" */ orientation?: "horizontal" | "vertical" | undefined; /** * The controlled size data of the panels */ size?: PanelSize[] | undefined; /** * The initial size of the panels when rendered. * Use when you don't need to control the size of the panels. */ defaultSize?: PanelSize[] | undefined; /** * The size constraints of the panels. */ panels: PanelData[]; /** * Function called when the splitter is resized. */ onResize?: ((details: ResizeDetails) => void) | undefined; /** * Function called when the splitter resize starts. */ onResizeStart?: (() => void) | undefined; /** * Function called when the splitter resize ends. */ onResizeEnd?: ((details: ResizeEndDetails) => void) | undefined; /** * The ids of the elements in the splitter. Useful for composition. */ ids?: ElementIds | undefined; /** * The number of pixels to resize the panel by when the keyboard is used. */ keyboardResizeBy?: number | null | undefined; /** * The nonce for the injected splitter cursor stylesheet. */ nonce?: string | undefined; /** * Function called when a panel is collapsed. */ onCollapse?: ((details: ExpandCollapseDetails) => void) | undefined; /** * Function called when a panel is expanded. */ onExpand?: ((details: ExpandCollapseDetails) => void) | undefined; /** * The splitter registry to use for multi-drag support. * When provided, enables dragging at the intersection of multiple splitters. */ registry?: SplitterRegistry | undefined; } type PropsWithDefault = "orientation" | "panels"; interface DragState { resizeTriggerId: string; resolvedResizeTriggerId: `${PanelId}:${PanelId}`; resizeTriggerRect: DOMRect; initialCursorPosition: number; initialSize: number[]; } interface KeyboardState { resizeTriggerId: string; resolvedResizeTriggerId: `${PanelId}:${PanelId}` | null; } interface Context { dragState: DragState | null; keyboardState: KeyboardState | null; size: number[]; panels: NormalizedPanelData[]; } interface Refs { panelSizeBeforeCollapse: Map<string, number>; panelIdToLastNotifiedSizeMap: Map<string, number>; prevDelta: number; initialSize: number[] | null; prevInitialLayout: string | null; prevGroupSize: number | null; lastRequestedSize: number[] | null; suppressOnResize: boolean; } interface SplitterSchema { state: "idle" | "hover:temp" | "hover" | "dragging" | "focused"; tag: "focus"; props: RequiredBy<SplitterProps, PropsWithDefault>; context: Context; computed: { horizontal: boolean; }; refs: Refs; action: string; event: EventObject; effect: string; guard: string; } type SplitterService = Service<SplitterSchema>; type SplitterMachine = Machine<SplitterSchema>; interface PanelProps { id: PanelId; } interface ResizeTriggerProps { id: ResizeTriggerId; disabled?: boolean | undefined; } interface ResizeTriggerState { dragging: boolean; focused: boolean; disabled: boolean; } interface PanelItem { type: "panel"; id: PanelId; } interface ResizeTriggerItem { type: "handle"; id: ResizeTriggerId; } type SplitterItem = PanelItem | ResizeTriggerItem; interface SplitterApi<T extends PropTypes = PropTypes> { /** * Whether the splitter is currently being resized. */ dragging: boolean; /** * The orientation of the splitter. */ orientation: "horizontal" | "vertical"; /** * Returns the current sizes of the panels. */ getSizes: () => number[]; /** * Sets the sizes of the panels. */ setSizes: (size: PanelSize[]) => void; /** * Returns the items of the splitter. */ getItems: () => SplitterItem[]; /** * Returns the panels of the splitter. */ getPanels: () => PanelData[]; /** * Returns the panel with the specified id. */ getPanelById: (id: PanelId) => PanelData; /** * Returns the size of the specified panel. */ getPanelSize: (id: PanelId) => number; /** * Returns whether the specified panel is collapsed. */ isPanelCollapsed: (id: PanelId) => boolean; /** * Returns whether the specified panel is expanded. */ isPanelExpanded: (id: PanelId) => boolean; /** * Collapses the specified panel. */ collapsePanel: (id: PanelId) => void; /** * Expands the specified panel. */ expandPanel: (id: PanelId, minSize?: number) => void; /** * Resizes the specified panel. */ resizePanel: (id: PanelId, unsafePanelSize: number) => void; /** * Returns the layout of the splitter. */ getLayout: () => string; /** * Resets the splitter to its initial state. */ resetSizes: VoidFunction; /** * Returns the state of the resize trigger. */ getResizeTriggerState: (props: ResizeTriggerProps) => ResizeTriggerState; getRootProps: () => T["element"]; getPanelProps: (props: PanelProps) => T["element"]; getResizeTriggerProps: (props: ResizeTriggerProps) => T["element"]; getResizeTriggerIndicator: (props: ResizeTriggerProps) => T["element"]; } export type { CursorState, DragState, ElementIds, ExpandCollapseDetails, KeyboardState, NormalizedPanelData, PanelData, PanelId, PanelItem, PanelProps, PanelResizeBehavior, PanelSize, ResizeDetails, ResizeEndDetails, ResizeEvent, ResizeTriggerId, ResizeTriggerItem, ResizeTriggerProps, ResizeTriggerState, SplitterApi, SplitterItem, SplitterMachine, SplitterProps, SplitterSchema, SplitterService };