[1/2] Move docs to brivate (#1640)
This PR moves the docs site to the private repo while keeping the docs content on the public repo. ### Change Type - [x] `documentation`
This commit is contained in:
parent
245f74010c
commit
096df3209b
638 changed files with 64004 additions and 2983 deletions
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"steveruizok": {
|
||||
"name": "Steve Ruiz",
|
||||
"email": "steve@tldraw.com",
|
||||
"twitter": "steveruizok",
|
||||
"image": "steve_ruiz.jpg"
|
||||
},
|
||||
"api": {
|
||||
"name": "API",
|
||||
"email": "hello@tldraw.com",
|
||||
"twitter": "tldraw",
|
||||
"image": "api.jpg"
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
---
|
||||
title: Contributing
|
||||
status: published
|
||||
author: steveruizok
|
||||
date: 3/22/2023
|
||||
order: 0
|
||||
---
|
||||
|
||||
Interested in contributing to the open source project?
|
||||
|
||||
You can find tldraw on GitHub at [github.com/tldraw/tldraw](https://github.com/tldraw/tldraw). You can [create an issue](https://github.com/tldraw/tldraw/issues/new/choose) and submit pull requests for our review.
|
||||
|
||||
Please see our [Contributing guide](https://github.com/tldraw/tldraw/blob/main/CONTRIBUTING.md) for more information.
|
||||
|
||||
Please also see our [Code of Conduct](https://github.com/tldraw/tldraw/blob/main/CODE_OF_CONDUCT.md) for our expectations around contributor culture.
|
|
@ -1,9 +0,0 @@
|
|||
---
|
||||
title: Embeds
|
||||
status: published
|
||||
author: steveruizok
|
||||
date: 3/22/2023
|
||||
order: 2
|
||||
---
|
||||
|
||||
Coming soon.
|
|
@ -1,11 +0,0 @@
|
|||
---
|
||||
title: Translations
|
||||
status: published
|
||||
author: steveruizok
|
||||
date: 3/22/2023
|
||||
order: 1
|
||||
---
|
||||
|
||||
The tldraw user interface (in [@tldraw/ui](/docs/user-interface)) is currently translated into over thirty different languages, with twenty languages at above 70% completion. Where a key's translation is missing in the user's current language language, the default (English) translation will be used instead.
|
||||
|
||||
We manage our translations through [Lokalise](https://www.lokalise.com), a long-time tldraw sponsor. If you would like to help by translating or reviewing translations, please let us know on [Discord](https://discord.gg/sKNgCZyrrf) so that we can add you to the project.
|
|
@ -1,69 +0,0 @@
|
|||
---
|
||||
title: Assets
|
||||
status: published
|
||||
author: steveruizok
|
||||
date: 6/9/2023
|
||||
order: 9
|
||||
---
|
||||
|
||||
In order to use the `<Tldraw/>` component, the app must be able to find certain assets. These are contained in the `embed-icons`, `fonts`, `icons`, and `translations` folders. We offer a few different ways of making these assets available to your app.
|
||||
|
||||
### 1. Using a public CDN
|
||||
|
||||
By default we serve these assets from a [public CDN called unpkg](https://unpkg.com/browse/@tldraw/assets@2.0.0-alpha.12/), so everything should work out of the box and is a good way to get started.
|
||||
|
||||
If you would like to customize some of the assets you can pass the customizations to our `<Tldraw />` component. For example, to use a custom icon for the `hand` tool you can do the following:
|
||||
|
||||
```javascript
|
||||
const assetUrls = {
|
||||
icons: {
|
||||
'tool-hand': './custom-tool-hand.svg',
|
||||
},
|
||||
}
|
||||
|
||||
<Tldraw assetUrls={assetUrls} />
|
||||
```
|
||||
|
||||
This will use the custom icon for the `hand` tool and the default assets for everything else.
|
||||
|
||||
|
||||
### 2. Hosting the assets yourself
|
||||
|
||||
If you want more flexibility you can also host these assets yourself:
|
||||
1. Download the `embed-icons`, `fonts`, `icons`, and `translations` folders from the [assets folder](https://github.com/tldraw/tldraw/tree/main/assets) of the tldraw repository.
|
||||
2. Place the folders in your project's public path.
|
||||
3. Pass `assetUrls` prop to our `<Tldraw/>` component to let the component know where the assets live.
|
||||
|
||||
You can use our `getAssetUrls` helper function from the `@tldraw/assets` package to generate these urls for you.
|
||||
```javascript
|
||||
import { getAssetUrls } from '@tldraw/assets/selfHosted'
|
||||
|
||||
const assetUrls = getAssetUrls()
|
||||
|
||||
<Tldraw assetUrls={assetUrls} />
|
||||
```
|
||||
|
||||
While these files must be available, you can overwrite the individual files: for example, by placing different icons under the same name or modifying / adding translations.
|
||||
|
||||
If you use a CDN for hosting these files you can specify the base url of your assets. To recreate the above option of serving the assets from unpkg you would do the following:
|
||||
|
||||
```javascript
|
||||
const assetUrls = getAssetUrls({
|
||||
baseUrl: 'https://unpkg.com/@tldraw/assets@2.0.0-alpha.12/',
|
||||
})
|
||||
```
|
||||
|
||||
### 3. Using a bundler
|
||||
|
||||
If you're using a bundler like webpack or rollup, you can import the assets directly from the `@tldraw/assets` package. Here you can use `getAssetUrlsByMetaUrl` helper function:
|
||||
```javascript
|
||||
import { getAssetUrlsByMetaUrl } from '@tldraw/assets/urls'
|
||||
|
||||
const assetUrls = getAssetUrlsByMetaUrl()
|
||||
|
||||
<Tldraw assetUrls={assetUrls} />
|
||||
```
|
||||
|
||||
### Adding custom assets
|
||||
|
||||
We have plans to offer more rich asset customizations in the future (e.g. custom cursors, custom fonts, translations for other languages etc).
|
|
@ -1,11 +0,0 @@
|
|||
---
|
||||
title: Collaboration
|
||||
status: published
|
||||
author: steveruizok
|
||||
date: 3/22/2023
|
||||
order: 7
|
||||
---
|
||||
|
||||
Coming soon.
|
||||
|
||||
See the [tldraw repository](https://github.com/tldraw/tldraw/tree/main/apps/examples) for an example of how to use yjs with the `@tldraw/tldraw` library.
|
|
@ -1,239 +0,0 @@
|
|||
---
|
||||
title: Editor
|
||||
status: published
|
||||
author: steveruizok
|
||||
date: 3/22/2023
|
||||
order: 3
|
||||
keywords:
|
||||
- ui
|
||||
- app
|
||||
- editor
|
||||
- control
|
||||
- select
|
||||
---
|
||||
|
||||
The `Editor` class is the main way of controlling tldraw's editor. You can use it to manage the editor's internal state, make changes to the document, or respond to changes that have occurred.
|
||||
|
||||
By design, the `Editor`'s surface area is [very large](/gen/editor/Editor-class). Almost everything is available through it. Need to create some shapes? Use `editor.createShapes()`. Need to delete them? Use `editor.deleteShapes()`. Need a sorted array of every shape on the current page? Use `editor.sortedShapesArray`.
|
||||
|
||||
This page gives a broad idea of how the `Editor` class is organized and some of the architectural concepts involved. The full reference is available in the [Editor API](/gen/editor/Editor-class).
|
||||
|
||||
## State
|
||||
|
||||
The editor holds the raw state of the document in its `store` property. Data is kept here as a table of JSON serializable records.
|
||||
|
||||
For example, the store contains a `page` record for each page in the current document, as well as an `instancePageState` record for each page that stores information about the editor's state for that page, and a single `instanceState` for each editor instance which stores the id of the user's current page.
|
||||
|
||||
The editor also exposes many _computed_ values which are derived from other records in the store. For example, `editor.selectedIds` is a computed property that will return the editor's current selected shape ids for its current page.
|
||||
|
||||
You can use these properties directly or you can use them in signals.
|
||||
|
||||
```tsx
|
||||
import { track, useEditor } from "@tldraw/tldraw"
|
||||
|
||||
export const SelectedIdsCount = track(() => {
|
||||
const editor = useEditor()
|
||||
|
||||
return (
|
||||
<div>{editor.selectedIds.length}</div>
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
### Changing the state
|
||||
|
||||
The `Editor` class has many methods for updating its state. For example, you can change the current page's selection using `editor.setSelectedIds`. You can also use other convenience methods, such as `editor.select`, `editor.deselect`, `editor.selectAll`, or `editor.selectNone`.
|
||||
|
||||
```ts
|
||||
editor.selectNone()
|
||||
editor.select(myShapeId, myOtherShapeId)
|
||||
editor.selectedIds // [myShapeId, myOtherShapeId]
|
||||
```
|
||||
|
||||
Each change to the state happens within a transaction. You can batch changes into a single transaction using the `editor.batch` method. It's a good idea to batch wherever possible, as this reduces the overhead for persisting or distributing those changes.
|
||||
|
||||
### Listening for changes
|
||||
|
||||
You can subscribe to changes using `editor.store.listen`. Each time a transaction completes, the editor will call the callback with a history entry. This entry contains information about the records that were added, changed, or deleted, as well as whether the change was caused by the user or from a remote change.
|
||||
|
||||
```ts
|
||||
editor.store.listen(entry => {
|
||||
entry // { changes, source }
|
||||
})
|
||||
```
|
||||
|
||||
### Remote changes
|
||||
|
||||
By default, changes to the editor's store are assumed to have come from the editor itself. You can use `editor.store.mergeRemoteChanges` to make changes in the store that will be emitted via `store.listen` with the `source` property as `'remote'`.
|
||||
|
||||
If you're setting up some kind of multiplayer backend, you would want to send only the `'user'` changes to the server and merge the changes from the server using `editor.store.mergeRemoteChanges`. (We'll have more information about this soon.)
|
||||
|
||||
### Undo and redo
|
||||
|
||||
The history stack in tldraw contains two types of data: "marks" and "commands". Commands have their own `undo` and `redo` methods that describe how the state should change when the command is undone or redone.
|
||||
|
||||
You can call `editor.mark(id)` to add a mark to the history stack with the given `id`.
|
||||
|
||||
When you call `editor.undo()`, the editor will undo each command until it finds either a mark or the start of the stack. When you call `editor.redo()`, the editor will redo each command until it finds either a mark or the end of the stack.
|
||||
|
||||
```ts
|
||||
// A
|
||||
editor.mark("duplicate everything")
|
||||
editor.selectAll()
|
||||
editor.duplicateShapes(editor.selectedIds)
|
||||
// B
|
||||
|
||||
editor.undo() // will return to A
|
||||
editor.redo() // will return to B
|
||||
```
|
||||
|
||||
You can call `editor.bail()` to undo and delete all commands in the stack until the first mark.
|
||||
|
||||
```ts
|
||||
// A
|
||||
editor.mark("duplicate everything")
|
||||
editor.selectAll()
|
||||
editor.duplicateShapes(editor.selectedIds)
|
||||
// B
|
||||
|
||||
editor.bail() // will return to A
|
||||
editor.redo() // will do nothing
|
||||
```
|
||||
|
||||
You can use `editor.bailToMark(id)` to undo and delete all commands and marks until you reach a mark with the given `id`.
|
||||
|
||||
```ts
|
||||
// A
|
||||
editor.mark("first")
|
||||
editor.selectAll()
|
||||
// B
|
||||
editor.mark("second")
|
||||
editor.duplicateShapes(editor.selectedIds)
|
||||
// C
|
||||
|
||||
editor.bailToMark("first") // will to A
|
||||
```
|
||||
|
||||
## Events and Tools
|
||||
|
||||
The `Editor` class receives events from the user interface via its `dispatch` method. When the `Editor` receives an event, it is first handled internally to update `editor.inputs` and other state before, and then sent into to the editor's state chart.
|
||||
|
||||
You shouldn't need to use the `dispatch` method directly, however you may write code in the state chart that responds to these events.
|
||||
|
||||
### State Chart
|
||||
|
||||
The `Editor` class has a "state chart", or a tree of `StateNode` instances, that contain the logic for the editor's tools such as the select tool or the draw tool. User interactions such as moving the cursor will produce different changes to the state depending on which nodes are active.
|
||||
|
||||
Each node be active or inactive. Each state node may also have zero or more children. When a state is active, and if the state has children, one (and only one) of its children must also be active. When a state node receives an event from its parent, it has the opportunity to handle the event before passing the event to its active child. The node can handle an event in any way: it can ignore the event, update records in the store, or run a _transition_ that changes which states nodes are active.
|
||||
|
||||
When a user interaction is sent to the editor via its `dispatch` method, this event is sent to the editor's root state node (`editor.root`) and passed then down through the chart's active states until either it reaches a leaf node or until one of those nodes produces a transaction.
|
||||
|
||||
<Image title="Events" src="/images/api/events.png" alt="A diagram showing an event being sent to the editor and handled in the state chart." title="The editor passes an event into the state start where it is handled by each active state in order."/>
|
||||
|
||||
### Path
|
||||
|
||||
You can get the editor's current "path" of active states via `editor.root.path`. In the above example, the value would be `"root.select.idle"`.
|
||||
|
||||
You can check whether a path is active via `editor.isIn`, or else check whether multiple paths are active via `editor.isInAny`.
|
||||
|
||||
```ts
|
||||
editor.store.path // 'root.select.idle'
|
||||
|
||||
editor.isIn('root.select') // true
|
||||
editor.isIn('root.select.idle') // true
|
||||
editor.isIn('root.select.pointing_shape') // false
|
||||
editor.isInAny('editor.select.idle', 'editor.select.pointing_shape') // true
|
||||
```
|
||||
|
||||
Note that the paths you pass to `isIn` or `isInAny` can be the full path or a partial of the start of the path. For example, if the full path is `root.select.idle`, then `isIn` would return true for the paths `root`, `root.select`, or `root.select.idle`.
|
||||
|
||||
> If all you're interested in is the state below `root`, there is a convenience property, `editor.currentToolId`, that can help with the editor's currently selected tool.
|
||||
|
||||
```tsx
|
||||
import { track, useEditor } from "@tldraw/tldraw"
|
||||
|
||||
export const CreatingBubbleToolUi = track(() => {
|
||||
const editor = useEditor()
|
||||
|
||||
const isSelected = editor.isIn('root.bubble.creating')
|
||||
|
||||
if (!editor.currentToolId === 'bubble') return
|
||||
|
||||
return (
|
||||
<div data-isSelected={isSelected}>Creating Bubble</div>
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
## Inputs
|
||||
|
||||
The editor's `inputs` object holds information about the user's current input state, including their cursor position (in page space _and_ screen space), which keys are pressed, what their multi-click state is, and whether they are dragging, pointing, pinching, and so on.
|
||||
|
||||
Note that the modifier keys include a short delay after being released in order to prevent certain errors when modeling interactions. For example, when a user releases the "Shift" key, `editor.inputs.shiftKey` will remain `true` for another 100 milliseconds or so.
|
||||
|
||||
This property is stored as regular data. It is not reactive.
|
||||
|
||||
## Common things to do with the editor
|
||||
|
||||
### Create shapes
|
||||
|
||||
```ts
|
||||
editor.createShapes([
|
||||
{
|
||||
id,
|
||||
type: 'geo',
|
||||
x: 0,
|
||||
y: 0,
|
||||
props: {
|
||||
geo: 'rectangle',
|
||||
w: 100,
|
||||
h: 100,
|
||||
dash: 'draw',
|
||||
color: 'blue',
|
||||
size: 'm',
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
### Update shapes
|
||||
|
||||
```ts
|
||||
const shape = editor.selectedShapes[0]
|
||||
|
||||
editor.updateShapes([
|
||||
{
|
||||
id: shape.id, // required
|
||||
type: shape.type, // required
|
||||
x: 100,
|
||||
y: 100,
|
||||
props: {
|
||||
w: 200,
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
### Delete shapes
|
||||
|
||||
```ts
|
||||
const shape = editor.selectedShapes[0]
|
||||
|
||||
editor.deleteShapes([shape.id])
|
||||
```
|
||||
|
||||
### Get a shape by its id
|
||||
|
||||
```ts
|
||||
editor.getShapeById(myShapeId)
|
||||
```
|
||||
|
||||
### Move the camera
|
||||
|
||||
```ts
|
||||
editor.setCamera(0, 0, 1)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
See the [tldraw repository](https://github.com/tldraw/tldraw/tree/main/apps/examples) for an example of how to use tldraw's Editor API to control the editor.
|
|
@ -1,18 +0,0 @@
|
|||
---
|
||||
title: Persistence
|
||||
status: published
|
||||
author: steveruizok
|
||||
date: 3/22/2023
|
||||
order: 6
|
||||
keywords:
|
||||
- data
|
||||
- sync
|
||||
- persistence
|
||||
- database
|
||||
- indexeddb
|
||||
- localstorage
|
||||
---
|
||||
|
||||
Coming soon.
|
||||
|
||||
See the [tldraw repository](https://github.com/tldraw/tldraw/tree/main/apps/examples) for an example of how to use persistence with the `@tldraw/tldraw` or `@tldraw/editor` libraries.
|
|
@ -1,136 +0,0 @@
|
|||
---
|
||||
title: Shapes
|
||||
status: published
|
||||
author: steveruizok
|
||||
date: 3/22/2023
|
||||
order: 4
|
||||
keywords:
|
||||
- custom
|
||||
- shapes
|
||||
- shapeutils
|
||||
- utils
|
||||
---
|
||||
|
||||
In tldraw, **shapes** are the things that are on the canvas. This article is about shapes: what they are, how they work, and how to create your own shapes. If you'd prefer to see an example, see the tldraw repository's [examples app](https://github.com/tldraw/tldraw/tree/main/apps/examples) for examples of how to create custom shapes in tldraw.
|
||||
|
||||
## Custom shapes
|
||||
|
||||
Let's create a custom "card" shape.
|
||||
|
||||
### Shape type
|
||||
|
||||
In tldraw's data model, each shape is represented by a JSON object. Let's first create a type that describes what this object will look like.
|
||||
|
||||
```ts
|
||||
import { TLBaseShape } from '@tldraw/tldraw'
|
||||
|
||||
type CardShape = TLBaseShape<
|
||||
'card',
|
||||
{ w: number, h: number }
|
||||
>
|
||||
```
|
||||
|
||||
With the `TLBaseShape` helper, we define the shape's `type` property (`card`) and the shape's `props` property (`{ w: number, h: number }`). The type can be any string but the props must be a regular [JSON-serializable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description) JavaScript object.
|
||||
|
||||
The `TLBaseShape` helper adds the other default properties of a shape, such as `parentId`, `x`, `y`, and `rotation`.
|
||||
|
||||
### Shape Util
|
||||
|
||||
While tldraw's shapes themselves are simple JSON objects, we use `ShapeUtil` classes to answer questions about shapes. For example, when the editor needs to know the bounding box of our card shape, it will find a `ShapeUtil` for the `card` type and call that util's `bounds` method, passing in the `CardShape` object as an argument.
|
||||
|
||||
Let's create a `ShapeUtil` class for the shape.
|
||||
|
||||
```tsx
|
||||
import { ShapeUtil, HTMLContainer } from '@tldraw/tldraw'
|
||||
|
||||
class CardShapeUtil extends ShapeUtil<CardShape> {
|
||||
static type = 'card' as const
|
||||
|
||||
getDefaultProps(): CardShape['props'] {
|
||||
return {
|
||||
w: 100,
|
||||
h: 100,
|
||||
}
|
||||
}
|
||||
|
||||
getBounds(shape: Shape) {
|
||||
return new Box2d(0, 0, shape.props.w, shape.props.h)
|
||||
}
|
||||
|
||||
component(shape: Shape) {
|
||||
return (
|
||||
<HTMLContainer>Hello</HTMLContainer>
|
||||
)
|
||||
}
|
||||
|
||||
indicator(shape: Shape) {
|
||||
return (
|
||||
<rect width={shape.props.w} height={shape.props.h}/>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is a minimal `ShapeUtil`. We've given it a static property `type` that matches the type of our shape, we've provided implementations for the abstract methods `getDefaultProps`, `getBounds`, `component`, and `indicator`.
|
||||
|
||||
We still have work to do on the `CardShapeUtil` class, but we'll come back to it later. For now, let's put the shape onto the canvas by passing it to the `<Tldraw>` component.
|
||||
|
||||
### Defining the shape
|
||||
|
||||
Before we pass the shape down, we need to package it up in a way using the `defineShape` function. We can then create an array of our defined shapes and pass them into the `<Tldraw>` component's `shapes` prop.
|
||||
|
||||
```tsx
|
||||
import { Tldraw } from '@tldraw/tldraw'
|
||||
import '@tldraw/tldraw/tldraw.css'
|
||||
|
||||
const MyCardShape = defineShape('card', { util: CardShapeUtil })
|
||||
const MyCustomShapes = [MyCardShape]
|
||||
|
||||
export default function () {
|
||||
return (
|
||||
<div style={{ position: 'fixed', inset: 0 }}>
|
||||
<Tldraw shapes={MyCustomShapes}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
The `defineShape` function can also be used to include a tool that we can use to create this type of shape. For now, let's create it using the `Editor` API.
|
||||
|
||||
```tsx
|
||||
export default function () {
|
||||
return (
|
||||
<div style={{ position: 'fixed', inset: 0 }}>
|
||||
<Tldraw shapes={MyCustomShapes} onMount={editor => {
|
||||
editor.createShapes([{ type: "card" }])
|
||||
}}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Once the page refreshes, we should now have our custom shape on the canvas.
|
||||
|
||||
## Using starter shapes
|
||||
|
||||
You can use "starter" shape utils like `BaseBoxShapeUtil` to get regular rectangular shape behavior.
|
||||
|
||||
> todo
|
||||
|
||||
## Flags
|
||||
|
||||
You can use flags like `hideRotateHandle` to hide different parts of the UI when the shape is selected, or else to control different behaviors of the shape.
|
||||
|
||||
> todo
|
||||
|
||||
## Interaction
|
||||
|
||||
You can turn on `pointer-events` to allow users to interact inside of the shape.
|
||||
|
||||
> todo
|
||||
|
||||
## Editing
|
||||
|
||||
You can make shapes "editable" to help decide when they're interactive or not.
|
||||
|
||||
> todo
|
|
@ -1,15 +0,0 @@
|
|||
---
|
||||
title: Tools
|
||||
status: published
|
||||
author: steveruizok
|
||||
date: 3/22/2023
|
||||
order: 5
|
||||
keywords:
|
||||
- custom
|
||||
- tools
|
||||
- state
|
||||
---
|
||||
|
||||
Coming soon.
|
||||
|
||||
See the [tldraw repository](https://github.com/tldraw/tldraw/tree/main/apps/examples) for an example of how to create custom tools in tldraw.
|
|
@ -1,38 +0,0 @@
|
|||
---
|
||||
title: User Interface
|
||||
status: published
|
||||
author: steveruizok
|
||||
date: 3/22/2023
|
||||
order: 8
|
||||
keywords:
|
||||
- ui
|
||||
- interface
|
||||
- tools
|
||||
- shapes
|
||||
- custom
|
||||
- button
|
||||
- toolbar
|
||||
- styles
|
||||
---
|
||||
|
||||
## Events
|
||||
|
||||
The `<Tldraw>` component has a prop, `onUiEvent`, that the user interface will call when certain events occur.
|
||||
|
||||
```tsx
|
||||
function Example() {
|
||||
function handleEvent(name, data) {
|
||||
// do something with the event
|
||||
}
|
||||
|
||||
return (
|
||||
<Tldraw onUiEvent={handleEvent}/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
The `onUiEvent` callback is called with the name of the event as a string and an object with information about the event's source (e.g. `menu` or `context-menu`) and possibly other data specific to each event, such as the direction in an `align-shapes` event.
|
||||
|
||||
Note that `onUiEvent` is only called when interacting with the user interface. It is not called when running commands manually against the app, e.g. `editor.alignShapes()` will not call `onUiEvent`.
|
||||
|
||||
See the [tldraw repository](https://github.com/tldraw/tldraw/tree/main/apps/examples) for an example of how to customize tldraw's user interface.
|
Loading…
Add table
Add a link
Reference in a new issue