Miraiclip SDK
React components

Transform box

Select, move, scale and rotate clips on the preview.

<TransformBox.*> turns the preview into a canvas you can edit: click a clip to select it, drag it to move, drag a corner to scale and the top knob to rotate. Moves snap to the frame's edges and center. Each drag is one undo step.

Loading demo…
Drag a title or the lower third. Drag a corner to scale, the top knob to rotate (Shift: 15° steps). Hold Alt while moving to turn off snapping. Arrow keys nudge the selection.

Example

CanvasEditor.tsx
import { Preview, TransformBox } from "@miraiclip/react";

const ACCENT = "#38bdf8";
const CORNERS = [0, 1, 2, 3] as const;

// Inside <ProjectProvider> and <PlaybackProvider>.
export function CanvasEditor() {
  return (
    <div className="flex h-80 items-center justify-center bg-neutral-900">
      <Preview fitPadding={24} className="bg-black">
        {/* Children of <Preview> sit over the canvas, at its size. */}
        <TransformBox.Root className="data-[dragging=move]:cursor-grabbing">
          <TransformBox.Outline target="hover" stroke={ACCENT} strokeOpacity={0.5} />
          <TransformBox.Guides stroke="#f472b6" strokeDasharray="4 3" />
          <TransformBox.Outline stroke={ACCENT} strokeWidth={1.5} />
          <TransformBox.RotateLine stroke={ACCENT} strokeWidth={1.5} />
          {/* Body: drag inside the box to move. Render it before the handles so they stay on top. */}
          <TransformBox.Body />
          {CORNERS.map((corner) => (
            <TransformBox.Handle
              key={corner}
              corner={corner}
              className="h-2.5 w-2.5 rounded-sm border border-sky-400 bg-white"
            />
          ))}
          <TransformBox.RotateHandle className="h-3 w-3 rounded-full border border-sky-400 bg-white" />
        </TransformBox.Root>
      </Preview>
    </div>
  );
}

Anatomy

<Preview>
  <TransformBox.Root>                       // the interaction layer over the canvas
    <TransformBox.Outline target="hover" /> // the clip under the pointer
    <TransformBox.Guides />                 // snap lines while moving
    <TransformBox.Outline />                // the selected clip
    <TransformBox.RotateLine />
    <TransformBox.Body />                   // drag inside the box to move
    <TransformBox.Handle corner={0} />      // … corners 0 to 3: scale
    <TransformBox.RotateHandle />           // rotate
  </TransformBox.Root>
</Preview>

It must be inside <Preview>, which gives it the preview's size and scale. It renders nothing unless the preview is live, so in "fallback" or "unsupported" mode there is nothing to click. Render order is paint order: put the handles last so they stay on top of the body.

Parts

Root

A layer over the canvas (position: absolute; inset: 0). A press picks the topmost clip under the pointer and starts moving it right away; a press on empty space clears the selection.

PropDefaultDescription
snapPx6Moves snap to the frame's edges and center within this many screen pixels. 0 turns snapping off. Alt places freely
dragThresholdPx2Movement below this is a click, not a drag
nudgetrueArrow keys move the selection 1 px, or 10 px with Shift. Keys typed into fields are left alone
canSelectvisual clips on unlocked tracks(clip) => boolean: which clips a click can pick
onClipDoubleClick(clipId) => void, for example to open a text editor

data-dragging is "move", "scale" or "rotate" during a drag.

Outline

The selected clip's outline as an SVG polygon, following its rotation. target="hover" outlines the clip under the pointer instead. Props go to the <polygon>: stroke, strokeWidth, strokeDasharray, className.

Guides

Full-width and full-height lines for the frame edges or center the current move snapped to. Props go to each <line>.

Body

A transparent polygon over the selected clip that starts a move. Only rendered while the selection can be edited.

Handle

A corner handle (corner: 0 top-left, then clockwise) that scales the clip uniformly around its anchor. It is centered on the corner; size and style it with CSS. Its cursor follows the clip's rotation.

RotateHandle and RotateLine

The rotate knob, distance screen pixels (default 26) beyond the middle of the top edge, and the line joining it to the edge. Rotation snaps gently to right angles, or to 15° steps with Shift.

data-active is set on a handle while its drag is in progress.

Keyframes

A drag writes transform.x, y, scale and rotation. When a property has keyframes, the drag sets a keyframe at the playhead instead of the static value, so animated clips stay animated. The logic is beginTransformDrag and writeClipTransform in @miraiclip/editor.

Custom handles

useTransformBox() returns the state the parts use, for drawing your own:

FieldDescription
boxThe selected clip's bounds (composition px): corners, centerXPx, centerYPx, rotationDeg, scale; or null
hoverBounds of the clip under the pointer, when it isn't the selection
guides{ x: number[], y: number[] }: the lines the move snapped to
editableThe selected clip can be transformed (visual, track unlocked)
dragging"move", "scale", "rotate" or null
scale, width, heightFrom the preview: multiply composition px by scale
startDrag(event, mode)Start a drag from your own element's onPointerDown
import { useTransformBox } from "@miraiclip/react";

/** A side handle that scales, like a corner. Render it inside <TransformBox.Root>. */
function RightEdgeHandle() {
  const { box, editable, scale, startDrag } = useTransformBox();
  if (!box || !editable) return null;
  const [, topRight, bottomRight] = box.corners;
  return (
    <div
      className="absolute h-4 w-1.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-white"
      style={{
        left: ((topRight.xPx + bottomRight.xPx) / 2) * scale,
        top: ((topRight.yPx + bottomRight.yPx) / 2) * scale,
      }}
      onPointerDown={(e) => startDrag(e, "scale")}
    />
  );
}

On this page