Interaction end callback

Run custom logic when a programmatically started drag ends with onInteractionEnd.

import { createShapeId, StateNode, TLPointerEventInfo, Tldraw } from 'tldraw'
import 'tldraw/tldraw.css'

// There's a guide at the bottom of this file!

// [1]
class QuickShapeTool extends StateNode {
	static override id = 'quick-shape'

	override onEnter() {
		this.editor.setCursor({ type: 'cross', rotation: 0 })
	}

	override onPointerDown(info: TLPointerEventInfo) {
		const currentPagePoint = this.editor.inputs.getCurrentPagePoint()
		const shapeId = createShapeId()

		this.editor.createShape({
			id: shapeId,
			type: 'geo',
			x: currentPagePoint.x - 50,
			y: currentPagePoint.y - 50,
			props: { w: 100, h: 100, fill: 'solid' },
		})

		this.editor.setSelectedShapes([shapeId])

		// [2]
		this.editor.setCurrentTool('select.translating', {
			...info,
			target: 'shape',
			shape: this.editor.getShape(shapeId),
			isCreating: true,
			// [3]
			onInteractionEnd: () => {
				this.editor.updateShape({
					id: shapeId,
					type: 'geo',
					props: { fill: 'pattern' },
				})
				this.editor.setCurrentTool('quick-shape')
			},
		})
	}
}

const tools = [QuickShapeTool]

export default function InteractionEndExample() {
	return (
		<div className="tldraw__editor">
			<Tldraw tools={tools} initialState="quick-shape" hideUi />
		</div>
	)
}

/*
[1]
A tool that creates a shape on pointer down and immediately hands off to the select tool's
translating state, so the shape follows the pointer until it's released.

[2]
`select.translating` is normally entered from `select.pointing_shape`. Entering it directly needs
the same info: the pointer event, the target shape, and `isCreating`, which tells the state that
the shape was just created so it won't Alt-clone it and will treat the drag as part of creation.

[3]
`onInteractionEnd` runs when the drag finishes. Passing a string such as 'quick-shape' mainly
masks the current tool id, so the UI keeps showing that tool during the drag; it only switches
back to it on completion when tool lock is on. Passing a function lets you do more first. Here we swap the fill
to a pattern and then return to this tool so the next click creates another shape.
*/

When a tool starts one of the select tool's interactions itself, for example editor.setCurrentTool('select.translating', info), the onInteractionEnd field in info controls what happens when the interaction finishes. Pass a tool id string to report that tool as the current one while the drag runs (and return to it if tool lock is on), or a function to run arbitrary code.

This example's tool creates a square on click and immediately starts translating it. Click and drag anywhere: when you release, the callback changes the fill to a pattern and returns to the tool so the next click creates another square.

Is this page helpful?
Prev
Hovered overlay
Next
Performance hooks