@mocanvas/editor · class
declare class Editor extends EventEmitter<EditorEvents>This one ships without a description. The signature above is read from the package, and the guides cover the area it belongs to.
On this page
Methods · 314
alignShapes(ids: readonly ShapeId[], operation: "left" | "center-horizontal" | "right" | "top" | "center-vertical" | "bottom" | "center"): thisAlign shapes against their common bounds. "center" centres on both axes
at once — the two center-* operations applied together.
animateShape(partial: ShapePartial<UnknownShape> | null | undefined, opts?: TLAnimationOptions): thisMove a shape to a new position/rotation/opacity over time.
animateShapes(partials: readonly (ShapePartial<UnknownShape> | null | undefined)[], opts?: TLAnimationOptions): thisanimateShape for many shapes on one clock.
get assetUtils(): ReadonlyMap<string, TLAssetUtilLike>Every registered asset util, keyed by asset type.
bail(): thisbailToMark(id: string): thisbatch<T>(fn: () => T): T@deprecated use run
blur(): thisTake keyboard focus off the canvas. The inverse of focus.
bringForward(ids?: readonly ShapeId[]): thisbringToFront(ids?: readonly ShapeId[]): thiscanBindShapes(opts: {Whether a binding may be created between two shapes. Both utils must agree.
cancel(): thiscancelDoubleClick(): thisRestart double-click detection, so the next press begins a fresh gesture.
A tool calls this after acting on a double click that changed what is under the pointer — opening a label editor, say — so the release that follows is not read as part of the same gesture.
canCreateShape(partial: ShapeCreate<UnknownShape>): booleanWhether one more shape can be added to the current page.
canCreateShapes(partials: readonly ShapeCreate<UnknownShape>[]): booleanWhether a whole batch can be added, as one decision.
canCropShape(shape: UnknownShape | ShapeId): booleanWhether a shape can enter cropping. No readonly or locked exemption.
canEditShape(shape: UnknownShape | ShapeId, info?: TLEditStartInfo): booleanWhether a shape can be put into its editing state right now.
canRedo(): booleanWhether there is anything to redo. The documented spelling of getCanRedo.
canUndo(): booleanWhether there is anything to undo. The documented spelling of getCanUndo.
centerOnPoint(point: VecLike, opts?: TLCameraMoveOptions): thisclearHistory(): thisThrow away the undo and redo stacks, keeping the document exactly as it is.
This is what a host calls once its own setup writes are done: loading a board, registering derived state or migrating shapes are all changes the user never made and must not be able to undo their way behind.
complete(): thisconstructor(opts: EditorOptions)get container(): HTMLElementThe element the canvas is mounted in.
createAsset<A extends Asset>(asset: AssetCreate<A>): thiscreateAssets<A extends Asset>(assets: readonly AssetCreate<A>[]): thiscreateBinding<B extends UnknownBinding>(partial: BindingCreate<B>): thiscreateBindings<B extends UnknownBinding>(partials: readonly BindingCreate<B>[]): thiscreateDeepLink(opts?: TLDeepLinkOptions & {A URL pointing at where we are now — the selection when there is one, the viewport otherwise.
Returns a URL rather than a string so a caller can keep its own query
parameters; the deep link occupies one parameter, d by default.
createPage(page?: Partial<Omit<Page, "id" | "typeName" | "index">> & {createShape<T extends UnknownShape>(partial: ShapeCreate<T>): thiscreateShapes<T extends UnknownShape>(partials: readonly ShapeCreate<T>[]): thiscreateTemporaryAssetPreview(assetId: AssetId, url: string): thisRemember a URL to paint for an asset while its upload is in flight.
static degToRad(d: number): numberDegrees → radians.
deleteAsset(id: AssetId | Asset): thisdeleteAssets(ids: readonly (AssetId | Asset)[]): thisdeleteBinding(id: BindingId | UnknownBinding, opts?: {deleteBindings(ids: readonly (BindingId | UnknownBinding)[], opts?: {deletePage(id: PageId): thisdeleteShape(id: ShapeId | UnknownShape): thisdeleteShapes(ids: readonly (ShapeId | UnknownShape)[]): thisdeselect(...ids: (ShapeId | UnknownShape)[]): thisRemove shapes from the selection, leaving the rest of it alone.
dispatch(info: EventInfo): thisRoute an event to the tool tree, updating inputs first.
dispose(): voiddistributeShapes(ids: readonly ShapeId[], operation: "horizontal" | "vertical"): thisSpace shapes evenly between the first and last along an axis.
duplicatePage(id?: PageId, createId?: PageId): PageId | undefinedCopy a page and everything on it. Returns the new page's id.
duplicateShapes(ids?: readonly ShapeId[], offset?: VecLike): ShapeId[]Duplicate shapes (and their descendants) with new ids, offset by offset in page space. Returns the new top-level ids.
findCommonAncestor(shapes: readonly (UnknownShape | ShapeId)[], predicate?: (shape: UnknownShape) => boolean): ShapeId | undefinedThe innermost shape all of shapes sit inside, or undefined.
findShapeAncestor(shape: UnknownShape | ShapeId | undefined, predicate: (parent: UnknownShape) => boolean): UnknownShape | undefinedThe nearest ancestor of shape that predicate accepts.
flipShapes(ids: readonly ShapeId[], operation: "horizontal" | "vertical"): thisMirror shapes across the center of their common bounds. Positions flip; geometry is not mirrored.
flushEngine(): voidApply pending engine commands. Cheap when nothing is pending.
focus(): thisPut keyboard focus on the canvas.
The one thing an app needs after its own DOM took a keystroke — closing a
caption editor, dismissing a menu — so that the next key press reaches the
editor's shortcuts instead of the body. preventScroll because focusing a
canvas that is partly off-screen must not scroll the page under it.
A no-op with no container (a headless editor, or one already torn down).
getAncestorPageId(shape: UnknownShape | ShapeId | undefined): PageId | undefinedgetAsset<A extends Asset = Asset>(id: AssetId | A): A | undefinedgetAssetForExternalContent(info: ExternalAssetContent): Promise<Asset | undefined>Produce (but do not store) an asset record for a file or url through the
registered asset handler. undefined when there is no handler or the
handler declines the content.
getAssets(): Asset[]Every asset in the document (assets are not per page).
getAssetUtil(type: string | Asset): TLAssetUtilLike | undefinedThe util for an asset type, or undefined.
getAssetUtilForMimeType(mimeType: string): TLAssetUtilLike | undefinedThe util that claims a MIME type — what a drop handler asks first.
getAttributionDisplayName(userId: UserId): stringA name to show for a person: their display name, or a short form of their id when nothing is known about them.
Never returns an empty string. A blank byline reads as a bug; an id reads as "we do not know who this is", which is the truth.
getAttributionUser(userId: UserId): User | null | Promise<User | null>Look a person up in the host's user store.
May return a promise, because a directory lookup usually is one — callers
render a placeholder until it settles. null when there is no store or the
id is unknown.
getAttributionUserId(record: {Who made (or last touched) a record, when the record says.
SEMANTICS-ASSUMED: read from meta.userId, falling back to
meta.createdBy. Attribution is carried in meta because that is the one
field every record type already has and the only one a host can write
without a schema change.
getBaseZoom(): numberThe zoom level that counts as 1 for this editor: zoomSteps are
multiples of it, so the same step list means the same thing whatever the
viewport size. Without camera constraints it is simply 1.
getBinding<B extends UnknownBinding = UnknownBinding>(id: BindingId): B | undefinedgetBindingsFromShape<B extends UnknownBinding = UnknownBinding>(shape: UnknownShape | ShapeId, type?: B["type"]): B[]getBindingsInvolvingShape<B extends UnknownBinding = UnknownBinding>(shape: UnknownShape | ShapeId, type?: B["type"]): B[]getBindingsToShape<B extends UnknownBinding = UnknownBinding>(shape: UnknownShape | ShapeId, type?: B["type"]): B[]getBindingUtil<B extends UnknownBinding>(binding: B | B["type"]): BindingUtil<B>getCamera(): CameragetCameraOptions(): TLCameraOptionsThe camera's current policy: lock, wheel behaviour, speeds, zoom steps.
getCameraState(): TLCameraState"moving" while the camera is in flight, "idle" once it settles.
getCanRedo(): booleangetCanUndo(): booleangetCollaborators(): InstancePresence[]Presence records of everyone else in the room, in arrival order.
"Else" means *another instance*, not another person: the filter is on the
presence record id (getInstancePresenceId), so a second tab, a
second window, or the same person on a phone and a laptop all show up as
collaborators. Filtering by user id instead made two tabs of one browser
invisible to each other while every message arrived correctly.
getCollaboratorsOnCurrentPage(): InstancePresence[]The subset of getCollaborators() looking at the page we are on.
getColorMode(): TLColorModeWhich half of the theme's colours applies right now.
getContainerDocument(): ContainerDocument | undefinedThe document the canvas is painted in. undefined when there is no DOM at
all (server rendering, node tests) or the container is not attached yet.
getContainerWindow(): ContainerWindow | undefinedThe window the canvas is painted in; undefined outside a DOM.
getContentFromCurrentPage(ids: readonly ShapeId[]): {Serializable content for the clipboard: shapes (with descendants) and bindings between them.
getCroppingShape(): UnknownShape | undefinedThe shape being cropped, as a record.
getCroppingShapeId(): ShapeId | nullThe shape being cropped, or null.
getCulledShapes(): Set<ShapeId>Shapes the renderer may skip: not visible, and willing to be skipped.
getCurrentPage(): PagegetCurrentPageBounds(): Box | undefinedUnion of every shape's page bounds on the current page, or undefined when the page is empty.
Geometry, not ink: this is the union of getShapePageBounds() and excludes
the half-stroke pad the engine keeps for culling. The pad is per-shape, so
including it would both inflate the box and shift its centre whenever the
outermost shapes carry different stroke widths.
getCurrentPageId(): PageIdgetCurrentPageRenderingShapesSorted(): UnknownShape[]The page's visible shapes in paint order, without the render metadata.
getCurrentPageShapeIds(): Set<ShapeId>getCurrentPageShapes(): Shape[]getCurrentPageShapesInReadingOrder(): UnknownShape[]The page's shapes in reading order — for a11y and keyboard navigation.
getCurrentPageShapesSorted(): UnknownShape[]getCurrentPageState(): InstancePageStategetCurrentTheme(): TLThemeThe live theme: ramps, roles and per-mode colour tables.
getCurrentThemeId(): TLThemeIdThe id of the current theme.
getCurrentTool(): StateNodegetCurrentToolId(): stringgetCursor(): Instance["cursor"]The cursor currently shown over the canvas.
getDebouncedZoomLevel(): numberThe zoom level as of the last time the camera settled.
getDocumentSettings(): Document$1getDraggingOverShape(): UnknownShape | undefinedThe frame-like shape a drag is currently over, or undefined.
SEMANTICS-ASSUMED: derived rather than stored. It is the topmost container
under the pointer during a drag, excluding whatever is being dragged —
which is the same rule the drop itself uses, so the highlight a tool draws
from this can never disagree with where the shapes actually land. Outside a
drag there is nothing to be over and the answer is undefined.
getEditingShape(): UnknownShape | undefinedgetEditingShapeId(): ShapeId | nullgetEfficientZoomLevel(): numberThe camera zoom, quantised to a small step.
Geometry that has to stay a constant size on screen divides by the zoom —
a heading band, a hit target, a hairline. Reading getZoomLevel
there makes the shape's geometry a function of the exact zoom, so every
frame of a pinch recomputes and re-uploads it. Rounding to a step means the
geometry only changes when the zoom has moved enough to be visible.
SEMANTICS-ASSUMED: the step is 1/64 of a zoom unit, chosen so the error is under half a pixel on a 32px band — small enough not to be seen, coarse enough that a slow pinch recomputes tens of times rather than hundreds.
getErasingShapeIds(): ShapeId[]getErasingShapes(): UnknownShape[]The shape records behind getErasingShapeIds(), skipping any that are gone.
getFocusedGroup(): UnknownShape | undefinedThe focused group's record, or undefined when the page is focused.
getFocusedGroupId(): ShapeId | PageIdThe container gestures are scoped to: a drilled-into group, or the page.
getFollowingUserId(): UserId | nullThe user id being followed, or null.
getFrameEpoch(): numberIncrements whenever the GPU frame must be redrawn.
getHasMeasuredViewport(): booleanWhether a host has ever told us how big the canvas is
(updateViewportScreenBounds).
Until it has, getViewportScreenBounds answers with the instance
record's default — a plausible-looking 1080x720 that is not this canvas —
or with zeros once a container that has not been laid out yet has been
measured. Both are wrong in the same way and neither announces itself,
which is why anything that needs the viewport asks this first.
getHighestIndexForParent(parentId: ParentId): IndexKeyThe index a new child of parentId should take: above every existing one.
An empty parent answers getIndexAbove(ZERO_INDEX_KEY), not
ZERO_INDEX_KEY itself. Two reasons, and the first stands on its own:
asking this package the same question two ways used to give two answers,
because getIndexAbove(ZERO_INDEX_KEY) from @mocanvas/store sits one
step above what this returned. The second is parity — a consumer whose
headless fold mirrors the store helper matched a live tldraw editor for
months on this and stopped matching ours.
Only newly created shapes move; an index already written into a document is data and is never recomputed. Order is relative, so a document built before this still sorts exactly as it did.
getHintingShape(): UnknownShape | undefinedThe single hinted shape, when exactly one is hinted.
undefined when none or several are: the singular accessor exists for the
common case of a drag with one drop target, and a caller handling several
wants getHintingShapes.
getHintingShapeIds(): ShapeId[]The shapes currently marked as drop targets.
getHintingShapes(): UnknownShape[]The shape records behind getHintingShapeIds(), skipping any that are gone.
getHitTestMargin(): numberThe hit-test tolerance for the pointer in use: a fingertip covers more of the canvas than a cursor and needs the larger target.
getHoveredShape(): UnknownShape | undefinedgetHoveredShapeId(): ShapeId | nullgetInitialMetaForShape(shape: UnknownShape): UnknownShape["meta"]The meta a newly created shape starts with. See getInitialMetaForShape.
getInitialZoom(): numberThe zoom the editor starts at: the base zoom implied by the camera
constraints, or 1 when there are none.
Not the same as getBaseZoom() only in intent — this is what
resetZoom() should return to, and a host with constraints wants that to
be the constrained fit rather than a literal 100%.
getInstancePresenceId(): InstancePresenceIdThis editor instance's presence record id — who *this tab* is, as opposed
to user.getId(), which is who the person is.
The two are not interchangeable and conflating them was a bug: a user id is per browser (it is the same in every tab, and the same on a phone and a laptop signed in as one person), while a presence record is per editor instance. Two tabs of one browser are two presences of one user, and they must see each other.
@mocanvas/sync publishes this tab's presence record under this id, which
is what lets getCollaborators drop our own record — and only our
own record — should it ever come back to us.
getInstanceState(): InstancegetIsDisposed(): booleangetIsFocused(): booleanWhether the canvas currently holds keyboard focus.
getIsMounted(): booleanWhether a host has told us the editor is on screen.
Reactive — reading it inside a signal re-runs when the editor mounts or
unmounts. Derived from the mount / unmount events, so it is a first
class state and not a guess made from the DOM.
This is what a global registry of live editors should be built on:
@mocanvas/mocanvas's tleditors currently registers editors from the
flagship component's effect, which means an editor mounted any other way
never appears in it. Deriving that list from this flag would fix it — see
the note in the workstream report.
getIsReadonly(): booleanWhether the document may be changed.
Read-only blocks EDITING, not participating: commenting, selecting, moving the camera and following someone else all stay available, so tools must ask this rather than assuming a read-only viewer can do nothing.
getLastFrameStats(): {Stats for the last rendered frame.
getNearestAdjacentShape(shape: UnknownShape | ShapeId, direction: TLAdjacentDirection): UnknownShape | undefinedThe shape an arrow key press from shape should move to.
getNotVisibleShapes(): Set<ShapeId>Shapes that are off screen or clipped away. Some must still be drawn.
getOnlySelectedShape(): UnknownShape | undefinedgetOnlySelectedShapeId(): ShapeId | nullThe id of the only selected shape, or null when zero or many are.
getOutermostSelectableShape(shape: UnknownShape | ShapeId): UnknownShape | undefinedThe outermost group containing a shape, or undefined.
getOverlayClips(): readonly (ClipRect | undefined)[]Page-space clip rects for the overlay shapes, parallel to
getOverlayShapeIds(); undefined where the shape is unclipped.
getOverlayShapeIds(): readonly ShapeId[]Shapes the DOM overlay must render this frame, in draw order.
getPage(id: PageId): Page | undefinedgetPages(): Page[]getPageShapeIds(pageId: PageId): ShapeId[]getPageStates(): InstancePageState[]The session state of every page: selection, hover, editing, cropping.
getPath(): stringgetPointInParentSpace(shape: UnknownShape | ShapeId, point: VecLike): VecgetPointInShapeSpace(shape: UnknownShape | ShapeId, point: VecLike): VecTransform a page point into the shape's local space.
getRenderingShapes(): TLRenderingShape[]Every shape on the page in paint order, with its render metadata.
getResizeScaleFactor(): numberHow much a newly placed shape should be scaled by so it looks the same size on screen at any zoom.
It is 1 normally; in the user's dynamic-size mode it is 1 / zoom, which
is what makes a sticky dropped on a zoomed-out board come out big enough to
read. Placement states multiply their default size by it and pass it on as
the shape's scale prop.
getRichTextEditor(): unknownThe rich-text editor instance currently attached to a shape's label, or
null.
Untyped on purpose: the editor package does not ship a text stack and must not depend on one. A host that installed a rich-text implementation casts this to whatever it installed.
getSelectedShapeAtPoint(point: VecLike): UnknownShape | undefinedThe topmost SELECTED shape under a page point.
getSelectedShapeIds(): ShapeId[]getSelectedShapes(): Shape[]getSelectionPageBounds(): Box | nullThe bounds of the current selection, or null when nothing is selected.
getSelectionRotatedPageBounds(): Box | nullThe selection's bounds in the selection's own rotated frame.
getSelectionRotatedScreenBounds(): Box | undefinedThe rotated selection bounds, in screen space.
getSelectionRotation(): numbergetSelectionScreenBounds(): Box | undefinedThe selection's bounds in screen space (window-relative pixels).
getShape<T extends UnknownShape = Shape>(id: ShapeId | UnknownShape): T | undefinedOne shape by id.
Defaults to Shape, the union of registered types, so
shape.type === "note" narrows its props at the call site. Pass a type
argument for a specific one, or UnknownShape for a type that is not
registered.
getShapeAncestors(shape: UnknownShape | ShapeId): UnknownShape[]Every shape between this one and the page, OUTERMOST FIRST — so the last entry is the shape's direct parent and the first is its top-level ancestor. Empty when the shape sits directly on the page.
A parent chain that loops (a corrupt snapshot) stops at the repeat rather than spinning forever.
getShapeAndDescendantIds(ids: readonly ShapeId[]): Set<ShapeId>Every id given, plus every descendant of each.
getShapeAtPoint(point: VecLike, opts?: HitTestOptions): UnknownShape | undefinedgetShapeClipPath(shape: UnknownShape | ShapeId): string | undefinedA CSS clip-path, in the shape's own space, or undefined when unclipped.
getShapeGeometry<G extends Geometry2d = Geometry2d>(shape: UnknownShape | ShapeId): GgetShapeGeometryBounds(shape: UnknownShape | ShapeId): Box | undefinedgetShapeHandles(shape: UnknownShape | ShapeId): ShapeHandle[] | undefinedA shape's drag handles in its own space, or undefined.
getShapeIdsInsideBounds(bounds: BoxLike): ShapeId[]The ids of every shape completely inside a page-space box.
getShapeLocalTransform(shape: UnknownShape): MatLocal → parent transform components.
getShapeMask(shape: UnknownShape | ShapeId): VecLike[] | undefinedThe page-space polygon a shape is clipped to by its ancestors, or
undefined when nothing clips it.
An ancestor contributes its ShapeUtil.getClipPath() — a polygon in the
ancestor's own coordinates, put through its page transform here — and
falls back to the ancestor's page-space bounds rectangle when it clips
(isClipShape()) but names no path. A frame clips, a plain container or a
group does not. Several clipping ancestors intersect into one polygon; an
intersection that is empty (a shape scrolled entirely out of its frame)
comes back as an empty array, which is not the same answer as "unclipped".
SEMANTICS-ASSUMED: the consumer only pins the undefined case (a section
clips nothing). Empty-array-for-empty-intersection is chosen over
undefined because collapsing them would make a fully clipped shape look
unclipped, which is the more damaging of the two mistakes.
LIMITATION: the intersection is Sutherland–Hodgman, so a *concave* clip path is honoured only where it agrees with its convex hull. Rectangles, rounded-corner approximations and any convex path — every shape mocanvas ships and the only kind the consumer defines — are exact.
getShapeMaskedPageBounds(shape: UnknownShape | ShapeId): Box | undefinedThe part of a shape's page bounds its clipping ancestors leave visible.
getShapePageBounds(shape: UnknownShape | ShapeId): Box | undefinedgetShapePageTransform(shape: UnknownShape | ShapeId): MatgetShapeParent(shape: UnknownShape | ShapeId): UnknownShape | undefinedgetShapeParentAtPoint(_point: VecLike): UnknownShape | undefinedgetShapeParentTransform(shape: UnknownShape | ShapeId): MatParent → page transform. Identity for a shape parented to the page.
getShapesAtPoint(point: VecLike, opts?: HitTestOptions): UnknownShape[]Shapes under a point, topmost first.
getShapesInsideBounds(box: BoxLike, opts?: HitTestOptions): UnknownShape[]Shapes fully inside a page box, in draw order.
getShapesIntersectingBounds(box: BoxLike, opts?: HitTestOptions): UnknownShape[]Shapes whose outline touches a page box, in draw order.
getShapeSketchPath(shape: UnknownShape | ShapeId): number[] | nullThe outline a shape is *drawn* with when the hand-drawn style is on, as path
words in shape space — or null for every other style.
The draw style does not stroke a shape's geometry. It strokes a perturbed, corner-rounded sketch of it, made in the renderer from the shape's own seed, and until now that curve existed only on the GPU. Anything here that wanted to trace what the user can see had nothing but the mathematical outline, and disagreed with the drawing wherever the two differ — most visibly at a corner, which the sketch rounds and the geometry does not.
null is the honest answer for a solid, dashed or dotted shape: those *are*
stroked along their geometry, so there is no second curve to ask about.
getShapesPageBounds(ids: readonly (ShapeId | UnknownShape)[]): Box | nullThe common masked page bounds of several shapes, or null.
getShapeStyleIfExists<T>(shape: UnknownShape | ShapeId, style: StyleProp<T>): T | undefinedA shape's value for one style prop, or undefined when it has no such style.
getShapeUtil<T extends UnknownShape | ShapeUtil = UnknownShape>(shape: UnknownShape | string): T extends ShapeUtil ? T : T extends UnknownShape ? ShapeUtil<T> : neverThe util registered for a shape or shape type.
The type argument may be either the shape (getShapeUtil<NoteShape>(s),
giving ShapeUtil<NoteShape>) or the util itself
(getShapeUtil<NoteShapeUtil>("note"), giving NoteShapeUtil) — the second
is what you want when reaching for methods a custom util adds.
getShapeUtilForAssetType(assetType: string): ShapeUtil | undefinedThe ShapeUtil that should represent an asset of this type on the canvas,
or undefined when nothing claims it.
Two lookups in one: the asset util names a shape type, and that shape type is resolved to its util here — so a caller placing a dropped file never has to know both registries exist.
getSharedOpacity(): TLSharedOpacity | undefinedThe opacity of the selection: a value, "mixed", or undefined.
getSharedStyles(): SharedStyleMapStyles of the selection (or of the next shape when nothing is selected).
getSnapshot(): TLEditorSnapshotThe document and this person's place in it, saved together.
getSortedChildIdsForParent(parentId: ParentId): ShapeId[]getStateDescendant<T extends StateNode>(path: string): T | undefinedgetStyleForNextShape<T>(style: StyleProp<T>): TgetStylePropsForType(type: string): Map<string, StyleProp<unknown>>Style props declared by the ShapeUtil for a type, keyed by prop name.
getSvgElement(ids?: readonly ShapeId[], opts?: EditorSvgExportOptions): SVGSVGElement | undefinedThe export as a live <svg> element rather than a string.
What a preview pane wants: an element can be measured, styled and inserted
without a parse step. undefined when there is nothing to export, or when
there is no DOM to build an element in.
getSvgString(ids?: readonly ShapeId[], opts?: EditorSvgExportOptions): EditorSvgExportResult | undefinedSerialize shapes to an SVG string; undefined when there is nothing to
export. Defaults to the selection, or the whole page when nothing is
selected. Throws until an export implementation is registered.
getTemporaryAssetPreview(assetId: AssetId): TLTemporaryAssetPreview | undefinedThe in-flight preview for an asset, or undefined once it has expired.
getTextOptions(): TLTextOptions | undefinedRich-text configuration, from textOptions or options.text.
getTextureResolution(): numberDevice-pixel scale at which rasterized textures (text labels) should be drawn: the device pixel ratio times the zoom, bucketed to powers of two.
getTheme(id: TLThemeId): TLTheme | undefinedOne registered theme by id, or undefined.
getThemes(): Record<TLThemeId, TLTheme>Every registered theme.
getViewportPageBounds(): BoxgetViewportPageCenter(): VecgetViewportScreenBounds(): BoxgetViewportScreenCenter(): VecgetVisibleCollaborators(): InstancePresence[]Everybody who counts as present, on any page: their presence record was
refreshed within collaboratorInactiveTimeoutMs.
The list a people-menu shows. For the cursors to draw, which is a narrower
question, use getVisibleCollaboratorsOnCurrentPage.
getVisibleCollaboratorsOnCurrentPage(): InstancePresence[]Collaborators on this page who are actually THERE: the ones whose presence record was refreshed within COLLABORATOR_INACTIVE_TIMEOUT.
A tab left open overnight keeps writing presence but stops being a person
to draw a cursor for, so anything that renders collaborators uses this and
not getCollaboratorsOnCurrentPage(). The flip side is that a long-running
participant (an agent working through a tool call) must keep its
lastActivityTimestamp fresh or it will vanish mid-turn.
getZoomLevel(): numbergroupShapes(ids?: readonly ShapeId[], groupId?: ShapeId): ShapeId | undefinedWrap shapes in a new group shape (requires a registered "group" ShapeUtil). Returns the group id.
hasAncestor(shape: UnknownShape | ShapeId | undefined, ancestorId: ShapeId): booleanWhether ancestorId is above shape in the tree. False for the shape itself.
hasAssetUtil(type: string): booleanWhether an asset type has a util registered.
hasBindingUtil(type: string): booleanhasExternalAssetHandler(type: ExternalAssetType): booleanhasExternalContentHandler(type: ExternalContentType): booleanhasShapeUtil(type: string): booleanhasTool(id: string): booleanWhether a tool id is still in service.
interrupt(): thisisAncestorSelected(shape: UnknownShape | ShapeId | undefined): booleanWhether any ancestor of shape is selected.
get isDisposed(): booleanWhether dispose() has run. Async work started before disposal checks this
before touching the store again; reading it inside a signal re-runs the
reader when the editor goes away.
isEventHandled(event: {Whether markEventAsHandled was called for this event.
isIn(path: string): booleanisInAny(...paths: string[]): booleanisPointInShape(shape: UnknownShape | ShapeId, point: VecLike, opts?: TLPointInShapeOptions): booleanWhether a page point falls on a shape, honouring what clips it.
isShapeFrameLike(shape: UnknownShape | ShapeId | undefined): booleanWhether a shape's util declares it a frame-like container.
isShapeHidden(shape: UnknownShape | ShapeId | undefined): booleanWhether the host's getShapeVisibility hides this shape.
isShapeInPage(shape: UnknownShape | ShapeId | undefined, pageId?: PageId): booleanWhether shape lives on pageId (the current page by default).
isShapeOfType<T extends UnknownShape>(shape: UnknownShape | ShapeId | undefined, type: T["type"]): shape is TWhether a shape is of a given type, narrowing it when so.
A type guard rather than a === because that is what makes it usable in a
filter: shapes.filter((s) => editor.isShapeOfType<NoteShape>(s, "note"))
comes back typed.
isShapeOrAncestorLocked(shape: UnknownShape | ShapeId): booleanloadSnapshot(snapshot: Parameters<typeof loadSnapshot>[1], opts?: TLLoadSnapshotOptions): thisRestore a snapshot, or just its document half. Not undoable.
mark(name?: string): stringAlias of markHistoryStoppingPoint.
markEventAsHandled(event: {Claim an event that a shape's own DOM has already dealt with, so the tool tree leaves it alone.
A shape rendered as real DOM (an editable label, an embedded input) handles its own double-click, pointer-down and Enter. Without claiming them they also bubble to the canvas, where the select tool reads them as gestures on the shape and, for instance, re-enters edit mode on the Enter that was meant to leave it.
React synthetic events and native events are both accepted; the underlying native event is what gets marked, so either wrapper of the same event is recognised afterwards.
SEMANTICS-ASSUMED: this is advisory, not a stopPropagation. The mark is
only honoured where something checks isEventHandled before turning
a DOM event into an editor event, which keeps the decision in the layer
that owns the DOM rather than mutating the event itself.
markHistoryStoppingPoint(name?: string): stringmoveShapesToPage(ids: readonly ShapeId[], pageId: PageId): thisMove shapes — and everything under them — to another page.
navigateToDeepLink(opts?: TLDeepLinkOptions & {Move the camera to whatever a deep link points at.
A link naming shapes that are gone, or a page that is gone, moves nothing — a stale link should leave you where you were, not somewhere arbitrary.
nudgeShapes(ids: readonly ShapeId[], offset: VecLike): thisMove shapes by a page-space offset.
packShapes(ids?: readonly ShapeId[], gap?: number): thisPack shapes into a tight block, in place, without resizing them.
pageToScreen(point: VecLike): VecPage -> screen (window-relative pixels, for position: fixed callers).
pageToViewport(point: VecLike): VecPage -> viewport (container-relative pixels).
pan(offsetScreen: VecLike, opts?: TLCameraMoveOptions): thisPan by a VIEWPORT-space offset (container-relative pixels).
popFocusedGroupId(): thisBack out one level of drill-in, selecting the group being left.
putContentOntoCurrentPage(content: {Insert clipboard content at a page point (centered), with fresh ids. Returns the new ids.
putExternalContent(info: ExternalContent): Promise<void>Handle content dropped or pasted onto the canvas by dispatching to the
registered handler for info.type. Resolves once the handler is done;
resolves immediately when no handler is registered.
redo(): thisregisterDeepLinkListener(opts?: TLDeepLinkOptions): () => voidKeep a URL in step with where the editor is, and return the unsubscribe.
Debounced, because the camera changes on every frame of a pan and a URL rewritten sixty times a second floods the browser's history.
registerExternalAssetHandler<T extends ExternalAssetType>(type: T, handler: ExternalAssetHandler<T> | null): () => voidRegister how an asset record is produced from a file or url. Returns a function that removes it again.
registerExternalContentHandler<T extends ExternalContentType>(type: T, handler: ExternalContentHandler<T> | null): () => voidRegister the handler for one kind of dropped/pasted content. Replaces any
previous handler for that type; null removes it. Returns a function that
removes the handler again (only if it is still the registered one).
removeTool(id: string): thisTake a tool out of service.
The tool's state node stays in the tree — removing it would strand any state a shape holds against it — but it stops being selectable, and an editor sitting in it is moved to the first remaining tool. This is how a host disables drawing in a review mode without rebuilding the editor.
renamePage(id: PageId, name: string): thisrenderFrame(backend: RenderBackend): FrameBuffersBuild and draw one frame. Called by the canvas component inside rAF.
reparentShapes(shapes: readonly (ShapeId | UnknownShape)[], parentId: ParentId, insertIndex?: IndexKey): thisMove shapes under a new parent, keeping their page position and rotation.
Takes ids or records: a caller that already has the shape in hand should
not have to reach for .id, and a caller that has only the id should not
have to look the shape up.
replaceExternalContent(info: ExternalContent & {Replace a shape's content with something dropped onto it, rather than creating a new shape beside it.
The "drop an image onto an image" gesture. Delegates to the registered external-content handler with the target shape attached, so an app that already customised drop handling customises this too; with no handler it does nothing rather than falling back to creating a shape, which is the behaviour the caller explicitly did not ask for.
resetZoom(point?: VecLike, opts?: TLCameraMoveOptions): thisresizeShape(id: ShapeId | UnknownShape, scale: VecLike, options?: ResizeShapeOptions): thisScale one shape by scale about a point, letting its ShapeUtil.onResize
produce the prop change — the same contract the select tool's resize state
uses. The shape's origin is moved by the same scale about scaleOrigin
(its page bounds center by default), measured in a frame rotated by
scaleAxisRotation.
Locked shapes and shapes whose util says canResize is false are left
alone. onResizeStart / onResizeEnd bracket the change; the util is
told the resize came from the bottom_right handle.
resizeShapes(ids: readonly (ShapeId | UnknownShape)[], scale: VecLike, options?: Omit<ResizeShapeOptions, "initialBounds">): thisScale several shapes by the same factor about one point — by default the center of their common page bounds, so the group scales as a unit.
resizeToBounds(ids: readonly ShapeId[], bounds: BoxLike): thisScale and move shapes so their common bounds become bounds exactly.
resolveAssetsInContent(content: {Resolve every asset referenced by a piece of content, so it can be rendered somewhere the store is not — an export, a thumbnail, a paste into another document.
Returns a map rather than mutating the content: the content may be frozen, and the caller usually wants both the original ids and the URLs.
resolveAssetUrl(asset: AssetId | Asset): string | null | Promise<string | null>The URL to paint an asset from.
A temporary preview wins while one is current, so a just-dropped image appears immediately and is replaced by the uploaded one without the shape having to re-render against a null src in between.
rotateShapesBy(ids: readonly ShapeId[], delta: number, center?: VecLike): thisRotate shapes by delta radians around the center of their common page
bounds.
The resulting rotation is canonicalized into [0, 2π). Without that, an
angle that only ever accumulates leaves two documents that look identical
carrying different numbers — and rotation is persisted and sent over the
wire, so a diff, a parity check and a sync merge all see a difference that
is not on screen.
run<T>(fn: () => T, opts?: {Run fn as one operation: one history entry, and one notification to the
store's listeners however many writes it makes.
It has to open an operation on the *store*, not just a signal transaction.
A transact alone batches recomputation and nothing else, so every write
inside still opened and closed an operation of its own — two
updateShapes calls in one run reached a listener as two notifications,
the first of them carrying a half-applied state that was never an intended
state of the document. A sync binding published that state to its peers.
screenToPage(point: VecLike): VecScreen (window-relative) -> page. The inverse of pageToScreen.
select(...ids: (ShapeId | UnknownShape)[]): thisselectAdjacentShape(direction: TLAdjacentDirection): thisMove the selection one shape in direction.
selectAll(): thisselectFirstChildShape(): thisSelect the first child of the only selected shape.
selectNone(): thisselectParentShape(): thisSelect the parents of the selected shapes.
sendBackward(ids?: readonly ShapeId[]): thissendToBack(ids?: readonly ShapeId[]): thissetCamera(point: Partial<VecModel>, opts?: TLCameraMoveOptions): thisMove the camera. Omitted components keep their current value and the zoom is clamped to the editor's zoom range.
With animation.duration the camera eases to the target over that many
milliseconds instead of jumping; any animation already in flight is
abandoned first, so the newest instruction always wins. A locked camera
(see setCameraOptions) ignores the move unless force is passed.
setCameraOptions(options: Partial<TLCameraOptions>): thisChange part of the camera policy; omitted keys keep their value.
setColorMode(scheme: TLColorScheme): thisChoose light, dark, or follow the host window.
Takes the full TLColorScheme and not only a resolved mode, because
"system" is a legitimate thing to set and there would otherwise be no way
back to it once a mode was pinned.
setCroppingShape(id: ShapeId | UnknownShape | null): thisEnter (or, with null, leave) cropping.
Refuses a shape that canCropShape rejects rather than entering a mode with nothing to do in it. Entering also selects the shape, since every crop handle is drawn against the selection.
setCurrentPage(pageId: PageId): thissetCurrentTheme(id: TLThemeId): thisSwitch themes. Unknown ids are ignored.
setCurrentTool(id: string, info?: Record<string, unknown>): thisSwitch tools. id may be a dotted path, e.g. "select.idle".
setCursor(cursor: Partial<Instance["cursor"]>): thisSet the cursor shown over the canvas. Tools set it on enter and put it back
to default on exit; nothing resets it for them.
setEditingShape(id: ShapeId | UnknownShape | null): thissetErasingShapes(ids: readonly ShapeId[]): thissetFocusedGroup(id: ShapeId | UnknownShape | null): thisDrill into a group, or back out to the page with null.
setHintingShapes(ids: readonly (ShapeId | UnknownShape)[]): thisMark shapes as hinted: the drop targets of the gesture in flight.
A hinted shape draws its indicator with a heavier stroke than a selected or hovered one, which is how "this is where it will land" reads. An empty array clears the hint, and every gesture that sets one is responsible for clearing it when it ends.
setHoveredShape(id: ShapeId | UnknownShape | null): thissetOpacityForNextShapes(opacity: number): thisSet the opacity newly created shapes will start with.
setOpacityForSelectedShapes(opacity: number): thisSet the opacity of the selected shapes.
setRichTextEditor(instance: unknown): thisAttach (or, with null, detach) the rich-text editor instance.
setSelectedShapes(ids: readonly (ShapeId | UnknownShape)[]): thissetStyleForNextShapes<T>(style: StyleProp<T>, value: T): thissetStyleForSelectedShapes<T>(style: StyleProp<T>, value: T): thisSet a style on every selected shape that declares it.
setTool(id: string, info?: Record<string, unknown>): thisSwitch tools. The documented spelling of setCurrentTool.
slideCamera(opts: {Fling the camera and let it coast to a stop.
direction is a screen-space velocity in pixels per frame; speed scales
it; friction comes from cameraSlideFriction. The slide stops early on any
other camera instruction, because the newest instruction always wins.
squashToMark(id: string): thisstackShapes(ids: readonly ShapeId[], operation: "horizontal" | "vertical", gap?: number): thisStack shapes edge to edge with a fixed gap along an axis (order by current position).
startFollowingUser(userId: UserId): thisFollow another person's camera. Their viewport is mirrored into ours until
stopFollowingUser or a camera move of our own.
The id is a USER id (InstancePresence.userId), not a presence record id:
one person with several tabs is still one person to follow.
stopCameraAnimation(): thisEnd any camera animation where it currently is.
This is what a hand on the board mid-flight does: the camera stops at the frame the user interrupted it on rather than continuing to a destination they have already overruled.
stopFollowingUser(): thisStop mirroring anyone's camera. A no-op when not following.
stretchShapes(ids: readonly ShapeId[], operation: "horizontal" | "vertical"): thisResize shapes so each one spans the common bounds of all of them on one axis. Every shape given contributes to the common bounds, but only the unlocked, resizable ones are stretched.
get styleProps(): ReadonlyMap<string, ReadonlyMap<string, StyleProp<unknown>>>Every style prop the registered shape utils declare, by shape type.
SEMANTICS-ASSUMED: keyed by shape type, then by prop name. The style panel needs both directions of this map and the type-first shape is the one that can be built without instantiating anything.
get textMeasure(): EditorTextMeasureMeasures runs of text the way the editor renders them. Installed through the same seam as the export functions; throws until something registers one.
toggleLock(ids?: readonly ShapeId[]): thisToggle the locked state of shapes.
toImage(ids?: readonly ShapeId[], opts?: EditorImageExportOptions): Promise<EditorImageExportResult>Render shapes to an image blob (png by default). Same shape selection
rules as getSvgString. Throws until an export implementation is
registered.
toImageDataUrl(ids?: readonly ShapeId[], opts?: EditorImageExportOptions): Promise<string>The export as a data: URL.
The form that can go straight into an <img src> or a clipboard write
without a blob URL to revoke afterwards — at the cost of about a third more
bytes, which is why toImage and its blob remain the default.
undo(): thisungroupShapes(ids?: readonly ShapeId[]): thisDissolve groups, re-parenting their children to the group's parent.
updateAsset<A extends Asset>(partial: AssetPartial<A>): thisupdateAssets<A extends Asset>(partials: readonly AssetPartial<A>[]): thisupdateBinding<B extends UnknownBinding>(partial: BindingPartial<B>): thisupdateBindings<B extends UnknownBinding>(partials: readonly BindingPartial<B>[]): thisupdateCurrentPageState(partial: Partial<Omit<InstancePageState, "id" | "typeName" | "pageId">>): thisupdateDocumentSettings(settings: Partial<Omit<Document$1, "id" | "typeName">>): thisupdateInstanceState(partial: Partial<Omit<Instance, "id" | "typeName">>): thisupdatePage(partial: Partial<Omit<Page, "typeName">> & {Change a page's name, index or meta.
updateShape<T extends UnknownShape>(partial: ShapePartial<T> | null | undefined): thisupdateShapes<T extends UnknownShape>(partials: readonly (ShapePartial<T> | null | undefined)[]): thisupdateTheme(id: TLThemeId, patch: Parameters<ThemeManager["updateTheme"]>[1]): thisPatch one theme in place.
updateThemes(patch: Parameters<ThemeManager["updateThemes"]>[0]): thisPatch several themes at once.
updateViewportScreenBounds(bounds: Box | BoxLike | HTMLElement, center?: boolean): thisTell the editor how big its viewport is on screen.
Takes the element as well as a box, because that is the call every host
actually makes — it has the container to hand, not a measured rectangle —
and because tldraw accepts one, so a drop-in host that passes its ref
would otherwise fail on a getBoundingClientRect that was never there.
uploadAsset(file: File): Promise<Asset | undefined>Turn a file into an asset record through the util that claims its type, and store it.
undefined when no util wants the file — the caller shows "unsupported
file type" rather than silently dropping it.
viewportToPage(point: VecLike): VecViewport (container-relative) -> page. The inverse of pageToViewport.
visitDescendants(parent: PageId | ShapeId, visitor: (id: ShapeId) => void | false): thisDepth-first walk of a parent's descendants; return false to prune a branch.
wasEventAlreadyHandled(event: {Whether something has already claimed this event.
The documented spelling of isEventHandled; both read the same
weakly-held set, so a shape's own DOM handler marking an event is visible
to the canvas handler that sees it next.
zoomIn(point?: VecLike, opts?: TLCameraMoveOptions): thiszoomOut(point?: VecLike, opts?: TLCameraMoveOptions): thiszoomToBounds(bounds: BoxLike, opts?: TLCameraMoveOptions & {Frame bounds in the viewport. inset is the screen-space margin left
around it and targetZoom a CEILING on the resulting zoom, so a tiny
target does not fill the screen.
zoomToFit(opts?: TLCameraMoveOptions & {zoomToPointAt(viewportPoint: VecLike, nextZoom: number, opts?: TLCameraMoveOptions): thisZoom keeping the given VIEWPORT point (container-relative) fixed.
zoomToSelection(opts?: TLCameraMoveOptions & {zoomToSelectionIfOffscreen(padding?: number, opts?: TLCameraMoveOptions & {Bring the selection into view, but only if it is not already there.
The camera does not move at all when the selection is fully on screen — which is what makes this safe to call after every operation that changes the selection, without yanking the view around when nothing needed to happen.
zoomToUser(userId: UserId, opts?: TLCameraMoveOptions & {Move the camera to whatever userId has selected, or — when they have
nothing selected — to their cursor.
A one-shot jump, unrelated to startFollowingUser: it does not keep
mirroring their camera afterwards. Returns without moving when that person
is not on this page, or is not here at all.
SEMANTICS-ASSUMED: framing their *selection* rather than replicating their viewport. Copying their camera would put a collaborator on a 27" display somewhere unusable on a laptop; "show me what they are working on" is the question this method is actually asked. With nothing selected there is no region to frame, so the cursor is centred at the current zoom instead.
Properties · 32
readonly assets: AssetStoreWhere this editor's asset bytes live.
readonly bindingUtils: Readonly<Record<string, BindingUtil>>readonly click: ClickManagerDouble-click detection for the dispatch loop and for tools.
readonly collaborators: CollaboratorsManagerWho else is in the room, and which of them to draw.
readonly contentElements: ContentElementManagerDOM elements owned by shape utils rather than by the React tree — see ShapeUtil.getContentElement. They outlive a re-render and the editor's own unmount, and are released with the shape or with the editor.
contextId: string | undefinedThe React context instance this editor belongs to, when a host set one.
Two editors rendered by two independent React trees on one page must not
read each other's context; a host that mounts editors in isolated trees
(a plugin host, a storybook page) sets this so context lookups can tell
them apart. undefined in the ordinary single-tree case.
readonly edgeScrollManager: EdgeScrollManagerPanning the camera when a drag reaches the viewport edge.
readonly engine: EngineBridgereadonly fonts: FontManagerOn-demand loading of the typefaces the canvas needs.
readonly getContainer: () => HTMLElementreadonly getInitialMetaForShapeHandler: ((shape: UnknownShape) => UnknownShape["meta"]) | undefinedreadonly getShapeVisibility: TLGetShapeVisibility | undefinedreadonly handles: HandleTablereadonly history: HistoryManager<EditorRecord>readonly id: stringThis editor's own id. Stable for its lifetime, unique within the process, and the thing to key per-editor caches and log lines by.
readonly inputs: EditorInputsreadonly menus: MenuManagerWhich menus are open right now. Backed by the instance session record.
readonly options: EditorConfigreadonly overlays: OverlayManagerThe registry of canvas overlay painters.
readonly performance: PerformanceManagerTiming events for profilers and dev overlays. Nothing depends on it.
readonly root: StateNodereadonly scribbles: ScribbleManagerThe trails a laser, an eraser or a select brush leaves behind.
readonly shapeUtils: Readonly<Record<string, ShapeUtil>>readonly sideEffects: EditorStore["sideEffects"]readonly snaps: SnapManagerreadonly store: EditorStorereadonly text: TextManagerText measurement and span layout. See TextManager.
readonly textOptions: TLTextOptions | undefinedRich-text configuration, from textOptions or options.text.
readonly textures: TextureManagerGPU textures referenced by shape styles (images, rasterized text).
readonly theme: ThemeManagerColours and display values for the current theme and colour mode.
readonly timers: TimersTimeouts, intervals and animation frames that die with the editor.
readonly user: UserPreferencesManagerThe local person's preferences: identity (id, name, colour) plus the behaviour flags that change how the editor treats their input, such as dynamic-size mode. Session-only, never persisted with the document.
mocanvas is built by Symbio. Free for non-commercial, non-profit and development use; commercial use needs an agreement. Not affiliated with or endorsed by tldraw.