apps/docs/content/sdk-features/input-handling.mdx
The InputsManager class tracks pointer and keyboard state for the editor. It stores pointer positions in both screen space and page space, tracks pressed keys and buttons, detects device types (mouse, touch, pen), and calculates pointer velocity. Access it through editor.inputs.
All input state is reactive. The manager stores values as atoms from @tldraw/state, so components that read input state automatically update when those values change. The manager updates on every input event, converting coordinates between screen space and page space.
The manager tracks pointer positions in two coordinate spaces. Screen space is pixels relative to the canvas container's origin. Page space is the position on the infinite canvas, adjusted for camera position and zoom.
In each space, the manager keeps three positions: current, previous, and origin.
editor.inputs.getCurrentScreenPoint() // Current position in screen space
editor.inputs.getCurrentPagePoint() // Current position in page space
editor.inputs.getPreviousScreenPoint() // Previous position in screen space
editor.inputs.getPreviousPagePoint() // Previous position in page space
The current position updates on every pointer event. The previous position stores where the pointer was before the most recent update. You can use these together to calculate deltas for dragging and panning:
const delta = Vec.Sub(editor.inputs.getCurrentPagePoint(), editor.inputs.getPreviousPagePoint())
The origin position captures where the most recent pointer_down event occurred:
editor.inputs.getOriginScreenPoint() // Where pointer_down occurred in screen space
editor.inputs.getOriginPagePoint() // Where pointer_down occurred in page space
Tools use the origin to calculate drag distances and determine whether an interaction has moved far enough to trigger behaviors like dragging. The origin resets on every pointer_down event and continuously while a pinch is in progress.
The manager converts screen coordinates to page coordinates using the camera's position and zoom:
// Screen to page conversion
const pageX = screenX / camera.z - camera.x
const pageY = screenY / camera.z - camera.y
The manager tracks pointer velocity for gesture detection:
editor.inputs.getPointerVelocity() // Vec with x/y velocity in pixels per millisecond
The manager listens to the editor's frame event and recomputes velocity once per frame (not on each pointer event) from the screen-space distance traveled since the previous frame. It smooths the result against the previous value and clamps components below 0.01 to zero to prevent jitter.
Tools use velocity to distinguish between slow, precise interactions and fast flick gestures. Velocity resets to zero on pointer_down events and continuously while a pinch is in progress.
The manager tracks whether the most recent pointer event came from a pen (pointerType === 'pen'):
editor.inputs.getIsPen() // true for stylus input
Pen mode ignores non-pen input to prevent accidental touch interactions while using a stylus. The editor only turns pen mode on automatically for direct-display pens (Apple Pencil, Surface Pen), flagged as isPenDirect on the pointer event; desktop graphics tablets still draw as pens without enabling it.
The manager tracks modifier key states:
editor.inputs.getShiftKey()
editor.inputs.getAltKey()
editor.inputs.getCtrlKey()
editor.inputs.getMetaKey()
editor.inputs.getAccelKey() // Cmd on Mac, Ctrl elsewhere
The getAccelKey() method returns true for Command on macOS and Control on other platforms. Use this for cross-platform shortcuts.
The manager tracks currently pressed pointer buttons in a reactive set:
editor.inputs.buttons.has(0) // Primary button (left click)
editor.inputs.buttons.has(1) // Middle button
editor.inputs.buttons.has(2) // Secondary button (right click)
Buttons are added on pointer_down events and removed on pointer_up events.
The manager tracks pressed keyboard keys in a reactive set:
editor.inputs.keys.has('Space')
editor.inputs.keys.has('ShiftLeft')
The editor adds keys on key_down and removes them on key_up. Tools can use this to detect held keys during pointer operations. For example, the editor checks keys.has('Space') on pointer up to decide whether to keep spacebar panning active.
The manager tracks the current interaction state:
editor.inputs.getIsPointing() // Pointer button is down
editor.inputs.getIsRightPointing() // Right button is down, before the drag threshold
editor.inputs.getIsDragging() // Pointer moved beyond drag threshold while pointing
editor.inputs.getIsPinching() // Two-finger pinch gesture active
editor.inputs.getIsEditing() // Editing text or other content
editor.inputs.getIsPanning() // Panning the canvas
editor.inputs.getIsSpacebarPanning() // Panning via spacebar (vs. other panning modes)
The editor sets these flags during event processing. For example, isPointing becomes true on pointer_down and false on pointer_up. The isDragging flag becomes true when the pointer moves beyond the drag distance threshold (dragDistanceSquared or coarseDragDistanceSquared in the editor's options) while pointing.
When an input event occurs, the editor processes it through these stages:
pointer_move, wheel, and pinch events are queued and flushed once per frame; other events flush immediatelybefore-event, then updates modifier key stateupdateFromEvent() on the InputsManagerClickManager for double-click detectionroot.handleEvent(), which propagates it through active tool states, then emits eventSee Events for subscribing to before-event and event.
The typed event info objects are:
| Event type | Info type |
|---|---|
| Pointer events | TLPointerEventInfo |
| Click events | TLClickEventInfo |
| Keyboard events | TLKeyboardEventInfo |
| Wheel events | TLWheelEventInfo |
| Pinch events | TLPinchEventInfo |
In collaborative sessions, updateFromEvent() also updates the user's pointer presence record in the store, broadcasting pointer position to other users.
The editor listens to Pointer Events, so mouse, touch, and pen input arrive through the same handlers, and the manager tracks the device type through the isPen flag.
Pointer positions include a z coordinate carrying the pointer event's pressure; synthetic events without a z default to 0.5. The manager subtracts the container's screen bounds from client coordinates, so the editor works correctly in nested layouts and scrolled containers.
The manager provides a toJson() method for debugging:
const state = editor.inputs.toJson()
// Returns all position vectors, modifier key states, interaction flags,
// device type, and the contents of the keys and buttons sets
We use this serialized form when generating crash reports.