UNPKG

@tldraw/editor

Version:

tldraw infinite canvas SDK (editor).

4 lines • 532 kB
{ "version": 3, "sources": ["../../../src/lib/editor/Editor.ts"], "sourcesContent": ["import {\n\tAtom,\n\tEMPTY_ARRAY,\n\tatom,\n\tcomputed,\n\treact,\n\ttransact,\n\tunsafe__withoutCapture,\n} from '@tldraw/state'\nimport {\n\tComputedCache,\n\tRecordType,\n\tStoreSideEffects,\n\tStoreSnapshot,\n\tUnknownRecord,\n\treverseRecordsDiff,\n} from '@tldraw/store'\nimport {\n\tCameraRecordType,\n\tInstancePageStateRecordType,\n\tPageRecordType,\n\tStyleProp,\n\tStylePropValue,\n\tTLAsset,\n\tTLAssetId,\n\tTLAssetPartial,\n\tTLBinding,\n\tTLBindingCreate,\n\tTLBindingId,\n\tTLBindingUpdate,\n\tTLCamera,\n\tTLCreateShapePartial,\n\tTLCursor,\n\tTLCursorType,\n\tTLDOCUMENT_ID,\n\tTLDocument,\n\tTLGroupShape,\n\tTLHandle,\n\tTLINSTANCE_ID,\n\tTLImageAsset,\n\tTLInstance,\n\tTLInstancePageState,\n\tTLInstancePresence,\n\tTLPage,\n\tTLPageId,\n\tTLParentId,\n\tTLRecord,\n\tTLShape,\n\tTLShapeId,\n\tTLShapePartial,\n\tTLStore,\n\tTLStoreSnapshot,\n\tTLTheme,\n\tTLThemeId,\n\tTLThemes,\n\tTLUser,\n\tTLUserId,\n\tTLVideoAsset,\n\tUserRecordType,\n\tcreateBindingId,\n\tcreateShapeId,\n\tcreateUserId,\n\tgetShapePropKeysByStyle,\n\tisPageId,\n\tisShapeId,\n} from '@tldraw/tlschema'\nimport {\n\tFileHelpers,\n\tIndexKey,\n\tJsonObject,\n\tPerformanceTracker,\n\tResult,\n\tZERO_INDEX_KEY,\n\tannotateError,\n\tassert,\n\tassertExists,\n\tbind,\n\tcompact,\n\tdebounce,\n\tdedupe,\n\texhaustiveSwitchError,\n\tfetch,\n\tgetIndexAbove,\n\tgetIndexBetween,\n\tgetIndices,\n\tgetIndicesAbove,\n\tgetIndicesBetween,\n\tgetOwnProperty,\n\thasOwnProperty,\n\tlast,\n\tlerp,\n\tminBy,\n\tsortById,\n\tsortByIndex,\n\tstructuredClone,\n\tuniqueId,\n} from '@tldraw/utils'\nimport EventEmitter from 'eventemitter3'\nimport { TLCurrentUser, createTLCurrentUser } from '../config/createTLCurrentUser'\nimport { TLAnyAssetUtilConstructor, checkAssets } from '../config/defaultAssets'\nimport { TLAnyBindingUtilConstructor, checkBindings } from '../config/defaultBindings'\nimport { TLAnyShapeUtilConstructor, checkShapesAndAddCore } from '../config/defaultShapes'\nimport {\n\tTLEditorSnapshot,\n\tTLLoadSnapshotOptions,\n\tgetSnapshot,\n\tloadSnapshot,\n} from '../config/TLEditorSnapshot'\nimport {\n\tDEFAULT_ANIMATION_OPTIONS,\n\tDEFAULT_CAMERA_OPTIONS,\n\tINTERNAL_POINTER_IDS,\n\tLEFT_MOUSE_BUTTON,\n\tMIDDLE_MOUSE_BUTTON,\n\tRIGHT_MOUSE_BUTTON,\n\tSTYLUS_ERASER_BUTTON,\n} from '../constants'\nimport { getOwnerWindow } from '../exports/domUtils'\nimport { exportToSvg } from '../exports/exportToSvg'\nimport { getSvgAsImageWithOptions, trimSvgToContent } from '../exports/getSvgAsImage'\nimport { tlmenus } from '../globals/menus'\nimport { tltime } from '../globals/time'\nimport { TldrawOptions, defaultTldrawOptions } from '../options'\nimport { Box, BoxLike } from '../primitives/Box'\nimport { EASINGS } from '../primitives/easings'\nimport { Geometry2d } from '../primitives/geometry/Geometry2d'\nimport { Group2d } from '../primitives/geometry/Group2d'\nimport { intersectPolygonPolygon } from '../primitives/intersect'\nimport { Mat, MatLike } from '../primitives/Mat'\nimport { PI, approximately, areAnglesCompatible, clamp, pointInPolygon } from '../primitives/utils'\nimport { Vec, VecLike } from '../primitives/Vec'\nimport { areShapesContentEqual } from '../utils/areShapesContentEqual'\nimport { dataUrlToFile } from '../utils/assets'\nimport { debugFlags } from '../utils/debug-flags'\nimport {\n\tTLDeepLink,\n\tTLDeepLinkOptions,\n\tcreateDeepLinkString,\n\tparseDeepLinkString,\n} from '../utils/deepLinks'\nimport { getIncrementedName } from '../utils/getIncrementedName'\nimport { getReorderingShapesChanges } from '../utils/reorderShapes'\nimport { getDroppedShapesToNewParents, kickoutOccludedShapes } from '../utils/reparenting'\nimport { TLTextOptions, TiptapEditor } from '../utils/richText'\nimport { applyRotationToSnapshotShapes, getRotationSnapshot } from '../utils/rotation'\nimport { ReadonlySharedStyleMap, SharedStyle, SharedStyleMap } from '../utils/SharedStylesMap'\nimport { AssetUtil } from './assets/AssetUtil'\nimport { BindingOnDeleteOptions, BindingUtil } from './bindings/BindingUtil'\nimport { bindingsIndex } from './derivations/bindingsIndex'\nimport { notVisibleShapes } from './derivations/notVisibleShapes'\nimport { parentsToChildren } from './derivations/parentsToChildren'\nimport { deriveShapeIdsInCurrentPage } from './derivations/shapeIdsInCurrentPage'\nimport { ClickManager } from './managers/ClickManager/ClickManager'\nimport { CollaboratorsManager } from './managers/CollaboratorsManager/CollaboratorsManager'\nimport { EdgeScrollManager } from './managers/EdgeScrollManager/EdgeScrollManager'\nimport { FocusManager } from './managers/FocusManager/FocusManager'\nimport { FontManager } from './managers/FontManager/FontManager'\nimport { HistoryManager } from './managers/HistoryManager/HistoryManager'\nimport { InputsManager } from './managers/InputsManager/InputsManager'\nimport { PerformanceManager } from './managers/PerformanceManager/PerformanceManager'\nimport { ScribbleManager } from './managers/ScribbleManager/ScribbleManager'\nimport { SnapManager } from './managers/SnapManager/SnapManager'\nimport { SpatialIndexManager } from './managers/SpatialIndexManager/SpatialIndexManager'\nimport { TextManager } from './managers/TextManager/TextManager'\nimport { ThemeManager, resolveThemes } from './managers/ThemeManager/ThemeManager'\nimport { TickManager } from './managers/TickManager/TickManager'\nimport { UserPreferencesManager } from './managers/UserPreferencesManager/UserPreferencesManager'\nimport { OverlayManager } from './overlays/OverlayManager'\nimport { TLAnyOverlayUtilConstructor } from './overlays/OverlayUtil'\nimport {\n\tShapeUtil,\n\tTLEditStartInfo,\n\tTLGeometryOpts,\n\tTLResizeMode,\n\tTLShapeUtilCanBeLaidOutOpts,\n\tTLShapeUtilCanBindOpts,\n} from './shapes/ShapeUtil'\nimport { RootState } from './tools/RootState'\nimport { StateNode, TLStateNodeConstructor } from './tools/StateNode'\nimport { TLContent } from './types/clipboard-types'\nimport { TLEventMap } from './types/emit-types'\nimport { TLEventInfo, TLPointerEventInfo } from './types/event-types'\nimport { TLExternalAsset, TLExternalContent } from './types/external-content'\nimport { TLHistoryBatchOptions } from './types/history-types'\nimport {\n\tOptionalKeys,\n\tRequiredKeys,\n\tTLCameraMoveOptions,\n\tTLCameraOptions,\n\tTLGetShapeAtPointOptions,\n\tTLImageExportOptions,\n\tTLSvgExportOptions,\n\tTLUpdatePointerOptions,\n} from './types/misc-types'\nimport { TLAdjacentDirection, TLResizeHandle } from './types/selection-types'\n\n/** @public */\nexport type TLResizeShapeOptions = Partial<{\n\tinitialBounds: Box\n\tscaleOrigin: VecLike\n\tscaleAxisRotation: number\n\tinitialShape: TLShape\n\tinitialPageTransform: MatLike\n\tdragHandle: TLResizeHandle\n\tisAspectRatioLocked: boolean\n\tmode: TLResizeMode\n\tskipStartAndEndCallbacks: boolean\n}>\n\n/** @public */\nexport interface TLEditorOptions {\n\t/**\n\t * The Store instance to use for keeping the editor's data. This may be prepopulated, e.g. by loading\n\t * from a server or database.\n\t */\n\tstore: TLStore\n\t/**\n\t * An array of shapes to use in the editor. These will be used to create and manage shapes in the editor.\n\t */\n\tshapeUtils: readonly TLAnyShapeUtilConstructor[]\n\t/**\n\t * An array of bindings to use in the editor. These will be used to create and manage bindings in the editor.\n\t */\n\tbindingUtils: readonly TLAnyBindingUtilConstructor[]\n\t/**\n\t * An array of asset utils to use in the editor. These will be used to handle asset-type-specific behavior.\n\t */\n\tassetUtils?: readonly TLAnyAssetUtilConstructor[]\n\t/**\n\t * An array of overlay utils to use in the editor. These define canvas overlay UI elements\n\t * like selection handles, rotation corners, shape handles, etc.\n\t */\n\toverlayUtils?: readonly TLAnyOverlayUtilConstructor[]\n\t/**\n\t * An array of tools to use in the editor. These will be used to handle events and manage user interactions in the editor.\n\t */\n\ttools: readonly TLStateNodeConstructor[]\n\t/**\n\t * A user defined externally to replace the default user.\n\t */\n\tuser?: TLCurrentUser\n\t/**\n\t * The editor's initial active tool (or other state node id).\n\t */\n\tinitialState?: string\n\t/**\n\t * Whether to automatically focus the editor when it mounts.\n\t */\n\tautoFocus?: boolean\n\tlicenseKey?: string\n\tfontAssetUrls?: { [key: string]: string | undefined }\n\t/**\n\t * Should return a containing html element which has all the styles applied to the editor. If not\n\t * given, the body element will be used.\n\t */\n\tgetContainer(): HTMLElement\n\t/**\n\t * Provides a way to hide shapes.\n\t *\n\t * @example\n\t * ```ts\n\t * getShapeVisibility={(shape, editor) => shape.meta.hidden ? 'hidden' : 'inherit'}\n\t * ```\n\t *\n\t * - `'inherit' | undefined` - (default) The shape will be visible unless its parent is hidden.\n\t * - `'hidden'` - The shape will be hidden.\n\t * - `'visible'` - The shape will be visible.\n\t *\n\t * @param shape - The shape to check.\n\t * @param editor - The editor instance.\n\t */\n\tgetShapeVisibility?(\n\t\tshape: TLShape,\n\t\teditor: Editor\n\t): 'visible' | 'hidden' | 'inherit' | null | undefined\n\t/**\n\t * Named theme definitions for the editor. Each theme contains shared\n\t * properties (font size, line height, stroke width) and color palettes\n\t * for both light and dark modes.\n\t */\n\tthemes?: Partial<TLThemes>\n\t/**\n\t * The id of the initially active theme. Defaults to `'default'`.\n\t */\n\tinitialTheme?: TLThemeId\n\t/**\n\t * The editor's color scheme preference, controls the default color mode. Defaults to `'light'`.\n\t *\n\t * - `'light'` - Always use light mode.\n\t * - `'dark'` - Always use dark mode.\n\t * - `'system'` - Follow the OS color scheme preference.\n\t */\n\tcolorScheme?: 'light' | 'dark' | 'system'\n\t/**\n\t * Additional configuration options for the tldraw editor.\n\t */\n\toptions?: Partial<TldrawOptions>\n\t// --- Deprecated ----\n\t/**\n\t * Options for the editor's camera.\n\t *\n\t * @deprecated Use `options.cameraOptions` instead. This will be removed in a future release.\n\t */\n\tcameraOptions?: Partial<TLCameraOptions>\n\t/**\n\t * Text options for the editor.\n\t *\n\t * @deprecated Use `options.text` instead. This prop will be removed in a future release.\n\t */\n\ttextOptions?: TLTextOptions\n}\n\n/**\n * Options for {@link Editor.(run:1)}.\n * @public\n */\nexport interface TLEditorRunOptions extends TLHistoryBatchOptions {\n\tignoreShapeLock?: boolean\n}\n\n/** @public */\nexport interface TLRenderingShape {\n\tid: TLShapeId\n\tshape: TLShape\n\tutil: ShapeUtil\n\tindex: number\n\tbackgroundIndex: number\n\topacity: number\n}\n\n/** @public */\nexport class Editor extends EventEmitter<TLEventMap> {\n\treadonly id = uniqueId()\n\tconstructor({\n\t\tstore,\n\t\tuser,\n\t\tshapeUtils,\n\t\tbindingUtils,\n\t\tassetUtils: assetUtilConstructors,\n\t\toverlayUtils: overlayUtilConstructors,\n\t\ttools,\n\t\tgetContainer,\n\t\t// needs to be here for backwards compatibility with TldrawEditor\n\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\tcameraOptions,\n\t\tinitialState,\n\t\tautoFocus,\n\t\toptions: _options,\n\t\t// needs to be here for backwards compatibility with TldrawEditor\n\t\t// eslint-disable-next-line @typescript-eslint/no-deprecated\n\t\ttextOptions: _textOptions,\n\t\tgetShapeVisibility,\n\t\tcolorScheme,\n\t\tfontAssetUrls,\n\t\tthemes,\n\t\tinitialTheme,\n\t}: TLEditorOptions) {\n\t\tsuper()\n\n\t\tthis._getShapeVisibility = getShapeVisibility\n\n\t\t// Merge deprecated textOptions prop with options.text\n\t\t// options.text takes precedence over the deprecated textOptions prop\n\t\tconst options = _textOptions ? { ..._options, text: _options?.text ?? _textOptions } : _options\n\n\t\tthis.options = { ...defaultTldrawOptions, ...options }\n\n\t\tthis.store = store\n\t\tthis.history = new HistoryManager<TLRecord>({\n\t\t\tstore,\n\t\t\tannotateError: (error: any) => {\n\t\t\t\tthis.annotateError(error, { origin: 'history.batch', willCrashApp: true })\n\t\t\t\tthis.crash(error)\n\t\t\t},\n\t\t})\n\n\t\tthis.snaps = new SnapManager(this)\n\n\t\tthis._spatialIndex = new SpatialIndexManager(this)\n\t\tthis.disposables.add(() => this._spatialIndex.dispose())\n\n\t\tthis.disposables.add(this.timers.dispose)\n\n\t\t// Merge camera options: options.cameraOptions takes precedence over deprecated cameraOptions prop\n\t\tthis._cameraOptions.set({\n\t\t\t...DEFAULT_CAMERA_OPTIONS,\n\t\t\t...cameraOptions,\n\t\t\t...options?.camera,\n\t\t})\n\n\t\tthis.getContainer = getContainer\n\n\t\tthis._textOptions = atom('text options', options?.text ?? null)\n\n\t\tthis.user = new UserPreferencesManager(user ?? createTLCurrentUser(), colorScheme ?? 'light')\n\t\tthis.disposables.add(() => this.user.dispose())\n\n\t\tthis.textMeasure = new TextManager(this)\n\t\tthis.disposables.add(() => this.textMeasure.dispose())\n\n\t\tthis._themeManager = new ThemeManager(this, {\n\t\t\tthemes: resolveThemes(themes),\n\t\t\tinitial: initialTheme ?? 'default',\n\t\t})\n\t\tthis.disposables.add(() => this._themeManager.dispose())\n\n\t\tthis._tickManager = new TickManager(this)\n\t\tthis.disposables.add(() => this._tickManager.dispose())\n\t\tthis.disposables.add(() => {\n\t\t\t// Reset camera state to 'idle' so the store isn't left stuck at 'moving'\n\t\t\t// when tick events stop (e.g. React strict mode disposes while camera is moving)\n\t\t\tthis.off('tick', this._decayCameraStateTimeout)\n\t\t\tthis._setCameraState('idle')\n\t\t})\n\n\t\tthis.fonts = new FontManager(this, fontAssetUrls)\n\n\t\tthis.inputs = new InputsManager(this)\n\t\tthis.performance = new PerformanceManager(this)\n\t\tthis.disposables.add(() => this.performance.dispose())\n\t\tthis.collaborators = new CollaboratorsManager(this)\n\n\t\tclass NewRoot extends RootState {\n\t\t\tstatic override initial = initialState ?? ''\n\t\t}\n\n\t\tthis.root = new NewRoot(this)\n\t\tthis.root.children = {}\n\n\t\tthis.markEventAsHandled = this.markEventAsHandled.bind(this)\n\n\t\tconst allShapeUtils = checkShapesAndAddCore(shapeUtils)\n\n\t\tconst _shapeUtils = {} as Record<string, ShapeUtil<any>>\n\t\tconst _styleProps = {} as Record<string, Map<StyleProp<unknown>, string>>\n\t\tconst allStylesById = new Map<string, StyleProp<unknown>>()\n\n\t\tfor (const Util of allShapeUtils) {\n\t\t\tconst util = new Util(this)\n\t\t\t_shapeUtils[Util.type] = util\n\n\t\t\tconst propKeysByStyle = getShapePropKeysByStyle(Util.props ?? {})\n\t\t\t_styleProps[Util.type] = propKeysByStyle\n\n\t\t\tfor (const style of propKeysByStyle.keys()) {\n\t\t\t\tif (!allStylesById.has(style.id)) {\n\t\t\t\t\tallStylesById.set(style.id, style)\n\t\t\t\t} else if (allStylesById.get(style.id) !== style) {\n\t\t\t\t\tthrow Error(\n\t\t\t\t\t\t`Multiple style props with id \"${style.id}\" in use. Style prop IDs must be unique.`\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.shapeUtils = _shapeUtils\n\t\tthis.styleProps = _styleProps\n\n\t\tconst _shapeUtilsByAssetType = {} as Record<string, ShapeUtil<any>>\n\t\tfor (const Util of allShapeUtils) {\n\t\t\tconst assetTypes = Util.handledAssetTypes\n\t\t\tif (assetTypes) {\n\t\t\t\tfor (const assetType of assetTypes) {\n\t\t\t\t\t_shapeUtilsByAssetType[assetType] = _shapeUtils[Util.type]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tthis._shapeUtilsByAssetType = _shapeUtilsByAssetType\n\n\t\tconst allBindingUtils = checkBindings(bindingUtils)\n\t\tconst _bindingUtils = {} as Record<string, BindingUtil<any>>\n\t\tfor (const Util of allBindingUtils) {\n\t\t\tconst util = new Util(this)\n\t\t\t_bindingUtils[Util.type] = util\n\t\t}\n\t\tthis.bindingUtils = _bindingUtils\n\n\t\t// Asset utils\n\t\tif (assetUtilConstructors) {\n\t\t\tconst allAssetUtils = checkAssets(assetUtilConstructors)\n\t\t\tconst _assetUtils = {} as Record<string, AssetUtil<any>>\n\t\t\tfor (const Util of allAssetUtils) {\n\t\t\t\tconst util = new Util(this)\n\t\t\t\t_assetUtils[Util.type] = util\n\t\t\t}\n\t\t\tthis.assetUtils = _assetUtils\n\t\t}\n\n\t\t// Tools.\n\t\t// Accept tools from constructor parameters which may not conflict with the root note's default or\n\t\t// \"baked in\" tools, select and zoom.\n\t\tfor (const Tool of [...tools]) {\n\t\t\tif (hasOwnProperty(this.root.children!, Tool.id)) {\n\t\t\t\tthrow Error(`Can't override tool with id \"${Tool.id}\"`)\n\t\t\t}\n\t\t\tthis.root.children![Tool.id] = new Tool(this, this.root)\n\t\t}\n\n\t\tthis.scribbles = new ScribbleManager(this)\n\n\t\t// Overlay utils\n\t\tthis.overlays = new OverlayManager(this)\n\t\tif (overlayUtilConstructors) {\n\t\t\tfor (const Util of overlayUtilConstructors) {\n\t\t\t\tconst util = new Util(this)\n\t\t\t\tthis.overlays.registerUtil(util)\n\t\t\t}\n\t\t}\n\n\t\t// Cleanup\n\n\t\tconst cleanupInstancePageState = (\n\t\t\tprevPageState: TLInstancePageState,\n\t\t\tshapesNoLongerInPage: Set<TLShapeId>\n\t\t) => {\n\t\t\tlet nextPageState = null as null | TLInstancePageState\n\n\t\t\tconst selectedShapeIds = prevPageState.selectedShapeIds.filter(\n\t\t\t\t(id) => !shapesNoLongerInPage.has(id)\n\t\t\t)\n\t\t\tif (selectedShapeIds.length !== prevPageState.selectedShapeIds.length) {\n\t\t\t\tif (!nextPageState) nextPageState = { ...prevPageState }\n\t\t\t\tnextPageState.selectedShapeIds = selectedShapeIds\n\t\t\t}\n\n\t\t\tconst erasingShapeIds = prevPageState.erasingShapeIds.filter(\n\t\t\t\t(id) => !shapesNoLongerInPage.has(id)\n\t\t\t)\n\t\t\tif (erasingShapeIds.length !== prevPageState.erasingShapeIds.length) {\n\t\t\t\tif (!nextPageState) nextPageState = { ...prevPageState }\n\t\t\t\tnextPageState.erasingShapeIds = erasingShapeIds\n\t\t\t}\n\n\t\t\tif (prevPageState.hoveredShapeId && shapesNoLongerInPage.has(prevPageState.hoveredShapeId)) {\n\t\t\t\tif (!nextPageState) nextPageState = { ...prevPageState }\n\t\t\t\tnextPageState.hoveredShapeId = null\n\t\t\t}\n\n\t\t\tif (prevPageState.editingShapeId && shapesNoLongerInPage.has(prevPageState.editingShapeId)) {\n\t\t\t\tif (!nextPageState) nextPageState = { ...prevPageState }\n\t\t\t\tnextPageState.editingShapeId = null\n\t\t\t}\n\n\t\t\tconst hintingShapeIds = prevPageState.hintingShapeIds.filter(\n\t\t\t\t(id) => !shapesNoLongerInPage.has(id)\n\t\t\t)\n\t\t\tif (hintingShapeIds.length !== prevPageState.hintingShapeIds.length) {\n\t\t\t\tif (!nextPageState) nextPageState = { ...prevPageState }\n\t\t\t\tnextPageState.hintingShapeIds = hintingShapeIds\n\t\t\t}\n\n\t\t\tif (prevPageState.focusedGroupId && shapesNoLongerInPage.has(prevPageState.focusedGroupId)) {\n\t\t\t\tif (!nextPageState) nextPageState = { ...prevPageState }\n\t\t\t\tnextPageState.focusedGroupId = null\n\t\t\t}\n\t\t\treturn nextPageState\n\t\t}\n\n\t\tthis.sideEffects = this.store.sideEffects\n\n\t\tlet deletedBindings = new Map<TLBindingId, BindingOnDeleteOptions<any>>()\n\t\tconst deletedShapeIds = new Set<TLShapeId>()\n\t\tconst invalidParents = new Set<TLShapeId>()\n\t\tlet invalidBindingTypes = new Set<TLBinding['type']>()\n\n\t\tthis.disposables.add(\n\t\t\tthis.sideEffects.registerOperationCompleteHandler(() => {\n\t\t\t\t// this needs to be cleared here because further effects may delete more shapes\n\t\t\t\t// and we want the next invocation of this handler to handle those separately\n\t\t\t\tdeletedShapeIds.clear()\n\n\t\t\t\tfor (const parentId of invalidParents) {\n\t\t\t\t\tinvalidParents.delete(parentId)\n\t\t\t\t\tconst parent = this.getShape(parentId)\n\t\t\t\t\tif (!parent) continue\n\n\t\t\t\t\tconst util = this.getShapeUtil(parent)\n\t\t\t\t\tconst changes = util.onChildrenChange?.(parent)\n\n\t\t\t\t\tif (changes?.length) {\n\t\t\t\t\t\tthis.updateShapes(changes)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (invalidBindingTypes.size) {\n\t\t\t\t\tconst t = invalidBindingTypes\n\t\t\t\t\tinvalidBindingTypes = new Set()\n\t\t\t\t\tfor (const type of t) {\n\t\t\t\t\t\tconst util = this.getBindingUtil(type)\n\t\t\t\t\t\tutil.onOperationComplete?.()\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (deletedBindings.size) {\n\t\t\t\t\tconst t = deletedBindings\n\t\t\t\t\tdeletedBindings = new Map()\n\t\t\t\t\tfor (const opts of t.values()) {\n\t\t\t\t\t\tthis.getBindingUtil(opts.binding).onAfterDelete?.(opts)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tthis.emit('update')\n\t\t\t})\n\t\t)\n\n\t\tthis.disposables.add(\n\t\t\tthis.sideEffects.register({\n\t\t\t\tshape: {\n\t\t\t\t\tafterChange: (shapeBefore, shapeAfter) => {\n\t\t\t\t\t\tfor (const binding of this.getBindingsInvolvingShape(shapeAfter)) {\n\t\t\t\t\t\t\tinvalidBindingTypes.add(binding.type)\n\t\t\t\t\t\t\tif (binding.fromId === shapeAfter.id) {\n\t\t\t\t\t\t\t\tthis.getBindingUtil(binding).onAfterChangeFromShape?.({\n\t\t\t\t\t\t\t\t\tbinding,\n\t\t\t\t\t\t\t\t\tshapeBefore,\n\t\t\t\t\t\t\t\t\tshapeAfter,\n\t\t\t\t\t\t\t\t\treason: 'self',\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (binding.toId === shapeAfter.id) {\n\t\t\t\t\t\t\t\tthis.getBindingUtil(binding).onAfterChangeToShape?.({\n\t\t\t\t\t\t\t\t\tbinding,\n\t\t\t\t\t\t\t\t\tshapeBefore,\n\t\t\t\t\t\t\t\t\tshapeAfter,\n\t\t\t\t\t\t\t\t\treason: 'self',\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// if the shape's parent changed and it has a binding, update the binding\n\t\t\t\t\t\tif (shapeBefore.parentId !== shapeAfter.parentId) {\n\t\t\t\t\t\t\tconst notifyBindingAncestryChange = (id: TLShapeId) => {\n\t\t\t\t\t\t\t\tconst descendantShape = this.getShape(id)\n\t\t\t\t\t\t\t\tif (!descendantShape) return\n\n\t\t\t\t\t\t\t\tfor (const binding of this.getBindingsInvolvingShape(descendantShape)) {\n\t\t\t\t\t\t\t\t\tinvalidBindingTypes.add(binding.type)\n\n\t\t\t\t\t\t\t\t\tif (binding.fromId === descendantShape.id) {\n\t\t\t\t\t\t\t\t\t\tthis.getBindingUtil(binding).onAfterChangeFromShape?.({\n\t\t\t\t\t\t\t\t\t\t\tbinding,\n\t\t\t\t\t\t\t\t\t\t\tshapeBefore: descendantShape,\n\t\t\t\t\t\t\t\t\t\t\tshapeAfter: descendantShape,\n\t\t\t\t\t\t\t\t\t\t\treason: 'ancestry',\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif (binding.toId === descendantShape.id) {\n\t\t\t\t\t\t\t\t\t\tthis.getBindingUtil(binding).onAfterChangeToShape?.({\n\t\t\t\t\t\t\t\t\t\t\tbinding,\n\t\t\t\t\t\t\t\t\t\t\tshapeBefore: descendantShape,\n\t\t\t\t\t\t\t\t\t\t\tshapeAfter: descendantShape,\n\t\t\t\t\t\t\t\t\t\t\treason: 'ancestry',\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnotifyBindingAncestryChange(shapeAfter.id)\n\t\t\t\t\t\t\tthis.visitDescendants(shapeAfter.id, notifyBindingAncestryChange)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// if this shape moved to a new page, clean up any previous page's instance state\n\t\t\t\t\t\tif (shapeBefore.parentId !== shapeAfter.parentId && isPageId(shapeAfter.parentId)) {\n\t\t\t\t\t\t\tconst allMovingIds = new Set([shapeBefore.id])\n\t\t\t\t\t\t\tthis.visitDescendants(shapeBefore.id, (id) => {\n\t\t\t\t\t\t\t\tallMovingIds.add(id)\n\t\t\t\t\t\t\t})\n\n\t\t\t\t\t\t\tfor (const instancePageState of this.getPageStates()) {\n\t\t\t\t\t\t\t\tif (instancePageState.pageId === shapeAfter.parentId) continue\n\t\t\t\t\t\t\t\tconst nextPageState = cleanupInstancePageState(instancePageState, allMovingIds)\n\n\t\t\t\t\t\t\t\tif (nextPageState) {\n\t\t\t\t\t\t\t\t\tthis.store.put([nextPageState])\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (shapeBefore.parentId && isShapeId(shapeBefore.parentId)) {\n\t\t\t\t\t\t\tinvalidParents.add(shapeBefore.parentId)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (shapeAfter.parentId !== shapeBefore.parentId && isShapeId(shapeAfter.parentId)) {\n\t\t\t\t\t\t\tinvalidParents.add(shapeAfter.parentId)\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tbeforeDelete: (shape) => {\n\t\t\t\t\t\t// if we triggered this delete with a recursive call, don't do anything\n\t\t\t\t\t\tif (deletedShapeIds.has(shape.id)) return\n\t\t\t\t\t\t// if the deleted shape has a parent shape make sure we call it's onChildrenChange callback\n\t\t\t\t\t\tif (shape.parentId && isShapeId(shape.parentId)) {\n\t\t\t\t\t\t\tinvalidParents.add(shape.parentId)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tdeletedShapeIds.add(shape.id)\n\n\t\t\t\t\t\tconst deleteBindingIds: TLBindingId[] = []\n\t\t\t\t\t\tfor (const binding of this.getBindingsInvolvingShape(shape)) {\n\t\t\t\t\t\t\tinvalidBindingTypes.add(binding.type)\n\t\t\t\t\t\t\tdeleteBindingIds.push(binding.id)\n\t\t\t\t\t\t\tconst util = this.getBindingUtil(binding)\n\t\t\t\t\t\t\tif (binding.fromId === shape.id) {\n\t\t\t\t\t\t\t\tutil.onBeforeIsolateToShape?.({ binding, removedShape: shape })\n\t\t\t\t\t\t\t\tutil.onBeforeDeleteFromShape?.({ binding, shape })\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tutil.onBeforeIsolateFromShape?.({ binding, removedShape: shape })\n\t\t\t\t\t\t\t\tutil.onBeforeDeleteToShape?.({ binding, shape })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (deleteBindingIds.length) {\n\t\t\t\t\t\t\tthis.deleteBindings(deleteBindingIds)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst deletedIds = new Set([shape.id])\n\t\t\t\t\t\tconst updates = compact(\n\t\t\t\t\t\t\tthis.getPageStates().map((pageState) => {\n\t\t\t\t\t\t\t\treturn cleanupInstancePageState(pageState, deletedIds)\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t)\n\n\t\t\t\t\t\tif (updates.length) {\n\t\t\t\t\t\t\tthis.store.put(updates)\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tbinding: {\n\t\t\t\t\tbeforeCreate: (binding) => {\n\t\t\t\t\t\tconst next = this.getBindingUtil(binding).onBeforeCreate?.({ binding })\n\t\t\t\t\t\tif (next) return next\n\t\t\t\t\t\treturn binding\n\t\t\t\t\t},\n\t\t\t\t\tafterCreate: (binding) => {\n\t\t\t\t\t\tinvalidBindingTypes.add(binding.type)\n\t\t\t\t\t\tthis.getBindingUtil(binding).onAfterCreate?.({ binding })\n\t\t\t\t\t},\n\t\t\t\t\tbeforeChange: (bindingBefore, bindingAfter) => {\n\t\t\t\t\t\tconst updated = this.getBindingUtil(bindingAfter).onBeforeChange?.({\n\t\t\t\t\t\t\tbindingBefore,\n\t\t\t\t\t\t\tbindingAfter,\n\t\t\t\t\t\t})\n\t\t\t\t\t\tif (updated) return updated\n\t\t\t\t\t\treturn bindingAfter\n\t\t\t\t\t},\n\t\t\t\t\tafterChange: (bindingBefore, bindingAfter) => {\n\t\t\t\t\t\tinvalidBindingTypes.add(bindingAfter.type)\n\t\t\t\t\t\tthis.getBindingUtil(bindingAfter).onAfterChange?.({ bindingBefore, bindingAfter })\n\t\t\t\t\t},\n\t\t\t\t\tbeforeDelete: (binding) => {\n\t\t\t\t\t\tthis.getBindingUtil(binding).onBeforeDelete?.({ binding })\n\t\t\t\t\t},\n\t\t\t\t\tafterDelete: (binding) => {\n\t\t\t\t\t\tthis.getBindingUtil(binding).onAfterDelete?.({ binding })\n\t\t\t\t\t\tinvalidBindingTypes.add(binding.type)\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tpage: {\n\t\t\t\t\tafterCreate: (record) => {\n\t\t\t\t\t\tconst cameraId = CameraRecordType.createId(record.id)\n\t\t\t\t\t\tconst _pageStateId = InstancePageStateRecordType.createId(record.id)\n\t\t\t\t\t\tif (!this.store.has(cameraId)) {\n\t\t\t\t\t\t\tthis.store.put([CameraRecordType.create({ id: cameraId })])\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!this.store.has(_pageStateId)) {\n\t\t\t\t\t\t\tthis.store.put([\n\t\t\t\t\t\t\t\tInstancePageStateRecordType.create({ id: _pageStateId, pageId: record.id }),\n\t\t\t\t\t\t\t])\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tafterDelete: (record, source) => {\n\t\t\t\t\t\t// page was deleted, need to check whether it's the current page and select another one if so\n\t\t\t\t\t\tif (this.getInstanceState()?.currentPageId === record.id) {\n\t\t\t\t\t\t\tconst backupPageId = this.getPages().find((p) => p.id !== record.id)?.id\n\t\t\t\t\t\t\tif (backupPageId) {\n\t\t\t\t\t\t\t\tthis.store.put([{ ...this.getInstanceState(), currentPageId: backupPageId }])\n\t\t\t\t\t\t\t} else if (source === 'user') {\n\t\t\t\t\t\t\t\t// fall back to ensureStoreIsUsable:\n\t\t\t\t\t\t\t\tthis.store.ensureStoreIsUsable()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// delete the camera and state for the page if necessary\n\t\t\t\t\t\tconst cameraId = CameraRecordType.createId(record.id)\n\t\t\t\t\t\tconst instance_PageStateId = InstancePageStateRecordType.createId(record.id)\n\t\t\t\t\t\tthis.store.remove([cameraId, instance_PageStateId])\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tinstance: {\n\t\t\t\t\tafterChange: (prev, next, source) => {\n\t\t\t\t\t\t// instance should never be updated to a page that no longer exists (this can\n\t\t\t\t\t\t// happen when undoing a change that involves switching to a page that has since\n\t\t\t\t\t\t// been deleted by another user)\n\t\t\t\t\t\tif (!this.store.has(next.currentPageId)) {\n\t\t\t\t\t\t\tconst backupPageId = this.store.has(prev.currentPageId)\n\t\t\t\t\t\t\t\t? prev.currentPageId\n\t\t\t\t\t\t\t\t: this.getPages()[0]?.id\n\t\t\t\t\t\t\tif (backupPageId) {\n\t\t\t\t\t\t\t\tthis.store.update(next.id, (instance) => ({\n\t\t\t\t\t\t\t\t\t...instance,\n\t\t\t\t\t\t\t\t\tcurrentPageId: backupPageId,\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t} else if (source === 'user') {\n\t\t\t\t\t\t\t\t// fall back to ensureStoreIsUsable:\n\t\t\t\t\t\t\t\tthis.store.ensureStoreIsUsable()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tinstance_page_state: {\n\t\t\t\t\tafterChange: (prev, next) => {\n\t\t\t\t\t\tif (prev?.selectedShapeIds !== next?.selectedShapeIds) {\n\t\t\t\t\t\t\t// ensure that descendants and ancestors are not selected at the same time\n\t\t\t\t\t\t\tconst filtered = next.selectedShapeIds.filter((id) => {\n\t\t\t\t\t\t\t\tlet parentId = this.getShape(id)?.parentId\n\t\t\t\t\t\t\t\twhile (isShapeId(parentId)) {\n\t\t\t\t\t\t\t\t\tif (next.selectedShapeIds.includes(parentId)) {\n\t\t\t\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tparentId = this.getShape(parentId)?.parentId\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn true\n\t\t\t\t\t\t\t})\n\n\t\t\t\t\t\t\tlet nextFocusedGroupId: null | TLShapeId = null\n\n\t\t\t\t\t\t\tif (filtered.length > 0) {\n\t\t\t\t\t\t\t\tconst commonGroupAncestor = this.findCommonAncestor(\n\t\t\t\t\t\t\t\t\tcompact(filtered.map((id) => this.getShape(id))),\n\t\t\t\t\t\t\t\t\t(shape) => this.isShapeOfType(shape, 'group')\n\t\t\t\t\t\t\t\t)\n\n\t\t\t\t\t\t\t\tif (commonGroupAncestor) {\n\t\t\t\t\t\t\t\t\tnextFocusedGroupId = commonGroupAncestor\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (next?.focusedGroupId) {\n\t\t\t\t\t\t\t\t\tnextFocusedGroupId = next.focusedGroupId\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tfiltered.length !== next.selectedShapeIds.length ||\n\t\t\t\t\t\t\t\tnextFocusedGroupId !== next.focusedGroupId\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\tthis.store.put([\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t...next,\n\t\t\t\t\t\t\t\t\t\tselectedShapeIds: filtered,\n\t\t\t\t\t\t\t\t\t\tfocusedGroupId: nextFocusedGroupId ?? null,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t])\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t)\n\n\t\tthis._currentPageShapeIds = deriveShapeIdsInCurrentPage(this.store, () =>\n\t\t\tthis.getCurrentPageId()\n\t\t)\n\t\tthis._parentIdsToChildIds = parentsToChildren(this.store)\n\n\t\tthis.disposables.add(\n\t\t\tthis.store.listen((changes) => {\n\t\t\t\tthis.emit('change', changes)\n\t\t\t})\n\t\t)\n\t\tthis.disposables.add(this.history.dispose)\n\n\t\tthis.run(\n\t\t\t() => {\n\t\t\t\tthis.store.ensureStoreIsUsable()\n\n\t\t\t\t// clear ephemeral state\n\t\t\t\tthis._updateCurrentPageState({\n\t\t\t\t\teditingShapeId: null,\n\t\t\t\t\thoveredShapeId: null,\n\t\t\t\t\terasingShapeIds: [],\n\t\t\t\t})\n\t\t\t},\n\t\t\t{ history: 'ignore' }\n\t\t)\n\n\t\tif (initialState && this.root.children[initialState] === undefined) {\n\t\t\tthrow Error(`No state found for initialState \"${initialState}\".`)\n\t\t}\n\n\t\tthis.root.enter(undefined, 'initial')\n\n\t\tthis.edgeScrollManager = new EdgeScrollManager(this)\n\t\tthis.focusManager = new FocusManager(this, autoFocus)\n\t\tthis.disposables.add(this.focusManager.dispose.bind(this.focusManager))\n\n\t\tif (this.getInstanceState().followingUserId) {\n\t\t\tthis.stopFollowingUser()\n\t\t}\n\n\t\tthis.on('tick', this._flushEventsForTick)\n\n\t\tthis.timers.requestAnimationFrame(() => {\n\t\t\tthis._tickManager.start()\n\t\t})\n\n\t\tthis.performanceTracker = new PerformanceTracker()\n\n\t\tif (this.store.props.collaboration?.mode) {\n\t\t\tconst mode = this.store.props.collaboration.mode\n\t\t\tthis.disposables.add(\n\t\t\t\treact('update collaboration mode', () => {\n\t\t\t\t\tthis.store.put([{ ...this.getInstanceState(), isReadonly: mode.get() === 'readonly' }])\n\t\t\t\t})\n\t\t\t)\n\t\t}\n\n\t\tthis.disposables.add(\n\t\t\treact('sync current user record', () => {\n\t\t\t\tconst user = this.store.props.users.currentUser.get()\n\t\t\t\tif (user) {\n\t\t\t\t\tthis._ensureUserRecord(user)\n\t\t\t\t}\n\t\t\t})\n\t\t)\n\t}\n\n\tprivate readonly _getShapeVisibility?: TLEditorOptions['getShapeVisibility']\n\n\t@computed\n\tprivate getIsShapeHiddenCache() {\n\t\tif (!this._getShapeVisibility) return null\n\t\treturn this.store.createComputedCache<boolean, TLShape>('isShapeHidden', (shape: TLShape) => {\n\t\t\tconst visibility = this._getShapeVisibility!(shape, this)\n\t\t\tconst isParentHidden = PageRecordType.isId(shape.parentId)\n\t\t\t\t? false\n\t\t\t\t: this.isShapeHidden(shape.parentId)\n\n\t\t\tif (isParentHidden) return visibility !== 'visible'\n\t\t\treturn visibility === 'hidden'\n\t\t})\n\t}\n\tisShapeHidden(shapeOrId: TLShape | TLShapeId): boolean {\n\t\tif (!this._getShapeVisibility) return false\n\t\treturn !!this.getIsShapeHiddenCache!()!.get(\n\t\t\ttypeof shapeOrId === 'string' ? shapeOrId : shapeOrId.id\n\t\t)\n\t}\n\n\treadonly options: TldrawOptions\n\n\treadonly contextId = uniqueId()\n\n\t/**\n\t * The editor's store\n\t *\n\t * @public\n\t */\n\treadonly store: TLStore\n\n\t/**\n\t * The root state of the statechart.\n\t *\n\t * @public\n\t */\n\treadonly root: StateNode\n\n\t/**\n\t * Set a tool. Useful if you need to add a tool to the state chart on demand,\n\t * after the editor has already been initialized.\n\t *\n\t * @param Tool - The tool to set.\n\t * @param parent - The parent state node to set the tool on.\n\t *\n\t * @public\n\t */\n\tsetTool(Tool: TLStateNodeConstructor, parent?: StateNode) {\n\t\tparent ??= this.root\n\t\tif (hasOwnProperty(parent.children!, Tool.id)) {\n\t\t\tthrow Error(`Can't override tool with id \"${Tool.id}\"`)\n\t\t}\n\t\tparent.children![Tool.id] = new Tool(this, parent)\n\t}\n\n\t/**\n\t * Remove a tool. Useful if you need to remove a tool from the state chart on demand,\n\t * after the editor has already been initialized.\n\t *\n\t * @param Tool - The tool to delete.\n\t * @param parent - The parent state node to remove the tool from.\n\t *\n\t * @public\n\t */\n\tremoveTool(Tool: TLStateNodeConstructor, parent?: StateNode) {\n\t\tparent ??= this.root\n\t\tif (hasOwnProperty(parent.children!, Tool.id)) {\n\t\t\tdelete parent.children![Tool.id]\n\t\t}\n\t}\n\n\t/**\n\t * A set of functions to call when the editor is disposed.\n\t *\n\t * @public\n\t */\n\treadonly disposables = new Set<() => void>()\n\n\t/**\n\t * Whether the editor is disposed.\n\t *\n\t * @public\n\t */\n\tisDisposed = false\n\n\t/**\n\t * A manager for the editor's tick events.\n\t *\n\t * @internal */\n\tprivate readonly _tickManager: TickManager\n\n\t/**\n\t * A manager for the editor's input state.\n\t *\n\t * @public\n\t */\n\treadonly inputs: InputsManager\n\n\t/**\n\t * A manager for the editor's snapping feature.\n\t *\n\t * @public\n\t */\n\treadonly snaps: SnapManager\n\n\t/**\n\t * A manager for performance measurement hooks.\n\t *\n\t * @public\n\t */\n\treadonly performance: PerformanceManager\n\n\t/**\n\t * A manager for the spatial index, tracking where shapes exist on the canvas.\n\t *\n\t * @internal\n\t */\n\tprivate readonly _spatialIndex: SpatialIndexManager\n\n\t/**\n\t * A manager for the any asynchronous events and making sure they're\n\t * cleaned up upon disposal.\n\t *\n\t * @public\n\t */\n\treadonly timers = tltime.forContext(this.contextId)\n\n\t/**\n\t * A manager for remote peer collaborators connected to this editor.\n\t *\n\t * @public\n\t */\n\treadonly collaborators: CollaboratorsManager\n\n\t/**\n\t * A manager for the user and their preferences.\n\t *\n\t * @public\n\t */\n\treadonly user: UserPreferencesManager\n\n\t/**\n\t * A manager for the editor's themes.\n\t *\n\t * @internal\n\t */\n\tprivate readonly _themeManager: ThemeManager\n\n\t/**\n\t * A helper for measuring text.\n\t *\n\t * @public\n\t */\n\treadonly textMeasure: TextManager\n\n\t/**\n\t * A utility for managing the set of fonts that should be rendered in the document.\n\t *\n\t * @public\n\t */\n\treadonly fonts: FontManager\n\n\t/**\n\t * A manager for the editor's scribbles.\n\t *\n\t * @public\n\t */\n\treadonly scribbles: ScribbleManager\n\n\t/**\n\t * A manager for canvas overlay UI elements (selection handles, shape handles, etc.).\n\t *\n\t * @public\n\t */\n\treadonly overlays: OverlayManager\n\n\t/**\n\t * A manager for side effects and correct state enforcement. See {@link @tldraw/store#StoreSideEffects} for details.\n\t *\n\t * @public\n\t */\n\treadonly sideEffects: StoreSideEffects<TLRecord>\n\n\t/**\n\t * A manager for moving the camera when the mouse is at the edge of the screen.\n\t *\n\t * @public\n\t */\n\tedgeScrollManager: EdgeScrollManager\n\n\t/**\n\t * A manager for ensuring correct focus. See FocusManager for details.\n\t *\n\t * @internal\n\t */\n\tprivate focusManager: FocusManager\n\n\t/**\n\t * The current HTML element containing the editor.\n\t *\n\t * @example\n\t * ```ts\n\t * const container = editor.getContainer()\n\t * ```\n\t *\n\t * @public\n\t */\n\tgetContainer: () => HTMLElement\n\n\t/**\n\t * The document that the editor's container element belongs to.\n\t * Use this instead of the global `document` to support cross-window embedding.\n\t *\n\t * @internal\n\t */\n\tgetContainerDocument(): Document {\n\t\treturn this.getContainer().ownerDocument\n\t}\n\n\t/**\n\t * The window that the editor's container element belongs to.\n\t * Use this instead of the global `window` to support cross-window embedding.\n\t *\n\t * @internal\n\t */\n\tgetContainerWindow(): Window & typeof globalThis {\n\t\treturn getOwnerWindow(this.getContainer())\n\t}\n\n\t/**\n\t * Dispose the editor.\n\t *\n\t * @public\n\t */\n\tdispose() {\n\t\t// Stop any in-progress camera animations and following before\n\t\t// running disposables, so their cleanup listeners fire first\n\t\tthis.stopCameraAnimation()\n\t\tif (this.getInstanceState().followingUserId) {\n\t\t\tthis.stopFollowingUser()\n\t\t}\n\n\t\tthis.disposables.forEach((dispose) => dispose())\n\t\tthis.disposables.clear()\n\n\t\t// Clear any open menus for this editor's context\n\t\tthis.menus.clearOpenMenus()\n\n\t\tthis.store.dispose()\n\t\tthis.isDisposed = true\n\t\tthis.emit('dispose')\n\t}\n\n\t/* ------------------ Themes (shadowing the theme manager) ------------------ */\n\n\t/**\n\t * Get the current color mode (`'light'` or `'dark'`), based on the user's dark mode preference.\n\t *\n\t * @public\n\t */\n\tgetColorMode(): 'light' | 'dark' {\n\t\treturn this._themeManager.getColorMode()\n\t}\n\n\t/**\n\t * Set the color mode. Note that this is a convenience method that passes the mode to\n\t * `user.updateUserPreferences`, which is the source of truth for the user's color mode preference.\n\t *\n\t * @public\n\t */\n\tsetColorMode(mode: 'light' | 'dark') {\n\t\tthis.user.updateUserPreferences({ colorScheme: mode })\n\t\treturn this\n\t}\n\n\t/**\n\t * Get the id of the current theme.\n\t *\n\t * @public\n\t */\n\tgetCurrentThemeId(): TLThemeId {\n\t\treturn this._themeManager.getCurrentThemeId()\n\t}\n\n\t/**\n\t * Get the current theme definition.\n\t *\n\t * @public\n\t */\n\tgetCurrentTheme(): TLTheme {\n\t\treturn this._themeManager.getCurrentTheme()\n\t}\n\n\t/**\n\t * Set the current theme by id.\n\t *\n\t * @public\n\t */\n\tsetCurrentTheme(id: TLThemeId) {\n\t\tthis._themeManager.setCurrentTheme(id)\n\t\treturn this\n\t}\n\n\t/**\n\t * Get all registered theme definitions.\n\t *\n\t * @public\n\t */\n\tgetThemes(): TLThemes {\n\t\treturn this._themeManager.getThemes()\n\t}\n\n\t/**\n\t * Get a single theme definition by id.\n\t *\n\t * @public\n\t */\n\tgetTheme(id: TLThemeId): TLTheme | undefined {\n\t\treturn this._themeManager.getTheme(id)\n\t}\n\n\t/**\n\t * Replace all theme definitions, or update them via a callback that receives a deep copy.\n\t * The `'default'` theme must always be present in the result.\n\t *\n\t * @example\n\t * ```ts\n\t * // Replace all themes\n\t * editor.updateThemes({ default: myDefaultTheme, ocean: myOceanTheme })\n\t *\n\t * // Update via callback\n\t * editor.updateThemes((themes) => {\n\t * delete themes.ocean\n\t * return themes\n\t * })\n\t * ```\n\t *\n\t * @public\n\t */\n\tupdateThemes(themes: TLThemes | ((themes: TLThemes) => TLThemes)) {\n\t\tthis._themeManager.updateThemes(themes)\n\t\treturn this\n\t}\n\n\t/**\n\t * Register or update a single theme definition. The theme is keyed by its `id` property.\n\t *\n\t * @example\n\t * ```ts\n\t * // Override a property on the default theme\n\t * editor.updateTheme({ ...editor.getTheme('default')!, fontSize: 24 })\n\t *\n\t * // Register a new theme\n\t * editor.updateTheme({ id: 'ocean', ...myOceanTheme })\n\t * ```\n\t *\n\t * @public\n\t */\n\tupdateTheme(theme: TLTheme) {\n\t\tthis._themeManager.updateTheme(theme)\n\t\treturn this\n\t}\n\n\t/* ------------------- Shape Utils ------------------ */\n\n\t/**\n\t * A map of shape utility classes (TLShapeUtils) by shape type.\n\t *\n\t * @public\n\t */\n\tshapeUtils: { readonly [K in string]?: ShapeUtil<TLShape> }\n\n\t/** @internal */\n\tprivate _shapeUtilsByAssetType: { readonly [K in string]?: ShapeUtil<TLShape> } = {}\n\n\tstyleProps: { [key: string]: Map<StyleProp<any>, string> }\n\n\t/**\n\t * Get a shape util from a shape itself.\n\t *\n\t * @example\n\t * ```ts\n\t * const util = editor.getShapeUtil(myArrowShape)\n\t * const util = editor.getShapeUtil('arrow')\n\t * const util = editor.getShapeUtil<TLArrowShape>(myArrowShape)\n\t * const util = editor.getShapeUtil(TLArrowShape)('arrow')\n\t * ```\n\t *\n\t * @param shape - A shape, shape partial, or shape type.\n\t *\n\t * @public\n\t */\n\tgetShapeUtil<K extends TLShape['type']>(type: K): ShapeUtil<Extract<TLShape, { type: K }>>\n\tgetShapeUtil<S extends TLShape>(shape: S | TLShapePartial<S> | S['type']): ShapeUtil<S>\n\tgetShapeUtil<T extends ShapeUtil>(type: T extends ShapeUtil<infer R> ? R['type'] : string): T\n\tgetShapeUtil(arg: string | { type: string }) {\n\t\tconst type = typeof arg === 'string' ? arg : arg.type\n\t\tconst shapeUtil = getOwnProperty(this.shapeUtils, type)\n\t\tassert(shapeUtil, `No shape util found for type \"${type}\"`)\n\t\treturn shapeUtil\n\t}\n\n\t/**\n\t * Returns true if the editor has a shape util for the given shape / shape type.\n\t *\n\t * @param shape - A shape, shape partial, or shape type.\n\t */\n\thasShapeUtil(shape: TLShape | TLShapePartial<TLShape>): boolean\n\thasShapeUtil(type: TLShape['type']): boolean\n\thasShapeUtil<T extends ShapeUtil>(\n\t\ttype: T extends ShapeUtil<infer R> ? R['type'] : string\n\t): boolean\n\thasShapeUtil(arg: string | { type: string }): boolean {\n\t\tconst type = typeof arg === 'string' ? arg : arg.type\n\t\treturn hasOwnProperty(this.shapeUtils, type)\n\t}\n\n\t/**\n\t * Get the shape util that handles the given asset type.\n\t * Returns the shape util whose {@link ShapeUtil.handledAssetTypes} includes\n\t * the given asset type, or undefined if none matches.\n\t *\n\t * @param assetType - The asset type string.\n\t * @public\n\t */\n\tgetShapeUtilForAssetType(assetType: string): ShapeUtil | undefined {\n\t\treturn getOwnProperty(this._shapeUtilsByAssetType, assetType)\n\t}\n\n\t/* ------------------- Binding Utils ------------------ */\n\t/**\n\t * A map of shape utility classes (TLShapeUtils) by shape type.\n\t *\n\t * @public\n\t */\n\tbindingUtils: { readonly [K in string]?: BindingUtil<TLBinding> }\n\n\t/**\n\t * Get a binding util from a binding itself.\n\t *\n\t * @example\n\t * ```ts\n\t * const util = editor.getBindingUtil(myArrowBinding)\n\t * const util = editor.getBindingUtil('arrow')\n\t * const util = editor.getBindingUtil<TLArrowBinding>(myArrowBinding)\n\t * const util = editor.getBindingUtil(TLArrowBinding)('arrow')\n\t * ```\n\t *\n\t * @param binding - A binding, binding partial, or binding type.\n\t *\n\t * @public\n\t */\n\tgetBindingUtil<K extends TLBinding['type']>(type: K): BindingUtil<Extract<TLBinding, { type: K }>>\n\tgetBindingUtil<S extends TLBinding>(binding: S | { type: S['type'] }): BindingUtil<S>\n\tgetBindingUtil<T extends BindingUtil>(\n\t\ttype: T extends BindingUtil<infer R> ? R['type'] : string\n\t): T\n\tgetBindingUtil(arg: string | { type: string }) {\n\t\tconst type = typeof arg === 'string' ? arg : arg.type\n\t\tconst bindingUtil = getOwnProperty(this.bindingUtils, type)\n\t\tassert(bindingUtil, `No binding util found for type \"${type}\"`)\n\t\treturn bindingUtil\n\t}\n\n\t/* ------------------- Asset Utils ------------------ */\n\n\t/**\n\t * A map of asset utility classes by asset type.\n\t *\n\t * @public\n\t */\n\tassetUtils: { readonly [K in string]?: AssetUtil<TLAsset> } = {}\n\n\t/**\n\t * Get an asset util from an asset or asset type.\n\t *\n\t * @param arg - An asset, asset type string, or object with type.\n\t *\n\t * @public\n\t */\n\tgetAssetUtil<S extends TLAsset>(asset: S | { type: S['type'] }): AssetUtil<S>\n\tgetAssetUtil(type: string): AssetUtil\n\tgetAssetUtil(arg: string | { type: string }) {\n\t\tconst type = typeof arg === 'string' ? arg : arg.type\n\t\tconst assetUtil = getOwnProperty(this.assetUtils, type)\n\t\tassert(assetUtil, `No asset util found for type \"${type}\"`)\n\t\treturn assetUtil\n\t}\n\n\t/**\n\t * Returns true if the editor has an asset util for the given asset type.\n\t *\n\t * @public\n\t */\n\thasAssetUtil(arg: string | { type: string }): boolean {\n\t\tconst type = typeof arg === 'string' ? arg : arg.type\n\t\treturn hasOwnProperty(this.assetUtils, type)\n\t}\n\n\t/**\n\t * Get the asset util that accepts the given MIME type.\n\t * Returns null if no registered asset util accepts the MIME type.\n\t *\n\t * @public\n\t */\n\tgetAssetUtilForMimeType(mimeType: string): AssetUtil | null {\n\t\tfor (const util of Object.values(this.assetUtils)) {\n\t\t\tif (util && util.acceptsMimeType(mimeType)) {\n\t\t\t\treturn util\n\t\t\t}\n\t\t}\n\t\treturn null\n\t}\n\n\t/* --------------------- History -------------------- */\n\n\t/**\n\t * A manager for the editor's history.\n\t *\n\t * @readonly\n\t */\n\tprotected readonly history: HistoryManager<TLRecord>\n\n\t/**\n\t * Undo to the last mark.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.undo()\n\t * ```\n\t *\n\t * @public\n\t */\n\tundo(): this {\n\t\tthis._flushEventsForTick(0)\n\t\tthis.complete()\n\t\tthis.history.undo()\n\t\tthis.performance._notifyUndoRedo('undo', this.history.getNumUndos(), this.history.getNumRedos())\n\t\treturn this\n\t}\n\n\t/**\n\t * Whether the editor can undo.\n\t *\n\t * @public\n\t */\n\t@computed canUndo(): boolean {\n\t\treturn this.history.getNumUndos() > 0\n\t}\n\n\tgetCanUndo() {\n\t\treturn this.canUndo()\n\t}\n\n\t/**\n\t * Redo to the next mark.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.redo()\n\t * ```\n\t *\n\t * @public\n\t */\n\tredo(): this {\n\t\tthis._flushEventsForTick(0)\n\t\tthis.complete()\n\t\tthis.history.redo()\n\t\tthis.performance._notifyUndoRedo('redo', this.history.getNumUndos(), this.history.getNumRedos())\n\t\treturn this\n\t}\n\n\t/**\n\t * Whether the editor can redo.\n\t *\n\t * @public\n\t */\n\t@computed canRedo(): boolean {\n\t\treturn this.history.getNumRedos() > 0\n\t}\n\n\tgetCanRedo() {\n\t\treturn this.canRedo()\n\t}\n\n\tclearHistory() {\n\t\tthis.history.clear()\n\t\treturn this\n\t}\n\n\t/**\n\t * Create a new \"mark\", or stopping point, in the undo redo history. Creating a mark will clear\n\t * any redos. You typically want to do this just before a user interaction begins or is handled.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.markHistoryStoppingPoint()\n\t * editor.flipShapes(editor.getSelectedShapes())\n\t * ```\n\t * @example\n\t * ```ts\n\t * const beginRotateMark = editor.markHistoryStoppingPoint()\n\t * // if the use cancels the rotation, you can bail back to this mark\n\t * editor.bailToMark(beginRotateMark)\n\t * ```\n\t *\n\t * @public\n\t * @param name - The name of the mark, useful for debugging the undo/redo stacks\n\t * @returns a unique id for the mark that can be used with `squashToMark` or `bailToMark`.\n\t */\n\tmarkHistoryStoppingPoint(name?: string): string {\n\t\tconst id = `[${name ?? 'stop'}]_${uniqueId()}`\n\t\tthis.history._mark(id)\n\t\treturn id\n\t}\n\n\t/**\n\t * @internal this is only used to implement some backwards-compatibility logic. Should be fine to delete after 6 months or whatever.\n\t */\n\tgetMarkIdMatching(idSubstring: string) {\n\t\treturn this.history.getMarkIdMatching(idSubstring)\n\t}\n\n\t/**\n\t * Coalesces all changes since the given mark into a single change, removing any intermediate marks.\n\t *\n\t * This is useful if you need to 'compress' the recent history to simplify the undo/redo experience of a complex interaction.\n\t *\n\t * @example\n\t * ```ts\n\t * const bumpShapesMark = editor.markHistoryStoppingPoint()\n\t * // ... some changes\n\t * editor.squashToMark(bumpShapesMark)\n\t * ```\n\t *\n\t * @param markId - The mark id to squash to.\n\t */\n\tsquashToMark(markId: string): this {\n\t\tthis.history.squashToMark(markId)\n\t\treturn this\n\t}\n\n\t/**\n\t * Undo to the closest mark, discarding the changes so they cannot be redone.\n\t *\n\t * @example\n\t * ```ts\n\t * editor.bail()\n\t * ```\n\t *\n\t * @public\n\t */\n\tbail() {\n\t\tthis.history.bail()\n\t\treturn this\n\t}\n\n\t/**\n\t * Undo to the given mark, discarding the changes so they cannot be redone.\n\t *\n\t * @example\n\t * ```ts\n\t * const beginDrag = editor.markHistoryStopp