Skip to content

Resize

Every shape resizes the same way, because every shape has a frame. This page covers the frame half of a resize. Adapters that store their own points use the returned scale and origin to fit their content.

Handles

ResizeHandle names one of the eight handles: top-left, top, top-right, right, bottom-right, bottom, bottom-left and left. RESIZE_HANDLES lists them clockwise from the top-left.

resizeHandlePoint(frame, handle) returns where a handle sits, in page space. Corner handles sit on the frame’s corners and edge handles on the middle of each edge. Both follow the frame’s rotation.

Resizing

resizeFrame(start, handle, pointer, options) returns the frame after a drag. It is stateless. Pass the frame as it was when the drag started and the current pointer, every time the pointer moves. The pointer is where the dragged handle should land.

By default the opposite handle is the anchor and stays fixed in page space, whatever the rotation. An edge handle changes only its own axis.

ResizeOptions changes the behaviour:

Option Default Effect
lockAspect false Keeps the start frame’s width-to-height ratio, or a square when the start frame has no area. A corner follows whichever axis the pointer moved further. An edge scales the other axis about the edge’s midline.
fromCenter false Anchors the frame’s centre instead of the opposite handle, so the frame grows on both sides.
minSize 1 The smallest width or height a dragged axis can reach. With lockAspect, both axes grow together so the ratio holds.

Flips and scale

FrameChange holds the new frame, the scaleX and scaleY factors from the start frame, origin, anchor and handle. handle is the ResizeHandle that was dragged. A scale cannot tell a corner drag from an edge drag, and a drawable such as text behaves differently for each.

Dragging a handle past the anchor flips that axis. The frame stays a normal frame with a positive width and height, and the flip shows up as a negative scale. An edge handle under lockAspect never flips the other axis.

When the start frame has no size on an axis, as in the drag that creates a new shape from a single point, that axis reports a scale of 1, or -1 when it flipped.

Origin

origin is where the start frame’s local (0, 0) lands in the new frame’s local space. A start-local point p maps to origin + (p.x * scaleX, p.y * scaleY) in the new frame. The anchor keeps its page position under that mapping, including on a rotated frame.

Scale alone cannot say this. On an axis where the start frame has no size, the scale is 1 or -1 whichever handle moved. A horizontal line has a height of 0. Dragging its top handle up and its bottom handle down both report scaleY of 1, yet the content must stay on the anchor edge in one case and on the opposite edge in the other. origin carries that difference.

Drag origin on the affected axis
Handle moves away from the anchor, no flip 0
Handle crosses the anchor and flips The new size
Zero-size start, handle on the low side moved out The new size
Zero-size start with fromCenter Half the new size

Anchor

anchor is the point that keeps its page position, in the new frame’s local space. Without fromCenter it is the handle opposite the dragged one, or the middle of the opposite edge for an edge handle. With fromCenter it is the centre. On a flipped axis it sits on the far side of the new frame.

Content that does not stretch with the frame uses it. Text keeps its size from the layout, not from the drag, so it places itself around anchor instead of mapping every point through origin and the scale.

docs/examples/resize-flip.ts
import { pagePoint, radians, resizeFrame } from "@draw/shapes";
import type { Frame, FrameChange } from "@draw/shapes";
const start: Frame = { x: 100, y: 100, width: 200, height: 100, rotation: radians(0) };
export const flipped: FrameChange = resizeFrame(start, "right", pagePoint(50, 150));

The right handle starts at (300, 150). Dropping it at x = 50 moves it 50 units past the left edge, which is the anchor. The result is a 50 by 100 frame at x = 50, with scaleX of -0.25, an origin of (50, 0) and an anchor of (50, 50). The left edge of the start frame lands at local x = 50, which is the right edge of the new frame.