Extension
Custom Shapes with Telva Core
Build low-level shape systems with Renderer and TLShapeUtil, then scale to advanced state-machine driven editors.
Custom Shapes with Telva Core
When you need custom behavior beyond stock Telva tools, use telva-core directly.
Core primitives
From telva-core:
RendererTLShapeUtil- page and event types (
TLPage,TLPageState,TLPointerEventHandler, ...)
Minimal shape utility (core-example pattern)
import { TLBounds, TLShapeUtil } from 'telva-core'
import type { RectShape } from './RectShape'
export class RectUtil extends TLShapeUtil<RectShape, SVGSVGElement> {
Component = RectComponent
Indicator = RectIndicator
getBounds = (shape: RectShape) => {
const [x, y] = shape.point
const [width, height] = shape.size
return {
minX: x,
maxX: x + width,
minY: y,
maxY: y + height,
width,
height,
} as TLBounds
}
}Then wire it into renderer:
<Renderer
shapeUtils={{ rect: new RectUtil() }}
page={page}
pageState={pageState}
onPointShape={onPointShape}
onDragShape={onDragShape}
onPointCanvas={onPointCanvas}
onPointerMove={onPointerMove}
meta={{ isDarkMode: false }}
theme={theme}
/>Advanced shape architecture (core-example-advanced pattern)
The advanced example introduces:
- abstract
CustomShapeUtilcontract (getShape,transform, hit tests, center) - multiple shape types (
box,arrow,pencil) - explicit shape-utils map +
getShapeUtilsresolver - app state machine orchestration (
@state-designer/react)
Abstract util contract
export abstract class CustomShapeUtil<T extends TLShape, E extends Element = Element>
extends TLShapeUtil<T, E> {
canBind = false
hideBounds = false
abstract getCenter(shape: T): number[]
abstract getShape(shape: Partial<T>): T
abstract transform(shape: T, bounds: TLBounds, initialShape: T, scale: number[]): void
abstract hitTestPoint(shape: T, point: number[]): boolean
abstract hitTestLineSegment(shape: T, A: number[], B: number[]): boolean
}State-machine driven event routing
In advanced setups, renderer events are forwarded to a state machine:
const onPointShape: TLPointerEventHandler = (info) => {
machine.send('POINTED_SHAPE', info)
}
const onPointerMove: TLPointerEventHandler = (info) => {
machine.send('MOVED_POINTER', info)
}
const onPan: TLWheelEventHandler = (info) => {
machine.send('PANNED', info)
}This enables complex interaction modes (transforming, brush-selecting, creating tools, undo/redo) outside the renderer internals.
Choosing extension level
| Goal | Preferred path |
|---|---|
| Add rich visual blocks in Telva editor | reactComponents registry in telva |
| Keep default tools and add host automation | TelvaApp commands + callbacks |
| Implement entirely custom interaction grammar | telva-core + custom shape utils + custom state machine |