mcut

@mcut/react

Editor provider, state hooks, preview canvas, and gesture math.

@mcut/react provides the editor engine to a React tree, exposes hooks over its state, and renders the preview canvas.

Install

bun add @mcut/react

Types

EditorContextValue

interface EditorContextValue {
  engine: EditorEngine
  pool: PreviewMediaPool
}

The value carried by the editor context, an engine and its preview media pool.

EditorProviderProps

interface EditorProviderProps {
  engine?: EditorEngine
  project?: Project
  maxHistorySize?: number
  children: ReactNode
}

Props for EditorProvider, with an optional external engine or an initial project and history size for an internal one.

PlayerCanvasProps

interface PlayerCanvasProps {
  className?: string
  interactive?: boolean
  background?: string
  hiddenElementIds?: ReadonlySet<string>
  onElementDoubleClick?: (elementId: ElementId) => void
  quality?: PreviewQuality
  renderer?: 'canvas2d' | 'webgpu'
}

Props for PlayerCanvas, covering styling, interactivity, background, hidden elements, double-click handling, preview quality, and renderer choice.

PreviewQuality

type PreviewQuality = 'auto' | 'full' | number

Preview raster resolution, where 'auto' follows the displayed size, a number caps the short side in pixels, and 'full' uses the project resolution.

BoxResizeResult

interface BoxResizeResult {
  transform: Transform
  displayWidth: number
  displayHeight: number
}

The result of applyBoxResize, a transform plus the resulting display width and height.

GesturePoint

interface GesturePoint {
  x: number
  y: number
}

A pointer position in canvas pixels.

Disposable

interface Disposable {
  dispose: () => void
}

Anything useDisposable can own, an object with a dispose method to call on unmount.

EngineStore

interface EngineStore<T> {
  get: () => T
  subscribe: (listener: (state: T) => void) => { unsubscribe: () => void }
}

The store shape useEngineSubscription reads, matched by EditorEngine.store and EditorEngine.playback.

WebSocketHandlers

interface WebSocketHandlers {
  onOpen: (socket: WebSocket) => void
  onMessage: (socket: WebSocket, event: MessageEvent) => void
}

The callbacks useWebSocket runs when a connection opens and when a frame arrives, each handed the socket to reply on.

Functions

applyMove

function applyMove(base: Transform, start: GesturePoint, current: GesturePoint): Transform

Returns a copy of the base transform offset by the pointer delta between the start and current points.

applyBoxResize

function applyBoxResize(
  base: Transform,
  obb: OBB,
  handle: Exclude<HandleId, 'rotate'>,
  current: GesturePoint,
  minSize = MIN_BOX_SIZE,
): BoxResizeResult

Moves one edge or corner of the element box to the pointer, keeps the box at least minSize on each axis, and returns the recentered transform with the new display size.

applyResize

function applyResize(
  base: Transform,
  obb: OBB,
  handle: Exclude<HandleId, 'rotate'>,
  start: GesturePoint,
  current: GesturePoint,
  preserveAspect = false,
): Transform

Scales the base transform from a handle, uniformly for corners and along one axis for edges, with pointer positions mapped into the element's unrotated space.

applyRotate

function applyRotate(
  base: Transform,
  obb: OBB,
  start: GesturePoint,
  current: GesturePoint,
): Transform

Returns the base transform rotated by the pointer angle change around the element center.

Hooks

useEditorContext

function useEditorContext(): EditorContextValue

Returns the engine and preview pool from the nearest EditorProvider, and throws when called outside one.

useEditor

function useEditor(): EditorEngine

Returns the EditorEngine from context for dispatching commands, undo and redo, and transport.

useEditorState

function useEditorState<TSelected = EditorState>(
  selector?: (state: EditorState) => TSelected,
  compare?: (a: TSelected, b: TSelected) => boolean,
): TSelected

Subscribes to the engine store and returns the selected slice of EditorState, re-rendering when the compare function reports a change.

