Skip to content

Changelog

All notable changes to @draw/shapes. The format follows Keep a Changelog. New entries go under Unreleased.

  • Text alignment. TextAlign (left, center, right) and a required align on the text drawable and on Label. A new text is left aligned and a new label is centred (DEFAULT_LABEL_ALIGN). TextLayoutInput.align is optional and defaults to left. TextLayout gains align and x, the anchor of every line, which labelLayout returns in frame coordinates. alignedX(width, align) is the rule. parseShape fills a missing align (text left, label centre) so older documents still parse, and rejects an unknown value.
  • FONT_SIZES, the Excalidraw size presets: small 16, medium 20, large 28 and extraLarge 36.
  • bindingGap(target) and bindingDistance(zoom), matching Excalidraw. A bound end stops BINDING_GAP (5) plus half the target’s stroke width short of its outline, so 6 for the default stroke. A terminal reaches bindingDistance page units: 15 at 100% zoom and above, widening to 20 at zoom 0.5 and at most 30. This replaces the flat gap of 5 and the reach of 15 screen pixels.
  • Binding gap and distance. A bound end now stops BINDING_GAP (5) page units short of the target’s outline, and BINDING_DISTANCE (15) is the reach of a terminal in screen pixels, both like Excalidraw. bindTerminal anchors a point outside a closed target at its centre instead of clamping it to the frame edge.
  • Text inside shapes (ADR 0010). Label, and an optional label on the rectangle, ellipse and diamond drawables. isContainer, labelWidth, labelLayout, fitLabel, editLabel, clearLabel, LABEL_PADDING and DEFAULT_LABEL_FONT_SIZE, with the Container, ContainerKind and Labelled types. The text wraps at Excalidraw’s inner width for each kind, and the frame grows taller to fit. The box adapters’ resize re-fits the label. parseShape reads the optional label, so older documents still parse.
  • GroupId, groupId and createGroupId, and Shape.groupIds, the groups a shape belongs to, innermost first. parseShape reads it and defaults a missing field to [], so documents saved before groups still parse (ADR 0009).
  • Package scaffold with the PACKAGE_NAME placeholder export.
  • Math primitives: Vec, branded Radians and points (PagePoint, LocalPoint, ScreenPoint), Mat and Box. They replace the PACKAGE_NAME placeholder.
  • ShapeId with createShapeId, and Frame with conversions between local and page space (frameToPage, localToPage, pageToLocal, frameCorners, frameBounds).
  • Box drawables (rectangle, ellipse, diamond) with typed adapters, adapterFor, drawableGeometry and hitTest.
  • Frame resize: resizeFrame with flips, aspect lock, from-centre and minimum size, plus RESIZE_HANDLES and resizeHandlePoint.
  • Adapter editing members resize, handles and dragHandle, with the Fitted, Handle and HandleDrag types. The box adapters implement them as frame-only no-ops.
  • Line drawable with LinePoints: open geometry, vertex handles, resize with flips, vertex drags, and a frame that refits to the points.
  • Freehand drawable with FreehandSample and FreehandSamples. Points and pressures share one sample, and the stroke resizes with the same fit as a line.
  • Text layout: layoutText with an injected MeasureText, grapheme-aware wrapping, TEXT_LINE_HEIGHT and minTextWidth.
  • Text drawable with editText: the frame is the laid-out text, a corner drag scales the font, a side drag re-wraps at a fixed width, and a top or bottom drag changes nothing.
  • Result with ok and err, shapeId to brand a known id, and the Shape record with Fill and the branded ZIndex.
  • Fractional z-order: parseZIndex returns a Result, compareShapes orders by index then id, and indexAbove gives a new shape the top index. fractional-indexing is the first runtime dependency, wrapped in one module.
  • Reorder operations bringToFront, sendToBack, bringForward and sendBackward. They return only the changed shapes as IndexPatch values, in the new z-order. A step jumps past the next unselected shape that overlaps the selection.
  • bounds for the page box around a selection, and boxIntersects.
  • pickShape picks the shape under a click. The closest outline wins, then a filled shape that hides what is below, then the smallest hollow shape.
  • Arrow drawable { start, end, bend }: a circular arc flattened to an open polyline, a frame that refits to the curve, and resize that maps the curve’s midpoint and mirrors the bend on a flip.
  • Arrow handles: terminal handles on both ends and a bend handle on the curve’s midpoint. A terminal drag moves that end and keeps the bend. A bend drag sets the bend to the pointer’s signed distance from the chord.
  • Bindings: the Binding record with the branded BindingId and NormalizedPoint, ArrowShape with isArrowShape, bindingTarget to choose what a terminal attaches to, bindTerminal, arrowsToResolve and removeBindingsFor.
  • resolveArrow moves bound ends to their anchors and clips them back along the curve to the target’s outline. It keeps the bend and is idempotent.
  • Style with Stroke, StrokeStyle and the Fill union, the branded Color and Seed with their color and seed constructors, and DEFAULT_STYLE. parseShape checks the style and adds the reasons not-integer and invalid-color.
  • FontFamily and Font, and a fontFamily field on the text drawable. A new text is "hand-drawn".
  • Arrowhead, and startHead and endHead fields on the arrow drawable. A new arrow has no start head and an "arrow" end head. Resize, handle drags and resolveArrow keep them. parseShape reads all three new fields.
  • Named values for comparisons (ADR 0006): Arrowhead, DrawableKind, HandleKind, FillKind, StrokeStyle and TextSizing are const objects next to their types, and NamedValues<U> checks an object against a union. Arrowhead, StrokeStyle and TextSizing are now derived from their objects. Internal comparisons use them.
  • indicesAbove(shapes, count) returns count ascending indices above every shape, for pasting several shapes on top in their order.
  • resolveArrow no longer clips an end whose anchor lies on the target’s outline. Rounding made such ends land at different points on repeated calls.
  • Numeric literals in src are named constants, as the new no-magic-numbers lint rule requires. Behaviour is unchanged.
  • Geometry gains closed. An open outline is hit only near its stroke and ignores filled.
  • FrameChange gains origin, the point where the start frame’s local origin lands in the new frame. It keeps zero-size axes anchored under a resize.
  • FrameChange gains handle, the resize handle that was dragged. Text needs it to tell a corner scale from a side re-wrap.
  • FrameChange gains anchor, the point that keeps its page position, in the new frame’s local space. Text resize now follows fromCenter and lockAspect.
  • create, resize and dragHandle on DrawableAdapter take an AdapterEnv with the text measurer. Callers always pass it. The box and points adapters ignore it.
  • Handle is a tagged union of VertexHandle, TerminalHandle and BendHandle, with the shared Terminal type. A line ignores handles that are not vertices.
  • parseShape for the box drawables (rectangle, ellipse, diamond), with ParseError and ParseReason. It builds a Shape from known fields, rejects the first bad value with its path, and never throws.
  • parseShape also parses line, freehand, text and arrow drawables. A line needs 2 points, a stroke 1 sample, a pressure lies in 0 to 1, and a font size is above 0.
  • parseBinding parses a Binding. An anchor outside 0 to 1 is rejected with out-of-range, never clamped.
  • parseShapeDocument and ShapeDocument parse shapes and bindings together. They reject duplicate ids, bindings to unknown shapes, to non-arrows or to arrows, and a second binding on one terminal.
  • Breaking. Shape.fill is replaced by Shape.style, and Fill is now the union { kind: "none" } or a hachure, cross-hatch or solid fill with a Color. pickShape counts any fill but none as filled. parseShape reads style where it read fill.
  • Breaking. MeasureText takes a Font, { size, family }, where it took a font size. TextLayoutInput holds font where it held fontSize, and minTextWidth takes a Font as its second argument.
  • Breaking. The text drawable requires fontFamily, and the arrow drawable requires startHead and endHead. parseShape rejects a shape without them.