Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"@tiptap/starter-kit": "^3.12.1",
"@tldraw/assets": "workspace:*",
"@tldraw/dotcom-shared": "workspace:*",
"@tldraw/driver": "workspace:*",
"@tldraw/state": "workspace:*",
"@tldraw/sync": "workspace:*",
"ag-grid-community": "^32.3.3",
Expand Down
3 changes: 2 additions & 1 deletion apps/examples/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
{ "path": "../../packages/sync" },
{ "path": "../../packages/state" },
{ "path": "../../packages/tldraw" },
{ "path": "../../packages/dotcom-shared" }
{ "path": "../../packages/dotcom-shared" },
{ "path": "../../packages/driver" }
]
}
119 changes: 119 additions & 0 deletions packages/driver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# @tldraw/driver

Imperative API for driving the tldraw editor programmatically. Useful for scripting, automation, REPL usage, and testing.

## Installation

```bash
npm install @tldraw/driver
```

## Quick start

```ts
import { Driver } from '@tldraw/driver'

const driver = new Driver(editor)

// Simulate user interactions
driver.click(100, 200).pointerDown(300, 400).pointerMove(500, 600).pointerUp()

// Keyboard input
driver.keyPress('a')
driver.keyDown('Shift')
driver.keyUp('Shift')

// Clipboard
driver.copy()
driver.paste({ x: 100, y: 100 })

// Selection manipulation
driver.translateSelection(50, 0)
driver.rotateSelection(Math.PI / 4)
driver.resizeSelection({ scaleX: 2 }, 'bottom_right')

// Camera
driver.pan({ x: 100, y: 0 })
driver.wheel(0, -100)

// Cleanup
driver.dispose()
```

## API

### Constructor

#### `new Driver(editor: Editor)`

Wraps an existing `Editor` instance. All methods use only public Editor APIs.

### Input events

All input methods return `this` for fluent chaining. Coordinates are in screen space.

| Method | Description |
| --------------------------------------------- | -------------------------------------- |
| `pointerDown(x?, y?, options?, modifiers?)` | Dispatch a pointer down event |
| `pointerMove(x?, y?, options?, modifiers?)` | Dispatch a pointer move event |
| `pointerUp(x?, y?, options?, modifiers?)` | Dispatch a pointer up event |
| `click(x?, y?, options?, modifiers?)` | Pointer down + up |
| `rightClick(x?, y?, options?, modifiers?)` | Right-click (button 2) down + up |
| `doubleClick(x?, y?, options?, modifiers?)` | Double-click sequence |
| `keyDown(key, options?)` | Dispatch a key down event |
| `keyUp(key, options?)` | Dispatch a key up event |
| `keyPress(key, options?)` | Key down + up |
| `keyRepeat(key, options?)` | Dispatch a key repeat event |
| `wheel(dx, dy, options?)` | Dispatch a wheel/scroll event |
| `pinchStart(x?, y?, z, dx, dy, dz, options?)` | Begin a pinch gesture |
| `pinchTo(x?, y?, z, dx, dy, dz, options?)` | Continue a pinch gesture |
| `pinchEnd(x?, y?, z, dx, dy, dz, options?)` | End a pinch gesture |
| `forceTick(count?)` | Emit tick events to advance the editor |

### Clipboard

| Method | Description |
| --------------- | --------------------------------------------------- |
| `copy(ids?)` | Copy shapes to the driver's clipboard |
| `cut(ids?)` | Cut shapes (copy + delete) |
| `paste(point?)` | Paste from the driver's clipboard |
| `clipboard` | The current clipboard content (`TLContent \| null`) |

### Selection manipulation

These methods work in page coordinates and handle the screen-space conversion internally.

| Method | Description |
| ------------------------------------------- | ------------------------------------------- |
| `translateSelection(dx, dy, options?)` | Move the selection by a page-space delta |
| `rotateSelection(angle, options?)` | Rotate the selection by an angle in radians |
| `resizeSelection(scale?, handle, options?)` | Resize the selection via a handle |

