@tldraw/editor
Version:
tldraw infinite canvas SDK (editor).
405 lines (404 loc) • 15.4 kB
JavaScript
;
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