@tldraw/editor 3.13.0-canary.6cb770c78958 → 3.13.0-canary.8754dedf4cf0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/components/Shape.js +10 -7
- package/dist-cjs/lib/components/Shape.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +21 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +17 -11
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +1 -1
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/exports/getSvgJsx.js +12 -3
- package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
- package/dist-cjs/lib/utils/nearestMultiple.js +34 -0
- package/dist-cjs/lib/utils/nearestMultiple.js.map +7 -0
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/components/Shape.mjs +10 -7
- package/dist-esm/lib/components/Shape.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +21 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +17 -11
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +1 -1
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/exports/getSvgJsx.mjs +12 -3
- package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
- package/dist-esm/lib/utils/nearestMultiple.mjs +14 -0
- package/dist-esm/lib/utils/nearestMultiple.mjs.map +7 -0
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +7 -7
- package/src/lib/components/Shape.tsx +12 -6
- package/src/lib/components/default-components/DefaultCanvas.tsx +21 -2
- package/src/lib/components/default-components/DefaultShapeIndicator.tsx +17 -8
- package/src/lib/editor/Editor.ts +1 -1
- package/src/lib/exports/getSvgJsx.tsx +16 -7
- package/src/lib/utils/nearestMultiple.ts +13 -0
- package/src/version.ts +3 -3
|
@@ -275,6 +275,17 @@ function SvgExport({
|
|
|
275
275
|
if (shapeElements === null) return;
|
|
276
276
|
onMount();
|
|
277
277
|
}, [onMount, shapeElements]);
|
|
278
|
+
let backgroundColor = background ? theme.background : "transparent";
|
|
279
|
+
if (singleFrameShapeId && background) {
|
|
280
|
+
const frameShapeUtil = editor.getShapeUtil("frame");
|
|
281
|
+
if (frameShapeUtil?.options.showColors) {
|
|
282
|
+
const shape = editor.getShape(singleFrameShapeId);
|
|
283
|
+
const color = theme[shape.props.color];
|
|
284
|
+
backgroundColor = color.frame.fill;
|
|
285
|
+
} else {
|
|
286
|
+
backgroundColor = theme.solid;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
278
289
|
return /* @__PURE__ */ jsx(SvgExportContextProvider, { editor, context: exportContext, children: /* @__PURE__ */ jsxs(
|
|
279
290
|
"svg",
|
|
280
291
|
{
|
|
@@ -285,9 +296,7 @@ function SvgExport({
|
|
|
285
296
|
viewBox: `${bbox.minX} ${bbox.minY} ${bbox.width} ${bbox.height}`,
|
|
286
297
|
strokeLinecap: "round",
|
|
287
298
|
strokeLinejoin: "round",
|
|
288
|
-
style: {
|
|
289
|
-
backgroundColor: background ? singleFrameShapeId ? theme.solid : theme.background : "transparent"
|
|
290
|
-
},
|
|
299
|
+
style: { backgroundColor },
|
|
291
300
|
"data-color-mode": isDarkMode ? "dark" : "light",
|
|
292
301
|
className: `tl-container tl-theme__force-sRGB ${isDarkMode ? "tl-theme__dark" : "tl-theme__light"}`,
|
|
293
302
|
children: [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/exports/getSvgJsx.tsx"],
|
|
4
|
-
"sourcesContent": ["import { useAtom, useValue } from '@tldraw/state-react'\nimport {\n\tTLFrameShape,\n\tTLGroupShape,\n\tTLShape,\n\tTLShapeId,\n\tgetDefaultColorTheme,\n} from '@tldraw/tlschema'\nimport { hasOwnProperty, promiseWithResolve, uniqueId } from '@tldraw/utils'\nimport {\n\tComponentType,\n\tFragment,\n\tReactElement,\n\tReactNode,\n\tuseEffect,\n\tuseLayoutEffect,\n\tuseMemo,\n\tuseRef,\n} from 'react'\nimport { flushSync } from 'react-dom'\nimport { ErrorBoundary } from '../components/ErrorBoundary'\nimport { InnerShape, InnerShapeBackground } from '../components/Shape'\nimport { Editor, TLRenderingShape } from '../editor/Editor'\nimport { TLFontFace } from '../editor/managers/FontManager'\nimport { ShapeUtil } from '../editor/shapes/ShapeUtil'\nimport {\n\tSvgExportContext,\n\tSvgExportContextProvider,\n\tSvgExportDef,\n} from '../editor/types/SvgExportContext'\nimport { TLImageExportOptions } from '../editor/types/misc-types'\nimport { useEditor } from '../hooks/useEditor'\nimport { useEvent } from '../hooks/useEvent'\nimport { suffixSafeId, useUniqueSafeId } from '../hooks/useSafeId'\nimport { Box } from '../primitives/Box'\nimport { Mat } from '../primitives/Mat'\nimport { ExportDelay } from './ExportDelay'\n\nexport function getSvgJsx(editor: Editor, ids: TLShapeId[], opts: TLImageExportOptions = {}) {\n\tif (!window.document) throw Error('No document')\n\n\tconst {\n\t\tscale = 1,\n\t\t// should we include the background in the export? or is it transparent?\n\t\tbackground = editor.getInstanceState().exportBackground,\n\t\tpadding = editor.options.defaultSvgPadding,\n\t\tpreserveAspectRatio,\n\t} = opts\n\n\tconst isDarkMode = opts.darkMode ?? editor.user.getIsDarkMode()\n\n\t// ---Figure out which shapes we need to include\n\tconst shapeIdsToInclude = editor.getShapeAndDescendantIds(ids)\n\tconst renderingShapes = editor\n\t\t.getUnorderedRenderingShapes(false)\n\t\t.filter(({ id }) => shapeIdsToInclude.has(id))\n\n\t// --- Common bounding box of all shapes\n\tlet bbox: null | Box = null\n\tif (opts.bounds) {\n\t\tbbox = opts.bounds\n\t} else {\n\t\tfor (const { id } of renderingShapes) {\n\t\t\tconst maskedPageBounds = editor.getShapeMaskedPageBounds(id)\n\t\t\tif (!maskedPageBounds) continue\n\t\t\tif (bbox) {\n\t\t\t\tbbox.union(maskedPageBounds)\n\t\t\t} else {\n\t\t\t\tbbox = maskedPageBounds.clone()\n\t\t\t}\n\t\t}\n\t}\n\n\t// no unmasked shapes to export\n\tif (!bbox) return\n\n\tconst singleFrameShapeId =\n\t\tids.length === 1 && editor.isShapeOfType<TLFrameShape>(editor.getShape(ids[0])!, 'frame')\n\t\t\t? ids[0]\n\t\t\t: null\n\tif (!singleFrameShapeId) {\n\t\t// Expand by an extra 32 pixels\n\t\tbbox.expandBy(padding)\n\t}\n\n\t// We want the svg image to be BIGGER THAN USUAL to account for image quality\n\tconst w = bbox.width * scale\n\tconst h = bbox.height * scale\n\n\ttry {\n\t\tdocument.body.focus?.() // weird but necessary\n\t} catch {\n\t\t// not implemented\n\t}\n\n\tconst exportDelay = new ExportDelay(editor.options.maxExportDelayMs)\n\n\tconst initialEffectPromise = promiseWithResolve<void>()\n\texportDelay.waitUntil(initialEffectPromise)\n\n\tconst svg = (\n\t\t<SvgExport\n\t\t\teditor={editor}\n\t\t\tpreserveAspectRatio={preserveAspectRatio}\n\t\t\tscale={scale}\n\t\t\tpixelRatio={opts.pixelRatio ?? null}\n\t\t\tbbox={bbox}\n\t\t\tbackground={background}\n\t\t\tsingleFrameShapeId={singleFrameShapeId}\n\t\t\tisDarkMode={isDarkMode}\n\t\t\trenderingShapes={renderingShapes}\n\t\t\tonMount={initialEffectPromise.resolve}\n\t\t\twaitUntil={exportDelay.waitUntil}\n\t\t>\n\t\t\t{}\n\t\t</SvgExport>\n\t)\n\n\treturn { jsx: svg, width: w, height: h, exportDelay }\n}\n\nfunction SvgExport({\n\teditor,\n\tpreserveAspectRatio,\n\tscale,\n\tpixelRatio,\n\tbbox,\n\tbackground,\n\tsingleFrameShapeId,\n\tisDarkMode,\n\trenderingShapes,\n\tonMount,\n\twaitUntil,\n}: {\n\teditor: Editor\n\tpreserveAspectRatio?: string\n\tscale: number\n\tpixelRatio: number | null\n\tbbox: Box\n\tbackground: boolean\n\tsingleFrameShapeId: TLShapeId | null\n\tisDarkMode: boolean\n\trenderingShapes: TLRenderingShape[]\n\tonMount(): void\n\twaitUntil(promise: Promise<void>): void\n}) {\n\tconst masksId = useUniqueSafeId()\n\tconst theme = getDefaultColorTheme({ isDarkMode })\n\n\tconst stateAtom = useAtom<{\n\t\tdefsById: Record<\n\t\t\tstring,\n\t\t\t{ pending: false; element: ReactNode } | { pending: true; element: Promise<ReactNode> }\n\t\t>\n\t\tshapeElements: ReactElement[] | null\n\t}>('export state', { defsById: {}, shapeElements: null })\n\tconst { defsById, shapeElements } = useValue(stateAtom)\n\n\tconst addExportDef = useEvent((def: SvgExportDef) => {\n\t\tstateAtom.update((state) => {\n\t\t\tif (hasOwnProperty(state.defsById, def.key)) return state\n\n\t\t\tconst promise = Promise.resolve(def.getElement())\n\t\t\twaitUntil(\n\t\t\t\tpromise.then((result) => {\n\t\t\t\t\tstateAtom.update((state) => ({\n\t\t\t\t\t\t...state,\n\t\t\t\t\t\tdefsById: { ...state.defsById, [def.key]: { pending: false, element: result } },\n\t\t\t\t\t}))\n\t\t\t\t})\n\t\t\t)\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\tdefsById: { ...state.defsById, [def.key]: { pending: true, element: promise } },\n\t\t\t}\n\t\t})\n\t})\n\n\tconst exportContext = useMemo(\n\t\t(): SvgExportContext => ({\n\t\t\tisDarkMode,\n\t\t\twaitUntil,\n\t\t\taddExportDef,\n\t\t\tscale,\n\t\t\tpixelRatio,\n\t\t\tasync resolveAssetUrl(assetId, width) {\n\t\t\t\tconst asset = editor.getAsset(assetId)\n\t\t\t\tif (!asset || (asset.type !== 'image' && asset.type !== 'video')) return null\n\n\t\t\t\treturn await editor.resolveAssetUrl(assetId, {\n\t\t\t\t\tscreenScale: scale * (width / asset.props.w),\n\t\t\t\t\tshouldResolveToOriginal: pixelRatio === null,\n\t\t\t\t\tdpr: pixelRatio ?? undefined,\n\t\t\t\t})\n\t\t\t},\n\t\t}),\n\t\t[isDarkMode, waitUntil, addExportDef, scale, pixelRatio, editor]\n\t)\n\n\tconst didRenderRef = useRef(false)\n\tuseLayoutEffect(() => {\n\t\tif (didRenderRef.current) {\n\t\t\tthrow new Error('SvgExport should only render once - do not use with react strict mode')\n\t\t}\n\t\tdidRenderRef.current = true\n\t\t;(async () => {\n\t\t\tconst shapeDefs: Record<string, { pending: false; element: ReactElement }> = {}\n\n\t\t\t// Then render everything. The shapes with assets should all hit the cache\n\t\t\tconst unorderedShapeElementPromises = renderingShapes.map(\n\t\t\t\tasync ({ id, opacity, index, backgroundIndex }) => {\n\t\t\t\t\t// Don't render the frame if we're only exporting a single frame and it's children\n\t\t\t\t\tif (id === singleFrameShapeId) return []\n\n\t\t\t\t\tconst shape = editor.getShape(id)!\n\n\t\t\t\t\tif (editor.isShapeOfType<TLGroupShape>(shape, 'group')) return []\n\n\t\t\t\t\tconst elements = []\n\t\t\t\t\tconst util = editor.getShapeUtil(shape)\n\n\t\t\t\t\tif (util.toSvg || util.toBackgroundSvg) {\n\t\t\t\t\t\t// If the shape has any sort of custom svg export, let's use that.\n\t\t\t\t\t\tconst [toSvgResult, toBackgroundSvgResult] = await Promise.all([\n\t\t\t\t\t\t\tutil.toSvg?.(shape, exportContext),\n\t\t\t\t\t\t\tutil.toBackgroundSvg?.(shape, exportContext),\n\t\t\t\t\t\t])\n\n\t\t\t\t\t\tconst pageTransform = editor.getShapePageTransform(shape)\n\t\t\t\t\t\tlet pageTransformString = pageTransform!.toCssString()\n\t\t\t\t\t\tlet scale = 1\n\t\t\t\t\t\tif ('scale' in shape.props) {\n\t\t\t\t\t\t\tif (shape.props.scale !== 1) {\n\t\t\t\t\t\t\t\tscale = shape.props.scale\n\t\t\t\t\t\t\t\tpageTransformString = `${pageTransformString} scale(${shape.props.scale}, ${shape.props.scale})`\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Create svg mask if shape has a frame as parent\n\t\t\t\t\t\tconst pageMask = editor.getShapeMask(shape.id)\n\t\t\t\t\t\tconst shapeMask = pageMask\n\t\t\t\t\t\t\t? Mat.From(Mat.Inverse(pageTransform)).applyToPoints(pageMask)\n\t\t\t\t\t\t\t: null\n\t\t\t\t\t\tconst shapeMaskId = suffixSafeId(masksId, shape.id)\n\t\t\t\t\t\tif (shapeMask) {\n\t\t\t\t\t\t\t// Create a clip path and add it to defs\n\t\t\t\t\t\t\tshapeDefs[shapeMaskId] = {\n\t\t\t\t\t\t\t\tpending: false,\n\t\t\t\t\t\t\t\telement: (\n\t\t\t\t\t\t\t\t\t<clipPath id={shapeMaskId}>\n\t\t\t\t\t\t\t\t\t\t{/* Create a polyline mask that does the clipping */}\n\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\td={`M${shapeMask.map(({ x, y }) => `${x / scale},${y / scale}`).join('L')}Z`}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t</clipPath>\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 (toSvgResult) {\n\t\t\t\t\t\t\telements.push({\n\t\t\t\t\t\t\t\tzIndex: index,\n\t\t\t\t\t\t\t\telement: (\n\t\t\t\t\t\t\t\t\t<g\n\t\t\t\t\t\t\t\t\t\tkey={`fg_${shape.id}`}\n\t\t\t\t\t\t\t\t\t\ttransform={pageTransformString}\n\t\t\t\t\t\t\t\t\t\topacity={opacity}\n\t\t\t\t\t\t\t\t\t\tclipPath={pageMask ? `url(#${shapeMaskId})` : undefined}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{toSvgResult}\n\t\t\t\t\t\t\t\t\t</g>\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\tif (toBackgroundSvgResult) {\n\t\t\t\t\t\t\telements.push({\n\t\t\t\t\t\t\t\tzIndex: backgroundIndex,\n\t\t\t\t\t\t\t\telement: (\n\t\t\t\t\t\t\t\t\t<g\n\t\t\t\t\t\t\t\t\t\tkey={`bg_${shape.id}`}\n\t\t\t\t\t\t\t\t\t\ttransform={pageTransformString}\n\t\t\t\t\t\t\t\t\t\topacity={opacity}\n\t\t\t\t\t\t\t\t\t\tclipPath={pageMask ? `url(#${shapeMaskId})` : undefined}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{toBackgroundSvgResult}\n\t\t\t\t\t\t\t\t\t</g>\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} else {\n\t\t\t\t\t\t// If the shape doesn't have a custom svg export, we'll use its normal HTML\n\t\t\t\t\t\t// renderer in a foreignObject.\n\t\t\t\t\t\telements.push({\n\t\t\t\t\t\t\tzIndex: index,\n\t\t\t\t\t\t\telement: (\n\t\t\t\t\t\t\t\t<ForeignObjectShape\n\t\t\t\t\t\t\t\t\tkey={`fg_${shape.id}`}\n\t\t\t\t\t\t\t\t\tshape={shape}\n\t\t\t\t\t\t\t\t\tutil={util}\n\t\t\t\t\t\t\t\t\tcomponent={InnerShape}\n\t\t\t\t\t\t\t\t\tclassName=\"tl-shape\"\n\t\t\t\t\t\t\t\t\tbbox={bbox}\n\t\t\t\t\t\t\t\t\topacity={opacity}\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 (util.backgroundComponent) {\n\t\t\t\t\t\t\telements.push({\n\t\t\t\t\t\t\t\tzIndex: backgroundIndex,\n\t\t\t\t\t\t\t\telement: (\n\t\t\t\t\t\t\t\t\t<ForeignObjectShape\n\t\t\t\t\t\t\t\t\t\tkey={`bg_${shape.id}`}\n\t\t\t\t\t\t\t\t\t\tshape={shape}\n\t\t\t\t\t\t\t\t\t\tutil={util}\n\t\t\t\t\t\t\t\t\t\tcomponent={InnerShapeBackground}\n\t\t\t\t\t\t\t\t\t\tclassName=\"tl-shape tl-shape-background\"\n\t\t\t\t\t\t\t\t\t\tbbox={bbox}\n\t\t\t\t\t\t\t\t\t\topacity={opacity}\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\n\t\t\t\t\treturn elements\n\t\t\t\t}\n\t\t\t)\n\n\t\t\tconst unorderedShapeElements = (await Promise.all(unorderedShapeElementPromises)).flat()\n\n\t\t\tflushSync(() => {\n\t\t\t\tstateAtom.update((state) => ({\n\t\t\t\t\t...state,\n\t\t\t\t\tshapeElements: unorderedShapeElements\n\t\t\t\t\t\t.sort((a, b) => a.zIndex - b.zIndex)\n\t\t\t\t\t\t.map(({ element }) => element),\n\t\t\t\t\tdefsById: { ...state.defsById, ...shapeDefs },\n\t\t\t\t}))\n\t\t\t})\n\t\t})()\n\t}, [bbox, editor, exportContext, masksId, renderingShapes, singleFrameShapeId, stateAtom])\n\n\tuseEffect(() => {\n\t\tconst fontsInUse = new Set<TLFontFace>()\n\t\tfor (const { id } of renderingShapes) {\n\t\t\tfor (const font of editor.fonts.getShapeFontFaces(id)) {\n\t\t\t\tfontsInUse.add(font)\n\t\t\t}\n\t\t}\n\n\t\tfor (const font of fontsInUse) {\n\t\t\taddExportDef({\n\t\t\t\tkey: uniqueId(),\n\t\t\t\tgetElement: async () => {\n\t\t\t\t\tconst declaration = await editor.fonts.toEmbeddedCssDeclaration(font)\n\t\t\t\t\treturn <style nonce={editor.options.nonce}>{declaration}</style>\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t}, [editor, renderingShapes, addExportDef])\n\n\tuseEffect(() => {\n\t\tif (shapeElements === null) return\n\t\tonMount()\n\t}, [onMount, shapeElements])\n\n\treturn (\n\t\t<SvgExportContextProvider editor={editor} context={exportContext}>\n\t\t\t<svg\n\t\t\t\tpreserveAspectRatio={preserveAspectRatio}\n\t\t\t\tdirection=\"ltr\"\n\t\t\t\twidth={bbox.width * scale}\n\t\t\t\theight={bbox.height * scale}\n\t\t\t\tviewBox={`${bbox.minX} ${bbox.minY} ${bbox.width} ${bbox.height}`}\n\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\tstyle={{\n\t\t\t\t\tbackgroundColor: background\n\t\t\t\t\t\t? singleFrameShapeId\n\t\t\t\t\t\t\t? theme.solid\n\t\t\t\t\t\t\t: theme.background\n\t\t\t\t\t\t: 'transparent',\n\t\t\t\t}}\n\t\t\t\tdata-color-mode={isDarkMode ? 'dark' : 'light'}\n\t\t\t\tclassName={`tl-container tl-theme__force-sRGB ${isDarkMode ? 'tl-theme__dark' : 'tl-theme__light'}`}\n\t\t\t>\n\t\t\t\t<defs>\n\t\t\t\t\t{Object.entries(defsById).map(([key, def]) =>\n\t\t\t\t\t\tdef.pending ? null : <Fragment key={key}>{def.element}</Fragment>\n\t\t\t\t\t)}\n\t\t\t\t</defs>\n\t\t\t\t{shapeElements}\n\t\t\t</svg>\n\t\t</SvgExportContextProvider>\n\t)\n}\n\nfunction ForeignObjectShape({\n\tshape,\n\tutil,\n\tclassName,\n\tcomponent: Component,\n\tbbox,\n\topacity,\n}: {\n\tshape: TLShape\n\tutil: ShapeUtil\n\tclassName?: string\n\tcomponent: ComponentType<{ shape: TLShape; util: ShapeUtil }>\n\tbbox: Box\n\topacity: number\n}) {\n\tconst editor = useEditor()\n\n\tconst transform = Mat.Translate(-bbox.minX, -bbox.minY).multiply(\n\t\teditor.getShapePageTransform(shape.id)!\n\t)\n\n\tconst bounds = editor.getShapeGeometry(shape.id).bounds\n\tconst width = Math.max(bounds.width, 1)\n\tconst height = Math.max(bounds.height, 1)\n\n\treturn (\n\t\t<ErrorBoundary fallback={() => null}>\n\t\t\t<foreignObject\n\t\t\t\tx={bbox.minX}\n\t\t\t\ty={bbox.minY}\n\t\t\t\twidth={bbox.w}\n\t\t\t\theight={bbox.h}\n\t\t\t\tclassName=\"tl-shape-foreign-object tl-export-embed-styles\"\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tclassName={className}\n\t\t\t\t\tdata-shape-type={shape.type}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tclipPath: editor.getShapeClipPath(shape.id),\n\t\t\t\t\t\ttransform: transform.toCssString(),\n\t\t\t\t\t\twidth,\n\t\t\t\t\t\theight,\n\t\t\t\t\t\topacity,\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<Component shape={shape} util={util} />\n\t\t\t\t</div>\n\t\t\t</foreignObject>\n\t\t</ErrorBoundary>\n\t)\n}\n"],
|
|
5
|
-
"mappings": "AAqGE,
|
|
4
|
+
"sourcesContent": ["import { useAtom, useValue } from '@tldraw/state-react'\nimport {\n\tTLFrameShape,\n\tTLGroupShape,\n\tTLShape,\n\tTLShapeId,\n\tgetDefaultColorTheme,\n} from '@tldraw/tlschema'\nimport { hasOwnProperty, promiseWithResolve, uniqueId } from '@tldraw/utils'\nimport {\n\tComponentType,\n\tFragment,\n\tReactElement,\n\tReactNode,\n\tuseEffect,\n\tuseLayoutEffect,\n\tuseMemo,\n\tuseRef,\n} from 'react'\nimport { flushSync } from 'react-dom'\nimport { ErrorBoundary } from '../components/ErrorBoundary'\nimport { InnerShape, InnerShapeBackground } from '../components/Shape'\nimport { Editor, TLRenderingShape } from '../editor/Editor'\nimport { TLFontFace } from '../editor/managers/FontManager'\nimport { ShapeUtil } from '../editor/shapes/ShapeUtil'\nimport {\n\tSvgExportContext,\n\tSvgExportContextProvider,\n\tSvgExportDef,\n} from '../editor/types/SvgExportContext'\nimport { TLImageExportOptions } from '../editor/types/misc-types'\nimport { useEditor } from '../hooks/useEditor'\nimport { useEvent } from '../hooks/useEvent'\nimport { suffixSafeId, useUniqueSafeId } from '../hooks/useSafeId'\nimport { Box } from '../primitives/Box'\nimport { Mat } from '../primitives/Mat'\nimport { ExportDelay } from './ExportDelay'\n\nexport function getSvgJsx(editor: Editor, ids: TLShapeId[], opts: TLImageExportOptions = {}) {\n\tif (!window.document) throw Error('No document')\n\n\tconst {\n\t\tscale = 1,\n\t\t// should we include the background in the export? or is it transparent?\n\t\tbackground = editor.getInstanceState().exportBackground,\n\t\tpadding = editor.options.defaultSvgPadding,\n\t\tpreserveAspectRatio,\n\t} = opts\n\n\tconst isDarkMode = opts.darkMode ?? editor.user.getIsDarkMode()\n\n\t// ---Figure out which shapes we need to include\n\tconst shapeIdsToInclude = editor.getShapeAndDescendantIds(ids)\n\tconst renderingShapes = editor\n\t\t.getUnorderedRenderingShapes(false)\n\t\t.filter(({ id }) => shapeIdsToInclude.has(id))\n\n\t// --- Common bounding box of all shapes\n\tlet bbox: null | Box = null\n\tif (opts.bounds) {\n\t\tbbox = opts.bounds\n\t} else {\n\t\tfor (const { id } of renderingShapes) {\n\t\t\tconst maskedPageBounds = editor.getShapeMaskedPageBounds(id)\n\t\t\tif (!maskedPageBounds) continue\n\t\t\tif (bbox) {\n\t\t\t\tbbox.union(maskedPageBounds)\n\t\t\t} else {\n\t\t\t\tbbox = maskedPageBounds.clone()\n\t\t\t}\n\t\t}\n\t}\n\n\t// no unmasked shapes to export\n\tif (!bbox) return\n\n\tconst singleFrameShapeId =\n\t\tids.length === 1 && editor.isShapeOfType<TLFrameShape>(editor.getShape(ids[0])!, 'frame')\n\t\t\t? ids[0]\n\t\t\t: null\n\tif (!singleFrameShapeId) {\n\t\t// Expand by an extra 32 pixels\n\t\tbbox.expandBy(padding)\n\t}\n\n\t// We want the svg image to be BIGGER THAN USUAL to account for image quality\n\tconst w = bbox.width * scale\n\tconst h = bbox.height * scale\n\n\ttry {\n\t\tdocument.body.focus?.() // weird but necessary\n\t} catch {\n\t\t// not implemented\n\t}\n\n\tconst exportDelay = new ExportDelay(editor.options.maxExportDelayMs)\n\n\tconst initialEffectPromise = promiseWithResolve<void>()\n\texportDelay.waitUntil(initialEffectPromise)\n\n\tconst svg = (\n\t\t<SvgExport\n\t\t\teditor={editor}\n\t\t\tpreserveAspectRatio={preserveAspectRatio}\n\t\t\tscale={scale}\n\t\t\tpixelRatio={opts.pixelRatio ?? null}\n\t\t\tbbox={bbox}\n\t\t\tbackground={background}\n\t\t\tsingleFrameShapeId={singleFrameShapeId}\n\t\t\tisDarkMode={isDarkMode}\n\t\t\trenderingShapes={renderingShapes}\n\t\t\tonMount={initialEffectPromise.resolve}\n\t\t\twaitUntil={exportDelay.waitUntil}\n\t\t>\n\t\t\t{}\n\t\t</SvgExport>\n\t)\n\n\treturn { jsx: svg, width: w, height: h, exportDelay }\n}\n\nfunction SvgExport({\n\teditor,\n\tpreserveAspectRatio,\n\tscale,\n\tpixelRatio,\n\tbbox,\n\tbackground,\n\tsingleFrameShapeId,\n\tisDarkMode,\n\trenderingShapes,\n\tonMount,\n\twaitUntil,\n}: {\n\teditor: Editor\n\tpreserveAspectRatio?: string\n\tscale: number\n\tpixelRatio: number | null\n\tbbox: Box\n\tbackground: boolean\n\tsingleFrameShapeId: TLShapeId | null\n\tisDarkMode: boolean\n\trenderingShapes: TLRenderingShape[]\n\tonMount(): void\n\twaitUntil(promise: Promise<void>): void\n}) {\n\tconst masksId = useUniqueSafeId()\n\tconst theme = getDefaultColorTheme({ isDarkMode })\n\n\tconst stateAtom = useAtom<{\n\t\tdefsById: Record<\n\t\t\tstring,\n\t\t\t{ pending: false; element: ReactNode } | { pending: true; element: Promise<ReactNode> }\n\t\t>\n\t\tshapeElements: ReactElement[] | null\n\t}>('export state', { defsById: {}, shapeElements: null })\n\tconst { defsById, shapeElements } = useValue(stateAtom)\n\n\tconst addExportDef = useEvent((def: SvgExportDef) => {\n\t\tstateAtom.update((state) => {\n\t\t\tif (hasOwnProperty(state.defsById, def.key)) return state\n\n\t\t\tconst promise = Promise.resolve(def.getElement())\n\t\t\twaitUntil(\n\t\t\t\tpromise.then((result) => {\n\t\t\t\t\tstateAtom.update((state) => ({\n\t\t\t\t\t\t...state,\n\t\t\t\t\t\tdefsById: { ...state.defsById, [def.key]: { pending: false, element: result } },\n\t\t\t\t\t}))\n\t\t\t\t})\n\t\t\t)\n\t\t\treturn {\n\t\t\t\t...state,\n\t\t\t\tdefsById: { ...state.defsById, [def.key]: { pending: true, element: promise } },\n\t\t\t}\n\t\t})\n\t})\n\n\tconst exportContext = useMemo(\n\t\t(): SvgExportContext => ({\n\t\t\tisDarkMode,\n\t\t\twaitUntil,\n\t\t\taddExportDef,\n\t\t\tscale,\n\t\t\tpixelRatio,\n\t\t\tasync resolveAssetUrl(assetId, width) {\n\t\t\t\tconst asset = editor.getAsset(assetId)\n\t\t\t\tif (!asset || (asset.type !== 'image' && asset.type !== 'video')) return null\n\n\t\t\t\treturn await editor.resolveAssetUrl(assetId, {\n\t\t\t\t\tscreenScale: scale * (width / asset.props.w),\n\t\t\t\t\tshouldResolveToOriginal: pixelRatio === null,\n\t\t\t\t\tdpr: pixelRatio ?? undefined,\n\t\t\t\t})\n\t\t\t},\n\t\t}),\n\t\t[isDarkMode, waitUntil, addExportDef, scale, pixelRatio, editor]\n\t)\n\n\tconst didRenderRef = useRef(false)\n\tuseLayoutEffect(() => {\n\t\tif (didRenderRef.current) {\n\t\t\tthrow new Error('SvgExport should only render once - do not use with react strict mode')\n\t\t}\n\t\tdidRenderRef.current = true\n\t\t;(async () => {\n\t\t\tconst shapeDefs: Record<string, { pending: false; element: ReactElement }> = {}\n\n\t\t\t// Then render everything. The shapes with assets should all hit the cache\n\t\t\tconst unorderedShapeElementPromises = renderingShapes.map(\n\t\t\t\tasync ({ id, opacity, index, backgroundIndex }) => {\n\t\t\t\t\t// Don't render the frame if we're only exporting a single frame and it's children\n\t\t\t\t\tif (id === singleFrameShapeId) return []\n\n\t\t\t\t\tconst shape = editor.getShape(id)!\n\n\t\t\t\t\tif (editor.isShapeOfType<TLGroupShape>(shape, 'group')) return []\n\n\t\t\t\t\tconst elements = []\n\t\t\t\t\tconst util = editor.getShapeUtil(shape)\n\n\t\t\t\t\tif (util.toSvg || util.toBackgroundSvg) {\n\t\t\t\t\t\t// If the shape has any sort of custom svg export, let's use that.\n\t\t\t\t\t\tconst [toSvgResult, toBackgroundSvgResult] = await Promise.all([\n\t\t\t\t\t\t\tutil.toSvg?.(shape, exportContext),\n\t\t\t\t\t\t\tutil.toBackgroundSvg?.(shape, exportContext),\n\t\t\t\t\t\t])\n\n\t\t\t\t\t\tconst pageTransform = editor.getShapePageTransform(shape)\n\t\t\t\t\t\tlet pageTransformString = pageTransform!.toCssString()\n\t\t\t\t\t\tlet scale = 1\n\t\t\t\t\t\tif ('scale' in shape.props) {\n\t\t\t\t\t\t\tif (shape.props.scale !== 1) {\n\t\t\t\t\t\t\t\tscale = shape.props.scale\n\t\t\t\t\t\t\t\tpageTransformString = `${pageTransformString} scale(${shape.props.scale}, ${shape.props.scale})`\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Create svg mask if shape has a frame as parent\n\t\t\t\t\t\tconst pageMask = editor.getShapeMask(shape.id)\n\t\t\t\t\t\tconst shapeMask = pageMask\n\t\t\t\t\t\t\t? Mat.From(Mat.Inverse(pageTransform)).applyToPoints(pageMask)\n\t\t\t\t\t\t\t: null\n\t\t\t\t\t\tconst shapeMaskId = suffixSafeId(masksId, shape.id)\n\t\t\t\t\t\tif (shapeMask) {\n\t\t\t\t\t\t\t// Create a clip path and add it to defs\n\t\t\t\t\t\t\tshapeDefs[shapeMaskId] = {\n\t\t\t\t\t\t\t\tpending: false,\n\t\t\t\t\t\t\t\telement: (\n\t\t\t\t\t\t\t\t\t<clipPath id={shapeMaskId}>\n\t\t\t\t\t\t\t\t\t\t{/* Create a polyline mask that does the clipping */}\n\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\td={`M${shapeMask.map(({ x, y }) => `${x / scale},${y / scale}`).join('L')}Z`}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t</clipPath>\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 (toSvgResult) {\n\t\t\t\t\t\t\telements.push({\n\t\t\t\t\t\t\t\tzIndex: index,\n\t\t\t\t\t\t\t\telement: (\n\t\t\t\t\t\t\t\t\t<g\n\t\t\t\t\t\t\t\t\t\tkey={`fg_${shape.id}`}\n\t\t\t\t\t\t\t\t\t\ttransform={pageTransformString}\n\t\t\t\t\t\t\t\t\t\topacity={opacity}\n\t\t\t\t\t\t\t\t\t\tclipPath={pageMask ? `url(#${shapeMaskId})` : undefined}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{toSvgResult}\n\t\t\t\t\t\t\t\t\t</g>\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\tif (toBackgroundSvgResult) {\n\t\t\t\t\t\t\telements.push({\n\t\t\t\t\t\t\t\tzIndex: backgroundIndex,\n\t\t\t\t\t\t\t\telement: (\n\t\t\t\t\t\t\t\t\t<g\n\t\t\t\t\t\t\t\t\t\tkey={`bg_${shape.id}`}\n\t\t\t\t\t\t\t\t\t\ttransform={pageTransformString}\n\t\t\t\t\t\t\t\t\t\topacity={opacity}\n\t\t\t\t\t\t\t\t\t\tclipPath={pageMask ? `url(#${shapeMaskId})` : undefined}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{toBackgroundSvgResult}\n\t\t\t\t\t\t\t\t\t</g>\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} else {\n\t\t\t\t\t\t// If the shape doesn't have a custom svg export, we'll use its normal HTML\n\t\t\t\t\t\t// renderer in a foreignObject.\n\t\t\t\t\t\telements.push({\n\t\t\t\t\t\t\tzIndex: index,\n\t\t\t\t\t\t\telement: (\n\t\t\t\t\t\t\t\t<ForeignObjectShape\n\t\t\t\t\t\t\t\t\tkey={`fg_${shape.id}`}\n\t\t\t\t\t\t\t\t\tshape={shape}\n\t\t\t\t\t\t\t\t\tutil={util}\n\t\t\t\t\t\t\t\t\tcomponent={InnerShape}\n\t\t\t\t\t\t\t\t\tclassName=\"tl-shape\"\n\t\t\t\t\t\t\t\t\tbbox={bbox}\n\t\t\t\t\t\t\t\t\topacity={opacity}\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 (util.backgroundComponent) {\n\t\t\t\t\t\t\telements.push({\n\t\t\t\t\t\t\t\tzIndex: backgroundIndex,\n\t\t\t\t\t\t\t\telement: (\n\t\t\t\t\t\t\t\t\t<ForeignObjectShape\n\t\t\t\t\t\t\t\t\t\tkey={`bg_${shape.id}`}\n\t\t\t\t\t\t\t\t\t\tshape={shape}\n\t\t\t\t\t\t\t\t\t\tutil={util}\n\t\t\t\t\t\t\t\t\t\tcomponent={InnerShapeBackground}\n\t\t\t\t\t\t\t\t\t\tclassName=\"tl-shape tl-shape-background\"\n\t\t\t\t\t\t\t\t\t\tbbox={bbox}\n\t\t\t\t\t\t\t\t\t\topacity={opacity}\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\n\t\t\t\t\treturn elements\n\t\t\t\t}\n\t\t\t)\n\n\t\t\tconst unorderedShapeElements = (await Promise.all(unorderedShapeElementPromises)).flat()\n\n\t\t\tflushSync(() => {\n\t\t\t\tstateAtom.update((state) => ({\n\t\t\t\t\t...state,\n\t\t\t\t\tshapeElements: unorderedShapeElements\n\t\t\t\t\t\t.sort((a, b) => a.zIndex - b.zIndex)\n\t\t\t\t\t\t.map(({ element }) => element),\n\t\t\t\t\tdefsById: { ...state.defsById, ...shapeDefs },\n\t\t\t\t}))\n\t\t\t})\n\t\t})()\n\t}, [bbox, editor, exportContext, masksId, renderingShapes, singleFrameShapeId, stateAtom])\n\n\tuseEffect(() => {\n\t\tconst fontsInUse = new Set<TLFontFace>()\n\t\tfor (const { id } of renderingShapes) {\n\t\t\tfor (const font of editor.fonts.getShapeFontFaces(id)) {\n\t\t\t\tfontsInUse.add(font)\n\t\t\t}\n\t\t}\n\n\t\tfor (const font of fontsInUse) {\n\t\t\taddExportDef({\n\t\t\t\tkey: uniqueId(),\n\t\t\t\tgetElement: async () => {\n\t\t\t\t\tconst declaration = await editor.fonts.toEmbeddedCssDeclaration(font)\n\t\t\t\t\treturn <style nonce={editor.options.nonce}>{declaration}</style>\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t}, [editor, renderingShapes, addExportDef])\n\n\tuseEffect(() => {\n\t\tif (shapeElements === null) return\n\t\tonMount()\n\t}, [onMount, shapeElements])\n\n\tlet backgroundColor = background ? theme.background : 'transparent'\n\n\tif (singleFrameShapeId && background) {\n\t\tconst frameShapeUtil = editor.getShapeUtil('frame') as any as\n\t\t\t| undefined\n\t\t\t| { options: { showColors: boolean } }\n\t\tif (frameShapeUtil?.options.showColors) {\n\t\t\tconst shape = editor.getShape(singleFrameShapeId)! as TLFrameShape\n\t\t\tconst color = theme[shape.props.color]\n\t\t\tbackgroundColor = color.frame.fill\n\t\t} else {\n\t\t\tbackgroundColor = theme.solid\n\t\t}\n\t}\n\n\treturn (\n\t\t<SvgExportContextProvider editor={editor} context={exportContext}>\n\t\t\t<svg\n\t\t\t\tpreserveAspectRatio={preserveAspectRatio}\n\t\t\t\tdirection=\"ltr\"\n\t\t\t\twidth={bbox.width * scale}\n\t\t\t\theight={bbox.height * scale}\n\t\t\t\tviewBox={`${bbox.minX} ${bbox.minY} ${bbox.width} ${bbox.height}`}\n\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\tstrokeLinejoin=\"round\"\n\t\t\t\tstyle={{ backgroundColor }}\n\t\t\t\tdata-color-mode={isDarkMode ? 'dark' : 'light'}\n\t\t\t\tclassName={`tl-container tl-theme__force-sRGB ${isDarkMode ? 'tl-theme__dark' : 'tl-theme__light'}`}\n\t\t\t>\n\t\t\t\t<defs>\n\t\t\t\t\t{Object.entries(defsById).map(([key, def]) =>\n\t\t\t\t\t\tdef.pending ? null : <Fragment key={key}>{def.element}</Fragment>\n\t\t\t\t\t)}\n\t\t\t\t</defs>\n\t\t\t\t{shapeElements}\n\t\t\t</svg>\n\t\t</SvgExportContextProvider>\n\t)\n}\n\nfunction ForeignObjectShape({\n\tshape,\n\tutil,\n\tclassName,\n\tcomponent: Component,\n\tbbox,\n\topacity,\n}: {\n\tshape: TLShape\n\tutil: ShapeUtil\n\tclassName?: string\n\tcomponent: ComponentType<{ shape: TLShape; util: ShapeUtil }>\n\tbbox: Box\n\topacity: number\n}) {\n\tconst editor = useEditor()\n\n\tconst transform = Mat.Translate(-bbox.minX, -bbox.minY).multiply(\n\t\teditor.getShapePageTransform(shape.id)!\n\t)\n\n\tconst bounds = editor.getShapeGeometry(shape.id).bounds\n\tconst width = Math.max(bounds.width, 1)\n\tconst height = Math.max(bounds.height, 1)\n\n\treturn (\n\t\t<ErrorBoundary fallback={() => null}>\n\t\t\t<foreignObject\n\t\t\t\tx={bbox.minX}\n\t\t\t\ty={bbox.minY}\n\t\t\t\twidth={bbox.w}\n\t\t\t\theight={bbox.h}\n\t\t\t\tclassName=\"tl-shape-foreign-object tl-export-embed-styles\"\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tclassName={className}\n\t\t\t\t\tdata-shape-type={shape.type}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tclipPath: editor.getShapeClipPath(shape.id),\n\t\t\t\t\t\ttransform: transform.toCssString(),\n\t\t\t\t\t\twidth,\n\t\t\t\t\t\theight,\n\t\t\t\t\t\topacity,\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t<Component shape={shape} util={util} />\n\t\t\t\t</div>\n\t\t\t</foreignObject>\n\t\t</ErrorBoundary>\n\t)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["state", "scale"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function gcd(a, b) {
|
|
2
|
+
return b === 0 ? a : gcd(b, a % b);
|
|
3
|
+
}
|
|
4
|
+
function nearestMultiple(float) {
|
|
5
|
+
const decimal = float.toString().split(".")[1];
|
|
6
|
+
if (!decimal) return 1;
|
|
7
|
+
const denominator = Math.pow(10, decimal.length);
|
|
8
|
+
const numerator = parseInt(decimal, 10);
|
|
9
|
+
return denominator / gcd(numerator, denominator);
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
nearestMultiple
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=nearestMultiple.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/utils/nearestMultiple.ts"],
|
|
4
|
+
"sourcesContent": ["// Euclidean algorithm to find the GCD\nfunction gcd(a: number, b: number): number {\n\treturn b === 0 ? a : gcd(b, a % b)\n}\n\n// Returns the lowest value that the given number can be multiplied by to reach an integer\nexport function nearestMultiple(float: number) {\n\tconst decimal = float.toString().split('.')[1]\n\tif (!decimal) return 1\n\tconst denominator = Math.pow(10, decimal.length)\n\tconst numerator = parseInt(decimal, 10)\n\treturn denominator / gcd(numerator, denominator)\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,IAAI,GAAW,GAAmB;AAC1C,SAAO,MAAM,IAAI,IAAI,IAAI,GAAG,IAAI,CAAC;AAClC;AAGO,SAAS,gBAAgB,OAAe;AAC9C,QAAM,UAAU,MAAM,SAAS,EAAE,MAAM,GAAG,EAAE,CAAC;AAC7C,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,cAAc,KAAK,IAAI,IAAI,QAAQ,MAAM;AAC/C,QAAM,YAAY,SAAS,SAAS,EAAE;AACtC,SAAO,cAAc,IAAI,WAAW,WAAW;AAChD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist-esm/version.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const version = "3.13.0-canary.
|
|
1
|
+
const version = "3.13.0-canary.8754dedf4cf0";
|
|
2
2
|
const publishDates = {
|
|
3
3
|
major: "2024-09-13T14:36:29.063Z",
|
|
4
|
-
minor: "2025-
|
|
5
|
-
patch: "2025-
|
|
4
|
+
minor: "2025-05-02T10:16:31.753Z",
|
|
5
|
+
patch: "2025-05-02T10:16:31.753Z"
|
|
6
6
|
};
|
|
7
7
|
export {
|
|
8
8
|
publishDates,
|
package/dist-esm/version.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/version.ts"],
|
|
4
|
-
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.13.0-canary.
|
|
4
|
+
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.13.0-canary.8754dedf4cf0'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-05-02T10:16:31.753Z',\n\tpatch: '2025-05-02T10:16:31.753Z',\n}\n"],
|
|
5
5
|
"mappings": "AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/editor",
|
|
3
3
|
"description": "A tiny little drawing app (editor).",
|
|
4
|
-
"version": "3.13.0-canary.
|
|
4
|
+
"version": "3.13.0-canary.8754dedf4cf0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
"@tiptap/core": "^2.9.1",
|
|
49
49
|
"@tiptap/pm": "^2.9.1",
|
|
50
50
|
"@tiptap/react": "^2.9.1",
|
|
51
|
-
"@tldraw/state": "3.13.0-canary.
|
|
52
|
-
"@tldraw/state-react": "3.13.0-canary.
|
|
53
|
-
"@tldraw/store": "3.13.0-canary.
|
|
54
|
-
"@tldraw/tlschema": "3.13.0-canary.
|
|
55
|
-
"@tldraw/utils": "3.13.0-canary.
|
|
56
|
-
"@tldraw/validate": "3.13.0-canary.
|
|
51
|
+
"@tldraw/state": "3.13.0-canary.8754dedf4cf0",
|
|
52
|
+
"@tldraw/state-react": "3.13.0-canary.8754dedf4cf0",
|
|
53
|
+
"@tldraw/store": "3.13.0-canary.8754dedf4cf0",
|
|
54
|
+
"@tldraw/tlschema": "3.13.0-canary.8754dedf4cf0",
|
|
55
|
+
"@tldraw/utils": "3.13.0-canary.8754dedf4cf0",
|
|
56
|
+
"@tldraw/validate": "3.13.0-canary.8754dedf4cf0",
|
|
57
57
|
"@types/core-js": "^2.5.8",
|
|
58
58
|
"@use-gesture/react": "^10.3.1",
|
|
59
59
|
"classnames": "^2.5.1",
|
|
@@ -27,6 +27,7 @@ export const Shape = memo(function Shape({
|
|
|
27
27
|
index,
|
|
28
28
|
backgroundIndex,
|
|
29
29
|
opacity,
|
|
30
|
+
dprMultiple,
|
|
30
31
|
}: {
|
|
31
32
|
id: TLShapeId
|
|
32
33
|
shape: TLShape
|
|
@@ -34,6 +35,7 @@ export const Shape = memo(function Shape({
|
|
|
34
35
|
index: number
|
|
35
36
|
backgroundIndex: number
|
|
36
37
|
opacity: number
|
|
38
|
+
dprMultiple: number
|
|
37
39
|
}) {
|
|
38
40
|
const editor = useEditor()
|
|
39
41
|
|
|
@@ -88,14 +90,18 @@ export const Shape = memo(function Shape({
|
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
// Width / Height
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
// We round the shape width and height up to the nearest multiple of dprMultiple
|
|
94
|
+
// to avoid the browser making miscalculations when applying the transform.
|
|
95
|
+
const widthRemainder = bounds.w % dprMultiple
|
|
96
|
+
const heightRemainder = bounds.h % dprMultiple
|
|
97
|
+
const width = widthRemainder === 0 ? bounds.w : bounds.w + (dprMultiple - widthRemainder)
|
|
98
|
+
const height = heightRemainder === 0 ? bounds.h : bounds.h + (dprMultiple - heightRemainder)
|
|
93
99
|
|
|
94
100
|
if (width !== prev.width || height !== prev.height) {
|
|
95
|
-
setStyleProperty(containerRef.current, 'width', width + 'px')
|
|
96
|
-
setStyleProperty(containerRef.current, 'height', height + 'px')
|
|
97
|
-
setStyleProperty(bgContainerRef.current, 'width', width + 'px')
|
|
98
|
-
setStyleProperty(bgContainerRef.current, 'height', height + 'px')
|
|
101
|
+
setStyleProperty(containerRef.current, 'width', Math.max(width, dprMultiple) + 'px')
|
|
102
|
+
setStyleProperty(containerRef.current, 'height', Math.max(height, dprMultiple) + 'px')
|
|
103
|
+
setStyleProperty(bgContainerRef.current, 'width', Math.max(width, dprMultiple) + 'px')
|
|
104
|
+
setStyleProperty(bgContainerRef.current, 'height', Math.max(height, dprMultiple) + 'px')
|
|
99
105
|
prev.width = width
|
|
100
106
|
prev.height = height
|
|
101
107
|
}
|
|
@@ -22,6 +22,7 @@ import { Vec } from '../../primitives/Vec'
|
|
|
22
22
|
import { toDomPrecision } from '../../primitives/utils'
|
|
23
23
|
import { debugFlags } from '../../utils/debug-flags'
|
|
24
24
|
import { setStyleProperty } from '../../utils/dom'
|
|
25
|
+
import { nearestMultiple } from '../../utils/nearestMultiple'
|
|
25
26
|
import { GeometryDebuggingView } from '../GeometryDebuggingView'
|
|
26
27
|
import { LiveCollaborators } from '../LiveCollaborators'
|
|
27
28
|
import { MenuClickCapture } from '../MenuClickCapture'
|
|
@@ -388,9 +389,18 @@ function ShapesWithSVGs() {
|
|
|
388
389
|
|
|
389
390
|
const renderingShapes = useValue('rendering shapes', () => editor.getRenderingShapes(), [editor])
|
|
390
391
|
|
|
392
|
+
const dprMultiple = useValue(
|
|
393
|
+
'dpr multiple',
|
|
394
|
+
() =>
|
|
395
|
+
// dprMultiple is the smallest number we can multiply dpr by to get an integer
|
|
396
|
+
// it's usually 1, 2, or 4 (for e.g. dpr of 2, 2.5 and 2.25 respectively)
|
|
397
|
+
nearestMultiple(Math.floor(editor.getInstanceState().devicePixelRatio * 100) / 100),
|
|
398
|
+
[editor]
|
|
399
|
+
)
|
|
400
|
+
|
|
391
401
|
return renderingShapes.map((result) => (
|
|
392
402
|
<Fragment key={result.id + '_fragment'}>
|
|
393
|
-
<Shape {...result} />
|
|
403
|
+
<Shape {...result} dprMultiple={dprMultiple} />
|
|
394
404
|
<DebugSvgCopy id={result.id} mode="iframe" />
|
|
395
405
|
</Fragment>
|
|
396
406
|
))
|
|
@@ -425,10 +435,19 @@ function ShapesToDisplay() {
|
|
|
425
435
|
|
|
426
436
|
const renderingShapes = useValue('rendering shapes', () => editor.getRenderingShapes(), [editor])
|
|
427
437
|
|
|
438
|
+
const dprMultiple = useValue(
|
|
439
|
+
'dpr multiple',
|
|
440
|
+
() =>
|
|
441
|
+
// dprMultiple is the smallest number we can multiply dpr by to get an integer
|
|
442
|
+
// it's usually 1, 2, or 4 (for e.g. dpr of 2, 2.5 and 2.25 respectively)
|
|
443
|
+
nearestMultiple(Math.floor(editor.getInstanceState().devicePixelRatio * 100) / 100),
|
|
444
|
+
[editor]
|
|
445
|
+
)
|
|
446
|
+
|
|
428
447
|
return (
|
|
429
448
|
<>
|
|
430
449
|
{renderingShapes.map((result) => (
|
|
431
|
-
<Shape key={result.id + '_shape'} {...result} />
|
|
450
|
+
<Shape key={result.id + '_shape'} {...result} dprMultiple={dprMultiple} />
|
|
432
451
|
))}
|
|
433
452
|
{tlenv.isSafari && <ReflowIfNeeded />}
|
|
434
453
|
</>
|
|
@@ -9,13 +9,21 @@ import { useEditorComponents } from '../../hooks/useEditorComponents'
|
|
|
9
9
|
import { OptionalErrorBoundary } from '../ErrorBoundary'
|
|
10
10
|
|
|
11
11
|
// need an extra layer of indirection here to allow hooks to be used inside the indicator render
|
|
12
|
-
const EvenInnererIndicator = memo(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
const EvenInnererIndicator = memo(
|
|
13
|
+
({ shape, util }: { shape: TLShape; util: ShapeUtil<any> }) => {
|
|
14
|
+
return useStateTracking('Indicator: ' + shape.type, () =>
|
|
15
|
+
// always fetch the latest shape from the store even if the props/meta have not changed, to avoid
|
|
16
|
+
// calling the render method with stale data.
|
|
17
|
+
util.indicator(util.editor.store.unsafeGetWithoutCapture(shape.id) as TLShape)
|
|
18
|
+
)
|
|
19
|
+
},
|
|
20
|
+
(prevProps, nextProps) => {
|
|
21
|
+
return (
|
|
22
|
+
prevProps.shape.props === nextProps.shape.props &&
|
|
23
|
+
prevProps.shape.meta === nextProps.shape.meta
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
)
|
|
19
27
|
|
|
20
28
|
const InnerIndicator = memo(({ editor, id }: { editor: Editor; id: TLShapeId }) => {
|
|
21
29
|
const shape = useValue('shape for indicator', () => editor.store.get(id), [editor, id])
|
|
@@ -61,13 +69,14 @@ export const DefaultShapeIndicator = memo(function DefaultShapeIndicator({
|
|
|
61
69
|
useQuickReactor(
|
|
62
70
|
'indicator transform',
|
|
63
71
|
() => {
|
|
72
|
+
if (hidden) return
|
|
64
73
|
const elm = rIndicator.current
|
|
65
74
|
if (!elm) return
|
|
66
75
|
const pageTransform = editor.getShapePageTransform(shapeId)
|
|
67
76
|
if (!pageTransform) return
|
|
68
77
|
elm.style.setProperty('transform', pageTransform.toCssString())
|
|
69
78
|
},
|
|
70
|
-
[editor, shapeId]
|
|
79
|
+
[editor, shapeId, hidden]
|
|
71
80
|
)
|
|
72
81
|
|
|
73
82
|
useLayoutEffect(() => {
|
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -10214,7 +10214,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10214
10214
|
|
|
10215
10215
|
// If the camera behavior is "zoom" and the ctrl key is pressed, then pan;
|
|
10216
10216
|
// If the camera behavior is "pan" and the ctrl key is not pressed, then zoom
|
|
10217
|
-
if (
|
|
10217
|
+
if (info.ctrlKey) behavior = wheelBehavior === 'pan' ? 'zoom' : 'pan'
|
|
10218
10218
|
|
|
10219
10219
|
switch (behavior) {
|
|
10220
10220
|
case 'zoom': {
|
|
@@ -365,6 +365,21 @@ function SvgExport({
|
|
|
365
365
|
onMount()
|
|
366
366
|
}, [onMount, shapeElements])
|
|
367
367
|
|
|
368
|
+
let backgroundColor = background ? theme.background : 'transparent'
|
|
369
|
+
|
|
370
|
+
if (singleFrameShapeId && background) {
|
|
371
|
+
const frameShapeUtil = editor.getShapeUtil('frame') as any as
|
|
372
|
+
| undefined
|
|
373
|
+
| { options: { showColors: boolean } }
|
|
374
|
+
if (frameShapeUtil?.options.showColors) {
|
|
375
|
+
const shape = editor.getShape(singleFrameShapeId)! as TLFrameShape
|
|
376
|
+
const color = theme[shape.props.color]
|
|
377
|
+
backgroundColor = color.frame.fill
|
|
378
|
+
} else {
|
|
379
|
+
backgroundColor = theme.solid
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
368
383
|
return (
|
|
369
384
|
<SvgExportContextProvider editor={editor} context={exportContext}>
|
|
370
385
|
<svg
|
|
@@ -375,13 +390,7 @@ function SvgExport({
|
|
|
375
390
|
viewBox={`${bbox.minX} ${bbox.minY} ${bbox.width} ${bbox.height}`}
|
|
376
391
|
strokeLinecap="round"
|
|
377
392
|
strokeLinejoin="round"
|
|
378
|
-
style={{
|
|
379
|
-
backgroundColor: background
|
|
380
|
-
? singleFrameShapeId
|
|
381
|
-
? theme.solid
|
|
382
|
-
: theme.background
|
|
383
|
-
: 'transparent',
|
|
384
|
-
}}
|
|
393
|
+
style={{ backgroundColor }}
|
|
385
394
|
data-color-mode={isDarkMode ? 'dark' : 'light'}
|
|
386
395
|
className={`tl-container tl-theme__force-sRGB ${isDarkMode ? 'tl-theme__dark' : 'tl-theme__light'}`}
|
|
387
396
|
>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Euclidean algorithm to find the GCD
|
|
2
|
+
function gcd(a: number, b: number): number {
|
|
3
|
+
return b === 0 ? a : gcd(b, a % b)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// Returns the lowest value that the given number can be multiplied by to reach an integer
|
|
7
|
+
export function nearestMultiple(float: number) {
|
|
8
|
+
const decimal = float.toString().split('.')[1]
|
|
9
|
+
if (!decimal) return 1
|
|
10
|
+
const denominator = Math.pow(10, decimal.length)
|
|
11
|
+
const numerator = parseInt(decimal, 10)
|
|
12
|
+
return denominator / gcd(numerator, denominator)
|
|
13
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// This file is automatically generated by internal/scripts/refresh-assets.ts.
|
|
2
2
|
// Do not edit manually. Or do, I'm a comment, not a cop.
|
|
3
3
|
|
|
4
|
-
export const version = '3.13.0-canary.
|
|
4
|
+
export const version = '3.13.0-canary.8754dedf4cf0'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2024-09-13T14:36:29.063Z',
|
|
7
|
-
minor: '2025-
|
|
8
|
-
patch: '2025-
|
|
7
|
+
minor: '2025-05-02T10:16:31.753Z',
|
|
8
|
+
patch: '2025-05-02T10:16:31.753Z',
|
|
9
9
|
}
|