### Queries

| Method | Description |
| ------------------------------ | ------------------------------------------- |
| `getViewportPageCenter()` | Center of the viewport in page coordinates |
| `getSelectionPageCenter()` | Center of the selection in page coordinates |
| `getPageCenter(shape)` | Center of a shape in page coordinates |
| `getPageRotation(shape)` | Rotation of a shape in page space (radians) |
| `getPageRotationById(id)` | Rotation of a shape by ID (radians) |
| `getArrowsBoundTo(shapeId)` | All arrows bound to a shape |
| `getLastCreatedShape()` | The most recently created shape |
| `getLastCreatedShapes(count?)` | The last N created shapes |

### Camera

| Method | Description |
| ------------- | ------------------------------------- |
| `pan(offset)` | Pan the camera by a page-space offset |

### Lifecycle

| Method | Description |
| ----------- | -------------------------------------------- |
| `dispose()` | Remove side-effect handlers. Call when done. |

## License

See [LICENSE.md](../../LICENSE.md).
4 changes: 4 additions & 0 deletions packages/driver/api-extractor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"extends": "../../internal/config/api-extractor.json"
}
78 changes: 78 additions & 0 deletions packages/driver/api-report.api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
## API Report File for "@tldraw/driver"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).

```ts

import { Editor } from '@tldraw/editor';
import { RotateCorner } from '@tldraw/editor';
import { SelectionHandle } from '@tldraw/editor';
import { TLArrowShape } from '@tldraw/editor';
import { TLContent } from '@tldraw/editor';
import { TLKeyboardEventInfo } from '@tldraw/editor';
import { TLPageId } from '@tldraw/editor';
import { TLPinchEventInfo } from '@tldraw/editor';
import { TLPointerEventInfo } from '@tldraw/editor';
import { TLShape } from '@tldraw/editor';
import { TLShapeId } from '@tldraw/editor';
import { TLWheelEventInfo } from '@tldraw/editor';
import { Vec } from '@tldraw/editor';
import { VecLike } from '@tldraw/editor';

// @public
export class Driver {
constructor(editor: Editor);
click(x?: number, y?: number, options?: PointerEventInit_2, modifiers?: EventModifiers): this;
clipboard: null | TLContent;
copy(ids?: TLShapeId[]): this;
createPageID(id: string): TLPageId;
createShapeID(id: string): TLShapeId;
cut(ids?: TLShapeId[]): this;
dispose(): void;
doubleClick(x?: number, y?: number, options?: PointerEventInit_2, modifiers?: EventModifiers): this;
// (undocumented)
readonly editor: Editor;
forceTick(count?: number): this;
getArrowsBoundTo(shapeId: TLShapeId): TLArrowShape[];
getLastCreatedShape<T extends TLShape>(): T;
getLastCreatedShapes(count?: number): TLShape[];
getPageCenter(shape: TLShape): null | Vec;
getPageRotation(shape: TLShape): number;
getPageRotationById(id: TLShapeId): number;
getSelectionPageCenter(): null | Vec;
getViewportPageCenter(): Vec;
keyDown(key: string, options?: Partial<Omit<TLKeyboardEventInfo, "key">>): this;
keyPress(key: string, options?: Partial<Omit<TLKeyboardEventInfo, "key">>): this;
keyRepeat(key: string, options?: Partial<Omit<TLKeyboardEventInfo, "key">>): this;
keyUp(key: string, options?: Partial<Omit<TLKeyboardEventInfo, "key">>): this;
pan(offset: VecLike): this;
paste(point?: VecLike): this;
pinchEnd(x: number | undefined, y: number | undefined, z: number, dx: number, dy: number, dz: number, options?: Partial<Omit<TLPinchEventInfo, "delta" | "offset" | "point">>): this;
pinchStart(x: number | undefined, y: number | undefined, z: number, dx: number, dy: number, dz: number, options?: Partial<Omit<TLPinchEventInfo, "delta" | "offset" | "point">>): this;
pinchTo(x: number | undefined, y: number | undefined, z: number, dx: number, dy: number, dz: number, options?: Partial<Omit<TLPinchEventInfo, "delta" | "offset" | "point">>): this;
pointerDown(x?: number, y?: number, options?: PointerEventInit_2, modifiers?: EventModifiers): this;
pointerMove(x?: number, y?: number, options?: PointerEventInit_2, modifiers?: EventModifiers): this;
pointerUp(x?: number, y?: number, options?: PointerEventInit_2, modifiers?: EventModifiers): this;
resizeSelection(scale: {
scaleX?: number | undefined;
scaleY?: number | undefined;
} | undefined, handle: SelectionHandle, options?: Partial<TLPointerEventInfo>): this;
rightClick(x?: number, y?: number, options?: PointerEventInit_2, modifiers?: EventModifiers): this;
rotateSelection(angleRadians: number, options?: {
handle?: RotateCorner;
shiftKey?: boolean;
}): this;
translateSelection(dx: number, dy: number, options?: Partial<TLPointerEventInfo>): this;
wheel(dx: number, dy: number, options?: Partial<Omit<TLWheelEventInfo, "delta">>): this;
}

// @public
export type EventModifiers = Partial<Pick<TLPointerEventInfo, 'altKey' | 'ctrlKey' | 'shiftKey'>>;

// @public
type PointerEventInit_2 = Partial<TLPointerEventInfo> | TLShapeId;
export { PointerEventInit_2 as PointerEventInit }

// (No @packageDocumentation comment for this package)

```
48 changes: 48 additions & 0 deletions packages/driver/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "@tldraw/driver",
"description": "Imperative API for driving the tldraw editor programmatically.",
"version": "4.3.0",
"author": {
"name": "tldraw Inc.",
"email": "hello@tldraw.com"
},
"homepage": "https://tldraw.dev",
"license": "SEE LICENSE IN LICENSE.md",
"repository": {
"type": "git",
"url": "https://github.com/tldraw/tldraw"
},
"bugs": {
"url": "https://github.com/tldraw/tldraw/issues"
},
"keywords": [
"tldraw",
"sdk",
"driver",
"automation",
"scripting"
],
"/* NOTE */": "These `main` and `types` fields are rewritten by the build script. They are not the actual values we publish",
"main": "./src/index.ts",
"types": "./.tsbuild/index.d.ts",
"type": "module",
"scripts": {
"test-ci": "yarn run -T vitest run --passWithNoTests",
"test": "yarn run -T vitest --passWithNoTests",
"test-coverage": "yarn run -T vitest run --coverage --passWithNoTests",
"build": "yarn run -T tsx ../../internal/scripts/build-package.ts",
"build-api": "yarn run -T tsx ../../internal/scripts/build-api.ts",
"prepack": "yarn run -T tsx ../../internal/scripts/prepack.ts",
"postpack": "../../internal/scripts/postpack.sh",
"pack-tarball": "yarn pack",
"lint": "yarn run -T tsx ../../internal/scripts/lint.ts"
},
"dependencies": {
"@tldraw/editor": "workspace:*",
"@tldraw/utils": "workspace:*"
},
"devDependencies": {
"lazyrepo": "0.0.0-alpha.27",
"vitest": "^3.2.4"
}
}
10 changes: 10 additions & 0 deletions packages/driver/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { registerTldrawLibraryVersion } from '@tldraw/utils'

export { Driver } from './lib/Driver'
export type { EventModifiers, PointerEventInit } from './lib/Driver'

registerTldrawLibraryVersion(
(globalThis as any).TLDRAW_LIBRARY_NAME,
(globalThis as any).TLDRAW_LIBRARY_VERSION,
(globalThis as any).TLDRAW_LIBRARY_MODULES
)
Loading
Loading