@tldraw/editor
Version:
tldraw infinite canvas SDK (editor).
1,134 lines • 284 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
var __typeError = (msg) => {
throw TypeError(msg);
};
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
var __runInitializers = (array, flags, self, value) => {
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value);
return value;
};
var __decorateElement = (array, flags, name, decorators, target, extra) => {
var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
return __privateGet(this, extra);
}, set [name](x) {
return __privateSet(this, extra, x);
} }, name));
k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name);
for (var i = decorators.length - 1; i >= 0; i--) {
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
if (k) {
ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
}
it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
else if (typeof it !== "object" || it === null) __typeError("Object expected");
else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
}
return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
};
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
var Editor_exports = {};
__export(Editor_exports, {
Editor: () => Editor
});
module.exports = __toCommonJS(Editor_exports);
var import_state = require("@tldraw/state");
var import_store = require("@tldraw/store");
var import_tlschema = require("@tldraw/tlschema");
var import_utils = require("@tldraw/utils");
var import_eventemitter3 = __toESM(require("eventemitter3"));
var import_TLEditorSnapshot = require("../config/TLEditorSnapshot");
var import_createTLUser = require("../config/createTLUser");
var import_defaultBindings = require("../config/defaultBindings");
var import_defaultShapes = require("../config/defaultShapes");
var import_constants = require("../constants");
var import_exportToSvg = require("../exports/exportToSvg");
var import_getSvgAsImage = require("../exports/getSvgAsImage");
var import_environment = require("../globals/environment");
var import_menus = require("../globals/menus");
var import_time = require("../globals/time");
var import_options = require("../options");
var import_Box = require("../primitives/Box");
var import_Mat = require("../primitives/Mat");
var import_Vec = require("../primitives/Vec");
var import_easings = require("../primitives/easings");
var import_Group2d = require("../primitives/geometry/Group2d");
var import_intersect = require("../primitives/intersect");
var import_utils2 = require("../primitives/utils");
var import_SharedStylesMap = require("../utils/SharedStylesMap");
var import_areShapesContentEqual = require("../utils/areShapesContentEqual");
var import_assets = require("../utils/assets");
var import_debug_flags = require("../utils/debug-flags");
var import_deepLinks = require("../utils/deepLinks");
var import_getIncrementedName = require("../utils/getIncrementedName");
var import_keyboard = require("../utils/keyboard");
var import_reorderShapes = require("../utils/reorderShapes");
var import_rotation = require("../utils/rotation");
var import_bindingsIndex = require("./derivations/bindingsIndex");
var import_notVisibleShapes = require("./derivations/notVisibleShapes");
var import_parentsToChildren = require("./derivations/parentsToChildren");
var import_shapeIdsInCurrentPage = require("./derivations/shapeIdsInCurrentPage");
var import_ClickManager = require("./managers/ClickManager/ClickManager");
var import_EdgeScrollManager = require("./managers/EdgeScrollManager/EdgeScrollManager");
var import_FocusManager = require("./managers/FocusManager/FocusManager");
var import_FontManager = require("./managers/FontManager/FontManager");
var import_HistoryManager = require("./managers/HistoryManager/HistoryManager");
var import_ScribbleManager = require("./managers/ScribbleManager/ScribbleManager");
var import_SnapManager = require("./managers/SnapManager/SnapManager");
var import_TextManager = require("./managers/TextManager/TextManager");
var import_TickManager = require("./managers/TickManager/TickManager");
var import_UserPreferencesManager = require("./managers/UserPreferencesManager/UserPreferencesManager");
var import_RootState = require("./tools/RootState");
var __setMetaKeyTimeout_dec, __setCtrlKeyTimeout_dec, __setAltKeyTimeout_dec, __setShiftKeyTimeout_dec, _getIsReadonly_dec, _getIsFocused_dec, _getSharedOpacity_dec, _getSharedStyles_dec, __getSelectionSharedStyles_dec, __getBindingsIndexCache_dec, _getCurrentPageRenderingShapesSorted_dec, _getCurrentPageShapesSorted_dec, _getCurrentPageShapes_dec, _getCurrentPageBounds_dec, _getCulledShapes_dec, _getNotVisibleShapes_dec, __getShapeMaskedPageBoundsCache_dec, __getShapeMaskCache_dec, __getShapeClipPathCache_dec, __getShapePageBoundsCache_dec, __getShapePageTransformCache_dec, __getShapeHandlesCache_dec, __getAllAssetsQuery_dec, _getCurrentPageShapeIdsSorted_dec, _getCurrentPageId_dec, _getPages_dec, __getAllPagesQuery_dec, _getRenderingShapes_dec, _getCollaboratorsOnCurrentPage_dec, _getCollaborators_dec, __getCollaboratorsQuery_dec, _getViewportPageBounds_dec, _getViewportScreenCenter_dec, _getViewportScreenBounds_dec, _getZoomLevel_dec, _getCameraForFollowing_dec, _getViewportPageBoundsForFollowing_dec, _getCamera_dec, __unsafe_getCameraId_dec, _getErasingShapes_dec, _getErasingShapeIds_dec, _getHintingShape_dec, _getHintingShapeIds_dec, _getHoveredShape_dec, _getHoveredShapeId_dec, _getRichTextEditor_dec, _getEditingShape_dec, _getEditingShapeId_dec, _getFocusedGroup_dec, _getFocusedGroupId_dec, _getSelectionRotatedScreenBounds_dec, _getSelectionRotatedPageBounds_dec, _getSelectionRotation_dec, _getSelectionPageBounds_dec, _getOnlySelectedShape_dec, _getOnlySelectedShapeId_dec, _getCurrentPageShapesInReadingOrder_dec, _getSelectedShapes_dec, _getSelectedShapeIds_dec, __getCurrentPageStateId_dec, _getCurrentPageState_dec, __getPageStatesQuery_dec, _getPageStates_dec, _getIsMenuOpen_dec, _getOpenMenus_dec, _getInstanceState_dec, _getDocumentSettings_dec, _getCurrentToolId_dec, _getCurrentTool_dec, _getPath_dec, _getCanRedo_dec, _getCanUndo_dec, _getIsShapeHiddenCache_dec, _a, _init;
class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_dec = [import_state.computed], _getCanUndo_dec = [import_state.computed], _getCanRedo_dec = [import_state.computed], _getPath_dec = [import_state.computed], _getCurrentTool_dec = [import_state.computed], _getCurrentToolId_dec = [import_state.computed], _getDocumentSettings_dec = [import_state.computed], _getInstanceState_dec = [import_state.computed], _getOpenMenus_dec = [import_state.computed], _getIsMenuOpen_dec = [import_state.computed], _getPageStates_dec = [import_state.computed], __getPageStatesQuery_dec = [import_state.computed], _getCurrentPageState_dec = [import_state.computed], __getCurrentPageStateId_dec = [import_state.computed], _getSelectedShapeIds_dec = [import_state.computed], _getSelectedShapes_dec = [import_state.computed], _getCurrentPageShapesInReadingOrder_dec = [import_state.computed], _getOnlySelectedShapeId_dec = [import_state.computed], _getOnlySelectedShape_dec = [import_state.computed], _getSelectionPageBounds_dec = [import_state.computed], _getSelectionRotation_dec = [import_state.computed], _getSelectionRotatedPageBounds_dec = [import_state.computed], _getSelectionRotatedScreenBounds_dec = [import_state.computed], _getFocusedGroupId_dec = [import_state.computed], _getFocusedGroup_dec = [import_state.computed], _getEditingShapeId_dec = [import_state.computed], _getEditingShape_dec = [import_state.computed], _getRichTextEditor_dec = [import_state.computed], _getHoveredShapeId_dec = [import_state.computed], _getHoveredShape_dec = [import_state.computed], _getHintingShapeIds_dec = [import_state.computed], _getHintingShape_dec = [import_state.computed], _getErasingShapeIds_dec = [import_state.computed], _getErasingShapes_dec = [import_state.computed], __unsafe_getCameraId_dec = [import_state.computed], _getCamera_dec = [import_state.computed], _getViewportPageBoundsForFollowing_dec = [import_state.computed], _getCameraForFollowing_dec = [import_state.computed], _getZoomLevel_dec = [import_state.computed], _getViewportScreenBounds_dec = [import_state.computed], _getViewportScreenCenter_dec = [import_state.computed], _getViewportPageBounds_dec = [import_state.computed], __getCollaboratorsQuery_dec = [import_state.computed], _getCollaborators_dec = [import_state.computed], _getCollaboratorsOnCurrentPage_dec = [import_state.computed], _getRenderingShapes_dec = [import_state.computed], __getAllPagesQuery_dec = [import_state.computed], _getPages_dec = [import_state.computed], _getCurrentPageId_dec = [import_state.computed], _getCurrentPageShapeIdsSorted_dec = [import_state.computed], __getAllAssetsQuery_dec = [import_state.computed], __getShapeHandlesCache_dec = [import_state.computed], __getShapePageTransformCache_dec = [import_state.computed], __getShapePageBoundsCache_dec = [import_state.computed], __getShapeClipPathCache_dec = [import_state.computed], __getShapeMaskCache_dec = [import_state.computed], __getShapeMaskedPageBoundsCache_dec = [import_state.computed], _getNotVisibleShapes_dec = [import_state.computed], _getCulledShapes_dec = [import_state.computed], _getCurrentPageBounds_dec = [import_state.computed], _getCurrentPageShapes_dec = [import_state.computed], _getCurrentPageShapesSorted_dec = [import_state.computed], _getCurrentPageRenderingShapesSorted_dec = [import_state.computed], __getBindingsIndexCache_dec = [import_state.computed], __getSelectionSharedStyles_dec = [import_state.computed], _getSharedStyles_dec = [(0, import_state.computed)({ isEqual: (a, b) => a.equals(b) })], _getSharedOpacity_dec = [import_state.computed], _getIsFocused_dec = [import_state.computed], _getIsReadonly_dec = [import_state.computed], __setShiftKeyTimeout_dec = [import_utils.bind], __setAltKeyTimeout_dec = [import_utils.bind], __setCtrlKeyTimeout_dec = [import_utils.bind], __setMetaKeyTimeout_dec = [import_utils.bind], _a) {
constructor({
store,
user,
shapeUtils,
bindingUtils,
tools,
getContainer,
cameraOptions,
textOptions,
initialState,
autoFocus,
inferDarkMode,
options,
// eslint-disable-next-line @typescript-eslint/no-deprecated
isShapeHidden,
getShapeVisibility,
fontAssetUrls
}) {
super();
__runInitializers(_init, 5, this);
__publicField(this, "id", (0, import_utils.uniqueId)());
__publicField(this, "_getShapeVisibility");
__publicField(this, "options");
__publicField(this, "contextId", (0, import_utils.uniqueId)());
/**
* The editor's store
*
* @public
*/
__publicField(this, "store");
/**
* The root state of the statechart.
*
* @public
*/
__publicField(this, "root");
/**
* A set of functions to call when the app is disposed.
*
* @public
*/
__publicField(this, "disposables", /* @__PURE__ */ new Set());
/**
* Whether the editor is disposed.
*
* @public
*/
__publicField(this, "isDisposed", false);
/** @internal */
__publicField(this, "_tickManager");
/**
* A manager for the app's snapping feature.
*
* @public
*/
__publicField(this, "snaps");
/**
* A manager for the any asynchronous events and making sure they're
* cleaned up upon disposal.
*
* @public
*/
__publicField(this, "timers", import_time.tltime.forContext(this.contextId));
/**
* A manager for the user and their preferences.
*
* @public
*/
__publicField(this, "user");
/**
* A helper for measuring text.
*
* @public
*/
__publicField(this, "textMeasure");
/**
* A utility for managing the set of fonts that should be rendered in the document.
*
* @public
*/
__publicField(this, "fonts");
/**
* A manager for the editor's environment.
*
* @deprecated This is deprecated and will be removed in a future version. Use the `tlenv` global export instead.
* @public
*/
__publicField(this, "environment", import_environment.tlenv);
/**
* A manager for the editor's scribbles.
*
* @public
*/
__publicField(this, "scribbles");
/**
* A manager for side effects and correct state enforcement. See {@link @tldraw/store#StoreSideEffects} for details.
*
* @public
*/
__publicField(this, "sideEffects");
/**
* A manager for moving the camera when the mouse is at the edge of the screen.
*
* @public
*/
__publicField(this, "edgeScrollManager");
/**
* A manager for ensuring correct focus. See FocusManager for details.
*
* @internal
*/
__publicField(this, "focusManager");
/**
* The current HTML element containing the editor.
*
* @example
* ```ts
* const container = editor.getContainer()
* ```
*
* @public
*/
__publicField(this, "getContainer");
/* ------------------- Shape Utils ------------------ */
/**
* A map of shape utility classes (TLShapeUtils) by shape type.
*
* @public
*/
__publicField(this, "shapeUtils");
__publicField(this, "styleProps");
/* ------------------- Binding Utils ------------------ */
/**
* A map of shape utility classes (TLShapeUtils) by shape type.
*
* @public
*/
__publicField(this, "bindingUtils");
/* --------------------- History -------------------- */
/**
* A manager for the app's history.
*
* @readonly
*/
__publicField(this, "history");
__publicField(this, "_shouldIgnoreShapeLock", false);
/** @internal */
__publicField(this, "_crashingError", null);
/** @internal */
__publicField(this, "_isChangingStyleTimeout", -1);
// Menus
__publicField(this, "menus", import_menus.tlmenus.forContext(this.contextId));
// Rich text editor
__publicField(this, "_currentRichTextEditor", (0, import_state.atom)("rich text editor", null));
__publicField(this, "_textOptions");
__publicField(this, "_cameraOptions", (0, import_state.atom)("camera options", import_constants.DEFAULT_CAMERA_OPTIONS));
/** @internal */
__publicField(this, "_viewportAnimation", null);
// Viewport
/** @internal */
__publicField(this, "_willSetInitialBounds", true);
// Following
// When we are 'locked on' to a user, our camera is derived from their camera.
__publicField(this, "_isLockedOnFollowingUser", (0, import_state.atom)("isLockedOnFollowingUser", false));
// Camera state
// Camera state does two things: first, it allows us to subscribe to whether
// the camera is moving or not; and second, it allows us to update the rendering
// shapes on the canvas. Changing the rendering shapes may cause shapes to
// unmount / remount in the DOM, which is expensive; and computing visibility is
// also expensive in large projects. For this reason, we use a second bounding
// box just for rendering, and we only update after the camera stops moving.
__publicField(this, "_cameraState", (0, import_state.atom)("camera state", "idle"));
__publicField(this, "_cameraStateTimeoutRemaining", 0);
/* @internal */
__publicField(this, "_currentPageShapeIds");
/* --------------------- Shapes --------------------- */
__publicField(this, "_shapeGeometryCaches", {});
__publicField(this, "_notVisibleShapes", (0, import_notVisibleShapes.notVisibleShapes)(this));
// Parents and children
/**
* A cache of parents to children.
*
* @internal
*/
__publicField(this, "_parentIdsToChildIds");
__publicField(this, "animatingShapes", /* @__PURE__ */ new Map());
/* --------------------- Content -------------------- */
/** @internal */
__publicField(this, "externalAssetContentHandlers", {
file: null,
url: null
});
/** @internal */
__publicField(this, "temporaryAssetPreview", /* @__PURE__ */ new Map());
/** @internal */
__publicField(this, "externalContentHandlers", {
text: null,
files: null,
"file-replace": null,
embed: null,
"svg-text": null,
url: null,
tldraw: null,
excalidraw: null
});
/* --------------------- Events --------------------- */
/**
* The app's current input state.
*
* @public
*/
__publicField(this, "inputs", {
/** The most recent pointer down's position in the current page space. */
originPagePoint: new import_Vec.Vec(),
/** The most recent pointer down's position in screen space. */
originScreenPoint: new import_Vec.Vec(),
/** The previous pointer position in the current page space. */
previousPagePoint: new import_Vec.Vec(),
/** The previous pointer position in screen space. */
previousScreenPoint: new import_Vec.Vec(),
/** The most recent pointer position in the current page space. */
currentPagePoint: new import_Vec.Vec(),
/** The most recent pointer position in screen space. */
currentScreenPoint: new import_Vec.Vec(),
/** A set containing the currently pressed keys. */
keys: /* @__PURE__ */ new Set(),
/** A set containing the currently pressed buttons. */
buttons: /* @__PURE__ */ new Set(),
/** Whether the input is from a pe. */
isPen: false,
/** Whether the shift key is currently pressed. */
shiftKey: false,
/** Whether the meta key is currently pressed. */
metaKey: false,
/** Whether the control or command key is currently pressed. */
ctrlKey: false,
/** Whether the alt or option key is currently pressed. */
altKey: false,
/** Whether the user is dragging. */
isDragging: false,
/** Whether the user is pointing. */
isPointing: false,
/** Whether the user is pinching. */
isPinching: false,
/** Whether the user is editing. */
isEditing: false,
/** Whether the user is panning. */
isPanning: false,
/** Whether the user is spacebar panning. */
isSpacebarPanning: false,
/** Velocity of mouse pointer, in pixels per millisecond */
pointerVelocity: new import_Vec.Vec()
});
/**
* A manager for recording multiple click events.
*
* @internal
*/
__publicField(this, "_clickManager", new import_ClickManager.ClickManager(this));
/**
* The previous cursor. Used for restoring the cursor after pan events.
*
* @internal
*/
__publicField(this, "_prevCursor", "default");
/** @internal */
__publicField(this, "_shiftKeyTimeout", -1);
/** @internal */
__publicField(this, "_altKeyTimeout", -1);
/** @internal */
__publicField(this, "_ctrlKeyTimeout", -1);
/** @internal */
__publicField(this, "_metaKeyTimeout", -1);
/** @internal */
__publicField(this, "_restoreToolId", "select");
/** @internal */
__publicField(this, "_pinchStart", 1);
/** @internal */
__publicField(this, "_didPinch", false);
/** @internal */
__publicField(this, "_selectedShapeIdsAtPointerDown", []);
/** @internal */
__publicField(this, "_longPressTimeout", -1);
/** @internal */
__publicField(this, "capturedPointerId", null);
/** @internal */
__publicField(this, "performanceTracker");
/** @internal */
__publicField(this, "performanceTrackerTimeout", -1);
__publicField(this, "_pendingEventsForNextTick", []);
(0, import_utils.assert)(
!(isShapeHidden && getShapeVisibility),
"Cannot use both isShapeHidden and getShapeVisibility"
);
this._getShapeVisibility = isShapeHidden ? (
// eslint-disable-next-line @typescript-eslint/no-deprecated
(shape, editor) => isShapeHidden(shape, editor) ? "hidden" : "inherit"
) : getShapeVisibility;
this.options = { ...import_options.defaultTldrawOptions, ...options };
this.store = store;
this.history = new import_HistoryManager.HistoryManager({
store,
annotateError: (error) => {
this.annotateError(error, { origin: "history.batch", willCrashApp: true });
this.crash(error);
}
});
this.snaps = new import_SnapManager.SnapManager(this);
this.disposables.add(this.timers.dispose);
this._cameraOptions.set({ ...import_constants.DEFAULT_CAMERA_OPTIONS, ...cameraOptions });
this._textOptions = (0, import_state.atom)("text options", textOptions ?? null);
this.user = new import_UserPreferencesManager.UserPreferencesManager(user ?? (0, import_createTLUser.createTLUser)(), inferDarkMode ?? false);
this.disposables.add(() => this.user.dispose());
this.getContainer = getContainer;
this.textMeasure = new import_TextManager.TextManager(this);
this.disposables.add(() => this.textMeasure.dispose());
this.fonts = new import_FontManager.FontManager(this, fontAssetUrls);
this._tickManager = new import_TickManager.TickManager(this);
class NewRoot extends import_RootState.RootState {
static initial = initialState ?? "";
}
this.root = new NewRoot(this);
this.root.children = {};
const allShapeUtils = (0, import_defaultShapes.checkShapesAndAddCore)(shapeUtils);
const _shapeUtils = {};
const _styleProps = {};
const allStylesById = /* @__PURE__ */ new Map();
for (const Util of allShapeUtils) {
const util = new Util(this);
_shapeUtils[Util.type] = util;
const propKeysByStyle = (0, import_tlschema.getShapePropKeysByStyle)(Util.props ?? {});
_styleProps[Util.type] = propKeysByStyle;
for (const style of propKeysByStyle.keys()) {
if (!allStylesById.has(style.id)) {
allStylesById.set(style.id, style);
} else if (allStylesById.get(style.id) !== style) {
throw Error(
`Multiple style props with id "${style.id}" in use. Style prop IDs must be unique.`
);
}
}
}
this.shapeUtils = _shapeUtils;
this.styleProps = _styleProps;
const allBindingUtils = (0, import_defaultBindings.checkBindings)(bindingUtils);
const _bindingUtils = {};
for (const Util of allBindingUtils) {
const util = new Util(this);
_bindingUtils[Util.type] = util;
}
this.bindingUtils = _bindingUtils;
for (const Tool of [...tools]) {
if ((0, import_utils.hasOwnProperty)(this.root.children, Tool.id)) {
throw Error(`Can't override tool with id "${Tool.id}"`);
}
this.root.children[Tool.id] = new Tool(this, this.root);
}
this.scribbles = new import_ScribbleManager.ScribbleManager(this);
const cleanupInstancePageState = (prevPageState, shapesNoLongerInPage) => {
let nextPageState = null;
const selectedShapeIds = prevPageState.selectedShapeIds.filter(
(id) => !shapesNoLongerInPage.has(id)
);
if (selectedShapeIds.length !== prevPageState.selectedShapeIds.length) {
if (!nextPageState) nextPageState = { ...prevPageState };
nextPageState.selectedShapeIds = selectedShapeIds;
}
const erasingShapeIds = prevPageState.erasingShapeIds.filter(
(id) => !shapesNoLongerInPage.has(id)
);
if (erasingShapeIds.length !== prevPageState.erasingShapeIds.length) {
if (!nextPageState) nextPageState = { ...prevPageState };
nextPageState.erasingShapeIds = erasingShapeIds;
}
if (prevPageState.hoveredShapeId && shapesNoLongerInPage.has(prevPageState.hoveredShapeId)) {
if (!nextPageState) nextPageState = { ...prevPageState };
nextPageState.hoveredShapeId = null;
}
if (prevPageState.editingShapeId && shapesNoLongerInPage.has(prevPageState.editingShapeId)) {
if (!nextPageState) nextPageState = { ...prevPageState };
nextPageState.editingShapeId = null;
}
const hintingShapeIds = prevPageState.hintingShapeIds.filter(
(id) => !shapesNoLongerInPage.has(id)
);
if (hintingShapeIds.length !== prevPageState.hintingShapeIds.length) {
if (!nextPageState) nextPageState = { ...prevPageState };
nextPageState.hintingShapeIds = hintingShapeIds;
}
if (prevPageState.focusedGroupId && shapesNoLongerInPage.has(prevPageState.focusedGroupId)) {
if (!nextPageState) nextPageState = { ...prevPageState };
nextPageState.focusedGroupId = null;
}
return nextPageState;
};
this.sideEffects = this.store.sideEffects;
let deletedBindings = /* @__PURE__ */ new Map();
const deletedShapeIds = /* @__PURE__ */ new Set();
const invalidParents = /* @__PURE__ */ new Set();
let invalidBindingTypes = /* @__PURE__ */ new Set();
this.disposables.add(
this.sideEffects.registerOperationCompleteHandler(() => {
deletedShapeIds.clear();
for (const parentId of invalidParents) {
invalidParents.delete(parentId);
const parent = this.getShape(parentId);
if (!parent) continue;
const util = this.getShapeUtil(parent);
const changes = util.onChildrenChange?.(parent);
if (changes?.length) {
this.updateShapes(changes);
}
}
if (invalidBindingTypes.size) {
const t = invalidBindingTypes;
invalidBindingTypes = /* @__PURE__ */ new Set();
for (const type of t) {
const util = this.getBindingUtil(type);
util.onOperationComplete?.();
}
}
if (deletedBindings.size) {
const t = deletedBindings;
deletedBindings = /* @__PURE__ */ new Map();
for (const opts of t.values()) {
this.getBindingUtil(opts.binding).onAfterDelete?.(opts);
}
}
this.emit("update");
})
);
this.disposables.add(
this.sideEffects.register({
shape: {
afterChange: (shapeBefore, shapeAfter) => {
for (const binding of this.getBindingsInvolvingShape(shapeAfter)) {
invalidBindingTypes.add(binding.type);
if (binding.fromId === shapeAfter.id) {
this.getBindingUtil(binding).onAfterChangeFromShape?.({
binding,
shapeBefore,
shapeAfter,
reason: "self"
});
}
if (binding.toId === shapeAfter.id) {
this.getBindingUtil(binding).onAfterChangeToShape?.({
binding,
shapeBefore,
shapeAfter,
reason: "self"
});
}
}
if (shapeBefore.parentId !== shapeAfter.parentId) {
const notifyBindingAncestryChange = (id) => {
const descendantShape = this.getShape(id);
if (!descendantShape) return;
for (const binding of this.getBindingsInvolvingShape(descendantShape)) {
invalidBindingTypes.add(binding.type);
if (binding.fromId === descendantShape.id) {
this.getBindingUtil(binding).onAfterChangeFromShape?.({
binding,
shapeBefore: descendantShape,
shapeAfter: descendantShape,
reason: "ancestry"
});
}
if (binding.toId === descendantShape.id) {
this.getBindingUtil(binding).onAfterChangeToShape?.({
binding,
shapeBefore: descendantShape,
shapeAfter: descendantShape,
reason: "ancestry"
});
}
}
};
notifyBindingAncestryChange(shapeAfter.id);
this.visitDescendants(shapeAfter.id, notifyBindingAncestryChange);
}
if (shapeBefore.parentId !== shapeAfter.parentId && (0, import_tlschema.isPageId)(shapeAfter.parentId)) {
const allMovingIds = /* @__PURE__ */ new Set([shapeBefore.id]);
this.visitDescendants(shapeBefore.id, (id) => {
allMovingIds.add(id);
});
for (const instancePageState of this.getPageStates()) {
if (instancePageState.pageId === shapeAfter.parentId) continue;
const nextPageState = cleanupInstancePageState(instancePageState, allMovingIds);
if (nextPageState) {
this.store.put([nextPageState]);
}
}
}
if (shapeBefore.parentId && (0, import_tlschema.isShapeId)(shapeBefore.parentId)) {
invalidParents.add(shapeBefore.parentId);
}
if (shapeAfter.parentId !== shapeBefore.parentId && (0, import_tlschema.isShapeId)(shapeAfter.parentId)) {
invalidParents.add(shapeAfter.parentId);
}
},
beforeDelete: (shape) => {
if (deletedShapeIds.has(shape.id)) return;
if (shape.parentId && (0, import_tlschema.isShapeId)(shape.parentId)) {
invalidParents.add(shape.parentId);
}
deletedShapeIds.add(shape.id);
const deleteBindingIds = [];
for (const binding of this.getBindingsInvolvingShape(shape)) {
invalidBindingTypes.add(binding.type);
deleteBindingIds.push(binding.id);
const util = this.getBindingUtil(binding);
if (binding.fromId === shape.id) {
util.onBeforeIsolateToShape?.({ binding, removedShape: shape });
util.onBeforeDeleteFromShape?.({ binding, shape });
} else {
util.onBeforeIsolateFromShape?.({ binding, removedShape: shape });
util.onBeforeDeleteToShape?.({ binding, shape });
}
}
if (deleteBindingIds.length) {
this.deleteBindings(deleteBindingIds);
}
const deletedIds = /* @__PURE__ */ new Set([shape.id]);
const updates = (0, import_utils.compact)(
this.getPageStates().map((pageState) => {
return cleanupInstancePageState(pageState, deletedIds);
})
);
if (updates.length) {
this.store.put(updates);
}
}
},
binding: {
beforeCreate: (binding) => {
const next = this.getBindingUtil(binding).onBeforeCreate?.({ binding });
if (next) return next;
return binding;
},
afterCreate: (binding) => {
invalidBindingTypes.add(binding.type);
this.getBindingUtil(binding).onAfterCreate?.({ binding });
},
beforeChange: (bindingBefore, bindingAfter) => {
const updated = this.getBindingUtil(bindingAfter).onBeforeChange?.({
bindingBefore,
bindingAfter
});
if (updated) return updated;
return bindingAfter;
},
afterChange: (bindingBefore, bindingAfter) => {
invalidBindingTypes.add(bindingAfter.type);
this.getBindingUtil(bindingAfter).onAfterChange?.({ bindingBefore, bindingAfter });
},
beforeDelete: (binding) => {
this.getBindingUtil(binding).onBeforeDelete?.({ binding });
},
afterDelete: (binding) => {
this.getBindingUtil(binding).onAfterDelete?.({ binding });
invalidBindingTypes.add(binding.type);
}
},
page: {
afterCreate: (record) => {
const cameraId = import_tlschema.CameraRecordType.createId(record.id);
const _pageStateId = import_tlschema.InstancePageStateRecordType.createId(record.id);
if (!this.store.has(cameraId)) {
this.store.put([import_tlschema.CameraRecordType.create({ id: cameraId })]);
}
if (!this.store.has(_pageStateId)) {
this.store.put([
import_tlschema.InstancePageStateRecordType.create({ id: _pageStateId, pageId: record.id })
]);
}
},
afterDelete: (record, source) => {
if (this.getInstanceState()?.currentPageId === record.id) {
const backupPageId = this.getPages().find((p) => p.id !== record.id)?.id;
if (backupPageId) {
this.store.put([{ ...this.getInstanceState(), currentPageId: backupPageId }]);
} else if (source === "user") {
this.store.ensureStoreIsUsable();
}
}
const cameraId = import_tlschema.CameraRecordType.createId(record.id);
const instance_PageStateId = import_tlschema.InstancePageStateRecordType.createId(record.id);
this.store.remove([cameraId, instance_PageStateId]);
}
},
instance: {
afterChange: (prev, next, source) => {
if (!this.store.has(next.currentPageId)) {
const backupPageId = this.store.has(prev.currentPageId) ? prev.currentPageId : this.getPages()[0]?.id;
if (backupPageId) {
this.store.update(next.id, (instance) => ({
...instance,
currentPageId: backupPageId
}));
} else if (source === "user") {
this.store.ensureStoreIsUsable();
}
}
}
},
instance_page_state: {
afterChange: (prev, next) => {
if (prev?.selectedShapeIds !== next?.selectedShapeIds) {
const filtered = next.selectedShapeIds.filter((id) => {
let parentId = this.getShape(id)?.parentId;
while ((0, import_tlschema.isShapeId)(parentId)) {
if (next.selectedShapeIds.includes(parentId)) {
return false;
}
parentId = this.getShape(parentId)?.parentId;
}
return true;
});
let nextFocusedGroupId = null;
if (filtered.length > 0) {
const commonGroupAncestor = this.findCommonAncestor(
(0, import_utils.compact)(filtered.map((id) => this.getShape(id))),
(shape) => this.isShapeOfType(shape, "group")
);
if (commonGroupAncestor) {
nextFocusedGroupId = commonGroupAncestor;
}
} else {
if (next?.focusedGroupId) {
nextFocusedGroupId = next.focusedGroupId;
}
}
if (filtered.length !== next.selectedShapeIds.length || nextFocusedGroupId !== next.focusedGroupId) {
this.store.put([
{
...next,
selectedShapeIds: filtered,
focusedGroupId: nextFocusedGroupId ?? null
}
]);
}
}
}
}
})
);
this._currentPageShapeIds = (0, import_shapeIdsInCurrentPage.deriveShapeIdsInCurrentPage)(
this.store,
() => this.getCurrentPageId()
);
this._parentIdsToChildIds = (0, import_parentsToChildren.parentsToChildren)(this.store);
this.disposables.add(
this.store.listen((changes) => {
this.emit("change", changes);
})
);
this.disposables.add(this.history.dispose);
this.run(
() => {
this.store.ensureStoreIsUsable();
this._updateCurrentPageState({
editingShapeId: null,
hoveredShapeId: null,
erasingShapeIds: []
});
},
{ history: "ignore" }
);
if (initialState && this.root.children[initialState] === void 0) {
throw Error(`No state found for initialState "${initialState}".`);
}
this.root.enter(void 0, "initial");
this.edgeScrollManager = new import_EdgeScrollManager.EdgeScrollManager(this);
this.focusManager = new import_FocusManager.FocusManager(this, autoFocus);
this.disposables.add(this.focusManager.dispose.bind(this.focusManager));
if (this.getInstanceState().followingUserId) {
this.stopFollowingUser();
}
this.on("tick", this._flushEventsForTick);
this.timers.requestAnimationFrame(() => {
this._tickManager.start();
});
this.performanceTracker = new import_utils.PerformanceTracker();
if (this.store.props.collaboration?.mode) {
const mode = this.store.props.collaboration.mode;
this.disposables.add(
(0, import_state.react)("update collaboration mode", () => {
this.store.put([{ ...this.getInstanceState(), isReadonly: mode.get() === "readonly" }]);
})
);
}
}
getIsShapeHiddenCache() {
if (!this._getShapeVisibility) return null;
return this.store.createComputedCache("isShapeHidden", (shape) => {
const visibility = this._getShapeVisibility(shape, this);
const isParentHidden = import_tlschema.PageRecordType.isId(shape.parentId) ? false : this.isShapeHidden(shape.parentId);
if (isParentHidden) return visibility !== "visible";
return visibility === "hidden";
});
}
isShapeHidden(shapeOrId) {
if (!this._getShapeVisibility) return false;
return !!this.getIsShapeHiddenCache().get(
typeof shapeOrId === "string" ? shapeOrId : shapeOrId.id
);
}
/**
* Dispose the editor.
*
* @public
*/
dispose() {
this.disposables.forEach((dispose) => dispose());
this.disposables.clear();
this.store.dispose();
this.isDisposed = true;
}
getShapeUtil(arg) {
const type = typeof arg === "string" ? arg : arg.type;
const shapeUtil = (0, import_utils.getOwnProperty)(this.shapeUtils, type);
(0, import_utils.assert)(shapeUtil, `No shape util found for type "${type}"`);
return shapeUtil;
}
hasShapeUtil(arg) {
const type = typeof arg === "string" ? arg : arg.type;
return (0, import_utils.hasOwnProperty)(this.shapeUtils, type);
}
getBindingUtil(arg) {
const type = typeof arg === "string" ? arg : arg.type;
const bindingUtil = (0, import_utils.getOwnProperty)(this.bindingUtils, type);
(0, import_utils.assert)(bindingUtil, `No binding util found for type "${type}"`);
return bindingUtil;
}
/**
* Undo to the last mark.
*
* @example
* ```ts
* editor.undo()
* ```
*
* @public
*/
undo() {
this._flushEventsForTick(0);
this.complete();
this.history.undo();
return this;
}
getCanUndo() {
return this.history.getNumUndos() > 0;
}
/**
* Redo to the next mark.
*
* @example
* ```ts
* editor.redo()
* ```
*
* @public
*/
redo() {
this._flushEventsForTick(0);
this.complete();
this.history.redo();
return this;
}
clearHistory() {
this.history.clear();
return this;
}
getCanRedo() {
return this.history.getNumRedos() > 0;
}
/**
* Create a new "mark", or stopping point, in the undo redo history. Creating a mark will clear
* any redos.
*
* @example
* ```ts
* editor.mark()
* editor.mark('flip shapes')
* ```
*
* @param markId - The mark's id, usually the reason for adding the mark.
*
* @public
* @deprecated use {@link Editor.markHistoryStoppingPoint} instead
*/
mark(markId) {
if (typeof markId === "string") {
console.warn(
`[tldraw] \`editor.history.mark("${markId}")\` is deprecated. Please use \`const myMarkId = editor.markHistoryStoppingPoint()\` instead.`
);
} else {
console.warn(
"[tldraw] `editor.mark()` is deprecated. Use `editor.markHistoryStoppingPoint()` instead."
);
}
this.history._mark(markId ?? (0, import_utils.uniqueId)());
return this;
}
/**
* Create a new "mark", or stopping point, in the undo redo history. Creating a mark will clear
* any redos. You typically want to do this just before a user interaction begins or is handled.
*
* @example
* ```ts
* editor.markHistoryStoppingPoint()
* editor.flipShapes(editor.getSelectedShapes())
* ```
* @example
* ```ts
* const beginRotateMark = editor.markHistoryStoppingPoint()
* // if the use cancels the rotation, you can bail back to this mark
* editor.bailToMark(beginRotateMark)
* ```
*
* @public
* @param name - The name of the mark, useful for debugging the undo/redo stacks
* @returns a unique id for the mark that can be used with `squashToMark` or `bailToMark`.
*/
markHistoryStoppingPoint(name) {
const id = `[${name ?? "stop"}]_${(0, import_utils.uniqueId)()}`;
this.history._mark(id);
return id;
}
/**
* @internal this is only used to implement some backwards-compatibility logic. Should be fine to delete after 6 months or whatever.
*/
getMarkIdMatching(idSubstring) {
return this.history.getMarkIdMatching(idSubstring);
}
/**
* Coalesces all changes since the given mark into a single change, removing any intermediate marks.
*
* This is useful if you need to 'compress' the recent history to simplify the undo/redo experience of a complex interaction.
*
* @example
* ```ts
* const bumpShapesMark = editor.markHistoryStoppingPoint()
* // ... some changes
* editor.squashToMark(bumpShapesMark)
* ```
*
* @param markId - The mark id to squash to.
*/
squashToMark(markId) {
this.history.squashToMark(markId);
return this;
}
/**
* Undo to the closest mark, discarding the changes so they cannot be redone.
*
* @example
* ```ts
* editor.bail()
* ```
*
* @public
*/
bail() {
this.history.bail();
return this;
}
/**
* Undo to the given mark, discarding the changes so they cannot be redone.
*
* @example
* ```ts
* const beginDrag = editor.markHistoryStoppingPoint()
* // ... some changes
* editor.bailToMark(beginDrag)
* ```
*
* @public
*/
bailToMark(id) {
this.history.bailToMark(id);
return this;
}
/**
* Run a function in a transaction with optional options for context.
* You can use the options to change the way that history is treated
* or allow changes to locked shapes.
*
* @example
* ```ts
* // updating with
* editor.run(() => {
* editor.updateShape({ ...myShape, x: 100 })
* }, { history: "ignore" })
*
* // forcing changes / deletions for locked shapes
* editor.toggleLock([myShape])
* editor.run(() => {
* editor.updateShape({ ...myShape, x: 100 })
* editor.deleteShape(myShape)
* }, { ignoreShapeLock: true }, )
* ```
*
* @param fn - The callback function to run.
* @param opts - The options for the batch.
*
*
* @public
*/
run(fn, opts) {
const previousIgnoreShapeLock = this._shouldIgnoreShapeLock;
this._shouldIgnoreShapeLock = opts?.ignoreShapeLock ?? previousIgnoreShapeLock;
try {
this.history.batch(fn, opts);
} finally {
this._shouldIgnoreShapeLock = previousIgnoreShapeLock;
}
return this;
}
/**
* @deprecated Use `Editor.run` instead.
*/
batch(fn, opts) {
return this.run(fn, opts);
}
/* --------------------- Errors --------------------- */
/** @internal */
annotateError(error, {
origin,
willCrashApp,
tags,
extras
}) {
const defaultAnnotations = this.createErrorAnnotations(origin, willCrashApp);
(0, import_utils.annotateError)(error, {
tags: { ...defaultAnnotations.tags, ...tags },
extras: { ...defaultAnnotations.extras, ...extras }
});
if (willCrashApp) {
this.store.markAsPossiblyCorrupted();
}
return this;
}
/** @internal */
createErrorAnnotations(origin, willCrashApp) {
try {
const editingShapeId = this.getEditingShapeId();
return {
tags: {
origin,
willCrashApp
},
extras: {
activeStateNode: this.root.getPath(),
selectedShapes: this.getSelectedShapes().map((s) => {
const { props, ...rest } = s;
const { text: _text, richText: _richText, ...restProps } = props;
return {
...rest,
props: restProps
};
}),
selectionCount: this.getSelectedShapes().length,
editingShape: editingShapeId ? this.getShape(editingShapeId) : void 0,
inputs: this.inputs,
pageState: this.getCurrentPageState(),
instanceState: this.getInstanceState(),
collaboratorCount: this.getCollaboratorsOnCurrentPage().length
}
};
} catch {
return {
tags: {
origin,
willCrashApp
},
extras: {}
};
}
}
/**
* We can't use an `atom` here because there's a chance that when `crashAndReportError` is called,
* we're in a transaction that's about to be rolled back due to the same error we're currently
* reporting.
*
* Instead, to listen to changes to this value, you need to listen to app's `crash` event.
*
* @internal
*/
getCrashingError() {
return this._crashingError;
}
/** @internal */
crash(error) {
this._crashingError = error;
this.store.markAsPossiblyCorrupted();
this.emit("crash", { error });
return this;
}
getPath() {
return this.root.getPath().split("root.")[1];
}
/**
* Get whether a certain tool (or other state node) is currently active.
*
* @example