Custom paste

This example adds a special rule for pasting single frame shapes, so they'll try to find an empty space instead of always pasting in the location they were copied from.

import {
	defaultHandleExternalTldrawContent,
	Editor,
	Tldraw,
	TLFrameShape,
	TLTldrawExternalContent,
} from 'tldraw'

// this example adds special behavior when pasting a single frame shape, matching the behavior of figma
export default function CustomPasteExample() {
	return (
		<div className="tldraw__editor">
			<Tldraw
				onMount={(editor) => {
					// on mount, override the default tldraw handler
					editor.registerExternalContentHandler('tldraw', (content) =>
						handleCustomTldrawPaste(editor, content)
					)
				}}
			/>
		</div>
	)
}

const SPACING_BETWEEN_FRAMES = 50

function handleCustomTldrawPaste(editor: Editor, { content, point }: TLTldrawExternalContent) {
	// find the only shape in the pasted content
	const onlyCopiedShape =
		content.rootShapeIds.length === 1
			? content.shapes.find((shape) => shape.id === content.rootShapeIds[0])
			: null

	// make sure that the shape is a frame. if it is, retrieve the current version of that frame
	// from the document.
	const onlyCopiedFrame =
		onlyCopiedShape?.type === 'frame' ? (onlyCopiedShape as TLFrameShape) : null

	// we only want to use our special behavior if the frame (current & pasted) will be a direct
	// descendant of the current page.
	const willPasteOnCurrentPage = onlyCopiedFrame
		? !editor.getShape(onlyCopiedFrame.parentId)
		: false

	// if the paste is happening at a specific point, or we didn't copy a single frame that belongs
	// to this page, fall back to the default paste behavior
	if (point || !onlyCopiedFrame || !willPasteOnCurrentPage) {
		defaultHandleExternalTldrawContent(editor, { content, point })
		return
	}

	// if we are pasting a single frame, and that frame is still in the document, we want to find a
	// free space to the right of the frame to put this one.
	editor.putContentOntoCurrentPage(content, { select: true })
	const newlyPastedFrame = editor.getOnlySelectedShape()
	if (!newlyPastedFrame || !editor.isShapeOfType<TLFrameShape>(newlyPastedFrame, 'frame')) return

	const siblingIds = editor.getSortedChildIdsForParent(newlyPastedFrame.parentId)
	const pastedBounds = editor.getShapePageBounds(newlyPastedFrame.id)!
	let targetPosition = pastedBounds.minX

	const siblingBounds = siblingIds
		.map((id) => ({ id, bounds: editor.getShapePageBounds(id)! }))
		.sort((a, b) => a.bounds.minX - b.bounds.minX)

	for (const sibling of siblingBounds) {
		if (sibling.id === newlyPastedFrame.id) continue

		// if this sibling is above or below the copied frame, we don't need to take it into account
		if (sibling.bounds.minY > pastedBounds.maxY || sibling.bounds.maxY < pastedBounds.minY) continue

		// if the sibling is to the left of the copied frame, we don't need to take it into account
		if (sibling.bounds.maxX < targetPosition) continue

		// if the sibling is to the right of where the pasted frame would end up, we don't care about it
		if (sibling.bounds.minX > targetPosition + pastedBounds.w) continue

		// otherwise, we need to shift our target right edge to the right of this sibling
		targetPosition = sibling.bounds.maxX + SPACING_BETWEEN_FRAMES
	}

	// translate the pasted frame into position:
	editor.nudgeShapes([newlyPastedFrame.id], {
		x: targetPosition - pastedBounds.minX,
		y: 0,
	})
}
Is this page helpful?
Prev
Custom options
Next
Infer dark mode
hi

We use cookies on this website.
Learn more in our Cookie Policy.