Skip to main content

Drag And Drop

Dropping a file onto the canvas is dispatched through a host-extensible registry. Built-in handlers cover images, videos and — in the Editor component — every importable diagram format (native JSON, Excalidraw, Mermaid, JSON Canvas, Graphviz DOT, draw.io: diagramFileDropHandler from @oh-just-another/importers, which parses the file and inserts it at the drop point via editor.insertScene). Hosts add their own for custom payloads (CSV, …).

File-drop registry

Each handler is a FileDropHandler: a stable id, a sync accept(file) predicate, and an async handle(file, ctx) that does the work (read, decode, push scene patches). The editor registers the image and video handlers at construction; register more at runtime.

import { Editor, type FileDropHandler } from "@oh-just-another/state";

const csvHandler: FileDropHandler = {
id: "host.csv",
accept: (file) => file.name.endsWith(".csv"),
async handle(file, ctx) {
const text = await file.text();
// turn rows into elements, push patches via ctx
},
};

editor.registerFileDropHandler(csvHandler);
// later: editor.unregisterFileDropHandler("host.csv");

Drop overlay

While a file is dragged over the canvas the editor shows FileDropOverlay: a dashed frame, a drop glyph with DROP, and a chip per registered handler that carries presentation metadata — label (what it is), formats (what it takes) and kind (image · video · scene · text · data · file, picks the glyph). Handlers without a label stay out of the hint; the built-in image and video handlers ship theirs.

const csvHandler: FileDropHandler = {
id: "host.csv",
label: "Spreadsheets",
kind: "data",
formats: ["CSV"],
accept: (file) => file.name.endsWith(".csv"),
handle: async (file, ctx) => {
/* … */
},
};

Hosts composing their own canvas drive it from usePalettePlacement({ onFileDrag }) and read the list via editor.getFileDropHandlers().

Helpers

The state package exports MIME constants and readers so handlers don't reinvent them:

import {
IMAGE_MIME_TYPES,
VIDEO_MIME_TYPES,
isImageFile,
isVideoFile,
isSceneJsonFile,
readFileAsDataURL,
readFileAsText,
walkDataTransfer,
} from "@oh-just-another/state";

walkDataTransfer flattens a dropped DataTransfer (including directories) into a file list.

With the React component

The Editor component accepts a fileDropHandlers prop registered on mount, so you don't need to reach the engine for the common case.

import { Editor } from "@oh-just-another/editor";

<Editor fileDropHandlers={[csvHandler]} />;

To route files from another source (e.g. a clipboard paste), call editor.dispatchFileDrop(file, worldPoint) — it walks the same registry as a pointer drop.