usePlayback

function usePlayback<TSelected = PlaybackState>(
  selector?: (state: PlaybackState) => TSelected,
  compare?: (a: TSelected, b: TSelected) => boolean,
): TSelected

Subscribes to the engine playback store and returns the selected slice of PlaybackState.

useProject

function useProject(): Project

Returns the current Project from editor state.

useSelectedElement

function useSelectedElement(): ElementLocation | undefined

Returns the first selected element together with its track and track index, or undefined when nothing is selected.

useSelection

function useSelection(): SelectionState

Returns the current SelectionState from editor state.

useLatest

function useLatest<T>(value: T): { readonly current: T }

Returns a ref whose current is the value from the latest committed render, so a long-lived callback can read fresh props without resubscribing.

useWindowEvent

function useWindowEvent<K extends keyof WindowEventMap>(
  type: K,
  handler: (event: WindowEventMap[K]) => void,
  options?: AddEventListenerOptions,
): void

Listens for a window event for the life of the component, always calling the latest handler without resubscribing when it changes.

useDocumentEvent

function useDocumentEvent<K extends keyof DocumentEventMap>(
  type: K,
  handler: (event: DocumentEventMap[K]) => void,
  options?: AddEventListenerOptions,
): void

Listens for a document event for the life of the component, always calling the latest handler without resubscribing when it changes.

useElementEvent

function useElementEvent<K extends keyof HTMLElementEventMap>(
  ref: RefObject<HTMLElement | null>,
  type: K,
  handler: (event: HTMLElementEventMap[K]) => void,
  options?: AddEventListenerOptions,
): void

Listens for an event on the element in ref for the life of the component, doing nothing while the ref is empty.

useEngineSubscription

function useEngineSubscription<T>(
  store: EngineStore<T>,
  onChange: (state: T, previous: T) => void,
  options?: { debounceMs?: number },
): void

Subscribes to an engine store outside React rendering and calls onChange with the new and previous state, coalescing bursts into one call when debounceMs is set.

useEngineSync

function useEngineSync<T, S>(
  store: EngineStore<T>,
  select: (state: T) => S,
  sync: (selected: S) => void,
): void

Runs sync once on mount with the selected slice of the store and again whenever the selected value changes by Object.is, for keeping something outside React in step with engine state.

useWebSocket

useWebSocket(url, { onOpen, onMessage }, { reconnectDelayMs: 1000 })

Opens a WebSocket to the string url for the life of the component, reconnects reconnectDelayMs milliseconds after every close, and closes the socket on unmount. The latest WebSocketHandlers run without reopening the connection, and a null url opens nothing.

useDocumentRootClass

function useDocumentRootClass(className: string, enabled: boolean): void

Toggles a class on document.documentElement while enabled is true and restores the previous state on unmount.

useDocumentRootAttribute

function useDocumentRootAttribute(name: string, value: string): void

Sets an attribute on document.documentElement and restores the previous value, or removes the attribute, on unmount.

useMediaQuery

function useMediaQuery(query: string): boolean

Subscribes to window.matchMedia(query) and returns whether it matches. The server snapshot is false.

useDisposable

function useDisposable<T extends Disposable>(create: () => T): T

Creates an object once for the life of the component and calls its dispose method on unmount.

Components

EditorProvider

function EditorProvider({
  engine: externalEngine,
  project,
  maxHistorySize,
  children,
}: EditorProviderProps)

Creates or adopts an EditorEngine and a PreviewMediaPool and provides both to the React tree below it.

PlayerCanvas

function PlayerCanvas({ renderer = 'webgpu', ...props }: PlayerCanvasProps)

Picks the WebGPU or Canvas2D playback loop for the chosen renderer, then renders the project at the current playback time onto a canvas and follows the pool's audio clock so the picture follows the sound, both while playing and while a paused sound plays out to its stop. When interactive, it handles element selection, drag, resize, and rotate gestures.

On this page