snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-23 16:12:52 +05:00
commit a774ac673a
4541 changed files with 770499 additions and 0 deletions

View File

@@ -0,0 +1,464 @@
---
title: Editor
created_at: 12/29/2025
updated_at: 12/29/2025
keywords:
- editor
- api
- architecture
- state
- manager
status: published
date: 12/29/2025
readability: 9
voice: 9
completeness: 9
accuracy: 10
notes: ''
---
The [Editor](?) class is the main way of controlling tldraw's editor. It has methods for creating, reading, updating, and deleting shapes, for managing selection and history, for controlling the camera, and for responding to user input. By design, the editor's surface area is very large: almost everything is available through it.
Need to create some shapes? Use [Editor#createShapes](?). Need to delete them? Use [Editor#deleteShapes](?). Want a sorted array of every shape on the current page? Use [Editor#getCurrentPageShapesSorted](?). The editor is your primary interface for interacting with the canvas.
## Accessing the editor
You can access the editor in two ways:
1. From the [Tldraw](?) component's `onMount` callback:
```tsx
import { Tldraw } from 'tldraw'
import 'tldraw/tldraw.css'
export default function App() {
return (
<div style={{ position: 'fixed', inset: 0 }}>
<Tldraw
onMount={(editor) => {
// Your editor code here
editor.createShape({ type: 'geo', x: 100, y: 100 })
}}
/>
</div>
)
}
```
2. Via the [useEditor](?) hook, which must be called from within the [Tldraw](?) component tree:
```tsx
function InsideOfContext() {
const editor = useEditor()
// Your editor code here
return null
}
function App() {
return (
<Tldraw>
<InsideOfContext />
</Tldraw>
)
}
```
## Architecture overview
The editor coordinates several interconnected systems.
### Store
The editor holds document data in its [Editor#store](?) property. The store is a reactive database containing records for shapes, pages, bindings, assets, and editor state. All records are JSON-serializable.
```ts
// Access a shape record directly from the store
const shape = editor.store.get(shapeId)
// Listen to store changes
editor.store.listen((entry) => {
console.log('Changed:', entry.changes)
})
```
The store is reactive: when data changes, the UI updates automatically. The editor wraps the store with higher-level methods like `createShapes()` and `deleteShapes()`, so you rarely need to interact with it directly.
See [Store](/sdk-features/store) for details on working with the store directly.
### Signals
Tldraw uses a signals-based reactive system. The editor exposes many of its internal values as signals—methods like `editor.getSelectedShapeIds()` and `editor.getCurrentPageShapes()` return reactive values that update automatically when the underlying state changes.
```tsx
import { track, useEditor } from 'tldraw'
const SelectedCount = track(function SelectedCount() {
const editor = useEditor()
return <div>{editor.getSelectedShapeIds().length} shapes selected</div>
})
```
The `track` higher-order component automatically subscribes to signals accessed during render. When those signals change, the component re-renders.
See [Signals](/sdk-features/signals) for the full reactive API.
### State chart
The editor uses a hierarchical state machine for handling user interactions. Tools like the select tool, draw tool, and hand tool are implemented as [StateNode](?) instances. Each node can have child states, and the active state determines how the editor responds to events.
```ts
// Change the current tool
editor.setCurrentTool('draw')
editor.setCurrentTool('hand')
// Check the current tool
editor.getCurrentToolId() // 'select', 'draw', 'hand', etc.
// Check the full state path
editor.root.getPath() // 'root.select.idle', 'root.draw.drawing', etc.
// Check if a state is active
editor.isIn('select') // true if select tool is active
editor.isIn('select.idle') // true if in idle state of select tool
// Check if any of several states are active
editor.isInAny('select.idle', 'hand.idle') // true if in either state
```
#### State transitions
Tools transition between child states as the user interacts. For example, the select tool has states like `idle`, `pointing_canvas`, `pointing_shape`, `brushing`, and `translating`. When you click and drag on empty canvas, the state flows like this:
1. `select.idle` — waiting for input
2. `select.pointing_canvas` — pointer down on the canvas, waiting to see if this is a click or drag
3. `select.brushing` — dragging to create a selection box
4. `select.idle` — pointer up, back to waiting
Each state handles events differently. The `idle` state responds to `pointer_down` on the canvas by transitioning to `pointing_canvas`. The `brushing` state updates the brush bounds on `pointer_move` and completes the selection on `pointer_up`.
#### Event flow
Events flow from the root state down through active children: `root` → `select` → `idle`. Each state runs its own handler first, then passes the event to its active child. If a handler transitions to a different child, the new child does not receive the same event.
States define handlers for events like `onPointerDown`, `onPointerMove`, `onKeyDown`, and `onEnter`/`onExit` for state transitions. The active state chain determines which handlers run.
See [Tools](/sdk-features/tools) for building custom tools.
### Managers
The editor delegates specialized functionality to manager classes:
| Manager | Responsibility |
| --------------------------- | ------------------------------------------------ |
| [HistoryManager](?) | Undo/redo stack and history marks |
| [SnapManager](?) | Shape snapping during transforms |
| [TextManager](?) | Text measurement and layout |
| [FontManager](?) | Font loading and management |
| [InputsManager](?) | Pointer position and modifier key tracking |
| [ClickManager](?) | Click, double-click, and long-press detection |
| [ScribbleManager](?) | Scribble trails (eraser, laser, scribble select) |
| [EdgeScrollManager](?) | Auto-scroll at viewport edges |
| [UserPreferencesManager](?) | User settings persistence |
Access managers through properties on the editor instance:
```ts
editor.snaps.getIndicators()
editor.inputs.getCurrentPagePoint()
editor.user.getUserPreferences()
editor.scribbles.addScribble({ color: 'accent' })
editor.edgeScrollManager.updateEdgeScrolling(elapsed)
```
## Working with shapes
Shapes are the content on your canvas. Each shape has a type, position, rotation, and type-specific props.
### Creating shapes
```ts
// Create a shape with auto-generated ID
editor.createShape({
type: 'geo',
x: 100,
y: 100,
props: {
geo: 'rectangle',
w: 200,
h: 150,
color: 'blue',
},
})
// Create with a specific ID
import { createShapeId } from 'tldraw'
const id = createShapeId('my-shape')
editor.createShape({
id,
type: 'geo',
x: 100,
y: 100,
})
```
### Reading shapes
```ts
// Get a shape by ID
const shape = editor.getShape(shapeId)
// Get all shapes on the current page
const shapes = editor.getCurrentPageShapes()
// Get selected shapes
const selected = editor.getSelectedShapes()
```
### Updating shapes
```ts
editor.updateShape({
id: shape.id,
type: shape.type, // Required
x: 200,
props: {
color: 'red',
},
})
```
### Deleting shapes
```ts
// Delete by ID
editor.deleteShapes([shapeId])
// Delete by shape record
editor.deleteShapes([shape])
```
See [Shapes](/sdk-features/shapes) for the complete shape system.
## Selection
The editor tracks which shapes are selected. Selection drives transform handles, copy/paste, and delete.
```ts
// Select shapes
editor.select(shapeId)
editor.select(shapeId1, shapeId2)
// Add to selection
editor.setSelectedShapes([...editor.getSelectedShapeIds(), newId])
// Clear selection
editor.selectNone()
// Select all on current page
editor.selectAll()
// Get selection
editor.getSelectedShapeIds()
editor.getSelectedShapes()
```
See [Selection](/sdk-features/selection) for selection details.
## History
The editor maintains an undo/redo stack through its history manager. Changes to the store accumulate until you create a mark, which becomes an undo stopping point.
```ts
// Mark before an operation
editor.markHistoryStoppingPoint('rotate shapes')
editor.rotateShapesBy(editor.getSelectedShapeIds(), Math.PI / 4)
// Undo returns to the mark
editor.undo()
// Redo reapplies changes
editor.redo()
```
See [History](/sdk-features/history) for the full history API.
## Transactions
Use [Editor#run](?) to batch changes into a single transaction. This improves performance and reduces intermediate renders.
```ts
editor.run(() => {
editor.createShapes(shapes)
editor.sendToBack(shapes)
editor.selectNone()
})
```
The `run` method also accepts options for controlling history:
```ts
// Ignore changes (don't add to undo stack)
editor.run(
() => {
editor.updateShape({ id, type: 'geo', x: 100 })
},
{ history: 'ignore' }
)
// Allow editing locked shapes
editor.run(
() => {
editor.updateShapes(lockedShapes)
},
{ ignoreShapeLock: true }
)
```
See [History](/sdk-features/history) for more on how transactions interact with undo/redo.
## Camera and viewport
The editor manages a camera that determines which part of the infinite canvas is visible. The camera has x, y, and z (zoom) coordinates.
```ts
// Move camera to specific coordinates
editor.setCamera({ x: 0, y: 0, z: 1 })
// Zoom controls
editor.zoomIn()
editor.zoomOut()
editor.resetZoom()
// Fit content in view
editor.zoomToFit()
editor.zoomToSelection()
// Center on a point
editor.centerOnPoint({ x: 500, y: 500 })
// Lock the camera
editor.setCameraOptions({ isLocked: true })
```
The viewport is the visible area of the canvas. Get its bounds in screen or page coordinates:
```ts
// Screen coordinates (component size)
editor.getViewportScreenBounds()
// Page coordinates (what's visible on the canvas)
editor.getViewportPageBounds()
```
See [Camera](/sdk-features/camera) and [Coordinates](/sdk-features/coordinates) for the full camera API.
## Input state
The [Editor#inputs](?) object tracks the user's current input state: cursor position, pressed keys, and drag state. All values on the inputs object are reactive signals. When you read them inside a tracked component or computed, your code re-runs when they change.
```ts
// Cursor position in page coordinates (reactive)
editor.inputs.getCurrentPagePoint()
// Cursor position in screen coordinates (reactive)
editor.inputs.getCurrentScreenPoint()
// Where the current drag started (reactive)
editor.inputs.getOriginPagePoint()
// Interaction state (reactive)
editor.inputs.getIsDragging()
editor.inputs.getIsPointing()
editor.inputs.getIsPinching()
// Modifier keys (reactive)
editor.inputs.getShiftKey()
editor.inputs.getCtrlKey()
editor.inputs.getAltKey()
```
See [Input handling](/sdk-features/input-handling) for input details.
## Instance state
The editor maintains per-instance state in a [TLInstance](?) record. This includes which page is current, whether the editor is in readonly mode, tool lock state, and UI state. See [Instance state](/sdk-features/instance-state) and [Readonly mode](/sdk-features/readonly).
```ts
// Get instance state
const instance = editor.getInstanceState()
// Update instance state
editor.updateInstanceState({ isReadonly: true })
// Enable tool lock (keeps current tool active after creating shapes)
editor.updateInstanceState({ isToolLocked: true })
```
See [Tools](/sdk-features/tools#Tool-lock) for more on tool lock.
Each page also has instance state ([TLInstancePageState](?)) tracking selection, hovered shape, and editing shape for that page:
```ts
// Get current page state
const pageState = editor.getCurrentPageState()
```
## User preferences
User preferences are shared across all editor instances. They control things like color scheme and locale.
```ts
// Turn on dark mode
editor.user.updateUserPreferences({ colorScheme: 'dark' })
// Use system color scheme
editor.user.updateUserPreferences({ colorScheme: 'system' })
// Get current preferences
editor.user.getUserPreferences()
```
See [User preferences](/sdk-features/user-preferences) for all preference options.
## Side effects
Register callbacks to respond to record lifecycle events. Side effects let you maintain relationships, enforce constraints, or sync external state.
```ts
// After a shape is created
editor.sideEffects.registerAfterCreateHandler('shape', (shape) => {
if (shape.type === 'arrow') {
console.log('Arrow created:', shape.id)
}
})
// Before a shape is deleted
editor.sideEffects.registerBeforeDeleteHandler('shape', (shape) => {
// Return false to prevent deletion
})
```
See [Side effects](/sdk-features/side-effects) for the complete API.
## Events
The editor receives events through [Editor#dispatch](?). You typically don't call this directly—the canvas handles DOM events and dispatches them for you. But you can listen for events on the editor:
```ts
editor.on('event', (info) => {
if (info.name === 'pointer_down') {
console.log('Pointer down at', info.point)
}
})
```
See [Events](/sdk-features/events) for event types.
## Related examples
- **[Controlling the canvas](https://github.com/tldraw/tldraw/tree/main/apps/examples/src/examples/editor-api/api)** — Create and manipulate shapes, selection, and camera through the editor API.
- **[Minimal editor](https://github.com/tldraw/tldraw/tree/main/apps/examples/src/examples/configuration/only-editor)** — Use `TldrawEditor` for a bare-bones editor without default shapes or UI.
- **[Sublibraries](https://github.com/tldraw/tldraw/tree/main/apps/examples/src/examples/configuration/exploded)** — Compose tldraw from individual sublibraries for full customization.
- **[Canvas events](https://github.com/tldraw/tldraw/tree/main/apps/examples/src/examples/events/canvas-events)** — Listen to editor events including pointer, keyboard, and shape changes.
- **[Editor focus](https://github.com/tldraw/tldraw/tree/main/apps/examples/src/examples/editor-api/editor-focus)** — Control editor focus state.