The **simple** example in the `example` folder shows a minimal use of the library. It does not do much but this should be a good reference for the API without too much else built on top.
The **advanced** example in the `example-advanced` folder shows a more realistic use of the library. (Try it [here](https://core-steveruiz.vercel.app/)). While the fundamental patterns are the same, this example contains features such as: panning, pinching, and zooming the camera; creating, cloning, resizing, and deleting shapes; keyboard shortcuts, brush-selection; shape-snapping; undo, redo; and more. Much of the code in the advanced example comes from the [@tldraw/tldraw](https://tldraw.com) codebase.
If you're working on an app that uses this library, I recommend referring back to the advanced example for tips on how you might implement these features for your own project.
## Usage
Import the `Renderer` React component and pass it the required props.
```tsx
import * as React from "react"
import { Renderer, TLShape, TLShapeUtil, Vec } from '@tldraw/core'
To avoid unnecessary renders, be sure to pass "stable" values as props to the `Renderer`. Either define these values outside of the parent component, or place them in React state, or memoize them with `React.useMemo`.
| `camera.zoom` | `number` | The camera's zoom level |
| `pointedId` | `string` | (optional) The currently pointed shape id |
| `hoveredId` | `string` | (optional) The currently hovered shape id |
| `editingId` | `string` | (optional) The currently editing shape id |
| `bindingId` | `string` | (optional) The currently editing binding. |
| `brush` | `TLBounds` | (optional) A `Bounds` for the current selection box |
### `TLShape`
An object that describes a shape on the page. The shapes in your document should extend this interface with other properties. See [Shape Type](#shape-type).
When a shape with handles is the only selected shape, the `Renderer` will display its handles. You can respond to interactions with these handles using the `on
### `TLBinding`
An object that describes a relationship between two shapes on the page.
| `fromId` | `string` | The id of the shape where the binding begins |
| `toId` | `string` | The id of the shape where the binding begins |
### `TLSnapLine`
A snapline is an array of points (formatted as `[x, y]`) that represent a "snapping" line.
### `TLShapeUtil`
The `TLShapeUtil` is an abstract class that you can extend to create utilities for your custom shapes. See the [Creating Shapes](#creating-shapes) guide to learn more.
### `TLUser`
A `TLUser` is the presence information for a multiplayer user. The user's pointer location and selections will be shown on the canvas. If the `TLUser`'s id matches the `Renderer`'s `userId` prop, then the user's cursor and selections will not be shown.
A general purpose utility class. See source for more.
## Guide: Creating Shapes
The `Renderer` component has no built-in shapes. It's up to you to define every shape that you want to see on the canvas. While these shapes are highly reusable between projects, you'll need to define them using the API described below.
> For several example shapes, see the folder `/example/src/shapes/`.
### Shape Type
Your first task is to define an interface for the shape that extends `TLShape`. It must have a `type` property.
```ts
// BoxShape.ts
import type { TLShape } from '@tldraw/core'
export interface BoxShape extends TLShape {
type: 'box'
size: number[]
}
```
### Component
Next, use `TLShapeUtil.Component` to create a second component for your shape's `Component`. The `Renderer` will use this component to display the shape on the canvas.
Your component can return HTML elements or SVG elements. If your shape is returning only SVG elements, wrap it in an `SVGContainer`. If your shape returns HTML elements, wrap it in an `HTMLContainer`. Not that you must set `pointerEvents` manually on the shapes you wish to receive pointer events.
| `shape` | `TLShape` | The shape from `page.shapes` that is being rendered |
| `meta` | `{}` | The value provided to the `Renderer`'s `meta` prop |
| `events` | `{}` | Several pointer events that should be set on the container element |
| `isSelected` | `boolean` | The shape is selected (its `id` is in `pageState.selectedIds`) |
| `isHovered` | `boolean` | The shape is hovered (its `id` is `pageState.hoveredId`) |
| `isEditing` | `boolean` | The shape is being edited (its `id` is `pageState.editingId`) |
| `isGhost` | `boolean` | The shape is ghosted or is the child of a ghosted shape. |
| `isChildOfSelected` | `boolean` | The shape is the child of a selected shape. |
| `onShapeChange` | `Function` | The callback provided to the `Renderer`'s `onShapeChange` prop |
| `onShapeBlur` | `Function` | The callback provided to the `Renderer`'s `onShapeBlur` prop |
### Indicator
Next, use `TLShapeUtil.Indicator` to create a second component for your shape's `Indicator`. This component is shown when the shape is hovered or selected. Your `Indicator` must return SVG elements only.
| `shape` | `TLShape` | The shape from `page.shapes` that is being rendered |
| `meta` | {} | The value provided to the `Renderer`'s `meta` prop |
| `user` | `TLUser` | The user when shown in a multiplayer session |
| `isSelected` | `boolean` | Whether the current shape is selected (true if its `id` is in `pageState.selectedIds`) |
| `isHovered` | `boolean` | Whether the current shape is hovered (true if its `id` is `pageState.hoveredId`) |
### ShapeUtil
Next, create a "shape util" for your shape. This is a class that extends `TLShapeUtil`. The `Renderer` will use an instance of this class to answer questions about the shape: what it should look like, where it is on screen, whether it can rotate, etc.
export class BoxUtil extends TLShapeUtil<BoxShape,SVGSVGElement> {
Component = BoxComponent
Indicator = BoxIndicator
getBounds = (shape: BoxShape): TLBounds => {
const [width, height] = shape.size
const bounds = {
minX: 0,
maxX: width,
minY: 0,
maxY: height,
width,
height,
}
return Utils.translateBounds(bounds, shape.point)
}
}
```
Set the `Component` field to your component and the `Indicator` field to your indicator component. Then define the `getBounds` method. This method will receive a shape and should return a `TLBounds` object.
| `showCloneHandles` | `boolean` | `false` | Whether to display clone handles when the shape is the only selected shape |
| `hideBounds` | `boolean` | `false` | Whether to hide the bounds when the shape is the only selected shape |
| `isStateful` | `boolean` | `false` | Whether the shape has its own React state. When true, the shape will not be unmounted when off-screen |
### ShapeUtils Object
Finally, create a mapping of your project's shape utils and the `type` properties of their corresponding shapes. Pass this object to the `Renderer`'s `shapeUtils` prop.
To start the development servers for the package and the advanced example:
- Run `yarn` to install dependencies.
- Run `yarn start`.
- Open `localhost:5420`.
You can also run:
-`start:advanced` to start development servers for the package and the advanced example.
-`start:simple` to start development servers for the package and the simple example.
-`test` to execute unit tests via [Jest](https://jestjs.io).
-`docs` to build the docs via [ts-doc](https://typedoc.org/).
-`build` to build the package.
## Example
See the `example` folder or this [CodeSandbox](https://codesandbox.io/s/laughing-elion-gp0kx) example.
## Community
### Support
Need help? Please [open an issue](https://github.com/tldraw/tldraw/issues/new) for support.
### Discussion
Want to connect with other devs? Visit the [Discord channel](https://discord.gg/SBBEVCA4PG).
### License
This project is licensed under MIT.
If you're using the library in a commercial product, please consider [becoming a sponsor](https://github.com/sponsors/steveruizok?frequency=recurring&sponsor=steveruizok).