Changelog
All notable changes to @draw/shapes. The format follows Keep a Changelog. New entries go under Unreleased.
Unreleased
Section titled “Unreleased”- Text alignment.
TextAlign(left,center,right) and a requiredalignon the text drawable and onLabel. A new text is left aligned and a new label is centred (DEFAULT_LABEL_ALIGN).TextLayoutInput.alignis optional and defaults toleft.TextLayoutgainsalignandx, the anchor of every line, whichlabelLayoutreturns in frame coordinates.alignedX(width, align)is the rule.parseShapefills a missingalign(text left, label centre) so older documents still parse, and rejects an unknown value. FONT_SIZES, the Excalidraw size presets:small16,medium20,large28 andextraLarge36.bindingGap(target)andbindingDistance(zoom), matching Excalidraw. A bound end stopsBINDING_GAP(5) plus half the target’s stroke width short of its outline, so 6 for the default stroke. A terminal reachesbindingDistancepage 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, andBINDING_DISTANCE(15) is the reach of a terminal in screen pixels, both like Excalidraw.bindTerminalanchors 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 optionallabelon the rectangle, ellipse and diamond drawables.isContainer,labelWidth,labelLayout,fitLabel,editLabel,clearLabel,LABEL_PADDINGandDEFAULT_LABEL_FONT_SIZE, with theContainer,ContainerKindandLabelledtypes. The text wraps at Excalidraw’s inner width for each kind, and the frame grows taller to fit. The box adapters’resizere-fits the label.parseShapereads the optional label, so older documents still parse. GroupId,groupIdandcreateGroupId, andShape.groupIds, the groups a shape belongs to, innermost first.parseShapereads it and defaults a missing field to[], so documents saved before groups still parse (ADR 0009).- Package scaffold with the
PACKAGE_NAMEplaceholder export. - Math primitives:
Vec, brandedRadiansand points (PagePoint,LocalPoint,ScreenPoint),MatandBox. They replace thePACKAGE_NAMEplaceholder. ShapeIdwithcreateShapeId, andFramewith conversions between local and page space (frameToPage,localToPage,pageToLocal,frameCorners,frameBounds).- Box drawables (rectangle, ellipse, diamond) with typed adapters,
adapterFor,drawableGeometryandhitTest. - Frame resize:
resizeFramewith flips, aspect lock, from-centre and minimum size, plusRESIZE_HANDLESandresizeHandlePoint. - Adapter editing members
resize,handlesanddragHandle, with theFitted,HandleandHandleDragtypes. 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
FreehandSampleandFreehandSamples. Points and pressures share one sample, and the stroke resizes with the same fit as a line. - Text layout:
layoutTextwith an injectedMeasureText, grapheme-aware wrapping,TEXT_LINE_HEIGHTandminTextWidth. - 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. Resultwithokanderr,shapeIdto brand a known id, and theShaperecord withFilland the brandedZIndex.- Fractional z-order:
parseZIndexreturns aResult,compareShapesorders by index then id, andindexAbovegives a new shape the top index.fractional-indexingis the first runtime dependency, wrapped in one module. - Reorder operations
bringToFront,sendToBack,bringForwardandsendBackward. They return only the changed shapes asIndexPatchvalues, in the new z-order. A step jumps past the next unselected shape that overlaps the selection. boundsfor the page box around a selection, andboxIntersects.pickShapepicks 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:
terminalhandles on both ends and abendhandle 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
Bindingrecord with the brandedBindingIdandNormalizedPoint,ArrowShapewithisArrowShape,bindingTargetto choose what a terminal attaches to,bindTerminal,arrowsToResolveandremoveBindingsFor. resolveArrowmoves bound ends to their anchors and clips them back along the curve to the target’s outline. It keeps the bend and is idempotent.StylewithStroke,StrokeStyleand theFillunion, the brandedColorandSeedwith theircolorandseedconstructors, andDEFAULT_STYLE.parseShapechecks the style and adds the reasonsnot-integerandinvalid-color.FontFamilyandFont, and afontFamilyfield on the text drawable. A new text is"hand-drawn".Arrowhead, andstartHeadandendHeadfields on the arrow drawable. A new arrow has no start head and an"arrow"end head. Resize, handle drags andresolveArrowkeep them.parseShapereads all three new fields.- Named values for comparisons (ADR 0006):
Arrowhead,DrawableKind,HandleKind,FillKind,StrokeStyleandTextSizingare const objects next to their types, andNamedValues<U>checks an object against a union.Arrowhead,StrokeStyleandTextSizingare now derived from their objects. Internal comparisons use them. indicesAbove(shapes, count)returnscountascending indices above every shape, for pasting several shapes on top in their order.
resolveArrowno longer clips an end whose anchor lies on the target’s outline. Rounding made such ends land at different points on repeated calls.
Changed
Section titled “Changed”- Numeric literals in
srcare named constants, as the newno-magic-numberslint rule requires. Behaviour is unchanged. Geometrygainsclosed. An open outline is hit only near its stroke and ignoresfilled.FrameChangegainsorigin, the point where the start frame’s local origin lands in the new frame. It keeps zero-size axes anchored under a resize.FrameChangegainshandle, the resize handle that was dragged. Text needs it to tell a corner scale from a side re-wrap.FrameChangegainsanchor, the point that keeps its page position, in the new frame’s local space. Text resize now followsfromCenterandlockAspect.create,resizeanddragHandleonDrawableAdaptertake anAdapterEnvwith the text measurer. Callers always pass it. The box and points adapters ignore it.Handleis a tagged union ofVertexHandle,TerminalHandleandBendHandle, with the sharedTerminaltype. A line ignores handles that are not vertices.parseShapefor the box drawables (rectangle, ellipse, diamond), withParseErrorandParseReason. It builds aShapefrom known fields, rejects the first bad value with its path, and never throws.parseShapealso 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.parseBindingparses aBinding. An anchor outside 0 to 1 is rejected without-of-range, never clamped.parseShapeDocumentandShapeDocumentparse 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.fillis replaced byShape.style, andFillis now the union{ kind: "none" }or ahachure,cross-hatchorsolidfill with aColor.pickShapecounts any fill butnoneas filled.parseShapereadsstylewhere it readfill. - Breaking.
MeasureTexttakes aFont,{ size, family }, where it took a font size.TextLayoutInputholdsfontwhere it heldfontSize, andminTextWidthtakes aFontas its second argument. - Breaking. The text drawable requires
fontFamily, and the arrow drawable requiresstartHeadandendHead.parseShaperejects a shape without them.