/* eslint-disable @typescript-eslint/no-unused-vars */ import { LegacyMigrations, MigrationSequence } from '@tldraw/store' import { ShapeProps, TLHandle, TLShape, TLShapePartial, TLShapePropsMigrations, TLUnknownShape, } from '@tldraw/tlschema' import { ReactElement } from 'react' import { Box } from '../../primitives/Box' import { Vec } from '../../primitives/Vec' import { Geometry2d } from '../../primitives/geometry/Geometry2d' import type { Editor } from '../Editor' import { BoundsSnapGeometry } from '../managers/SnapManager/BoundsSnaps' import { HandleSnapGeometry } from '../managers/SnapManager/HandleSnaps' import { SvgExportContext } from '../types/SvgExportContext' import { TLResizeHandle } from '../types/selection-types' /** @public */ export interface TLShapeUtilConstructor< T extends TLUnknownShape, U extends ShapeUtil = ShapeUtil, > { new (editor: Editor): U type: T['type'] props?: ShapeProps migrations?: LegacyMigrations | TLShapePropsMigrations | MigrationSequence } /** @public */ export type TLShapeUtilFlag = (shape: T) => boolean /** @public */ export interface TLShapeUtilCanvasSvgDef { key: string component: React.ComponentType } /** @public */ export abstract class ShapeUtil { constructor(public editor: Editor) {} static props?: ShapeProps static migrations?: LegacyMigrations | TLShapePropsMigrations /** * The type of the shape util, which should match the shape's type. * * @public */ static type: string /** * Get the default props for a shape. * * @public */ abstract getDefaultProps(): Shape['props'] /** * Get the shape's geometry. * * @param shape - The shape. * @public */ abstract getGeometry(shape: Shape): Geometry2d /** * Get a JSX element for the shape (as an HTML element). * * @param shape - The shape. * @public */ abstract component(shape: Shape): any /** * Get JSX describing the shape's indicator (as an SVG element). * * @param shape - The shape. * @public */ abstract indicator(shape: Shape): any /** * Whether the shape can be snapped to by another shape. * * @public */ canSnap: TLShapeUtilFlag = () => true /** * Whether the shape can be scrolled while editing. * * @public */ canScroll: TLShapeUtilFlag = () => false /** * Whether the shape can be bound to by an arrow. * * @param _otherShape - The other shape attempting to bind to this shape. * @public */ canBind = (_shape: Shape, _otherShape?: K) => true /** * Whether the shape can be double clicked to edit. * * @public */ canEdit: TLShapeUtilFlag = () => false /** * Whether the shape can be resized. * * @public */ canResize: TLShapeUtilFlag = () => true /** * Whether the shape can be edited in read-only mode. * * @public */ canEditInReadOnly: TLShapeUtilFlag = () => false /** * Whether the shape can be cropped. * * @public */ canCrop: TLShapeUtilFlag = () => false /** * Does this shape provide a background for its children? If this is true, * then any children with a `renderBackground` method will have their * backgrounds rendered _above_ this shape. Otherwise, the children's * backgrounds will be rendered above either the next ancestor that provides * a background, or the canvas background. * * @internal */ providesBackgroundForChildren(shape: Shape): boolean { return false } /** * Whether the shape should hide its resize handles when selected. * * @public */ hideResizeHandles: TLShapeUtilFlag = () => false /** * Whether the shape should hide its rotation handles when selected. * * @public */ hideRotateHandle: TLShapeUtilFlag = () => false /** * Whether the shape should hide its selection bounds background when selected. * * @public */ hideSelectionBoundsBg: TLShapeUtilFlag = () => false /** * Whether the shape should hide its selection bounds foreground when selected. * * @public */ hideSelectionBoundsFg: TLShapeUtilFlag = () => false /** * Whether the shape's aspect ratio is locked. * * @public */ isAspectRatioLocked: TLShapeUtilFlag = () => false /** * 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. * * @param shape - The shape. * @internal */ backgroundComponent?(shape: Shape): any /** * Get an array of handle models for the shape. This is an optional method. * * @example * * ```ts * util.getHandles?.(myShape) * ``` * * @param shape - The shape. * @public */ getHandles?(shape: Shape): TLHandle[] /** * Get whether the shape can receive children of a given type. * * @param type - The shape type. * @public */ canReceiveNewChildrenOfType(shape: Shape, type: TLShape['type']) { return false } /** * Get whether the shape can receive children of a given type. * * @param shape - The shape type. * @param shapes - The shapes that are being dropped. * @public */ canDropShapes(shape: Shape, shapes: TLShape[]) { return false } /** * Get the shape as an SVG object. * * @param shape - The shape. * @param ctx - The export context for the SVG - used for adding e.g. \s * @returns An SVG element. * @public */ toSvg?(shape: Shape, ctx: SvgExportContext): ReactElement | null | Promise /** * Get the shape's background layer as an SVG object. * * @param shape - The shape. * @param ctx - ctx - The export context for the SVG - used for adding e.g. \s * @returns An SVG element. * @public */ toBackgroundSvg?( shape: Shape, ctx: SvgExportContext ): ReactElement | null | Promise /** @internal */ expandSelectionOutlinePx(shape: Shape): number { return 0 } /** * Return elements to be added to the \ section of the canvases SVG context. This can be * used to define SVG content (e.g. patterns & masks) that can be referred to by ID from svg * elements returned by `component`. * * Each def should have a unique `key`. If multiple defs from different shapes all have the same * key, only one will be used. */ getCanvasSvgDefs(): TLShapeUtilCanvasSvgDef[] { return [] } /** * Get the geometry to use when snapping to this this shape in translate/resize operations. See * {@link BoundsSnapGeometry} for details. */ getBoundsSnapGeometry(shape: Shape): BoundsSnapGeometry { return {} } /** * Get the geometry to use when snapping handles to this shape. See {@link HandleSnapGeometry} * for details. */ getHandleSnapGeometry(shape: Shape): HandleSnapGeometry { return {} } // Events /** * A callback called just before a shape is created. This method provides a last chance to modify * the created shape. * * @example * * ```ts * onBeforeCreate = (next) => { * return { ...next, x: next.x + 1 } * } * ``` * * @param next - The next shape. * @returns The next shape or void. * @public */ onBeforeCreate?: TLOnBeforeCreateHandler /** * A callback called just before a shape is updated. This method provides a last chance to modify * the updated shape. * * @example * * ```ts * onBeforeUpdate = (prev, next) => { * if (prev.x === next.x) { * return { ...next, x: next.x + 1 } * } * } * ``` * * @param prev - The previous shape. * @param next - The next shape. * @returns The next shape or void. * @public */ onBeforeUpdate?: TLOnBeforeUpdateHandler /** * A callback called when some other shapes are dragged over this one. * * @example * * ```ts * onDragShapesOver = (shape, shapes) => { * this.editor.reparentShapes(shapes, shape.id) * } * ``` * * @param shape - The shape. * @param shapes - The shapes that are being dragged over this one. * @public */ onDragShapesOver?: TLOnDragHandler /** * A callback called when some other shapes are dragged out of this one. * * @param shape - The shape. * @param shapes - The shapes that are being dragged out. * @public */ onDragShapesOut?: TLOnDragHandler /** * A callback called when some other shapes are dropped over this one. * * @param shape - The shape. * @param shapes - The shapes that are being dropped over this one. * @public */ onDropShapesOver?: TLOnDragHandler /** * A callback called when a shape starts being resized. * * @param shape - The shape. * @returns A change to apply to the shape, or void. * @public */ onResizeStart?: TLOnResizeStartHandler /** * A callback called when a shape changes from a resize. * * @param shape - The shape at the start of the resize. * @param info - Info about the resize. * @returns A change to apply to the shape, or void. * @public */ onResize?: TLOnResizeHandler /** * A callback called when a shape finishes resizing. * * @param initial - The shape at the start of the resize. * @param current - The current shape. * @returns A change to apply to the shape, or void. * @public */ onResizeEnd?: TLOnResizeEndHandler /** * A callback called when a shape starts being translated. * * @param shape - The shape. * @returns A change to apply to the shape, or void. * @public */ onTranslateStart?: TLOnTranslateStartHandler /** * A callback called when a shape changes from a translation. * * @param initial - The shape at the start of the translation. * @param current - The current shape. * @returns A change to apply to the shape, or void. * @public */ onTranslate?: TLOnTranslateHandler /** * A callback called when a shape finishes translating. * * @param initial - The shape at the start of the translation. * @param current - The current shape. * @returns A change to apply to the shape, or void. * @public */ onTranslateEnd?: TLOnTranslateEndHandler /** * A callback called when a shape's handle changes. * * @param shape - The current shape. * @param info - An object containing the handle and whether the handle is 'precise' or not. * @returns A change to apply to the shape, or void. * @public */ onHandleDrag?: TLOnHandleDragHandler /** * A callback called when a shape starts being rotated. * * @param shape - The shape. * @returns A change to apply to the shape, or void. * @public */ onRotateStart?: TLOnRotateStartHandler /** * A callback called when a shape changes from a rotation. * * @param initial - The shape at the start of the rotation. * @param current - The current shape. * @returns A change to apply to the shape, or void. * @public */ onRotate?: TLOnRotateHandler /** * A callback called when a shape finishes rotating. * * @param initial - The shape at the start of the rotation. * @param current - The current shape. * @returns A change to apply to the shape, or void. * @public */ onRotateEnd?: TLOnRotateEndHandler /** * Not currently used. * * @internal */ onBindingChange?: TLOnBindingChangeHandler /** * A callback called when a shape's children change. * * @param shape - The shape. * @returns An array of shape updates, or void. * @public */ onChildrenChange?: TLOnChildrenChangeHandler /** * A callback called when a shape's handle is double clicked. * * @param shape - The shape. * @param handle - The handle that is double-clicked. * @returns A change to apply to the shape, or void. * @public */ onDoubleClickHandle?: TLOnDoubleClickHandleHandler /** * A callback called when a shape's edge is double clicked. * * @param shape - The shape. * @returns A change to apply to the shape, or void. * @public */ onDoubleClickEdge?: TLOnDoubleClickHandler /** * A callback called when a shape is double clicked. * * @param shape - The shape. * @returns A change to apply to the shape, or void. * @public */ onDoubleClick?: TLOnDoubleClickHandler /** * A callback called when a shape is clicked. * * @param shape - The shape. * @returns A change to apply to the shape, or void. * @public */ onClick?: TLOnClickHandler /** * A callback called when a shape finishes being editing. * * @param shape - The shape. * @public */ onEditEnd?: TLOnEditEndHandler } /** @public */ export type TLOnBeforeCreateHandler = (next: T) => T | void /** @public */ export type TLOnBeforeUpdateHandler = (prev: T, next: T) => T | void /** @public */ export type TLOnTranslateStartHandler = TLEventStartHandler /** @public */ export type TLOnTranslateHandler = TLEventChangeHandler /** @public */ export type TLOnTranslateEndHandler = TLEventChangeHandler /** @public */ export type TLOnRotateStartHandler = TLEventStartHandler /** @public */ export type TLOnRotateHandler = TLEventChangeHandler /** @public */ export type TLOnRotateEndHandler = TLEventChangeHandler /** * The type of resize. * * 'scale_shape' - The shape is being scaled, usually as part of a larger selection. * * 'resize_bounds' - The user is directly manipulating an individual shape's bounds using a resize * handle. It is up to shape util implementers to decide how they want to handle the two * situations. * * @public */ export type TLResizeMode = 'scale_shape' | 'resize_bounds' /** * Info about a resize. * @param newPoint - The new local position of the shape. * @param handle - The handle being dragged. * @param mode - The type of resize. * @param scaleX - The scale in the x-axis. * @param scaleY - The scale in the y-axis. * @param initialBounds - The bounds of the shape at the start of the resize. * @param initialShape - The shape at the start of the resize. * @public */ export type TLResizeInfo = { newPoint: Vec handle: TLResizeHandle mode: TLResizeMode scaleX: number scaleY: number initialBounds: Box initialShape: T } /** @public */ export type TLOnResizeHandler = ( shape: T, info: TLResizeInfo ) => Omit, 'id' | 'type'> | undefined | void /** @public */ export type TLOnResizeStartHandler = TLEventStartHandler /** @public */ export type TLOnResizeEndHandler = TLEventChangeHandler /* -------------------- Dragging -------------------- */ /** @public */ export type TLOnDragHandler = (shape: T, shapes: TLShape[]) => R /** @internal */ export type TLOnBindingChangeHandler = (shape: T) => TLShapePartial | void /** @public */ export type TLOnChildrenChangeHandler = (shape: T) => TLShapePartial[] | void /** @public */ export type TLOnHandleDragHandler = ( shape: T, info: { handle: TLHandle isPrecise: boolean initial?: T | undefined } ) => TLShapePartial | void /** @public */ export type TLOnClickHandler = (shape: T) => TLShapePartial | void /** @public */ export type TLOnEditEndHandler = (shape: T) => void /** @public */ export type TLOnDoubleClickHandler = (shape: T) => TLShapePartial | void /** @public */ export type TLOnDoubleClickHandleHandler = ( shape: T, handle: TLHandle ) => TLShapePartial | void type TLEventStartHandler = (shape: T) => TLShapePartial | void type TLEventChangeHandler = (initial: T, current: T) => TLShapePartial | void