# Add canvas tools

Use a mounted [`DiagramInstance`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-renderer-instance-diagraminstance#diagraminstance) as the host, then register the tool that owns the gesture. Grafloria supplies freehand drawing, erasing, and rectangle creation; use [`registerTool`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-renderer-ext-functions#registertool) when you need a host-specific interaction such as marquee selection.

## Mount a canvas and add a drawing tool

Give the host a resolved height. [`render`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-element-core#render) returns the live instance, and the drawing tool writes a committed vector stroke into its model when the pointer is released.

```ts
import {
  createDrawTool,
  registerTool,
  render,
} from '@grafloria/element';

const host = document.createElement('div');
document.body.append(host);
host.style.height = '480px';

const instance = render({ nodes: [{ id: 'start', position: { x: 80, y: 80 }, label: 'Start' }], edges: [] }, host);
registerTool(createDrawTool(instance, {
  color: '#e11d48',
  width: 3,
  simplifyEpsilon: 0.8,
}));

// Keep the disposer returned by registerTool and call it when the feature is unloaded.
```

After a drag, the canvas shows a red SVG stroke. The model contains one simplified stroke for the gesture, so the gesture is one editable history operation rather than a screenshot.

![The canvas shows several crisp red freehand strokes.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/2428ce7f727ed4d4de25ac6536a7fb52.png)

## Choose the shipped whiteboard tools

The same mounted instance is the host for the eraser and rectangle tools. An eraser removes whole strokes that the pointer sweep crosses. A rectangle drag creates a real node, not ink, so the result participates in connection, resize, and layout behavior.

```ts
import {
  createEraserTool,
  createRectangleTool,
  registerTool,
  render,
} from '@grafloria/element';

const host = document.createElement('div');
document.body.append(host);
host.style.height = '480px';

const instance = render({ nodes: [{ id: 'start', position: { x: 80, y: 80 }, label: 'Start' }], edges: [] }, host);
registerTool(createEraserTool(instance, { radius: 10 }));
registerTool(createRectangleTool(instance, {
  fill: '#dbeafe',
  stroke: '#2563eb',
  strokeWidth: 2,
  label: 'Box',
}));

// Activate one point-agnostic mode at a time in your toolbar.
```

Do not leave competing mode tools active: draw, rectangle, and eraser can all claim an empty-canvas pointerdown. The registry chooses the claiming tool with the highest `priority`; ties fall back to registration order, which is not a contract. A tool disposer restores the previous tool with the same id.

![Two ink strokes remain after the eraser crosses the middle stroke.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/80d7e3cbd84778766d049b7e45270df2.png)

![Three blue labelled boxes appear on the canvas as diagram nodes.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/49a879b3320385a1ae8824b52c6cc0ee.png)

## Add a custom interaction

Use [`registerTool`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-renderer-ext-functions#registertool) for a gesture the built-in binder does not own. A [`CanvasTool`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-renderer-ext-interfaces-a-t#canvastool) claims a pointerdown in `hitTest`; its pointer handlers then receive the rest of that gesture. Set an explicit priority whenever another tool can claim the same press.

This skeleton claims only an empty-canvas press and gives you world and screen coordinates for a host-owned overlay:

```ts
import type { CanvasTool } from '@grafloria/renderer';
import { registerTool } from '@grafloria/element';
import { render } from '@grafloria/element';

const host = document.createElement('div');
host.style.height = '480px';
document.body.append(host);
render({ nodes: [{ id: 'start', position: { x: 80, y: 80 }, label: 'Start' }], edges: [] }, host);
let start: { x: number; y: number } | undefined;

const marquee: CanvasTool = {
  id: 'marquee',
  priority: 1,
  hitTest: (_event, hit) => hit.empty,
  onPointerDown: (event) => {
    start = { x: event.world.x, y: event.world.y };
  },
  onPointerMove: (event) => {
    if (start) {
      // Paint your overlay from start to event.screen here.
      console.log(start, event.screen.x, event.screen.y);
    }
  },
  onPointerUp: (event) => {
    if (start) {
      // Convert the world rectangle into the selection your application wants.
      console.log(start.x, start.y, event.world.x, event.world.y);
    }
    start = undefined;
  },
  onCancel: () => {
    start = undefined;
  },
};

registerTool(marquee);
```

The custom tool itself does not draw the overlay or select nodes: those are host responsibilities. The live marquee example shows the complete version, including a dashed overlay and full-containment selection.

![A dashed blue marquee surrounds the three nodes inside it.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/7c763513cbb2c3323960bde206e9271f.png)

## Use the same setup in each framework

Register after the binding provides the mounted instance, and keep the canvas host sized.

:::code-group
```html title="JavaScript"
<div id="canvas" style="height:480px"></div>
<script type="module">
import { createDrawTool, registerTool, render } from '@grafloria/element';
const host = document.getElementById('canvas');
if (!host) throw new Error('Missing #canvas');
const instance = render({ nodes: [{ id: 'start', position: { x: 80, y: 80 }, label: 'Start' }], edges: [] }, host);
registerTool(createDrawTool(instance, { color: '#e11d48', width: 3 }));
</script>
```
```tsx title="React"
import { useRef } from 'react';
import { GrafloriaFlow, type DiagramInstance } from '@grafloria/react';
import { createDrawTool, registerTool } from '@grafloria/element';

export function CanvasTools() {
  const host = useRef<HTMLDivElement | null>(null);
  const onInit = (instance: DiagramInstance) => {
    if (host.current) registerTool(createDrawTool(instance, { color: '#e11d48', width: 3 }));
  };
  return <div ref={host} style={{ height: '480px' }}><GrafloriaFlow defaultNodes={[{ id: 'start', position: { x: 80, y: 80 }, label: 'Start' }]} defaultEdges={[]} style={{ height: '100%' }} onInit={onInit} /></div>;
}
```
```vue title="Vue"
<script setup lang="ts">
import { ref } from 'vue';
import { GrafloriaFlow, type DiagramInstance } from '@grafloria/vue';
import { createDrawTool, registerTool } from '@grafloria/element';
const host = ref<HTMLElement | null>(null);
function onInit(instance: DiagramInstance) {
  if (host.value) registerTool(createDrawTool(instance, { color: '#e11d48', width: 3 }));
}
</script>
<template><div ref="host" style="height:480px"><GrafloriaFlow :default-nodes="[{ id: 'start', position: { x: 80, y: 80 }, label: 'Start' }]" :default-edges="[]" style="height:100%" @init="onInit" /></div></template>
```
```ts title="Angular"
import { Component } from '@angular/core';
import { GrafloriaDiagramComponent } from '@grafloria/angular';
import { createDrawTool, registerTool } from '@grafloria/element';
import type { DiagramInstance } from '@grafloria/renderer';

@Component({ standalone: true, imports: [GrafloriaDiagramComponent], template: '<grafloria-diagram [spec]="spec" (ready)="onReady($event)" style="display:block;height:480px"></grafloria-diagram>' })
export class CanvasToolsComponent {
  spec = { nodes: [{ id: 'start', position: { x: 80, y: 80 }, label: 'Start' }], edges: [] };
  onReady(instance: DiagramInstance): void {
    registerTool(createDrawTool(instance, { color: '#e11d48', width: 3 }));
  }
}
```
```tsx title="Qwik"
import { $, component$, useSignal } from '@builder.io/qwik';
import { GrafloriaFlow, type DiagramInstance } from '@grafloria/qwik';
import { createDrawTool, registerTool } from '@grafloria/element';

export default component$(() => {
  const host = useSignal<HTMLDivElement>();
  return <div ref={host} style={{ height: '480px' }}><GrafloriaFlow defaultNodes={[{ id: 'start', position: { x: 80, y: 80 }, label: 'Start' }]} defaultEdges={[]} style={{ height: '100%' }} onInit$={$((instance: DiagramInstance) => registerTool(createDrawTool(instance, { color: '#e11d48', width: 3 })))} /></div>;
});
```
:::

## Demos and related pages

- [Freehand draw](https://grafloria.com/demos/whiteboard/freehand-draw.html) · [eraser](https://grafloria.com/demos/whiteboard/eraser.html) · [rectangle](https://grafloria.com/demos/whiteboard/rectangle.html) · [marquee selection](https://grafloria.com/demos/interaction/marquee-select.html)
- [Commands, events, and undo](https://atloria.dev/p/grafloria-h7YM7amryF/developer/commands-events-and-undo)
- [The element vs `render()`](https://atloria.dev/p/grafloria-h7YM7amryF/developer/grafloria-element-core)
- [Style a diagram](https://atloria.dev/p/grafloria-h7YM7amryF/developer/style-a-diagram)
