@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/reactTypes
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' | numberPreview 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): TransformReturns 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,
): BoxResizeResultMoves 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,
): TransformScales 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,
): TransformReturns the base transform rotated by the pointer angle change around the element center.
Hooks
useEditorContext
function useEditorContext(): EditorContextValueReturns the engine and preview pool from the nearest EditorProvider, and throws when called outside one.
useEditor
function useEditor(): EditorEngineReturns 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,
): TSelectedSubscribes 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,
): TSelectedSubscribes to the engine playback store and returns the selected slice of PlaybackState.
useProject
function useProject(): ProjectReturns the current Project from editor state.
useSelectedElement
function useSelectedElement(): ElementLocation | undefinedReturns the first selected element together with its track and track index, or undefined when nothing is selected.
useSelection
function useSelection(): SelectionStateReturns 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,
): voidListens 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,
): voidListens 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,
): voidListens 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 },
): voidSubscribes 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,
): voidRuns 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): voidToggles a class on document.documentElement while enabled is true and restores the previous state on unmount.
useDocumentRootAttribute
function useDocumentRootAttribute(name: string, value: string): voidSets an attribute on document.documentElement and restores the previous value, or removes the attribute, on unmount.
useMediaQuery
function useMediaQuery(query: string): booleanSubscribes to window.matchMedia(query) and returns whether it matches. The server snapshot is false.
useDisposable
function useDisposable<T extends Disposable>(create: () => T): TCreates 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.