@tldraw/editor
Version:
tldraw infinite canvas SDK (editor).
1,462 lines (1,390 loc) • 322 kB
text/typescript
import { Atom } from '@tldraw/state';
import { AtomSet } from '@tldraw/store';
import { Awaitable } from '@tldraw/utils';
import { BoxModel } from '@tldraw/tlschema';
import { ComponentType } from 'react';
import { Computed } from '@tldraw/state';
import { CustomRecordInfo } from '@tldraw/tlschema';
import { Dispatch } from 'react';
import { Editor as Editor_2 } from '@tiptap/core';
import { EditorProviderProps as EditorProviderProps_2 } from '@tiptap/react';
import EventEmitter from 'eventemitter3';
import { ExoticComponent } from 'react';
import { ExtractShapeByProps } from '@tldraw/tlschema';
import { ForwardRefExoticComponent } from 'react';
import { FragmentProps } from 'react';
import { HistoryEntry } from '@tldraw/store';
import { IndexKey } from '@tldraw/utils';
import { JsonObject } from '@tldraw/utils';
import { JSX } from 'react/jsx-runtime';
import { LegacyMigrations } from '@tldraw/store';
import { MigrationSequence } from '@tldraw/store';
import { Node as Node_2 } from '@tiptap/pm/model';
import { PerformanceTracker } from '@tldraw/utils';
import * as React_2 from 'react';
import { default as React_3 } from 'react';
import { ReactElement } from 'react';
import { ReactNode } from 'react';
import { RecordProps } from '@tldraw/tlschema';
import { RecordsDiff } from '@tldraw/store';
import { RefAttributes } from 'react';
import { RefObject } from 'react';
import { SerializedSchema } from '@tldraw/store';
import { SerializedStore } from '@tldraw/store';
import { SetStateAction } from 'react';
import { Signal } from '@tldraw/state';
import { Store } from '@tldraw/store';
import { StoreSchema } from '@tldraw/store';
import { StoreSideEffects } from '@tldraw/store';
import { StyleProp } from '@tldraw/tlschema';
import { StylePropValue } from '@tldraw/tlschema';
import { T } from '@tldraw/validate';
import { Timers } from '@tldraw/utils';
import { TLAsset } from '@tldraw/tlschema';
import { TLAssetId } from '@tldraw/tlschema';
import { TLAssetPartial } from '@tldraw/tlschema';
import { TLAssetStore } from '@tldraw/tlschema';
import { TLBaseShape } from '@tldraw/tlschema';
import { TLBinding } from '@tldraw/tlschema';
import { TLBindingCreate } from '@tldraw/tlschema';
import { TLBindingId } from '@tldraw/tlschema';
import { TLBindingUpdate } from '@tldraw/tlschema';
import { TLBookmarkAsset } from '@tldraw/tlschema';
import { TLCamera } from '@tldraw/tlschema';
import { TLCreateShapePartial } from '@tldraw/tlschema';
import { TLCursor } from '@tldraw/tlschema';
import { TLCursorType } from '@tldraw/tlschema';
import { TLDefaultColor } from '@tldraw/tlschema';
import { TLDefaultColorStyle } from '@tldraw/tlschema';
import { TLDefaultDashStyle } from '@tldraw/tlschema';
import { TLDefaultHorizontalAlignStyle } from '@tldraw/tlschema';
import { TLDocument } from '@tldraw/tlschema';
import { TLFontFace } from '@tldraw/tlschema';
import { TLGroupShape } from '@tldraw/tlschema';
import { TLHandle } from '@tldraw/tlschema';
import { TLImageAsset } from '@tldraw/tlschema';
import { TLInstance } from '@tldraw/tlschema';
import { TLInstancePageState } from '@tldraw/tlschema';
import { TLInstancePresence } from '@tldraw/tlschema';
import { TLPage } from '@tldraw/tlschema';
import { TLPageId } from '@tldraw/tlschema';
import { TLParentId } from '@tldraw/tlschema';
import { TLPropsMigrations } from '@tldraw/tlschema';
import { TLRecord } from '@tldraw/tlschema';
import { TLRichText } from '@tldraw/tlschema';
import { TLScribble } from '@tldraw/tlschema';
import { TLShape } from '@tldraw/tlschema';
import { TLShapeCrop } from '@tldraw/tlschema';
import { TLShapeId } from '@tldraw/tlschema';
import { TLShapePartial } from '@tldraw/tlschema';
import { TLStore } from '@tldraw/tlschema';
import { TLStoreProps } from '@tldraw/tlschema';
import { TLStoreSchema } from '@tldraw/tlschema';
import { TLStoreSnapshot } from '@tldraw/tlschema';
import { TLTheme } from '@tldraw/tlschema';
import { TLThemeColors } from '@tldraw/tlschema';
import { TLThemeId } from '@tldraw/tlschema';
import { TLThemes } from '@tldraw/tlschema';
import { TLUnknownAsset } from '@tldraw/tlschema';
import { TLUnknownBinding } from '@tldraw/tlschema';
import { TLUnknownShape } from '@tldraw/tlschema';
import { TLUser } from '@tldraw/tlschema';
import { TLUserStore } from '@tldraw/tlschema';
import { TLVideoAsset } from '@tldraw/tlschema';
import { UnknownRecord } from '@tldraw/store';
import { VecModel } from '@tldraw/tlschema';
/* Excluded from this release type: activeElementShouldCaptureKeys */
/**
* Get the angle of a point on an arc.
* @param fromAngle - The angle from center to arc's start point (A) on the circle
* @param toAngle - The angle from center to arc's end point (B) on the circle
* @param direction - The direction of the arc (1 = counter-clockwise, -1 = clockwise)
* @returns The distance in radians between the two angles according to the direction
* @public
*/
export declare function angleDistance(fromAngle: number, toAngle: number, direction: number): number;
/* Excluded from this release type: applyRotationToSnapshotShapes */
/**
* Whether two numbers numbers a and b are approximately equal.
*
* @param a - The first point.
* @param b - The second point.
* @public
*/
export declare function approximately(a: number, b: number, precision?: number): boolean;
/** @public */
export declare class Arc2d extends Geometry2d {
private _center;
private _radius;
private _start;
private _end;
private _largeArcFlag;
private _sweepFlag;
private _measure;
private _angleStart;
private _angleEnd;
constructor(config: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & {
center: Vec;
end: Vec;
largeArcFlag: number;
start: Vec;
sweepFlag: number;
});
nearestPoint(point: VecLike): Vec;
hitTestLineSegment(A: VecLike, B: VecLike): boolean;
getVertices(): Vec[];
getSvgPathData(first?: boolean): string;
getLength(): number;
}
/**
* Checks whether two angles are approximately at right-angles or parallel to each other
*
* @param a - Angle a (radians)
* @param b - Angle b (radians)
* @returns True iff the angles are approximately at right-angles or parallel to each other
* @public
*/
export declare function areAnglesCompatible(a: number, b: number): boolean;
/**
* Abstract base class for defining asset-type-specific behavior.
*
* Each asset type (image, video, bookmark, etc.) has a corresponding AssetUtil that handles
* type-specific operations like determining supported MIME types and creating assets from files.
*
* @public
*/
export declare abstract class AssetUtil<Asset extends TLAsset = TLAsset> {
editor: Editor;
/** Configure this asset util's {@link AssetUtil.options | `options`}. */
static configure<T extends TLAssetUtilConstructor<any, any>>(this: T, options: T extends new (...args: any[]) => {
options: infer Options;
} ? Partial<Options> : never): T;
constructor(editor: Editor);
/**
* Options for this asset util. Override this to provide customization options for your asset.
* Use {@link AssetUtil.configure} to customize existing asset utils.
*/
options: {};
static props?: RecordProps<TLUnknownAsset>;
static migrations?: LegacyMigrations | MigrationSequence | TLPropsMigrations;
/**
* The type of the asset util, which should match the asset's type.
*/
static type: string;
/**
* Get the default props for an asset of this type.
*/
abstract getDefaultProps(): Asset['props'];
/**
* Get the MIME types that this asset type supports.
* Return an empty array if this asset type doesn't support files (e.g. bookmarks).
*/
getSupportedMimeTypes(): readonly string[];
/**
* Check whether this asset type accepts a given MIME type.
*/
acceptsMimeType(mimeType: string): boolean;
/**
* Create an asset from a file. Return null if this asset type can't handle the file.
*/
getAssetFromFile(_file: File, _assetId: TLAssetId): Promise<Asset | null>;
}
/** @public */
export declare function average(A: VecLike, B: VecLike): string;
/** @public */
export declare abstract class BaseBoxShapeTool extends StateNode {
static id: string;
static initial: string;
static children(): TLStateNodeConstructor[];
abstract shapeType: TLBaseBoxShape['type'];
onCreate?(_shape: null | TLShape): null | void;
}
/** @public */
export declare abstract class BaseBoxShapeUtil<Shape extends TLBaseBoxShape> extends ShapeUtil<Shape> {
getGeometry(shape: Shape): Geometry2d;
onResize(shape: any, info: TLResizeInfo<any>): any;
getHandleSnapGeometry(shape: Shape): HandleSnapGeometry;
getInterpolatedProps(startShape: Shape, endShape: Shape, t: number): Shape['props'];
}
/**
* A base class for frame-like shapes — containers that clip their children,
* require full-brush selection, block erasure from inside, and support
* drag-and-drop reparenting.
*
* Extending this class is the easiest way to create a custom frame-like shape.
* It provides sensible defaults for all frame-like behaviors:
*
* - `isFrameLike()` returns `true`
* - `providesBackgroundForChildren()` returns `true`
* - `canReceiveNewChildrenOfType()` returns `true` unless the container is locked
* - `canRemoveChildrenOfType()` returns `true` unless the container is locked
* - `getClipPath()` returns the shape geometry's vertices
* - `onDragShapesIn()` reparents shapes into the frame (with index restoration)
* - `onDragShapesOut()` reparents shapes back to the page
*
* All methods can be overridden for custom behavior.
*
* @example
* ```ts
* class MyContainerUtil extends BaseFrameLikeShapeUtil<MyContainerShape> {
* static override type = 'my-container' as const
* static override props = myContainerShapeProps
*
* override getDefaultProps() {
* return { w: 300, h: 200 }
* }
*
* override component(shape: MyContainerShape) {
* return <SVGContainer>...</SVGContainer>
* }
*
* override getIndicatorPath(shape: MyContainerShape) {
* const path = new Path2D()
* path.rect(0, 0, shape.props.w, shape.props.h)
* return path
* }
* }
* ```
*
* @public
*/
export declare abstract class BaseFrameLikeShapeUtil<Shape extends TLBaseBoxShape> extends BaseBoxShapeUtil<Shape> {
isFrameLike(_shape: Shape): boolean;
providesBackgroundForChildren(): boolean;
canReceiveNewChildrenOfType(shape: Shape, _type: TLShape['type']): boolean;
canRemoveChildrenOfType(shape: Shape, _type: TLShape['type']): boolean;
getClipPath(shape: Shape): undefined | Vec[];
onDragShapesIn(shape: Shape, draggingShapes: TLShape[], { initialParentIds, initialIndices }: TLDragShapesInInfo): void;
onDragShapesOut(shape: Shape, draggingShapes: TLShape[], info: TLDragShapesOutInfo): void;
}
/** @public */
export declare interface BatchMeasurementRequest {
html: string;
opts: TLMeasureTextOpts;
}
/**
* Options passed to {@link BindingUtil.onBeforeChange} and {@link BindingUtil.onAfterChange},
* describing the data associated with a binding being changed.
*
* @public
*/
export declare interface BindingOnChangeOptions<Binding extends TLBinding = TLBinding> {
/** The binding record before the change is made. */
bindingBefore: Binding;
/** The binding record after the change is made. */
bindingAfter: Binding;
}
/**
* Options passed to {@link BindingUtil.onBeforeCreate} and {@link BindingUtil.onAfterCreate},
* describing a the creating a binding.
*
* @public
*/
export declare interface BindingOnCreateOptions<Binding extends TLBinding = TLBinding> {
/** The binding being created. */
binding: Binding;
}
/**
* Options passed to {@link BindingUtil.onBeforeDelete} and {@link BindingUtil.onAfterDelete},
* describing a binding being deleted.
*
* @public
*/
export declare interface BindingOnDeleteOptions<Binding extends TLBinding = TLBinding> {
/** The binding being deleted. */
binding: Binding;
}
/**
* Options passed to {@link BindingUtil.onAfterChangeFromShape} and
* {@link BindingUtil.onAfterChangeToShape}, describing a bound shape being changed.
*
* @public
*/
export declare interface BindingOnShapeChangeOptions<Binding extends TLBinding = TLBinding> {
/** The binding record linking these two shapes. */
binding: Binding;
/** The shape record before the change is made. */
shapeBefore: TLShape;
/** The shape record after the change is made. */
shapeAfter: TLShape;
/**
* Why did this shape change?
* - 'self': the shape itself changed
* - 'ancestry': the ancestry of the shape changed, but the shape itself may not have done
*/
reason: 'ancestry' | 'self';
}
/**
* Options passed to {@link BindingUtil.onBeforeDeleteFromShape} and
* {@link BindingUtil.onBeforeDeleteToShape}, describing a bound shape that is about to be deleted.
*
* See {@link BindingOnShapeIsolateOptions} for discussion on when to use the delete vs. the isolate
* callbacks.
*
* @public
*/
export declare interface BindingOnShapeDeleteOptions<Binding extends TLBinding = TLBinding> {
/** The binding record that refers to the shape in question. */
binding: Binding;
/** The shape that is about to be deleted. */
shape: TLShape;
}
/**
* Options passed to {@link BindingUtil.onBeforeIsolateFromShape} and
* {@link BindingUtil.onBeforeIsolateToShape}, describing a shape that is about to be isolated from
* the one that it's bound to.
*
* Isolation happens whenever two bound shapes are separated. For example
* 1. One is deleted, but the other is not.
* 1. One is copied, but the other is not.
* 1. One is duplicated, but the other is not.
*
* In each of these cases, if the remaining shape depends on the binding for its rendering, it may
* now be in an inconsistent state. For example, tldraw's arrow shape depends on the binding to know
* where the end of the arrow is. If we removed the binding without doing anything else, the arrow
* would suddenly be pointing to the wrong location. Instead, when the shape the arrow is pointing
* to is deleted, or the arrow is copied/duplicated, we use an isolation callback. The callback
* updates the arrow based on the binding that's about to be removed, so it doesn't end up pointing
* to the wrong place.
*
* For this style of consistency update, use isolation callbacks. For actions specific to deletion
* (like deleting a sticker when the shape it's bound to is removed), use the delete callbacks
* ({@link BindingUtil.onBeforeDeleteFromShape} and {@link BindingUtil.onBeforeDeleteToShape})
* instead.
*
* @public
*/
export declare interface BindingOnShapeIsolateOptions<Binding extends TLBinding = TLBinding> {
/** The binding record that refers to the shape in question. */
binding: Binding;
/**
* The shape being removed. For deletion, this is the deleted shape. For copy/duplicate, this is
* the shape that _isn't_ being copied/duplicated and is getting left behind.
*/
removedShape: TLShape;
}
/** @public */
export declare abstract class BindingUtil<Binding extends TLBinding = TLBinding> {
editor: Editor;
constructor(editor: Editor);
static props?: RecordProps<TLUnknownBinding>;
static migrations?: TLPropsMigrations;
/**
* The type of the binding util, which should match the binding's type.
*
* @public
*/
static type: string;
/**
* Get the default props for a binding.
*
* @public
*/
abstract getDefaultProps(): Partial<Binding['props']>;
/**
* Called whenever a store operation involving this binding type has completed. This is useful
* for working with networks of related bindings that may need to update together.
*
* @example
* ```ts
* class MyBindingUtil extends BindingUtil<MyBinding> {
* changedBindingIds = new Set<TLBindingId>()
*
* onOperationComplete() {
* doSomethingWithChangedBindings(this.changedBindingIds)
* this.changedBindingIds.clear()
* }
*
* onAfterChange({ bindingAfter }: BindingOnChangeOptions<MyBinding>) {
* this.changedBindingIds.add(bindingAfter.id)
* }
* }
* ```
*
* @public
*/
onOperationComplete?(): void;
/**
* Called when a binding is about to be created. See {@link BindingOnCreateOptions} for details.
*
* You can optionally return a new binding to replace the one being created - for example, to
* set different initial props.
*
* @public
*/
onBeforeCreate?(options: BindingOnCreateOptions<Binding>): Binding | void;
/**
* Called after a binding has been created. See {@link BindingOnCreateOptions} for details.
*
* @public
*/
onAfterCreate?(options: BindingOnCreateOptions<Binding>): void;
/**
* Called when a binding is about to be changed. See {@link BindingOnChangeOptions} for details.
*
* Note that this only fires when the binding record is changing, not when the shapes
* associated change. Use {@link BindingUtil.onAfterChangeFromShape} and
* {@link BindingUtil.onAfterChangeToShape} for that.
*
* You can optionally return a new binding to replace the one being changed - for example, to
* enforce constraints on the binding's props.
*
* @public
*/
onBeforeChange?(options: BindingOnChangeOptions<Binding>): Binding | void;
/**
* Called after a binding has been changed. See {@link BindingOnChangeOptions} for details.
*
* Note that this only fires when the binding record is changing, not when the shapes
* associated change. Use {@link BindingUtil.onAfterChangeFromShape} and
* {@link BindingUtil.onAfterChangeToShape} for that.
*
* @public
*/
onAfterChange?(options: BindingOnChangeOptions<Binding>): void;
/**
* Called when a binding is about to be deleted. See {@link BindingOnDeleteOptions} for details.
*
* @public
*/
onBeforeDelete?(options: BindingOnDeleteOptions<Binding>): void;
/**
* Called after a binding has been deleted. See {@link BindingOnDeleteOptions} for details.
*
* @public
*/
onAfterDelete?(options: BindingOnDeleteOptions<Binding>): void;
/**
* Called after the shape referenced in a binding's `fromId` is changed. Use this to propagate
* any changes to the binding itself or the other shape as needed. See
* {@link BindingOnShapeChangeOptions} for details.
*
* @public
*/
onAfterChangeFromShape?(options: BindingOnShapeChangeOptions<Binding>): void;
/**
* Called after the shape referenced in a binding's `toId` is changed. Use this to propagate any
* changes to the binding itself or the other shape as needed. See
* {@link BindingOnShapeChangeOptions} for details.
*
* @public
*/
onAfterChangeToShape?(options: BindingOnShapeChangeOptions<Binding>): void;
/**
* Called before the shape referenced in a binding's `fromId` is about to be deleted. Use this
* with care - you may want to use {@link BindingUtil.onBeforeIsolateToShape} instead. See
* {@link BindingOnShapeDeleteOptions} for details.
*
* @public
*/
onBeforeDeleteFromShape?(options: BindingOnShapeDeleteOptions<Binding>): void;
/**
* Called before the shape referenced in a binding's `toId` is about to be deleted. Use this
* with care - you may want to use {@link BindingUtil.onBeforeIsolateFromShape} instead. See
* {@link BindingOnShapeDeleteOptions} for details.
*
* @public
*/
onBeforeDeleteToShape?(options: BindingOnShapeDeleteOptions<Binding>): void;
/**
* Called before the shape referenced in a binding's `fromId` is about to be isolated from the
* shape referenced in `toId`. See {@link BindingOnShapeIsolateOptions} for discussion on what
* isolation means, and when/how to use this callback.
*/
onBeforeIsolateFromShape?(options: BindingOnShapeIsolateOptions<Binding>): void;
/**
* Called before the shape referenced in a binding's `toId` is about to be isolated from the
* shape referenced in `fromId`. See {@link BindingOnShapeIsolateOptions} for discussion on what
* isolation means, and when/how to use this callback.
*/
onBeforeIsolateToShape?(options: BindingOnShapeIsolateOptions<Binding>): void;
}
/**
* When moving or resizing shapes, the bounds of the shape can snap to key geometry on other nearby
* shapes. Customize how a shape snaps to others with {@link ShapeUtil.getBoundsSnapGeometry}.
*
* @public
*/
export declare interface BoundsSnapGeometry {
/**
* Points that this shape will snap to. By default, this will be the corners and center of the
* shapes bounding box. To disable snapping to a specific point, use an empty array.
*/
points?: VecModel[];
}
/** @public */
export declare interface BoundsSnapPoint {
id: string;
x: number;
y: number;
handle?: SelectionCorner;
}
/** @public */
export declare class BoundsSnaps {
readonly manager: SnapManager;
readonly editor: Editor;
constructor(manager: SnapManager);
private getSnapPointsCache;
getSnapPoints(shapeId: TLShapeId): BoundsSnapPoint[];
private getSnappablePoints;
private getSnappableGapNodes;
private getVisibleGaps;
snapTranslateShapes({ lockedAxis, initialSelectionPageBounds, initialSelectionSnapPoints, dragDelta }: {
dragDelta: Vec;
initialSelectionPageBounds: Box;
initialSelectionSnapPoints: BoundsSnapPoint[];
lockedAxis: 'x' | 'y' | null;
}): SnapData;
snapResizeShapes({ initialSelectionPageBounds, dragDelta, handle: originalHandle, isAspectRatioLocked, isResizingFromCenter }: {
dragDelta: Vec;
handle: SelectionCorner | SelectionEdge;
initialSelectionPageBounds: Box;
isAspectRatioLocked: boolean;
isResizingFromCenter: boolean;
}): SnapData;
private collectPointSnaps;
private collectGapSnaps;
private getPointSnapLines;
private getGapSnapLines;
}
/** @public */
export declare class Box {
constructor(x?: number, y?: number, w?: number, h?: number);
x: number;
y: number;
w: number;
h: number;
get point(): Vec;
set point(val: Vec);
get minX(): number;
set minX(n: number);
get left(): number;
get midX(): number;
get maxX(): number;
get right(): number;
get minY(): number;
set minY(n: number);
get top(): number;
get midY(): number;
get maxY(): number;
get bottom(): number;
get width(): number;
set width(n: number);
get height(): number;
set height(n: number);
get aspectRatio(): number;
get center(): Vec;
set center(v: Vec);
get corners(): Vec[];
get cornersAndCenter(): Vec[];
get sides(): Array<[Vec, Vec]>;
get size(): Vec;
isValid(): boolean;
toFixed(): this;
setTo(B: Box): this;
set(x?: number, y?: number, w?: number, h?: number): this;
expand(A: Box): this;
expandBy(n: number): this;
scale(n: number): this;
clone(): Box;
translate(delta: VecLike): this;
snapToGrid(size: number): void;
collides(B: Box): boolean;
contains(B: Box): boolean;
includes(B: Box): boolean;
containsPoint(V: VecLike, margin?: number): boolean;
getHandlePoint(handle: SelectionCorner | SelectionEdge): Vec;
toJson(): BoxModel;
resize(handle: SelectionCorner | SelectionEdge | string, dx: number, dy: number): void;
union(box: BoxModel): this;
static From(box: BoxModel): Box;
static FromCenter(center: VecLike, size: VecLike): Box;
static FromPoints(points: VecLike[]): Box;
static Expand(A: Box, B: Box): Box;
static ExpandBy(A: Box, n: number): Box;
static Collides(A: Box, B: Box): boolean;
static Contains(A: Box, B: Box): boolean;
static ContainsApproximately(A: Box, B: Box, precision?: number): boolean;
static Includes(A: Box, B: Box): boolean;
static ContainsPoint(A: Box, B: VecLike, margin?: number): boolean;
static Common(boxes: Box[]): Box;
static Sides(A: Box, inset?: number): Vec[][];
static Resize(box: Box, handle: SelectionCorner | SelectionEdge | string, dx: number, dy: number, isAspectRatioLocked?: boolean): {
box: Box;
scaleX: number;
scaleY: number;
};
equals(other: Box | BoxModel): boolean;
static Equals(a: Box | BoxModel, b: Box | BoxModel): boolean;
zeroFix(): this;
static ZeroFix(other: Box | BoxModel): Box;
}
/** @public */
export declare type BoxLike = Box | BoxModel;
/**
* @param a - Any angle in radians
* @returns A number between 0 and 2 * PI
* @public
*/
export declare function canonicalizeRotation(a: number): number;
/* Excluded from this release type: CanvasMaxSize */
/**
* Get the center of a circle from three points.
*
* @param a - The first point
* @param b - The second point
* @param c - The third point
*
* @returns The center of the circle or null if the points are collinear
*
* @public
*/
export declare function centerOfCircleFromThreePoints(a: VecLike, b: VecLike, c: VecLike): null | Vec;
/** @public */
export declare class Circle2d extends Geometry2d {
config: Omit<Geometry2dOptions, 'isClosed'> & {
isFilled: boolean;
radius: number;
x?: number;
y?: number;
};
private _center;
private _radius;
private _x;
private _y;
constructor(config: Omit<Geometry2dOptions, 'isClosed'> & {
isFilled: boolean;
radius: number;
x?: number;
y?: number;
});
getBounds(): Box;
getVertices(): Vec[];
nearestPoint(point: VecLike): Vec;
distanceToPoint(point: VecLike, hitInside?: boolean): number;
hitTestPoint(point: VecLike, margin?: number, hitInside?: boolean): boolean;
hitTestLineSegment(A: VecLike, B: VecLike, distance?: number): boolean;
getSvgPathData(): string;
}
/**
* Clamp a value into a range.
*
* @example
*
* ```ts
* const A = clamp(0, 1) // 1
* ```
*
* @param n - The number to clamp.
* @param min - The minimum value.
* @public
*/
export declare function clamp(n: number, min: number): number;
/**
* Clamp a value into a range.
*
* @example
*
* ```ts
* const A = clamp(0, 1, 10) // 1
* const B = clamp(11, 1, 10) // 10
* const C = clamp(5, 1, 10) // 5
* ```
*
* @param n - The number to clamp.
* @param min - The minimum value.
* @param max - The maximum value.
* @public
*/
export declare function clamp(n: number, min: number, max: number): number;
/**
* Clamp radians within 0 and 2PI
*
* @param r - The radian value.
* @public
*/
export declare function clampRadians(r: number): number;
/* Excluded from this release type: clampToBrowserMaxCanvasSize */
/** @public */
export declare class ClickManager {
editor: Editor;
constructor(editor: Editor);
private _clickId;
private _clickTimeout?;
private _clickScreenPoint?;
private _previousScreenPoint?;
_getClickTimeout(state: TLClickState, id?: string): void;
/* Excluded from this release type: _clickState */
/**
* The current click state.
*
* @public
*/
get clickState(): TLClickState | undefined;
lastPointerInfo: TLPointerEventInfo;
handlePointerEvent(info: TLPointerEventInfo): TLClickEventInfo | TLPointerEventInfo;
/* Excluded from this release type: cancelDoubleClickTimeout */
}
/**
* Get the clockwise angle distance between two angles.
*
* @param a0 - The first angle.
* @param a1 - The second angle.
* @public
*/
export declare function clockwiseAngleDist(a0: number, a1: number): number;
/**
* Tracks remote peers and exposes the collaborator-related queries used by the
* editor and its overlays. Encapsulates the visibility clock that periodically
* re-evaluates which collaborators should be visible based on activity.
*
* Accessed via {@link Editor.collaborators}.
*
* @public
*/
export declare class CollaboratorsManager {
private readonly editor;
constructor(editor: Editor);
/**
* Drives reactive re-evaluation of {@link CollaboratorsManager.getVisibleCollaborators}.
* Ticked on a fixed interval so callers don't need to manage their own activity timers.
*/
private readonly _visibilityClock;
private _getCollaboratorsQuery;
/**
* Returns a list of presence records for all peer collaborators.
* This will return the latest presence record for each connected user.
*/
getCollaborators(): TLInstancePresence[];
/**
* Returns a list of presence records for all peer collaborators on the current page.
* This will return the latest presence record for each connected user.
*/
getCollaboratorsOnCurrentPage(): TLInstancePresence[];
/**
* Returns a list of presence records for peer collaborators who should currently be
* shown in the UI. Filters {@link CollaboratorsManager.getCollaborators} by activity
* state (active / idle / inactive) and visibility rules such as following and
* highlighted users. Re-evaluates on the visibility clock, so callers don't need to
* drive their own activity timer.
*/
getVisibleCollaborators(): TLInstancePresence[];
/**
* Returns a list of presence records for peer collaborators who should currently be
* shown in the UI, filtered to those on the current page.
*/
getVisibleCollaboratorsOnCurrentPage(): TLInstancePresence[];
}
/**
* @public
* @react
*/
export declare function ContainerProvider({ container, children }: ContainerProviderProps): JSX.Element;
/** @public */
export declare interface ContainerProviderProps {
container: HTMLElement;
children: React.ReactNode;
}
/** @public */
export declare const coreShapes: readonly [typeof GroupShapeUtil];
/**
* Get the counter-clockwise angle distance between two angles.
*
* @param a0 - The first angle.
* @param a1 - The second angle.
* @public
*/
export declare function counterClockwiseAngleDist(a0: number, a1: number): number;
/** @public */
export declare function createDebugValue<T>(name: string, { defaults, shouldStoreForSession }: {
defaults: DebugFlagDefaults<T>;
shouldStoreForSession?: boolean;
}): DebugFlag<T>;
/**
* Converts a deep link descriptor to a url-safe string
*
* @example
* ```ts
* const url = `https://example.com?d=${createDeepLinkString({ type: 'shapes', shapeIds: ['shape:1', 'shape:2'] })}`
* navigator.clipboard.writeText(url)
* ```
*
* @param deepLink - the deep link descriptor
* @returns a url-safe string
*
* @public
*/
export declare function createDeepLinkString(deepLink: TLDeepLink): string;
/**
* Creates a signal of the instance state for a given store.
* @public
* @param store - The store to create the instance state snapshot signal for
* @returns
*/
export declare function createSessionStateSnapshotSignal(store: TLStore): Signal<null | TLSessionStateSnapshot>;
/** @public */
export declare function createTLCurrentUser(opts?: {
setUserPreferences?: ((userPreferences: TLUserPreferences) => void) | undefined;
userPreferences?: Signal<TLUserPreferences, unknown> | undefined;
}): TLCurrentUser;
/**
* A helper for creating a TLStore schema from either an object with shapeUtils, bindingUtils, and
* migrations, or a schema.
*
* @param opts - Options for creating the schema.
*
* @public
*/
export declare function createTLSchemaFromUtils(opts: TLStoreSchemaOptions): StoreSchema<TLRecord, TLStoreProps>;
/**
* A helper for creating a TLStore.
*
* @param opts - Options for creating the store.
*
* @public
*/
export declare function createTLStore({ initialData, defaultName, id, assets, users, onMount, collaboration, themes, ...rest }?: TLStoreOptions): TLStore;
/** @public */
export declare class CubicBezier2d extends Polyline2d {
private _a;
private _b;
private _c;
private _d;
private _resolution;
constructor(config: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & {
cp1: Vec;
cp2: Vec;
end: Vec;
resolution?: number;
start: Vec;
});
getVertices(): Vec[];
nearestPoint(A: VecLike): Vec;
distanceToPoint(point: VecLike, _hitInside?: boolean): number;
getSvgPathData(first?: boolean): string;
static GetAtT(segment: CubicBezier2d, t: number): Vec;
getLength(_filters?: Geometry2dFilters, precision?: number): number;
}
/** @public */
export declare class CubicSpline2d extends Geometry2d {
private _points;
constructor(config: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & {
points: Vec[];
});
private _segments?;
get segments(): CubicBezier2d[];
getLength(): number;
getVertices(): Vec[];
nearestPoint(A: VecLike): Vec;
distanceToPoint(point: VecLike, _hitInside?: boolean): number;
hitTestLineSegment(A: VecLike, B: VecLike): boolean;
getSvgPathData(): string;
}
/**
* Converts a data URL to a file.
* @param url - The data URL to convert.
* @param filename - The name of the file.
* @param mimeType - The MIME type of the file.
* @returns A promise that resolves to a file.
* @public */
export declare function dataUrlToFile(url: string, filename: string, mimeType: string): Promise<File>;
/** @public */
export declare interface DebugFlag<T> extends DebugFlagDef<T>, Atom<T> {
reset(): void;
}
/** @public */
export declare interface DebugFlagDef<T> {
name: string;
defaults: DebugFlagDefaults<T>;
shouldStoreForSession: boolean;
}
/** @public */
export declare interface DebugFlagDefaults<T> {
development?: T;
staging?: T;
production?: T;
all: T;
}
/* Excluded from this release type: debugFlags */
/* Excluded from this release type: DEFAULT_ANIMATION_OPTIONS */
/* Excluded from this release type: DEFAULT_CAMERA_OPTIONS */
/**
* The default theme definition containing color palettes for both light and dark modes.
*
* @public
*/
export declare const DEFAULT_THEME: TLTheme;
/** @public @react */
export declare function DefaultBackground(): JSX.Element;
/** @public @react */
export declare function DefaultCanvas({ className }: TLCanvasComponentProps): JSX.Element;
/** @public @react */
export declare const DefaultErrorFallback: TLErrorFallbackComponent;
/** @public @react */
export declare function DefaultGrid({ x, y, z, size }: TLGridProps): JSX.Element;
/** @public @react */
export declare function DefaultSelectionBackground({ bounds, rotation }: TLSelectionBackgroundProps): JSX.Element;
/** @public @react */
export declare const DefaultShapeWrapper: ForwardRefExoticComponent<TLShapeWrapperProps & RefAttributes<HTMLDivElement>>;
/** @public @react */
export declare function DefaultSpinner(props: React.SVGProps<SVGSVGElement>): JSX.Element;
/** @public @react */
export declare const DefaultSvgDefs: () => null;
/** @public */
export declare const defaultTldrawOptions: {
readonly actionShortcutsLocation: "swap";
readonly adjacentShapeMargin: 10;
readonly animationMediumMs: 320;
readonly camera: TLCameraOptions;
readonly cameraMovingTimeoutMs: 64;
readonly cameraSlideFriction: 0.09;
readonly coarseDragDistanceSquared: 36;
readonly coarseHandleRadius: 20;
readonly coarsePointerWidth: 12;
readonly collaboratorCheckIntervalMs: 1200;
readonly collaboratorIdleTimeoutMs: 3000;
readonly collaboratorInactiveTimeoutMs: 60000;
readonly createTextOnCanvasDoubleClick: true;
readonly debouncedZoom: true;
readonly debouncedZoomThreshold: 500;
readonly deepLinks: undefined;
readonly defaultSvgPadding: 32;
readonly doubleClickDurationMs: 450;
readonly dragDistanceSquared: 16;
readonly edgeScrollDelay: 200;
readonly edgeScrollDistance: 8;
readonly edgeScrollEaseDuration: 200;
readonly edgeScrollSpeed: 25;
readonly enableToolbarKeyboardShortcuts: true;
readonly experimental__onDropOnCanvas: undefined;
readonly exportProvider: ExoticComponent<FragmentProps>;
readonly flattenImageBoundsExpand: 64;
readonly flattenImageBoundsPadding: 16;
readonly followChaseViewportSnap: 2;
readonly gridSteps: readonly [{
readonly mid: 0.15;
readonly min: -1;
readonly step: 64;
}, {
readonly mid: 0.375;
readonly min: 0.05;
readonly step: 16;
}, {
readonly mid: 1;
readonly min: 0.15;
readonly step: 4;
}, {
readonly mid: 2.5;
readonly min: 0.7;
readonly step: 1;
}];
readonly handleRadius: 12;
readonly hitTestMargin: 8;
readonly laserDelayMs: 1200;
readonly laserFadeoutMs: 500;
readonly longPressDurationMs: 500;
readonly maxExportDelayMs: 5000;
readonly maxFilesAtOnce: 100;
readonly maxFontsToLoadBeforeRender: number;
readonly maxPages: 40;
readonly maxShapesPerPage: 4000;
readonly multiClickDurationMs: 200;
readonly nonce: undefined;
readonly onBeforeCopyToClipboard: undefined;
readonly onBeforePasteFromClipboard: undefined;
readonly onClipboardPasteRaw: undefined;
readonly quickZoomPreservesScreenBounds: true;
readonly rightClickPanning: true;
readonly snapThreshold: 8;
readonly spacebarPanning: true;
readonly temporaryAssetPreviewLifetimeMs: 180000;
readonly text: {};
readonly textShadowLod: 0.35;
readonly tooltipDelayMs: 700;
readonly uiCoarseDragDistanceSquared: 625;
readonly uiDragDistanceSquared: 16;
readonly zoomToFitPadding: 128;
};
/** @public */
export declare const defaultUserPreferences: Readonly<{
animationSpeed: 0 | 1;
areKeyboardShortcutsEnabled: true;
color: "#02B1CC" | "#11B3A3" | "#39B178" | "#55B467" | "#7B66DC" | "#9D5BD2" | "#BD54C6" | "#E34BA9" | "#EC5E41" | "#F04F88" | "#F2555A" | "#FF802B";
colorScheme: "light";
edgeScrollSpeed: 1;
enhancedA11yMode: false;
inputMode: null;
isDynamicSizeMode: false;
isPasteAtCursorMode: false;
isSnapMode: false;
isWrapMode: false;
isZoomDirectionInverted: false;
locale: "ar" | "bn" | "ca" | "cs" | "da" | "de" | "el" | "en" | "es" | "fa" | "fi" | "fr" | "gl" | "gu-in" | "he" | "hi-in" | "hr" | "hu" | "id" | "it" | "ja" | "km-kh" | "kn" | "ko-kr" | "ml" | "mr" | "ms" | "ne" | "nl" | "no" | "pa" | "pl" | "pt-br" | "pt-pt" | "ro" | "ru" | "sl" | "so" | "sv" | "ta" | "te" | "th" | "tl" | "tr" | "uk" | "ur" | "vi" | "zh-cn" | "zh-tw";
name: "";
}>;
/** @public */
export declare const defaultUserStore: TLUserStore;
/**
* Convert degrees to radians.
*
* @param d - The degree in degrees.
* @public
*/
export declare function degreesToRadians(d: number): number;
/** @public */
export declare const EASINGS: {
readonly easeInCubic: (t: number) => number;
readonly easeInExpo: (t: number) => number;
readonly easeInOutCubic: (t: number) => number;
readonly easeInOutExpo: (t: number) => number;
readonly easeInOutQuad: (t: number) => number;
readonly easeInOutQuart: (t: number) => number;
readonly easeInOutQuint: (t: number) => number;
readonly easeInOutSine: (t: number) => number;
readonly easeInQuad: (t: number) => number;
readonly easeInQuart: (t: number) => number;
readonly easeInQuint: (t: number) => number;
readonly easeInSine: (t: number) => number;
readonly easeOutCubic: (t: number) => number;
readonly easeOutExpo: (t: number) => number;
readonly easeOutQuad: (t: number) => number;
readonly easeOutQuart: (t: number) => number;
readonly easeOutQuint: (t: number) => number;
readonly easeOutSine: (t: number) => number;
readonly linear: (t: number) => number;
};
/** @public */
export declare class Edge2d extends Geometry2d {
private _start;
private _end;
private _dx;
private _dy;
private _len2;
constructor(config: {
end: Vec;
start: Vec;
});
getLength(): number;
getVertices(): Vec[];
nearestPoint(point: VecLike): Vec;
distanceToPoint(point: VecLike, _hitInside?: boolean): number;
getSvgPathData(first?: boolean): string;
}
/** @public */
export declare class EdgeScrollManager {
editor: Editor;
constructor(editor: Editor);
private _isEdgeScrolling;
private _edgeScrollDuration;
getIsEdgeScrolling(): boolean;
/**
* Update the camera position when the mouse is close to the edge of the screen.
* Run this on every tick when in a state where edge scrolling is enabled.
*
* @public
*/
updateEdgeScrolling(elapsed: number): void;
/* Excluded from this release type: getEdgeProximityFactors */
private getEdgeScroll;
/**
* Moves the camera when the mouse is close to the edge of the screen.
* @public
*/
private moveCameraWhenCloseToEdge;
}
/** @public */
export declare class Editor extends EventEmitter<TLEventMap> {
readonly id: string;
constructor({ store, user, shapeUtils, bindingUtils, assetUtils: assetUtilConstructors, overlayUtils: overlayUtilConstructors, tools, getContainer, cameraOptions, initialState, autoFocus, options: _options, textOptions: _textOptions, getShapeVisibility, colorScheme, fontAssetUrls, themes, initialTheme }: TLEditorOptions);
private readonly _getShapeVisibility?;
private getIsShapeHiddenCache;
isShapeHidden(shapeOrId: TLShape | TLShapeId): boolean;
readonly options: TldrawOptions;
readonly contextId: string;
/**
* The editor's store
*
* @public
*/
readonly store: TLStore;
/**
* The root state of the statechart.
*
* @public
*/
readonly root: StateNode;
/**
* Set a tool. Useful if you need to add a tool to the state chart on demand,
* after the editor has already been initialized.
*
* @param Tool - The tool to set.
* @param parent - The parent state node to set the tool on.
*
* @public
*/
setTool(Tool: TLStateNodeConstructor, parent?: StateNode): void;
/**
* Remove a tool. Useful if you need to remove a tool from the state chart on demand,
* after the editor has already been initialized.
*
* @param Tool - The tool to delete.
* @param parent - The parent state node to remove the tool from.
*
* @public
*/
removeTool(Tool: TLStateNodeConstructor, parent?: StateNode): void;
/**
* A set of functions to call when the editor is disposed.
*
* @public
*/
readonly disposables: Set<() => void>;
/**
* Whether the editor is disposed.
*
* @public
*/
isDisposed: boolean;
/* Excluded from this release type: _tickManager */
/**
* A manager for the editor's input state.
*
* @public
*/
readonly inputs: InputsManager;
/**
* A manager for the editor's snapping feature.
*
* @public
*/
readonly snaps: SnapManager;
/**
* A manager for performance measurement hooks.
*
* @public
*/
readonly performance: PerformanceManager;
/* Excluded from this release type: _spatialIndex */
/**
* A manager for the any asynchronous events and making sure they're
* cleaned up upon disposal.
*
* @public
*/
readonly timers: {
dispose: () => void;
requestAnimationFrame: (callback: FrameRequestCallback) => number;
setInterval: (handler: TimerHandler, timeout?: number | undefined, ...args: any[]) => number;
setTimeout: (handler: TimerHandler, timeout?: number | undefined, ...args: any[]) => number;
};
/**
* A manager for remote peer collaborators connected to this editor.
*
* @public
*/
readonly collaborators: CollaboratorsManager;
/**
* A manager for the user and their preferences.
*
* @public
*/
readonly user: UserPreferencesManager;
/* Excluded from this release type: _themeManager */
/**
* A helper for measuring text.
*
* @public
*/
readonly textMeasure: TextManager;
/**
* A utility for managing the set of fonts that should be rendered in the document.
*
* @public
*/
readonly fonts: FontManager;
/**
* A manager for the editor's scribbles.
*
* @public
*/
readonly scribbles: ScribbleManager;
/**
* A manager for canvas overlay UI elements (selection handles, shape handles, etc.).
*
* @public
*/
readonly overlays: OverlayManager;
/**
* A manager for side effects and correct state enforcement. See {@link @tldraw/store#StoreSideEffects} for details.
*
* @public
*/
readonly sideEffects: StoreSideEffects<TLRecord>;
/**
* A manager for moving the camera when the mouse is at the edge of the screen.
*
* @public
*/
edgeScrollManager: EdgeScrollManager;
/* Excluded from this release type: focusManager */
/**
* The current HTML element containing the editor.
*
* @example
* ```ts
* const container = editor.getContainer()
* ```
*
* @public
*/
getContainer: () => HTMLElement;
/* Excluded from this release type: getContainerDocument */
/* Excluded from this release type: getContainerWindow */
/**
* Dispose the editor.
*
* @public
*/
dispose(): void;
/**
* Get the current color mode (`'light'` or `'dark'`), based on the user's dark mode preference.
*
* @public
*/
getColorMode(): 'dark' | 'light';
/**
* Set the color mode. Note that this is a convenience method that passes the mode to
* `user.updateUserPreferences`, which is the source of truth for the user's color mode preference.
*
* @public
*/
setColorMode(mode: 'dark' | 'light'): this;
/**
* Get the id of the current theme.
*
* @public
*/
getCurrentThemeId(): TLThemeId;
/**
* Get the current theme definition.
*
* @public
*/
getCurrentTheme(): TLTheme;
/**
* Set the current theme by id.
*
* @public
*/
setCurrentTheme(id: TLThemeId): this;
/**
* Get all registered theme definitions.
*
* @public
*/
getThemes(): TLThemes;
/**
* Get a single theme definition by id.
*
* @public
*/
getTheme(id: TLThemeId): TLTheme | undefined;
/**
* Replace all theme definitions, or update them via a callback that receives a deep copy.
* The `'default'` theme must always be present in the result.
*
* @example
* ```ts
* // Replace all themes
* editor.updateThemes({ default: myDefaultTheme, ocean: myOceanTheme })
*
* // Update via callback
* editor.updateThemes((themes) => {
* delete themes.ocean
* return themes
* })
* ```
*
* @public
*/
updateThemes(themes: ((themes: TLThemes) => TLThemes) | TLThemes): this;
/**
* Register or update a single theme definition. The theme is keyed by its `id` property.
*
* @example
* ```ts
* // Override a property on the default theme
* editor.updateTheme({ ...editor.getTheme('default')!, fontSize: 24 })
*
* // Register a new theme
* editor.updateTheme({ id: 'ocean', ...myOceanTheme })
* ```
*
* @public
*/
updateTheme(theme: TLTheme): this;
/**
* A map of shape utility classes (TLShapeUtils) by shape type.
*
* @public
*/
shapeUtils: {
readonly [K in string]?: ShapeUtil<TLShape>;
};
/* Excluded from this release type: _shapeUtilsByAssetType */
styleProps: {
[key: string]: Map<StyleProp<any>, string>;
};
/**
* Get a shape util from a shape itself.
*
* @example
* ```ts
* const util = editor.getShapeUtil(myArrowShape)
* const util = editor.getShapeUtil('arrow')
* const util = editor.getShapeUtil<TLArrowShape>(myArrowShape)
* const util = editor.getShapeUtil(TLArrowShape)('arrow')
* ```
*
* @param shape - A shape, shape partial, or shape type.
*
* @public
*/
getShapeUtil<K extends TLShape['type']>(type: K): ShapeUtil<Extract<TLShape, {
type: K;
}>>;
getShapeUtil<S extends TLShape>(shape: S | S['type'] | TLShapePartial<S>): ShapeUtil<S>;
getShapeUtil<T extends ShapeUtil>(type: T extends ShapeUtil<infer R> ? R['type'] : string): T;
/**
* Returns true if the editor has a shape util for the given shape / shape type.
*
* @param shape - A shape, shape partial, or shape type.
*/
hasShapeUtil(shape: TLShape | TLShapePartial<TLShape>): boolean;
hasShapeUtil(type: TLShape['type']): boolean;
hasShapeUtil<T extends ShapeUtil>(type: T extends ShapeUtil<infer R> ? R['type'] : string): boolean;
/**
* Get the shape util that handles the given asset type.
* Returns the shape util whose {@link ShapeUtil.handledAssetTypes} includes
* the given asset type, or undefined if none matches.
*
* @param assetType - The asset type string.
* @public
*/
getShapeUtilForAssetType(assetType: string): ShapeUtil | undefined;
/**
* A map of shape utility classes (TLShapeUtils) by shape type.
*
* @public
*/
bindingUtils: {
readonly [K in string]?: BindingUtil<TLBinding>;
};
/**
* Get a binding util from a binding itself.
*
* @example
* ```ts
* const util = editor.getBindingUtil(myArrowBinding)
* const util = editor.getBindingUtil('arrow')
* const util = editor.getBindingUtil<TLArrowBinding>(myArrowBinding)