UNPKG

@tldraw/editor

Version:

tldraw infinite canvas SDK (editor).

405 lines (404 loc) • 15.4 kB
"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 __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 TldrawEditor_exports = {}; __export(TldrawEditor_exports, { ErrorScreen: () => ErrorScreen, LoadingScreen: () => LoadingScreen, TL_CONTAINER_CLASS: () => TL_CONTAINER_CLASS, TldrawEditor: () => TldrawEditor, useOnMount: () => useOnMount }); module.exports = __toCommonJS(TldrawEditor_exports); var import_jsx_runtime = ( // Store is ready to go, whether externally synced or not require("react/jsx-runtime") ); var import_store = require("@tldraw/store"); var import_utils = require("@tldraw/utils"); var import_react = __toESM(require("react")); var import_classnames = __toESM(require("classnames")); var import_version = require("../version"); var import_ErrorBoundary = require("./components/ErrorBoundary"); var import_DefaultErrorFallback = require("./components/default-components/DefaultErrorFallback"); var import_createTLUser = require("./config/createTLUser"); var import_Editor = require("./editor/Editor"); var import_useContainer = require("./hooks/useContainer"); var import_useCursor = require("./hooks/useCursor"); var import_useDarkMode = require("./hooks/useDarkMode"); var import_useEditor = require("./hooks/useEditor"); var import_useEditorComponents = require("./hooks/useEditorComponents"); var import_useEvent = require("./hooks/useEvent"); var import_useForceUpdate = require("./hooks/useForceUpdate"); var import_useIdentity = require("./hooks/useIdentity"); var import_useLocalStore = require("./hooks/useLocalStore"); var import_useRefState = require("./hooks/useRefState"); var import_useZoomCss = require("./hooks/useZoomCss"); var import_LicenseProvider = require("./license/LicenseProvider"); var import_Watermark = require("./license/Watermark"); var import_dom = require("./utils/dom"); const EMPTY_SHAPE_UTILS_ARRAY = []; const EMPTY_BINDING_UTILS_ARRAY = []; const EMPTY_TOOLS_ARRAY = []; const TL_CONTAINER_CLASS = "tl-container"; const TldrawEditor = (0, import_react.memo)(function TldrawEditor2({ store, components, className, user: _user, options: _options, ...rest }) { const [container, setContainer] = (0, import_react.useState)(null); const user = (0, import_react.useMemo)(() => _user ?? (0, import_createTLUser.createTLUser)(), [_user]); const ErrorFallback = components?.ErrorFallback === void 0 ? import_DefaultErrorFallback.DefaultErrorFallback : components?.ErrorFallback; const withDefaults = { ...rest, shapeUtils: rest.shapeUtils ?? EMPTY_SHAPE_UTILS_ARRAY, bindingUtils: rest.bindingUtils ?? EMPTY_BINDING_UTILS_ARRAY, tools: rest.tools ?? EMPTY_TOOLS_ARRAY, components, options: (0, import_useIdentity.useShallowObjectIdentity)(_options) }; return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "div", { ref: setContainer, "data-tldraw": import_version.version, draggable: false, className: (0, import_classnames.default)(`${TL_CONTAINER_CLASS} tl-theme__light`, className), onPointerDown: import_dom.stopEventPropagation, tabIndex: -1, role: "application", "aria-label": _options?.branding ?? "tldraw", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_ErrorBoundary.OptionalErrorBoundary, { fallback: ErrorFallback, onError: (error) => (0, import_utils.annotateError)(error, { tags: { origin: "react.tldraw-before-app" } }), children: container && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_LicenseProvider.LicenseProvider, { licenseKey: rest.licenseKey, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_useContainer.ContainerProvider, { container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_useEditorComponents.EditorComponentsProvider, { overrides: components, children: store ? store instanceof import_store.Store ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TldrawEditorWithReadyStore, { ...withDefaults, store, user }) : ( // Store is a synced store, so handle syncing stages internally /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TldrawEditorWithLoadingStore, { ...withDefaults, store, user }) ) : ( // We have no store (it's undefined) so create one and possibly sync it /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TldrawEditorWithOwnStore, { ...withDefaults, store, user }) ) }) }) }) } ) } ); }); function TldrawEditorWithOwnStore(props) { const { defaultName, snapshot, initialData, shapeUtils, bindingUtils, persistenceKey, sessionId, user, assets, migrations } = props; const syncedStore = (0, import_useLocalStore.useLocalStore)({ shapeUtils, bindingUtils, initialData, persistenceKey, sessionId, defaultName, snapshot, assets, migrations }); return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TldrawEditorWithLoadingStore, { ...props, store: syncedStore, user }); } const TldrawEditorWithLoadingStore = (0, import_react.memo)(function TldrawEditorBeforeLoading({ store, user, ...rest }) { const container = (0, import_useContainer.useContainer)(); (0, import_react.useLayoutEffect)(() => { if (user.userPreferences.get().colorScheme === "dark") { container.classList.remove("tl-theme__light"); container.classList.add("tl-theme__dark"); } }, [container, user]); const { LoadingScreen: LoadingScreen2 } = (0, import_useEditorComponents.useEditorComponents)(); switch (store.status) { case "error": { throw store.error; } case "loading": { return LoadingScreen2 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoadingScreen2, {}) : null; } case "not-synced": { break; } case "synced-local": { break; } case "synced-remote": { break; } } return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TldrawEditorWithReadyStore, { ...rest, store: store.store, user }); }); const noAutoFocus = () => document.location.search.includes("tldraw_preserve_focus"); function TldrawEditorWithReadyStore({ onMount, children, store, tools, shapeUtils, bindingUtils, user, initialState, autoFocus = true, inferDarkMode, cameraOptions, textOptions, options, licenseKey, deepLinks: _deepLinks, // eslint-disable-next-line @typescript-eslint/no-deprecated isShapeHidden, getShapeVisibility, assetUrls }) { const { ErrorFallback } = (0, import_useEditorComponents.useEditorComponents)(); const container = (0, import_useContainer.useContainer)(); const [editor, setEditor] = (0, import_useRefState.useRefState)(null); const canvasRef = (0, import_react.useRef)(null); const deepLinks = (0, import_useIdentity.useShallowObjectIdentity)(_deepLinks === true ? {} : _deepLinks); const editorOptionsRef = (0, import_react.useRef)({ // for these, it's because they're only used when the editor first mounts: autoFocus: autoFocus && !noAutoFocus(), inferDarkMode, initialState, // for these, it's because we keep them up to date in a separate effect: cameraOptions, deepLinks }); (0, import_react.useLayoutEffect)(() => { editorOptionsRef.current = { autoFocus: autoFocus && !noAutoFocus(), inferDarkMode, initialState, cameraOptions, deepLinks }; }, [autoFocus, inferDarkMode, initialState, cameraOptions, deepLinks]); (0, import_react.useLayoutEffect)( () => { const { autoFocus: autoFocus2, inferDarkMode: inferDarkMode2, initialState: initialState2, cameraOptions: cameraOptions2, deepLinks: deepLinks2 } = editorOptionsRef.current; const editor2 = new import_Editor.Editor({ store, shapeUtils, bindingUtils, tools, getContainer: () => container, user, initialState: initialState2, // we should check for some kind of query parameter that turns off autofocus autoFocus: autoFocus2, inferDarkMode: inferDarkMode2, cameraOptions: cameraOptions2, textOptions, options, licenseKey, isShapeHidden, getShapeVisibility, fontAssetUrls: assetUrls?.fonts }); editor2.updateViewportScreenBounds(canvasRef.current ?? container); if (deepLinks2) { if (!deepLinks2?.getUrl) { editor2.navigateToDeepLink(deepLinks2); } else { editor2.navigateToDeepLink({ ...deepLinks2, url: deepLinks2.getUrl(editor2) }); } } setEditor(editor2); return () => { editor2.dispose(); }; }, // if any of these change, we need to recreate the editor. [ bindingUtils, container, options, shapeUtils, store, tools, user, setEditor, licenseKey, isShapeHidden, getShapeVisibility, textOptions, assetUrls ] ); (0, import_react.useLayoutEffect)(() => { if (!editor) return; if (deepLinks) { return editor.registerDeepLinkListener(deepLinks); } }, [editor, deepLinks]); (0, import_react.useLayoutEffect)(() => { if (editor && cameraOptions) { editor.setCameraOptions(cameraOptions); } }, [editor, cameraOptions]); const crashingError = (0, import_react.useSyncExternalStore)( (0, import_react.useCallback)( (onStoreChange) => { if (editor) { editor.on("crash", onStoreChange); return () => editor.off("crash", onStoreChange); } return () => { }; }, [editor] ), () => editor?.getCrashingError() ?? null ); (0, import_react.useEffect)( function handleFocusOnPointerDownForPreserveFocusMode() { if (!editor) return; function handleFocusOnPointerDown() { if (!editor) return; editor.focus(); } function handleBlurOnPointerDown() { if (!editor) return; editor.blur(); } if (autoFocus && noAutoFocus()) { editor.getContainer().addEventListener("pointerdown", handleFocusOnPointerDown); document.body.addEventListener("pointerdown", handleBlurOnPointerDown); return () => { editor.getContainer()?.removeEventListener("pointerdown", handleFocusOnPointerDown); document.body.removeEventListener("pointerdown", handleBlurOnPointerDown); }; } }, [editor, autoFocus] ); const [_fontLoadingState, setFontLoadingState] = (0, import_react.useState)(null); let fontLoadingState = _fontLoadingState; if (editor !== fontLoadingState?.editor) { fontLoadingState = null; } (0, import_react.useEffect)(() => { if (!editor) return; let isCancelled = false; setFontLoadingState({ editor, isLoaded: false }); editor.fonts.loadRequiredFontsForCurrentPage(editor.options.maxFontsToLoadBeforeRender).finally(() => { if (isCancelled) return; setFontLoadingState({ editor, isLoaded: true }); }); return () => { isCancelled = true; }; }, [editor]); const { Canvas, LoadingScreen: LoadingScreen2 } = (0, import_useEditorComponents.useEditorComponents)(); if (!editor || !fontLoadingState?.isLoaded) { return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [ LoadingScreen2 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoadingScreen2, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tl-canvas", ref: canvasRef }) ] }); } return ( // the top-level tldraw component also renders an error boundary almost // identical to this one. the reason we have two is because this one has // access to `App`, which means that here we can enrich errors with data // from app for reporting, and also still attempt to render the user's // document in the event of an error to reassure them that their work is // not lost. /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_ErrorBoundary.OptionalErrorBoundary, { fallback: ErrorFallback, onError: (error) => editor.annotateError(error, { origin: "react.tldraw", willCrashApp: true }), children: crashingError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Crash, { crashingError }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_useEditor.EditorProvider, { editor, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Layout, { onMount, children: [ children ?? (Canvas ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Canvas, {}, editor.contextId) : null), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Watermark.Watermark, {}) ] }) }) } ) ); } function Layout({ children, onMount }) { (0, import_useZoomCss.useZoomCss)(); (0, import_useCursor.useCursor)(); (0, import_useDarkMode.useDarkMode)(); (0, import_useForceUpdate.useForceUpdate)(); useOnMount((editor) => { const teardownStore = editor.store.props.onMount(editor); const teardownCallback = onMount?.(editor); return () => { teardownStore?.(); teardownCallback?.(); }; }); return children; } function Crash({ crashingError }) { throw crashingError; } function LoadingScreen({ children }) { return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tl-loading", "aria-busy": "true", tabIndex: 0, children }); } function ErrorScreen({ children }) { return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tl-loading", children }); } function useOnMount(onMount) { const editor = (0, import_useEditor.useEditor)(); const onMountEvent = (0, import_useEvent.useEvent)((editor2) => { let teardown = void 0; editor2.run( () => { teardown = onMount?.(editor2); editor2.emit("mount"); }, { history: "ignore" } ); window.tldrawReady = true; return teardown; }); import_react.default.useLayoutEffect(() => { if (editor) return onMountEvent?.(editor); }, [editor, onMountEvent]); } //# sourceMappingURL=TldrawEditor.js.map