import { createContext, use, useSyncExternalStore } from 'react'; import type { CropperEngine } from '../core/engine.ts'; import type { CropperConfig, CropperState, CropValue, Point, Rotation, Size } from '../core/types.ts'; export interface CropperSource { src: string | undefined; naturalSize: Size | null; } /** stable cropper controller. */ export interface CropperContextValue { aspectRatio: number | undefined; config: CropperConfig; engine: CropperEngine; reportNaturalSize: (size: Size) => void; rotate: (turns: number) => void; setPan: (pan: Partial) => void; setRotation: (rotation: Rotation) => void; setSource: (source: CropperSource) => void; setValue: (value: CropValue) => void; setViewportSize: (size: Size) => void; setZoom: (zoom: number, focal?: Point) => void; } export const CropperContext = createContext(null); /** * get the cropper controller. * * @returns the cropper controller. * @throws if called outside a ``. */ export const useCropper = (): CropperContextValue => { const ctx = use(CropperContext); if (!ctx) { throw new Error(`useCropper must be used within `); } return ctx; }; /** * subscribe to cropper state or a selected value. * * @param selector state selector that returns a stable value for unchanged input. * @returns full state or the selected value. * @throws when called outside a ``. */ export function useCropperState(): CropperState; export function useCropperState(selector: (state: CropperState) => T): T; export function useCropperState(selector?: (state: CropperState) => T): T | CropperState { const { engine } = useCropper(); const get = () => (selector ? selector(engine.getState()) : engine.getState()); return useSyncExternalStore(engine.subscribe, get, get); }