Permissions 2
This example demonstrates how to use tldraw's side effect APIs to enforce permissions or
constraints on shapes. We create a rectangle that can be dragged around, but its movement
is constrained to stay within an invisible container using the registerBeforeChangeHandler
side effect. This pattern is useful for implementing permission systems, bounded regions,
or any scenario where you need to restrict where shapes can be positioned.
import { Box, Editor, SVGContainer, TLGeoShape, Tldraw, toRichText } from 'tldraw'
import 'tldraw/tldraw.css'
// [1]
const CONTAINER_BOUNDS = new Box(100, 100, 400, 300)
// [2]
function constrainShapeToBounds(editor: Editor, shape: TLGeoShape) {
const shapeGeometry = editor.getShapeGeometry(shape)
const shapeBounds = shapeGeometry.bounds
// Calculate the shape's world-space bounds
const shapeWorldBounds = editor.getShapePageBounds(shape)
if (!shapeWorldBounds) return shape
// Check if the shape is completely within the container
if (CONTAINER_BOUNDS.contains(shapeWorldBounds)) {
return shape
}
// [3]
// Calculate maximum allowed dimensions based on container size
const maxWidth = CONTAINER_BOUNDS.w - shapeBounds.x * 2
const maxHeight = CONTAINER_BOUNDS.h - shapeBounds.y * 2
// Clamp the shape's size if it would exceed the container
const clampedW = Math.min(shape.props.w, maxWidth)
const clampedH = Math.min(shape.props.h, maxHeight)
// Recalculate bounds with the clamped size
const clampedShapeBounds = Box.From({
x: shape.x + shapeBounds.x,
y: shape.y + shapeBounds.y,
w: (clampedW / shape.props.w) * shapeBounds.w,
h: (clampedH / shape.props.h) * shapeBounds.h,
})
// Clamp the shape's position so it stays within bounds
let clampedX = Math.max(
CONTAINER_BOUNDS.x - shapeBounds.x,
Math.min(shape.x, CONTAINER_BOUNDS.maxX - shapeBounds.x - clampedShapeBounds.w)
)
let clampedY = Math.max(
CONTAINER_BOUNDS.y - shapeBounds.y,
Math.min(shape.y, CONTAINER_BOUNDS.maxY - shapeBounds.y - clampedShapeBounds.h)
)
if (shapeBounds.w >= CONTAINER_BOUNDS.w) {
clampedX = CONTAINER_BOUNDS.x
}
if (shapeBounds.h >= CONTAINER_BOUNDS.h) {
clampedY = CONTAINER_BOUNDS.y
}
return {
...shape,
x: clampedX,
y: clampedY,
props: {
...shape.props,
w: clampedW,
h: clampedH,
},
}
}
export default function PermissionsExample() {
return (
<div className="tldraw__editor">
<Tldraw
onMount={(editor) => {
// [4]
editor.sideEffects.registerBeforeChangeHandler('shape', (prevShape, nextShape) => {
// Only constrain geo shapes (our rectangle)
if (nextShape.type === 'geo') {
return constrainShapeToBounds(editor, nextShape as TLGeoShape)
}
return nextShape
})
// [5]
// Create the constrained rectangle
editor.createShape<TLGeoShape>({
type: 'geo',
x: 250,
y: 200,
props: {
geo: 'rectangle',
w: 150,
h: 100,
richText: toRichText('Try to drag me around'),
},
})
// Zoom to show the container area
editor.zoomToBounds(new Box(0, 0, 600, 500))
}}
components={{
// [6]
OnTheCanvas: () => (
<SVGContainer>
<rect
x={CONTAINER_BOUNDS.x}
y={CONTAINER_BOUNDS.y}
width={CONTAINER_BOUNDS.w}
height={CONTAINER_BOUNDS.h}
fill="none"
stroke="rgba(0, 0, 0, 0.2)"
strokeWidth={2}
strokeDasharray="8 4"
/>
</SVGContainer>
),
}}
/>
</div>
)
}
/*
[1]
Define the invisible container bounds that our shape will be constrained to. This is a box
with top-left at (100, 100) and dimensions of 400x300.
[2]
This function checks if a shape's bounds extend outside the container and returns a modified
shape with clamped position if needed. We use editor.getShapeGeometry() to get the shape's
actual geometry including any padding or offsets.
[3]
First, calculate the maximum allowed dimensions and clamp the shape's size if needed. Then
clamp the position to keep the shape within bounds. This ensures the shape can neither be
moved nor resized outside the container.
[4]
Register a beforeChange handler that runs whenever any shape is about to be modified. We only
apply the constraint to geo shapes (rectangles). This handler intercepts changes and returns
a modified version of the shape with position clamped to the container.
[5]
Create a rectangle shape with text positioned inside our container bounds. Users can drag and
resize this shape, but it will be prevented from leaving or exceeding the container area.
[6]
Draw a dashed rectangle on the canvas to visualize the container bounds. This uses SVGContainer
to render directly on the canvas in world coordinates.
*/
Is this page helpful?
Prev
PermissionsNext
Derived view