@tldraw/editor 3.13.0-canary.e0dbcebeb554 → 3.13.0-canary.e6138942377f
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.d.ts +11 -1
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/components/Shape.js +12 -8
- 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 +21 -8
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.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/areShapesContentEqual.js +25 -0
- package/dist-cjs/lib/utils/areShapesContentEqual.js.map +7 -0
- 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.d.mts +11 -1
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/components/Shape.mjs +12 -8
- 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 +21 -8
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.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/areShapesContentEqual.mjs +5 -0
- package/dist-esm/lib/utils/areShapesContentEqual.mjs.map +7 -0
- 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 +14 -10
- 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 +21 -8
- package/src/lib/editor/shapes/ShapeUtil.ts +13 -1
- package/src/lib/exports/getSvgJsx.tsx +16 -7
- package/src/lib/utils/areShapesContentEqual.ts +4 -0
- package/src/lib/utils/nearestMultiple.ts +13 -0
- package/src/version.ts +3 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/lib/editor/shapes/ShapeUtil.ts"],
|
|
4
|
-
"sourcesContent": ["/* eslint-disable @typescript-eslint/no-unused-vars */\nimport { EMPTY_ARRAY } from '@tldraw/state'\nimport { LegacyMigrations, MigrationSequence } from '@tldraw/store'\nimport {\n\tRecordProps,\n\tTLHandle,\n\tTLPropsMigrations,\n\tTLShape,\n\tTLShapeCrop,\n\tTLShapePartial,\n\tTLUnknownShape,\n} from '@tldraw/tlschema'\nimport { ReactElement } from 'react'\nimport { Box, SelectionHandle } from '../../primitives/Box'\nimport { Vec } from '../../primitives/Vec'\nimport { Geometry2d } from '../../primitives/geometry/Geometry2d'\nimport type { Editor } from '../Editor'\nimport { TLFontFace } from '../managers/FontManager'\nimport { BoundsSnapGeometry } from '../managers/SnapManager/BoundsSnaps'\nimport { HandleSnapGeometry } from '../managers/SnapManager/HandleSnaps'\nimport { SvgExportContext } from '../types/SvgExportContext'\nimport { TLResizeHandle } from '../types/selection-types'\n\n/** @public */\nexport interface TLShapeUtilConstructor<\n\tT extends TLUnknownShape,\n\tU extends ShapeUtil<T> = ShapeUtil<T>,\n> {\n\tnew (editor: Editor): U\n\ttype: T['type']\n\tprops?: RecordProps<T>\n\tmigrations?: LegacyMigrations | TLPropsMigrations | MigrationSequence\n}\n\n/**\n * Options passed to {@link ShapeUtil.canBind}. A binding that could be made. At least one of\n * `fromShapeType` or `toShapeType` will belong to this shape util.\n *\n * @public\n */\nexport interface TLShapeUtilCanBindOpts<Shape extends TLUnknownShape = TLUnknownShape> {\n\t/** The type of shape referenced by the `fromId` of the binding. */\n\tfromShapeType: string\n\t/** The type of shape referenced by the `toId` of the binding. */\n\ttoShapeType: string\n\t/** The type of binding. */\n\tbindingType: string\n}\n\n/**\n * Options passed to {@link ShapeUtil.canBeLaidOut}.\n *\n * @public\n */\nexport interface TLShapeUtilCanBeLaidOutOpts {\n\t/** The type of action causing the layout. */\n\ttype?: 'align' | 'distribute' | 'pack' | 'stack' | 'flip' | 'stretch'\n\t/** The other shapes being laid out */\n\tshapes?: TLShape[]\n}\n\n/** Additional options for the {@link ShapeUtil.getGeometry} method.\n *\n * @public\n */\nexport interface TLGeometryOpts {\n\t/** The context in which the geometry is being requested. */\n\tcontext?: string\n}\n\n/** @public */\nexport interface TLShapeUtilCanvasSvgDef {\n\tkey: string\n\tcomponent: React.ComponentType\n}\n\n/** @public */\nexport abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {\n\t/** Configure this shape utils {@link ShapeUtil.options | `options`}. */\n\tstatic configure<T extends TLShapeUtilConstructor<any, any>>(\n\t\tthis: T,\n\t\toptions: T extends new (...args: any[]) => { options: infer Options } ? Partial<Options> : never\n\t): T {\n\t\t// @ts-expect-error -- typescript has no idea what's going on here but it's fine\n\t\treturn class extends this {\n\t\t\t// @ts-expect-error\n\t\t\toptions = { ...this.options, ...options }\n\t\t}\n\t}\n\n\tconstructor(public editor: Editor) {}\n\n\t/**\n\t * Options for this shape util. If you're implementing a custom shape util, you can override\n\t * this to provide customization options for your shape. If using an existing shape util, you\n\t * can customizing this by calling {@link ShapeUtil.configure}.\n\t */\n\toptions = {}\n\n\t/**\n\t * Props allow you to define the shape's properties in a way that the editor can understand.\n\t * This has two main uses:\n\t *\n\t * 1. Validation. Shapes will be validated using these props to stop bad data from being saved.\n\t * 2. Styles. Each {@link @tldraw/tlschema#StyleProp} in the props can be set on many shapes at\n\t * once, and will be remembered from one shape to the next.\n\t *\n\t * @example\n\t * ```tsx\n\t * import {T, TLBaseShape, TLDefaultColorStyle, DefaultColorStyle, ShapeUtil} from 'tldraw'\n\t *\n\t * type MyShape = TLBaseShape<'mine', {\n\t * color: TLDefaultColorStyle,\n\t * text: string,\n\t * }>\n\t *\n\t * class MyShapeUtil extends ShapeUtil<MyShape> {\n\t * static props = {\n\t * // we use tldraw's built-in color style:\n\t * color: DefaultColorStyle,\n\t * // validate that the text prop is a string:\n\t * text: T.string,\n\t * }\n\t * }\n\t * ```\n\t */\n\tstatic props?: RecordProps<TLUnknownShape>\n\n\t/**\n\t * Migrations allow you to make changes to a shape's props over time. Read the\n\t * {@link https://www.tldraw.dev/docs/persistence#Shape-props-migrations | shape prop migrations}\n\t * guide for more information.\n\t */\n\tstatic migrations?: LegacyMigrations | TLPropsMigrations | MigrationSequence\n\n\t/**\n\t * The type of the shape util, which should match the shape's type.\n\t *\n\t * @public\n\t */\n\tstatic type: string\n\n\t/**\n\t * Get the default props for a shape.\n\t *\n\t * @public\n\t */\n\tabstract getDefaultProps(): Shape['props']\n\n\t/**\n\t * Get the shape's geometry.\n\t *\n\t * @param shape - The shape.\n\t * @param opts - Additional options for the request.\n\t * @public\n\t */\n\tabstract getGeometry(shape: Shape, opts?: TLGeometryOpts): Geometry2d\n\n\t/**\n\t * Get a JSX element for the shape (as an HTML element).\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tabstract component(shape: Shape): any\n\n\t/**\n\t * Get JSX describing the shape's indicator (as an SVG element).\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tabstract indicator(shape: Shape): any\n\n\t/**\n\t * Get the font faces that should be rendered in the document in order for this shape to render\n\t * correctly.\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tgetFontFaces(shape: Shape): TLFontFace[] {\n\t\treturn EMPTY_ARRAY\n\t}\n\n\t/**\n\t * Whether the shape can be snapped to by another shape.\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tcanSnap(_shape: Shape): boolean {\n\t\treturn true\n\t}\n\n\t/**\n\t * Whether the shape can be tabbed to.\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tcanTabTo(_shape: Shape): boolean {\n\t\treturn true\n\t}\n\n\t/**\n\t * Whether the shape can be scrolled while editing.\n\t *\n\t * @public\n\t */\n\tcanScroll(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape can be bound to. See {@link TLShapeUtilCanBindOpts} for details.\n\t *\n\t * @public\n\t */\n\tcanBind(_opts: TLShapeUtilCanBindOpts): boolean {\n\t\treturn true\n\t}\n\n\t/**\n\t * Whether the shape can be double clicked to edit.\n\t *\n\t * @public\n\t */\n\tcanEdit(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape can be resized.\n\t *\n\t * @public\n\t */\n\tcanResize(_shape: Shape): boolean {\n\t\treturn true\n\t}\n\n\t/**\n\t * Whether the shape can be edited in read-only mode.\n\t *\n\t * @public\n\t */\n\tcanEditInReadOnly(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape can be cropped.\n\t *\n\t * @public\n\t */\n\tcanCrop(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape can participate in layout functions such as alignment or distribution.\n\t *\n\t * @param shape - The shape.\n\t * @param info - Additional context information: the type of action causing the layout and the\n\t * @public\n\t *\n\t * @public\n\t */\n\tcanBeLaidOut(_shape: Shape, _info: TLShapeUtilCanBeLaidOutOpts): boolean {\n\t\treturn true\n\t}\n\n\t/**\n\t * Does this shape provide a background for its children? If this is true,\n\t * then any children with a `renderBackground` method will have their\n\t * backgrounds rendered _above_ this shape. Otherwise, the children's\n\t * backgrounds will be rendered above either the next ancestor that provides\n\t * a background, or the canvas background.\n\t *\n\t * @internal\n\t */\n\tprovidesBackgroundForChildren(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape should hide its resize handles when selected.\n\t *\n\t * @public\n\t */\n\thideResizeHandles(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape should hide its rotation handles when selected.\n\t *\n\t * @public\n\t */\n\thideRotateHandle(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape should hide its selection bounds background when selected.\n\t *\n\t * @public\n\t */\n\thideSelectionBoundsBg(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape should hide its selection bounds foreground when selected.\n\t *\n\t * @public\n\t */\n\thideSelectionBoundsFg(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape's aspect ratio is locked.\n\t *\n\t * @public\n\t */\n\tisAspectRatioLocked(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Get a JSX element for the shape (as an HTML element) to be rendered as part of the canvas background - behind any other shape content.\n\t *\n\t * @param shape - The shape.\n\t * @internal\n\t */\n\tbackgroundComponent?(shape: Shape): any\n\n\t/**\n\t * Get the interpolated props for an animating shape. This is an optional method.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * util.getInterpolatedProps?.(startShape, endShape, t)\n\t * ```\n\t *\n\t * @param startShape - The initial shape.\n\t * @param endShape - The initial shape.\n\t * @param progress - The normalized progress between zero (start) and 1 (end).\n\t * @public\n\t */\n\tgetInterpolatedProps?(startShape: Shape, endShape: Shape, progress: number): Shape['props']\n\n\t/**\n\t * Get an array of handle models for the shape. This is an optional method.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * util.getHandles?.(myShape)\n\t * ```\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tgetHandles?(shape: Shape): TLHandle[]\n\n\t/**\n\t * Get whether the shape can receive children of a given type.\n\t *\n\t * @param shape - The shape.\n\t * @param type - The shape type.\n\t * @public\n\t */\n\tcanReceiveNewChildrenOfType(_shape: Shape, _type: TLShape['type']) {\n\t\treturn false\n\t}\n\n\t/**\n\t * Get whether the shape can receive children of a given type.\n\t *\n\t * @param shape - The shape type.\n\t * @param shapes - The shapes that are being dropped.\n\t * @public\n\t */\n\tcanDropShapes(_shape: Shape, _shapes: TLShape[]) {\n\t\treturn false\n\t}\n\n\t/**\n\t * Get the shape as an SVG object.\n\t *\n\t * @param shape - The shape.\n\t * @param ctx - The export context for the SVG - used for adding e.g. \\<def\\>s\n\t * @returns An SVG element.\n\t * @public\n\t */\n\ttoSvg?(shape: Shape, ctx: SvgExportContext): ReactElement | null | Promise<ReactElement | null>\n\n\t/**\n\t * Get the shape's background layer as an SVG object.\n\t *\n\t * @param shape - The shape.\n\t * @param ctx - ctx - The export context for the SVG - used for adding e.g. \\<def\\>s\n\t * @returns An SVG element.\n\t * @public\n\t */\n\ttoBackgroundSvg?(\n\t\tshape: Shape,\n\t\tctx: SvgExportContext\n\t): ReactElement | null | Promise<ReactElement | null>\n\n\t/** @internal */\n\texpandSelectionOutlinePx(shape: Shape): number | Box {\n\t\treturn 0\n\t}\n\n\t/**\n\t * Return elements to be added to the \\<defs\\> section of the canvases SVG context. This can be\n\t * used to define SVG content (e.g. patterns & masks) that can be referred to by ID from svg\n\t * elements returned by `component`.\n\t *\n\t * Each def should have a unique `key`. If multiple defs from different shapes all have the same\n\t * key, only one will be used.\n\t */\n\tgetCanvasSvgDefs(): TLShapeUtilCanvasSvgDef[] {\n\t\treturn []\n\t}\n\n\t/**\n\t * Get the geometry to use when snapping to this this shape in translate/resize operations. See\n\t * {@link BoundsSnapGeometry} for details.\n\t */\n\tgetBoundsSnapGeometry(_shape: Shape): BoundsSnapGeometry {\n\t\treturn {}\n\t}\n\n\t/**\n\t * Get the geometry to use when snapping handles to this shape. See {@link HandleSnapGeometry}\n\t * for details.\n\t */\n\tgetHandleSnapGeometry(_shape: Shape): HandleSnapGeometry {\n\t\treturn {}\n\t}\n\n\tgetText(_shape: Shape): string | undefined {\n\t\treturn undefined\n\t}\n\n\tgetAriaDescriptor(_shape: Shape): string | undefined {\n\t\treturn undefined\n\t}\n\n\t// Events\n\n\t/**\n\t * A callback called just before a shape is created. This method provides a last chance to modify\n\t * the created shape.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * onBeforeCreate = (next) => {\n\t * \treturn { ...next, x: next.x + 1 }\n\t * }\n\t * ```\n\t *\n\t * @param next - The next shape.\n\t * @returns The next shape or void.\n\t * @public\n\t */\n\tonBeforeCreate?(next: Shape): Shape | void\n\n\t/**\n\t * A callback called just before a shape is updated. This method provides a last chance to modify\n\t * the updated shape.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * onBeforeUpdate = (prev, next) => {\n\t * \tif (prev.x === next.x) {\n\t * \t\treturn { ...next, x: next.x + 1 }\n\t * \t}\n\t * }\n\t * ```\n\t *\n\t * @param prev - The previous shape.\n\t * @param next - The next shape.\n\t * @returns The next shape or void.\n\t * @public\n\t */\n\tonBeforeUpdate?(prev: Shape, next: Shape): Shape | void\n\n\t/**\n\t * A callback called when a shape changes from a crop.\n\t *\n\t * @param shape - The shape at the start of the crop.\n\t * @param info - Info about the crop.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonCrop?(\n\t\tshape: Shape,\n\t\tinfo: TLCropInfo<Shape>\n\t): Omit<TLShapePartial<Shape>, 'id' | 'type'> | undefined | void\n\n\t/**\n\t * A callback called when some other shapes are dragged over this one.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * onDragShapesOver = (shape, shapes) => {\n\t * \tthis.editor.reparentShapes(shapes, shape.id)\n\t * }\n\t * ```\n\t *\n\t * @param shape - The shape.\n\t * @param shapes - The shapes that are being dragged over this one.\n\t * @public\n\t */\n\tonDragShapesOver?(shape: Shape, shapes: TLShape[]): void\n\n\t/**\n\t * A callback called when some other shapes are dragged out of this one.\n\t *\n\t * @param shape - The shape.\n\t * @param shapes - The shapes that are being dragged out.\n\t * @public\n\t */\n\tonDragShapesOut?(shape: Shape, shapes: TLShape[]): void\n\n\t/**\n\t * A callback called when some other shapes are dropped over this one.\n\t *\n\t * @param shape - The shape.\n\t * @param shapes - The shapes that are being dropped over this one.\n\t * @public\n\t */\n\tonDropShapesOver?(shape: Shape, shapes: TLShape[]): void\n\n\t/**\n\t * A callback called when a shape starts being resized.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonResizeStart?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape changes from a resize.\n\t *\n\t * @param shape - The shape at the start of the resize.\n\t * @param info - Info about the resize.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonResize?(\n\t\tshape: Shape,\n\t\tinfo: TLResizeInfo<Shape>\n\t): Omit<TLShapePartial<Shape>, 'id' | 'type'> | undefined | void\n\n\t/**\n\t * A callback called when a shape finishes resizing.\n\t *\n\t * @param initial - The shape at the start of the resize.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonResizeEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape starts being translated.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonTranslateStart?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape changes from a translation.\n\t *\n\t * @param initial - The shape at the start of the translation.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonTranslate?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape finishes translating.\n\t *\n\t * @param initial - The shape at the start of the translation.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonTranslateEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape's handle changes.\n\t *\n\t * @param shape - The current shape.\n\t * @param info - An object containing the handle and whether the handle is 'precise' or not.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonHandleDrag?(shape: Shape, info: TLHandleDragInfo<Shape>): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape starts being rotated.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonRotateStart?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape changes from a rotation.\n\t *\n\t * @param initial - The shape at the start of the rotation.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonRotate?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape finishes rotating.\n\t *\n\t * @param initial - The shape at the start of the rotation.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonRotateEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * Not currently used.\n\t *\n\t * @internal\n\t */\n\tonBindingChange?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape's children change.\n\t *\n\t * @param shape - The shape.\n\t * @returns An array of shape updates, or void.\n\t * @public\n\t */\n\tonChildrenChange?(shape: Shape): TLShapePartial[] | void\n\n\t/**\n\t * A callback called when a shape's handle is double clicked.\n\t *\n\t * @param shape - The shape.\n\t * @param handle - The handle that is double-clicked.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonDoubleClickHandle?(shape: Shape, handle: TLHandle): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape's edge is double clicked.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonDoubleClickEdge?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape is double clicked.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonDoubleClick?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape is clicked.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonClick?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape finishes being editing.\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tonEditEnd?(shape: Shape): void\n}\n\n/**\n * Info about a crop.\n * @param handle - The handle being dragged.\n * @param change - The distance the handle is moved.\n * @param initialShape - The shape at the start of the resize.\n * @public\n */\nexport interface TLCropInfo<T extends TLShape> {\n\thandle: SelectionHandle\n\tchange: Vec\n\tcrop: TLShapeCrop\n\tuncroppedSize: { w: number; h: number }\n\tinitialShape: T\n}\n\n/**\n * The type of resize.\n *\n * 'scale_shape' - The shape is being scaled, usually as part of a larger selection.\n *\n * 'resize_bounds' - The user is directly manipulating an individual shape's bounds using a resize\n * handle. It is up to shape util implementers to decide how they want to handle the two\n * situations.\n *\n * @public\n */\nexport type TLResizeMode = 'scale_shape' | 'resize_bounds'\n\n/**\n * Info about a resize.\n * @param newPoint - The new local position of the shape.\n * @param handle - The handle being dragged.\n * @param mode - The type of resize.\n * @param scaleX - The scale in the x-axis.\n * @param scaleY - The scale in the y-axis.\n * @param initialBounds - The bounds of the shape at the start of the resize.\n * @param initialShape - The shape at the start of the resize.\n * @public\n */\nexport interface TLResizeInfo<T extends TLShape> {\n\tnewPoint: Vec\n\thandle: TLResizeHandle\n\tmode: TLResizeMode\n\tscaleX: number\n\tscaleY: number\n\tinitialBounds: Box\n\tinitialShape: T\n}\n\n/* -------------------- Dragging -------------------- */\n\n/** @public */\nexport interface TLHandleDragInfo<T extends TLShape> {\n\thandle: TLHandle\n\tisPrecise: boolean\n\tinitial?: T | undefined\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,mBAAmB;
|
|
4
|
+
"sourcesContent": ["/* eslint-disable @typescript-eslint/no-unused-vars */\nimport { EMPTY_ARRAY } from '@tldraw/state'\nimport { LegacyMigrations, MigrationSequence } from '@tldraw/store'\nimport {\n\tRecordProps,\n\tTLHandle,\n\tTLPropsMigrations,\n\tTLShape,\n\tTLShapeCrop,\n\tTLShapePartial,\n\tTLUnknownShape,\n} from '@tldraw/tlschema'\nimport { ReactElement } from 'react'\nimport { Box, SelectionHandle } from '../../primitives/Box'\nimport { Vec } from '../../primitives/Vec'\nimport { Geometry2d } from '../../primitives/geometry/Geometry2d'\nimport type { Editor } from '../Editor'\nimport { TLFontFace } from '../managers/FontManager'\nimport { BoundsSnapGeometry } from '../managers/SnapManager/BoundsSnaps'\nimport { HandleSnapGeometry } from '../managers/SnapManager/HandleSnaps'\nimport { SvgExportContext } from '../types/SvgExportContext'\nimport { TLClickEventInfo } from '../types/event-types'\nimport { TLResizeHandle } from '../types/selection-types'\n\n/** @public */\nexport interface TLShapeUtilConstructor<\n\tT extends TLUnknownShape,\n\tU extends ShapeUtil<T> = ShapeUtil<T>,\n> {\n\tnew (editor: Editor): U\n\ttype: T['type']\n\tprops?: RecordProps<T>\n\tmigrations?: LegacyMigrations | TLPropsMigrations | MigrationSequence\n}\n\n/**\n * Options passed to {@link ShapeUtil.canBind}. A binding that could be made. At least one of\n * `fromShapeType` or `toShapeType` will belong to this shape util.\n *\n * @public\n */\nexport interface TLShapeUtilCanBindOpts<Shape extends TLUnknownShape = TLUnknownShape> {\n\t/** The type of shape referenced by the `fromId` of the binding. */\n\tfromShapeType: string\n\t/** The type of shape referenced by the `toId` of the binding. */\n\ttoShapeType: string\n\t/** The type of binding. */\n\tbindingType: string\n}\n\n/**\n * Options passed to {@link ShapeUtil.canBeLaidOut}.\n *\n * @public\n */\nexport interface TLShapeUtilCanBeLaidOutOpts {\n\t/** The type of action causing the layout. */\n\ttype?: 'align' | 'distribute' | 'pack' | 'stack' | 'flip' | 'stretch'\n\t/** The other shapes being laid out */\n\tshapes?: TLShape[]\n}\n\n/** Additional options for the {@link ShapeUtil.getGeometry} method.\n *\n * @public\n */\nexport interface TLGeometryOpts {\n\t/** The context in which the geometry is being requested. */\n\tcontext?: string\n}\n\n/** @public */\nexport interface TLShapeUtilCanvasSvgDef {\n\tkey: string\n\tcomponent: React.ComponentType\n}\n\n/** @public */\nexport abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {\n\t/** Configure this shape utils {@link ShapeUtil.options | `options`}. */\n\tstatic configure<T extends TLShapeUtilConstructor<any, any>>(\n\t\tthis: T,\n\t\toptions: T extends new (...args: any[]) => { options: infer Options } ? Partial<Options> : never\n\t): T {\n\t\t// @ts-expect-error -- typescript has no idea what's going on here but it's fine\n\t\treturn class extends this {\n\t\t\t// @ts-expect-error\n\t\t\toptions = { ...this.options, ...options }\n\t\t}\n\t}\n\n\tconstructor(public editor: Editor) {}\n\n\t/**\n\t * Options for this shape util. If you're implementing a custom shape util, you can override\n\t * this to provide customization options for your shape. If using an existing shape util, you\n\t * can customizing this by calling {@link ShapeUtil.configure}.\n\t */\n\toptions = {}\n\n\t/**\n\t * Props allow you to define the shape's properties in a way that the editor can understand.\n\t * This has two main uses:\n\t *\n\t * 1. Validation. Shapes will be validated using these props to stop bad data from being saved.\n\t * 2. Styles. Each {@link @tldraw/tlschema#StyleProp} in the props can be set on many shapes at\n\t * once, and will be remembered from one shape to the next.\n\t *\n\t * @example\n\t * ```tsx\n\t * import {T, TLBaseShape, TLDefaultColorStyle, DefaultColorStyle, ShapeUtil} from 'tldraw'\n\t *\n\t * type MyShape = TLBaseShape<'mine', {\n\t * color: TLDefaultColorStyle,\n\t * text: string,\n\t * }>\n\t *\n\t * class MyShapeUtil extends ShapeUtil<MyShape> {\n\t * static props = {\n\t * // we use tldraw's built-in color style:\n\t * color: DefaultColorStyle,\n\t * // validate that the text prop is a string:\n\t * text: T.string,\n\t * }\n\t * }\n\t * ```\n\t */\n\tstatic props?: RecordProps<TLUnknownShape>\n\n\t/**\n\t * Migrations allow you to make changes to a shape's props over time. Read the\n\t * {@link https://www.tldraw.dev/docs/persistence#Shape-props-migrations | shape prop migrations}\n\t * guide for more information.\n\t */\n\tstatic migrations?: LegacyMigrations | TLPropsMigrations | MigrationSequence\n\n\t/**\n\t * The type of the shape util, which should match the shape's type.\n\t *\n\t * @public\n\t */\n\tstatic type: string\n\n\t/**\n\t * Get the default props for a shape.\n\t *\n\t * @public\n\t */\n\tabstract getDefaultProps(): Shape['props']\n\n\t/**\n\t * Get the shape's geometry.\n\t *\n\t * @param shape - The shape.\n\t * @param opts - Additional options for the request.\n\t * @public\n\t */\n\tabstract getGeometry(shape: Shape, opts?: TLGeometryOpts): Geometry2d\n\n\t/**\n\t * Get a JSX element for the shape (as an HTML element).\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tabstract component(shape: Shape): any\n\n\t/**\n\t * Get JSX describing the shape's indicator (as an SVG element).\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tabstract indicator(shape: Shape): any\n\n\t/**\n\t * Get the font faces that should be rendered in the document in order for this shape to render\n\t * correctly.\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tgetFontFaces(shape: Shape): TLFontFace[] {\n\t\treturn EMPTY_ARRAY\n\t}\n\n\t/**\n\t * Whether the shape can be snapped to by another shape.\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tcanSnap(_shape: Shape): boolean {\n\t\treturn true\n\t}\n\n\t/**\n\t * Whether the shape can be tabbed to.\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tcanTabTo(_shape: Shape): boolean {\n\t\treturn true\n\t}\n\n\t/**\n\t * Whether the shape can be scrolled while editing.\n\t *\n\t * @public\n\t */\n\tcanScroll(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape can be bound to. See {@link TLShapeUtilCanBindOpts} for details.\n\t *\n\t * @public\n\t */\n\tcanBind(_opts: TLShapeUtilCanBindOpts): boolean {\n\t\treturn true\n\t}\n\n\t/**\n\t * Whether the shape can be double clicked to edit.\n\t *\n\t * @public\n\t */\n\tcanEdit(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape can be resized.\n\t *\n\t * @public\n\t */\n\tcanResize(_shape: Shape): boolean {\n\t\treturn true\n\t}\n\n\t/**\n\t * Whether the shape can be edited in read-only mode.\n\t *\n\t * @public\n\t */\n\tcanEditInReadOnly(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape can be cropped.\n\t *\n\t * @public\n\t */\n\tcanCrop(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape can participate in layout functions such as alignment or distribution.\n\t *\n\t * @param shape - The shape.\n\t * @param info - Additional context information: the type of action causing the layout and the\n\t * @public\n\t *\n\t * @public\n\t */\n\tcanBeLaidOut(_shape: Shape, _info: TLShapeUtilCanBeLaidOutOpts): boolean {\n\t\treturn true\n\t}\n\n\t/**\n\t * Does this shape provide a background for its children? If this is true,\n\t * then any children with a `renderBackground` method will have their\n\t * backgrounds rendered _above_ this shape. Otherwise, the children's\n\t * backgrounds will be rendered above either the next ancestor that provides\n\t * a background, or the canvas background.\n\t *\n\t * @internal\n\t */\n\tprovidesBackgroundForChildren(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape should hide its resize handles when selected.\n\t *\n\t * @public\n\t */\n\thideResizeHandles(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape should hide its rotation handles when selected.\n\t *\n\t * @public\n\t */\n\thideRotateHandle(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape should hide its selection bounds background when selected.\n\t *\n\t * @public\n\t */\n\thideSelectionBoundsBg(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape should hide its selection bounds foreground when selected.\n\t *\n\t * @public\n\t */\n\thideSelectionBoundsFg(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Whether the shape's aspect ratio is locked.\n\t *\n\t * @public\n\t */\n\tisAspectRatioLocked(_shape: Shape): boolean {\n\t\treturn false\n\t}\n\n\t/**\n\t * Get a JSX element for the shape (as an HTML element) to be rendered as part of the canvas background - behind any other shape content.\n\t *\n\t * @param shape - The shape.\n\t * @internal\n\t */\n\tbackgroundComponent?(shape: Shape): any\n\n\t/**\n\t * Get the interpolated props for an animating shape. This is an optional method.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * util.getInterpolatedProps?.(startShape, endShape, t)\n\t * ```\n\t *\n\t * @param startShape - The initial shape.\n\t * @param endShape - The initial shape.\n\t * @param progress - The normalized progress between zero (start) and 1 (end).\n\t * @public\n\t */\n\tgetInterpolatedProps?(startShape: Shape, endShape: Shape, progress: number): Shape['props']\n\n\t/**\n\t * Get an array of handle models for the shape. This is an optional method.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * util.getHandles?.(myShape)\n\t * ```\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tgetHandles?(shape: Shape): TLHandle[]\n\n\t/**\n\t * Get whether the shape can receive children of a given type.\n\t *\n\t * @param shape - The shape.\n\t * @param type - The shape type.\n\t * @public\n\t */\n\tcanReceiveNewChildrenOfType(_shape: Shape, _type: TLShape['type']) {\n\t\treturn false\n\t}\n\n\t/**\n\t * Get whether the shape can receive children of a given type.\n\t *\n\t * @param shape - The shape type.\n\t * @param shapes - The shapes that are being dropped.\n\t * @public\n\t */\n\tcanDropShapes(_shape: Shape, _shapes: TLShape[]) {\n\t\treturn false\n\t}\n\n\t/**\n\t * Get the shape as an SVG object.\n\t *\n\t * @param shape - The shape.\n\t * @param ctx - The export context for the SVG - used for adding e.g. \\<def\\>s\n\t * @returns An SVG element.\n\t * @public\n\t */\n\ttoSvg?(shape: Shape, ctx: SvgExportContext): ReactElement | null | Promise<ReactElement | null>\n\n\t/**\n\t * Get the shape's background layer as an SVG object.\n\t *\n\t * @param shape - The shape.\n\t * @param ctx - ctx - The export context for the SVG - used for adding e.g. \\<def\\>s\n\t * @returns An SVG element.\n\t * @public\n\t */\n\ttoBackgroundSvg?(\n\t\tshape: Shape,\n\t\tctx: SvgExportContext\n\t): ReactElement | null | Promise<ReactElement | null>\n\n\t/** @internal */\n\texpandSelectionOutlinePx(shape: Shape): number | Box {\n\t\treturn 0\n\t}\n\n\t/**\n\t * Return elements to be added to the \\<defs\\> section of the canvases SVG context. This can be\n\t * used to define SVG content (e.g. patterns & masks) that can be referred to by ID from svg\n\t * elements returned by `component`.\n\t *\n\t * Each def should have a unique `key`. If multiple defs from different shapes all have the same\n\t * key, only one will be used.\n\t */\n\tgetCanvasSvgDefs(): TLShapeUtilCanvasSvgDef[] {\n\t\treturn []\n\t}\n\n\t/**\n\t * Get the geometry to use when snapping to this this shape in translate/resize operations. See\n\t * {@link BoundsSnapGeometry} for details.\n\t */\n\tgetBoundsSnapGeometry(_shape: Shape): BoundsSnapGeometry {\n\t\treturn {}\n\t}\n\n\t/**\n\t * Get the geometry to use when snapping handles to this shape. See {@link HandleSnapGeometry}\n\t * for details.\n\t */\n\tgetHandleSnapGeometry(_shape: Shape): HandleSnapGeometry {\n\t\treturn {}\n\t}\n\n\tgetText(_shape: Shape): string | undefined {\n\t\treturn undefined\n\t}\n\n\tgetAriaDescriptor(_shape: Shape): string | undefined {\n\t\treturn undefined\n\t}\n\n\t// Events\n\n\t/**\n\t * A callback called just before a shape is created. This method provides a last chance to modify\n\t * the created shape.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * onBeforeCreate = (next) => {\n\t * \treturn { ...next, x: next.x + 1 }\n\t * }\n\t * ```\n\t *\n\t * @param next - The next shape.\n\t * @returns The next shape or void.\n\t * @public\n\t */\n\tonBeforeCreate?(next: Shape): Shape | void\n\n\t/**\n\t * A callback called just before a shape is updated. This method provides a last chance to modify\n\t * the updated shape.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * onBeforeUpdate = (prev, next) => {\n\t * \tif (prev.x === next.x) {\n\t * \t\treturn { ...next, x: next.x + 1 }\n\t * \t}\n\t * }\n\t * ```\n\t *\n\t * @param prev - The previous shape.\n\t * @param next - The next shape.\n\t * @returns The next shape or void.\n\t * @public\n\t */\n\tonBeforeUpdate?(prev: Shape, next: Shape): Shape | void\n\n\t/**\n\t * A callback called when a shape changes from a crop.\n\t *\n\t * @param shape - The shape at the start of the crop.\n\t * @param info - Info about the crop.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonCrop?(\n\t\tshape: Shape,\n\t\tinfo: TLCropInfo<Shape>\n\t): Omit<TLShapePartial<Shape>, 'id' | 'type'> | undefined | void\n\n\t/**\n\t * A callback called when some other shapes are dragged over this one.\n\t *\n\t * @example\n\t *\n\t * ```ts\n\t * onDragShapesOver = (shape, shapes) => {\n\t * \tthis.editor.reparentShapes(shapes, shape.id)\n\t * }\n\t * ```\n\t *\n\t * @param shape - The shape.\n\t * @param shapes - The shapes that are being dragged over this one.\n\t * @public\n\t */\n\tonDragShapesOver?(shape: Shape, shapes: TLShape[]): void\n\n\t/**\n\t * A callback called when some other shapes are dragged out of this one.\n\t *\n\t * @param shape - The shape.\n\t * @param shapes - The shapes that are being dragged out.\n\t * @public\n\t */\n\tonDragShapesOut?(shape: Shape, shapes: TLShape[]): void\n\n\t/**\n\t * A callback called when some other shapes are dropped over this one.\n\t *\n\t * @param shape - The shape.\n\t * @param shapes - The shapes that are being dropped over this one.\n\t * @public\n\t */\n\tonDropShapesOver?(shape: Shape, shapes: TLShape[]): void\n\n\t/**\n\t * A callback called when a shape starts being resized.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonResizeStart?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape changes from a resize.\n\t *\n\t * @param shape - The shape at the start of the resize.\n\t * @param info - Info about the resize.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonResize?(\n\t\tshape: Shape,\n\t\tinfo: TLResizeInfo<Shape>\n\t): Omit<TLShapePartial<Shape>, 'id' | 'type'> | undefined | void\n\n\t/**\n\t * A callback called when a shape finishes resizing.\n\t *\n\t * @param initial - The shape at the start of the resize.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonResizeEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape starts being translated.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonTranslateStart?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape changes from a translation.\n\t *\n\t * @param initial - The shape at the start of the translation.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonTranslate?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape finishes translating.\n\t *\n\t * @param initial - The shape at the start of the translation.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonTranslateEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape's handle changes.\n\t *\n\t * @param shape - The current shape.\n\t * @param info - An object containing the handle and whether the handle is 'precise' or not.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonHandleDrag?(shape: Shape, info: TLHandleDragInfo<Shape>): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape starts being rotated.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonRotateStart?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape changes from a rotation.\n\t *\n\t * @param initial - The shape at the start of the rotation.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonRotate?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape finishes rotating.\n\t *\n\t * @param initial - The shape at the start of the rotation.\n\t * @param current - The current shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonRotateEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * Not currently used.\n\t *\n\t * @internal\n\t */\n\tonBindingChange?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape's children change.\n\t *\n\t * @param shape - The shape.\n\t * @returns An array of shape updates, or void.\n\t * @public\n\t */\n\tonChildrenChange?(shape: Shape): TLShapePartial[] | void\n\n\t/**\n\t * A callback called when a shape's handle is double clicked.\n\t *\n\t * @param shape - The shape.\n\t * @param handle - The handle that is double-clicked.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonDoubleClickHandle?(shape: Shape, handle: TLHandle): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape's edge is double clicked.\n\t *\n\t * @param shape - The shape.\n\t * @param info - Info about the edge.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonDoubleClickEdge?(shape: Shape, info: TLClickEventInfo): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape's corner is double clicked.\n\t *\n\t * @param shape - The shape.\n\t * @param info - Info about the corner.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonDoubleClickCorner?(shape: Shape, info: TLClickEventInfo): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape is double clicked.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonDoubleClick?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape is clicked.\n\t *\n\t * @param shape - The shape.\n\t * @returns A change to apply to the shape, or void.\n\t * @public\n\t */\n\tonClick?(shape: Shape): TLShapePartial<Shape> | void\n\n\t/**\n\t * A callback called when a shape finishes being editing.\n\t *\n\t * @param shape - The shape.\n\t * @public\n\t */\n\tonEditEnd?(shape: Shape): void\n}\n\n/**\n * Info about a crop.\n * @param handle - The handle being dragged.\n * @param change - The distance the handle is moved.\n * @param initialShape - The shape at the start of the resize.\n * @public\n */\nexport interface TLCropInfo<T extends TLShape> {\n\thandle: SelectionHandle\n\tchange: Vec\n\tcrop: TLShapeCrop\n\tuncroppedSize: { w: number; h: number }\n\tinitialShape: T\n}\n\n/**\n * The type of resize.\n *\n * 'scale_shape' - The shape is being scaled, usually as part of a larger selection.\n *\n * 'resize_bounds' - The user is directly manipulating an individual shape's bounds using a resize\n * handle. It is up to shape util implementers to decide how they want to handle the two\n * situations.\n *\n * @public\n */\nexport type TLResizeMode = 'scale_shape' | 'resize_bounds'\n\n/**\n * Info about a resize.\n * @param newPoint - The new local position of the shape.\n * @param handle - The handle being dragged.\n * @param mode - The type of resize.\n * @param scaleX - The scale in the x-axis.\n * @param scaleY - The scale in the y-axis.\n * @param initialBounds - The bounds of the shape at the start of the resize.\n * @param initialShape - The shape at the start of the resize.\n * @public\n */\nexport interface TLResizeInfo<T extends TLShape> {\n\tnewPoint: Vec\n\thandle: TLResizeHandle\n\tmode: TLResizeMode\n\tscaleX: number\n\tscaleY: number\n\tinitialBounds: Box\n\tinitialShape: T\n}\n\n/* -------------------- Dragging -------------------- */\n\n/** @public */\nexport interface TLHandleDragInfo<T extends TLShape> {\n\thandle: TLHandle\n\tisPrecise: boolean\n\tinitial?: T | undefined\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -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,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/utils/areShapesContentEqual.ts"],
|
|
4
|
+
"sourcesContent": ["import { TLShape } from '@tldraw/tlschema'\n\nexport const areShapesContentEqual = (a: TLShape, b: TLShape) =>\n\ta.props === b.props && a.meta === b.meta\n"],
|
|
5
|
+
"mappings": "AAEO,MAAM,wBAAwB,CAAC,GAAY,MACjD,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE;",
|
|
6
|
+
"names": []
|
|
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.e6138942377f";
|
|
2
2
|
const publishDates = {
|
|
3
3
|
major: "2024-09-13T14:36:29.063Z",
|
|
4
|
-
minor: "2025-
|
|
5
|
-
patch: "2025-
|
|
4
|
+
minor: "2025-05-06T09:18:49.800Z",
|
|
5
|
+
patch: "2025-05-06T09:18:49.800Z"
|
|
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.e6138942377f'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-05-06T09:18:49.800Z',\n\tpatch: '2025-05-06T09:18:49.800Z',\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.e6138942377f",
|
|
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.e6138942377f",
|
|
52
|
+
"@tldraw/state-react": "3.13.0-canary.e6138942377f",
|
|
53
|
+
"@tldraw/store": "3.13.0-canary.e6138942377f",
|
|
54
|
+
"@tldraw/tlschema": "3.13.0-canary.e6138942377f",
|
|
55
|
+
"@tldraw/utils": "3.13.0-canary.e6138942377f",
|
|
56
|
+
"@tldraw/validate": "3.13.0-canary.e6138942377f",
|
|
57
57
|
"@types/core-js": "^2.5.8",
|
|
58
58
|
"@use-gesture/react": "^10.3.1",
|
|
59
59
|
"classnames": "^2.5.1",
|
|
@@ -6,6 +6,7 @@ import { ShapeUtil } from '../editor/shapes/ShapeUtil'
|
|
|
6
6
|
import { useEditor } from '../hooks/useEditor'
|
|
7
7
|
import { useEditorComponents } from '../hooks/useEditorComponents'
|
|
8
8
|
import { Mat } from '../primitives/Mat'
|
|
9
|
+
import { areShapesContentEqual } from '../utils/areShapesContentEqual'
|
|
9
10
|
import { setStyleProperty } from '../utils/dom'
|
|
10
11
|
import { OptionalErrorBoundary } from './ErrorBoundary'
|
|
11
12
|
|
|
@@ -27,6 +28,7 @@ export const Shape = memo(function Shape({
|
|
|
27
28
|
index,
|
|
28
29
|
backgroundIndex,
|
|
29
30
|
opacity,
|
|
31
|
+
dprMultiple,
|
|
30
32
|
}: {
|
|
31
33
|
id: TLShapeId
|
|
32
34
|
shape: TLShape
|
|
@@ -34,6 +36,7 @@ export const Shape = memo(function Shape({
|
|
|
34
36
|
index: number
|
|
35
37
|
backgroundIndex: number
|
|
36
38
|
opacity: number
|
|
39
|
+
dprMultiple: number
|
|
37
40
|
}) {
|
|
38
41
|
const editor = useEditor()
|
|
39
42
|
|
|
@@ -88,14 +91,18 @@ export const Shape = memo(function Shape({
|
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
// Width / Height
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
// We round the shape width and height up to the nearest multiple of dprMultiple
|
|
95
|
+
// to avoid the browser making miscalculations when applying the transform.
|
|
96
|
+
const widthRemainder = bounds.w % dprMultiple
|
|
97
|
+
const heightRemainder = bounds.h % dprMultiple
|
|
98
|
+
const width = widthRemainder === 0 ? bounds.w : bounds.w + (dprMultiple - widthRemainder)
|
|
99
|
+
const height = heightRemainder === 0 ? bounds.h : bounds.h + (dprMultiple - heightRemainder)
|
|
93
100
|
|
|
94
101
|
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')
|
|
102
|
+
setStyleProperty(containerRef.current, 'width', Math.max(width, dprMultiple) + 'px')
|
|
103
|
+
setStyleProperty(containerRef.current, 'height', Math.max(height, dprMultiple) + 'px')
|
|
104
|
+
setStyleProperty(bgContainerRef.current, 'width', Math.max(width, dprMultiple) + 'px')
|
|
105
|
+
setStyleProperty(bgContainerRef.current, 'height', Math.max(height, dprMultiple) + 'px')
|
|
99
106
|
prev.width = width
|
|
100
107
|
prev.height = height
|
|
101
108
|
}
|
|
@@ -184,10 +191,7 @@ export const InnerShape = memo(
|
|
|
184
191
|
[util, shape.id]
|
|
185
192
|
)
|
|
186
193
|
},
|
|
187
|
-
(prev, next) =>
|
|
188
|
-
prev.shape.props === next.shape.props &&
|
|
189
|
-
prev.shape.meta === next.shape.meta &&
|
|
190
|
-
prev.util === next.util
|
|
194
|
+
(prev, next) => areShapesContentEqual(prev.shape, next.shape) && prev.util === next.util
|
|
191
195
|
)
|
|
192
196
|
|
|
193
197
|
export const InnerShapeBackground = memo(
|
|
@@ -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
|
@@ -129,6 +129,7 @@ import { Group2d } from '../primitives/geometry/Group2d'
|
|
|
129
129
|
import { intersectPolygonPolygon } from '../primitives/intersect'
|
|
130
130
|
import { PI, approximately, areAnglesCompatible, clamp, pointInPolygon } from '../primitives/utils'
|
|
131
131
|
import { ReadonlySharedStyleMap, SharedStyle, SharedStyleMap } from '../utils/SharedStylesMap'
|
|
132
|
+
import { areShapesContentEqual } from '../utils/areShapesContentEqual'
|
|
132
133
|
import { dataUrlToFile } from '../utils/assets'
|
|
133
134
|
import { debugFlags } from '../utils/debug-flags'
|
|
134
135
|
import {
|
|
@@ -4574,7 +4575,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4574
4575
|
this.fonts.trackFontsForShape(shape)
|
|
4575
4576
|
return this.getShapeUtil(shape).getGeometry(shape, opts)
|
|
4576
4577
|
},
|
|
4577
|
-
{ areRecordsEqual:
|
|
4578
|
+
{ areRecordsEqual: areShapesContentEqual }
|
|
4578
4579
|
)
|
|
4579
4580
|
}
|
|
4580
4581
|
return this._shapeGeometryCaches[context].get(
|
|
@@ -4622,9 +4623,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4622
4623
|
|
|
4623
4624
|
/** @internal */
|
|
4624
4625
|
@computed private _getShapeHandlesCache(): ComputedCache<TLHandle[] | undefined, TLShape> {
|
|
4625
|
-
return this.store.createComputedCache(
|
|
4626
|
-
|
|
4627
|
-
|
|
4626
|
+
return this.store.createComputedCache(
|
|
4627
|
+
'handles',
|
|
4628
|
+
(shape) => {
|
|
4629
|
+
return this.getShapeUtil(shape).getHandles?.(shape)
|
|
4630
|
+
},
|
|
4631
|
+
{
|
|
4632
|
+
areRecordsEqual: areShapesContentEqual,
|
|
4633
|
+
}
|
|
4634
|
+
)
|
|
4628
4635
|
}
|
|
4629
4636
|
|
|
4630
4637
|
/**
|
|
@@ -5845,9 +5852,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5845
5852
|
@computed
|
|
5846
5853
|
private _getBindingsIndexCache() {
|
|
5847
5854
|
const index = bindingsIndex(this)
|
|
5848
|
-
return this.store.createComputedCache<TLBinding[], TLShape>(
|
|
5849
|
-
|
|
5850
|
-
|
|
5855
|
+
return this.store.createComputedCache<TLBinding[], TLShape>(
|
|
5856
|
+
'bindingsIndex',
|
|
5857
|
+
(shape) => {
|
|
5858
|
+
return index.get().get(shape.id)
|
|
5859
|
+
},
|
|
5860
|
+
// we can ignore the shape equality check here because the index is
|
|
5861
|
+
// computed incrementally based on what bindings are in the store
|
|
5862
|
+
{ areRecordsEqual: () => true }
|
|
5863
|
+
)
|
|
5851
5864
|
}
|
|
5852
5865
|
|
|
5853
5866
|
/**
|
|
@@ -10214,7 +10227,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
10214
10227
|
|
|
10215
10228
|
// If the camera behavior is "zoom" and the ctrl key is pressed, then pan;
|
|
10216
10229
|
// If the camera behavior is "pan" and the ctrl key is not pressed, then zoom
|
|
10217
|
-
if (
|
|
10230
|
+
if (info.ctrlKey) behavior = wheelBehavior === 'pan' ? 'zoom' : 'pan'
|
|
10218
10231
|
|
|
10219
10232
|
switch (behavior) {
|
|
10220
10233
|
case 'zoom': {
|
|
@@ -19,6 +19,7 @@ import { TLFontFace } from '../managers/FontManager'
|
|
|
19
19
|
import { BoundsSnapGeometry } from '../managers/SnapManager/BoundsSnaps'
|
|
20
20
|
import { HandleSnapGeometry } from '../managers/SnapManager/HandleSnaps'
|
|
21
21
|
import { SvgExportContext } from '../types/SvgExportContext'
|
|
22
|
+
import { TLClickEventInfo } from '../types/event-types'
|
|
22
23
|
import { TLResizeHandle } from '../types/selection-types'
|
|
23
24
|
|
|
24
25
|
/** @public */
|
|
@@ -671,10 +672,21 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
|
|
671
672
|
* A callback called when a shape's edge is double clicked.
|
|
672
673
|
*
|
|
673
674
|
* @param shape - The shape.
|
|
675
|
+
* @param info - Info about the edge.
|
|
674
676
|
* @returns A change to apply to the shape, or void.
|
|
675
677
|
* @public
|
|
676
678
|
*/
|
|
677
|
-
onDoubleClickEdge?(shape: Shape): TLShapePartial<Shape> | void
|
|
679
|
+
onDoubleClickEdge?(shape: Shape, info: TLClickEventInfo): TLShapePartial<Shape> | void
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* A callback called when a shape's corner is double clicked.
|
|
683
|
+
*
|
|
684
|
+
* @param shape - The shape.
|
|
685
|
+
* @param info - Info about the corner.
|
|
686
|
+
* @returns A change to apply to the shape, or void.
|
|
687
|
+
* @public
|
|
688
|
+
*/
|
|
689
|
+
onDoubleClickCorner?(shape: Shape, info: TLClickEventInfo): TLShapePartial<Shape> | void
|
|
678
690
|
|
|
679
691
|
/**
|
|
680
692
|
* A callback called when a shape is double clicked.
|