@tldraw/editor
Version:
tldraw infinite canvas SDK (editor).
332 lines (331 loc) • 15.2 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 DefaultCanvas_exports = {};
__export(DefaultCanvas_exports, {
DefaultCanvas: () => DefaultCanvas
});
module.exports = __toCommonJS(DefaultCanvas_exports);
var import_jsx_runtime = require("react/jsx-runtime");
var import_state = require("@tldraw/state");
var import_state_react = require("@tldraw/state-react");
var import_utils = require("@tldraw/utils");
var import_classnames = __toESM(require("classnames"), 1);
var import_react = require("react");
var import_environment = require("../../globals/environment");
var import_EditorComponentsContext = require("../../hooks/EditorComponentsContext");
var import_useCanvasEvents = require("../../hooks/useCanvasEvents");
var import_useCoarsePointer = require("../../hooks/useCoarsePointer");
var import_useContainer = require("../../hooks/useContainer");
var import_useDocumentEvents = require("../../hooks/useDocumentEvents");
var import_useEditor = require("../../hooks/useEditor");
var import_useFixSafariDoubleTapZoomPencilEvents = require("../../hooks/useFixSafariDoubleTapZoomPencilEvents");
var import_useGestureEvents = require("../../hooks/useGestureEvents");
var import_useScreenBounds = require("../../hooks/useScreenBounds");
var import_useShapeCulling = require("../../hooks/useShapeCulling");
var import_utils2 = require("../../primitives/utils");
var import_debug_flags = require("../../utils/debug-flags");
var import_dom = require("../../utils/dom");
var import_MenuClickCapture = require("../MenuClickCapture");
var import_Shape = require("../Shape");
var import_CanvasOverlays = require("./CanvasOverlays");
function DefaultCanvas({ className }) {
const editor = (0, import_useEditor.useEditor)();
const { SelectionBackground, Background, SvgDefs } = (0, import_EditorComponentsContext.useEditorComponents)();
const rCanvas = (0, import_react.useRef)(null);
const rHtmlLayer = (0, import_react.useRef)(null);
const container = (0, import_useContainer.useContainer)();
(0, import_useScreenBounds.useScreenBounds)(rCanvas);
(0, import_useDocumentEvents.useDocumentEvents)();
(0, import_useCoarsePointer.useCoarsePointer)();
(0, import_useGestureEvents.useGestureEvents)(rCanvas);
(0, import_useFixSafariDoubleTapZoomPencilEvents.useFixSafariDoubleTapZoomPencilEvents)(rCanvas);
(0, import_state_react.useQuickReactor)(
"update canvas state data attributes",
() => {
const canvas = rCanvas.current;
if (!canvas) return;
canvas.setAttribute(
"data-iseditinganything",
editor.getEditingShapeId() === null ? "false" : "true"
);
canvas.setAttribute(
"data-isselectinganything",
editor.getSelectedShapeIds().length === 0 ? "false" : "true"
);
},
[editor]
);
const rMemoizedStuff = (0, import_react.useRef)({ lodDisableTextOutline: false, allowTextOutline: true });
(0, import_state_react.useQuickReactor)(
"position layers",
function positionLayersWhenCameraMoves() {
const { x, y, z } = editor.getCamera();
if (rMemoizedStuff.current.allowTextOutline && import_environment.tlenv.isSafari) {
container.style.setProperty("--tl-text-outline", "none");
rMemoizedStuff.current.allowTextOutline = false;
}
if (rMemoizedStuff.current.allowTextOutline && z < editor.options.textShadowLod !== rMemoizedStuff.current.lodDisableTextOutline) {
const lodDisableTextOutline = z < editor.options.textShadowLod;
container.style.setProperty(
"--tl-text-outline",
lodDisableTextOutline ? "none" : `var(--tl-text-outline-reference)`
);
rMemoizedStuff.current.lodDisableTextOutline = lodDisableTextOutline;
}
const offset = z >= 1 ? (0, import_utils.modulate)(z, [1, 8], [0.125, 0.5], true) : (0, import_utils.modulate)(z, [0.1, 1], [-2, 0.125], true);
const transform = `scale(${(0, import_utils2.toDomPrecision)(z)}) translate(${(0, import_utils2.toDomPrecision)(
x + offset
)}px,${(0, import_utils2.toDomPrecision)(y + offset)}px)`;
(0, import_dom.setStyleProperty)(rHtmlLayer.current, "transform", transform);
},
[editor, container]
);
const events = (0, import_useCanvasEvents.useCanvasEvents)();
const shapeSvgDefs = (0, import_state_react.useValue)(
"shapeSvgDefs",
() => {
const shapeSvgDefsByKey = /* @__PURE__ */ new Map();
for (const util of (0, import_utils.objectMapValues)(editor.shapeUtils)) {
if (!util) return;
const defs = util.getCanvasSvgDefs();
for (const { key, component: Component } of defs) {
if (shapeSvgDefsByKey.has(key)) continue;
shapeSvgDefsByKey.set(key, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Component, {}, key));
}
}
return [...shapeSvgDefsByKey.values()];
},
[editor]
);
const hideShapes = (0, import_state_react.useValue)("debug_shapes", () => import_debug_flags.debugFlags.hideShapes.get(), [import_debug_flags.debugFlags]);
const isGridMode = (0, import_state_react.useValue)("isGridMode", () => editor.getInstanceState().isGridMode, [editor]);
const { Grid } = (0, import_EditorComponentsContext.useEditorComponents)();
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
"div",
{
ref: rCanvas,
draggable: false,
className: (0, import_classnames.default)("tl-canvas", className),
"data-testid": "canvas",
...events,
children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "tl-svg-context", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("defs", { children: [
shapeSvgDefs,
SvgDefs && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SvgDefs, {})
] }) }),
Background && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tl-background__wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Background, {}) }),
isGridMode && Grid && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GridWrapper, {}),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ref: rHtmlLayer, className: "tl-html-layer tl-shapes", draggable: false, children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(OnTheCanvasWrapper, {}),
SelectionBackground && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectionBackgroundWrapper, {}),
hideShapes ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ShapesLayer, { canvasRef: rCanvas })
] }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_CanvasOverlays.CanvasOverlays, {}),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MovingCameraHitTestBlocker, {})
]
}
),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(InFrontOfTheCanvasWrapper, {}),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_MenuClickCapture.MenuClickCapture, {})
] });
}
function InFrontOfTheCanvasWrapper() {
const editor = (0, import_useEditor.useEditor)();
const { InFrontOfTheCanvas } = (0, import_EditorComponentsContext.useEditorComponents)();
if (!InFrontOfTheCanvas) return null;
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"div",
{
className: "tl-canvas__in-front",
onPointerDown: editor.markEventAsHandled,
onPointerUp: editor.markEventAsHandled,
onTouchStart: editor.markEventAsHandled,
onTouchEnd: editor.markEventAsHandled,
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InFrontOfTheCanvas, {})
}
);
}
function GridWrapper() {
const editor = (0, import_useEditor.useEditor)();
const gridSize = (0, import_state_react.useValue)("gridSize", () => editor.getDocumentSettings().gridSize, [editor]);
const { x, y, z } = (0, import_state_react.useValue)("camera", () => editor.getCamera(), [editor]);
const { Grid } = (0, import_EditorComponentsContext.useEditorComponents)();
if (!Grid) return null;
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Grid, { x, y, z, size: gridSize });
}
function ShapesLayer({ canvasRef }) {
const editor = (0, import_useEditor.useEditor)();
const debugSvg = (0, import_state_react.useValue)("debug svg", () => import_debug_flags.debugFlags.debugSvg.get(), [import_debug_flags.debugFlags]);
const renderingShapes = (0, import_state_react.useValue)("rendering shapes", () => editor.getRenderingShapes(), [editor]);
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_useShapeCulling.ShapeCullingProvider, { children: [
renderingShapes.map(
(result) => debugSvg ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.Fragment, { children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Shape.Shape, { ...result }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DebugSvgCopy, { id: result.id, mode: "iframe" })
] }, result.id + "_fragment") : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Shape.Shape, { ...result }, result.id + "_shape")
),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CullingController, {}),
import_environment.tlenv.isSafari && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReflowIfNeeded, { canvasRef })
] });
}
function ReflowIfNeeded({ canvasRef }) {
const editor = (0, import_useEditor.useEditor)();
const culledShapesRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
(0, import_state_react.useQuickReactor)(
"reflow for culled shapes",
() => {
const culledShapes = editor.getCulledShapes();
if (culledShapesRef.current === culledShapes) return;
culledShapesRef.current = culledShapes;
const canvas = canvasRef.current;
if (!canvas) return;
const _height = canvas.offsetHeight;
},
[editor, canvasRef]
);
return null;
}
function CullingController() {
const editor = (0, import_useEditor.useEditor)();
const { updateCulling } = (0, import_useShapeCulling.useShapeCulling)();
(0, import_state_react.useQuickReactor)(
"update shape culling",
() => {
const culledShapes = editor.getCulledShapes();
updateCulling(culledShapes);
},
[editor, updateCulling]
);
return null;
}
function DebugSvgCopy({ id, mode }) {
const editor = (0, import_useEditor.useEditor)();
const [image, setImage] = (0, import_react.useState)(null);
const isInRoot = (0, import_state_react.useValue)(
"is in root",
() => {
const shape = editor.getShape(id);
return shape?.parentId === editor.getCurrentPageId();
},
[editor, id]
);
(0, import_react.useEffect)(() => {
if (!isInRoot) return;
let latest = null;
const unsubscribe = (0, import_state.react)("shape to svg", async () => {
const renderId = Math.random();
latest = renderId;
const shape = editor.getShape(id);
const isSingleFrame = !!shape && editor.isShapeFrameLike(shape);
const padding = isSingleFrame ? 0 : 10;
let bounds = editor.getShapePageBounds(id);
if (!bounds) return;
bounds = bounds.clone().expandBy(padding);
const result = await editor.getSvgString([id], { padding });
if (latest !== renderId || !result) return;
const svgDataUrl = `data:image/svg+xml;utf8,${encodeURIComponent(result.svg)}`;
setImage({ src: svgDataUrl, bounds });
});
return () => {
latest = null;
unsubscribe();
};
}, [editor, id, isInRoot]);
if (!isInRoot || !image) return null;
if (mode === "iframe") {
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"iframe",
{
src: image.src,
width: image.bounds.width,
height: image.bounds.height,
referrerPolicy: "no-referrer",
style: {
position: "absolute",
top: 0,
left: 0,
border: "none",
transform: `translate(${image.bounds.x}px, ${image.bounds.maxY + 12}px)`,
outline: "1px solid black",
maxWidth: "none"
}
}
);
}
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"img",
{
src: image.src,
width: image.bounds.width,
height: image.bounds.height,
referrerPolicy: "no-referrer",
style: {
position: "absolute",
top: 0,
left: 0,
transform: `translate(${image.bounds.x}px, ${image.bounds.maxY + 12}px)`,
outline: "1px solid black",
maxWidth: "none"
}
}
);
}
function SelectionBackgroundWrapper() {
const editor = (0, import_useEditor.useEditor)();
const selectionRotation = (0, import_state_react.useValue)("selection rotation", () => editor.getSelectionRotation(), [
editor
]);
const selectionBounds = (0, import_state_react.useValue)(
"selection bounds",
() => editor.getSelectionRotatedPageBounds(),
[editor]
);
const { SelectionBackground } = (0, import_EditorComponentsContext.useEditorComponents)();
if (!selectionBounds || !SelectionBackground) return null;
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectionBackground, { bounds: selectionBounds, rotation: selectionRotation });
}
function OnTheCanvasWrapper() {
const { OnTheCanvas } = (0, import_EditorComponentsContext.useEditorComponents)();
if (!OnTheCanvas) return null;
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(OnTheCanvas, {});
}
function MovingCameraHitTestBlocker() {
const editor = (0, import_useEditor.useEditor)();
const cameraState = (0, import_state_react.useValue)("camera state", () => editor.getCameraState(), [editor]);
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"div",
{
className: (0, import_classnames.default)("tl-hit-test-blocker", {
"tl-hit-test-blocker__hidden": cameraState === "idle"
})
}
);
}
//# sourceMappingURL=DefaultCanvas.js.map