@tldraw/editor
Version:
tldraw infinite canvas SDK (editor).
166 lines (165 loc) • 6.27 kB
JavaScript
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
import { react } from "@tldraw/state";
import { useQuickReactor, useStateTracking } from "@tldraw/state-react";
import { memo, useCallback, useEffect, useLayoutEffect, useRef } from "react";
import { useEditor } from "../hooks/useEditor.mjs";
import { useEditorComponents } from "../hooks/useEditorComponents.mjs";
import { Mat } from "../primitives/Mat.mjs";
import { areShapesContentEqual } from "../utils/areShapesContentEqual.mjs";
import { setStyleProperty } from "../utils/dom.mjs";
import { OptionalErrorBoundary } from "./ErrorBoundary.mjs";
const Shape = memo(function Shape2({
id,
shape,
util,
index,
backgroundIndex,
opacity,
dprMultiple
}) {
const editor = useEditor();
const { ShapeErrorFallback } = useEditorComponents();
const containerRef = useRef(null);
const bgContainerRef = useRef(null);
useEffect(() => {
return react("load fonts", () => {
const fonts = editor.fonts.getShapeFontFaces(id);
editor.fonts.requestFonts(fonts);
});
}, [editor, id]);
const memoizedStuffRef = useRef({
transform: "",
clipPath: "none",
width: 0,
height: 0,
x: 0,
y: 0,
isCulled: false
});
useQuickReactor(
"set shape stuff",
() => {
const shape2 = editor.getShape(id);
if (!shape2) return;
const prev = memoizedStuffRef.current;
const clipPath = editor.getShapeClipPath(id) ?? "none";
if (clipPath !== prev.clipPath) {
setStyleProperty(containerRef.current, "clip-path", clipPath);
setStyleProperty(bgContainerRef.current, "clip-path", clipPath);
prev.clipPath = clipPath;
}
const pageTransform = editor.getShapePageTransform(id);
const transform = Mat.toCssString(pageTransform);
const bounds = editor.getShapeGeometry(shape2).bounds;
if (transform !== prev.transform) {
setStyleProperty(containerRef.current, "transform", transform);
setStyleProperty(bgContainerRef.current, "transform", transform);
prev.transform = transform;
}
const widthRemainder = bounds.w % dprMultiple;
const heightRemainder = bounds.h % dprMultiple;
const width = widthRemainder === 0 ? bounds.w : bounds.w + (dprMultiple - widthRemainder);
const height = heightRemainder === 0 ? bounds.h : bounds.h + (dprMultiple - heightRemainder);
if (width !== prev.width || height !== prev.height) {
setStyleProperty(containerRef.current, "width", Math.max(width, dprMultiple) + "px");
setStyleProperty(containerRef.current, "height", Math.max(height, dprMultiple) + "px");
setStyleProperty(bgContainerRef.current, "width", Math.max(width, dprMultiple) + "px");
setStyleProperty(bgContainerRef.current, "height", Math.max(height, dprMultiple) + "px");
prev.width = width;
prev.height = height;
}
},
[editor]
);
useLayoutEffect(() => {
const container = containerRef.current;
const bgContainer = bgContainerRef.current;
setStyleProperty(container, "opacity", opacity);
setStyleProperty(bgContainer, "opacity", opacity);
setStyleProperty(container, "z-index", index);
setStyleProperty(bgContainer, "z-index", backgroundIndex);
}, [opacity, index, backgroundIndex]);
useQuickReactor(
"set display",
() => {
const shape2 = editor.getShape(id);
if (!shape2) return;
const culledShapes = editor.getCulledShapes();
const isCulled = culledShapes.has(id);
if (isCulled !== memoizedStuffRef.current.isCulled) {
setStyleProperty(containerRef.current, "display", isCulled ? "none" : "block");
setStyleProperty(bgContainerRef.current, "display", isCulled ? "none" : "block");
memoizedStuffRef.current.isCulled = isCulled;
}
},
[editor]
);
const annotateError = useCallback(
(error) => editor.annotateError(error, { origin: "shape", willCrashApp: false }),
[editor]
);
if (!shape) return null;
const isFilledShape = "fill" in shape.props && shape.props.fill !== "none";
return /* @__PURE__ */ jsxs(Fragment, { children: [
util.backgroundComponent && /* @__PURE__ */ jsx(
"div",
{
ref: bgContainerRef,
className: "tl-shape tl-shape-background",
"data-shape-type": shape.type,
"data-shape-id": shape.id,
draggable: false,
children: /* @__PURE__ */ jsx(OptionalErrorBoundary, { fallback: ShapeErrorFallback, onError: annotateError, children: /* @__PURE__ */ jsx(InnerShapeBackground, { shape, util }) })
}
),
/* @__PURE__ */ jsx(
"div",
{
ref: containerRef,
className: "tl-shape",
"data-shape-type": shape.type,
"data-shape-is-filled": isFilledShape,
"data-shape-id": shape.id,
draggable: false,
children: /* @__PURE__ */ jsx(OptionalErrorBoundary, { fallback: ShapeErrorFallback, onError: annotateError, children: /* @__PURE__ */ jsx(InnerShape, { shape, util }) })
}
)
] });
});
const InnerShape = memo(
function InnerShape2({ shape, util }) {
return useStateTracking(
"InnerShape:" + shape.type,
() => (
// always fetch the latest shape from the store even if the props/meta have not changed, to avoid
// calling the render method with stale data.
(util.component(util.editor.store.unsafeGetWithoutCapture(shape.id)))
),
[util, shape.id]
);
},
(prev, next) => areShapesContentEqual(prev.shape, next.shape) && prev.util === next.util
);
const InnerShapeBackground = memo(
function InnerShapeBackground2({
shape,
util
}) {
return useStateTracking(
"InnerShape:" + shape.type,
() => (
// always fetch the latest shape from the store even if the props/meta have not changed, to avoid
// calling the render method with stale data.
(util.backgroundComponent?.(util.editor.store.unsafeGetWithoutCapture(shape.id)))
),
[util, shape.id]
);
},
(prev, next) => prev.shape.props === next.shape.props && prev.shape.meta === next.shape.meta && prev.util === next.util
);
export {
InnerShape,
InnerShapeBackground,
Shape
};
//# sourceMappingURL=Shape.mjs.map