Drag & Drop
Veyrajs has no draggable flag — dragging is a small handshake you wire yourself: pointerdown
on a shape captures the grab offset, pointermove updates its world position, and pointerup
drops it. Explicit, and fully under your control.
Drag shapes
Section titled “Drag shapes”import { Circle, Rect, Shape } from '@veyrajs/core'import { createStage, cycle, disposeStage } from './_kit'
// There's no built-in `draggable` flag — drag is a three-event handshake you wire yourself:// pointerdown on a shape captures the grab offset, pointermove moves it in world space, and// pointerup drops it. Working in world coordinates keeps it correct under any camera zoom/pan.export function init(host: HTMLElement): () => void { const stage = createStage(host) const layer = stage.createLayer() layer.add( new Rect({ x: 80, y: 80, width: 130, height: 90, fill: cycle[0] }), new Circle({ x: 320, y: 140, radius: 56, fill: cycle[1] }), new Rect({ x: 470, y: 90, width: 110, height: 110, fill: cycle[2] }), )
let dragging: Shape | null = null let dx = 0 let dy = 0
stage.on('pointerdown', (e) => { if (e.target instanceof Shape) { dragging = e.target dx = e.worldPoint.x - dragging.x dy = e.worldPoint.y - dragging.y host.style.cursor = 'grabbing' } }) stage.on('pointermove', (e) => { if (dragging) { dragging.x = e.worldPoint.x - dx dragging.y = e.worldPoint.y - dy } }) stage.on('pointerup', () => { dragging = null host.style.cursor = '' })
return () => disposeStage(stage)}Snap to grid
Section titled “Snap to grid”Snapping is a one-line change to the drag math — round the world position to the nearest grid step:
import { Line, Rect, type Shape } from '@veyrajs/core'import { createStage, cycle, disposeStage, onThemeChange, roles, toolbar } from './_kit'
// Snapping is a one-line tweak to the drag math: round the world position to the nearest grid// step before applying it. The faint grid is just a layer of Lines beneath the tiles.const STEP = 32
export function init(host: HTMLElement): () => void { const stage = createStage(host) const gridLayer = stage.createLayer() const layer = stage.createLayer()
const lines: Line[] = [] const drawGrid = (): void => { for (const l of lines) l.remove() lines.length = 0 const color = roles().panelStroke for (let x = 0; x <= stage.width; x += STEP) { const l = new Line({ points: [ { x, y: 0 }, { x, y: stage.height }, ], stroke: color, strokeWidth: 1, }) gridLayer.add(l) lines.push(l) } for (let y = 0; y <= stage.height; y += STEP) { const l = new Line({ points: [ { x: 0, y }, { x: stage.width, y }, ], stroke: color, strokeWidth: 1, }) gridLayer.add(l) lines.push(l) } } drawGrid()
for (const tile of [ new Rect({ x: 2 * STEP, y: 2 * STEP, width: 3 * STEP, height: 2 * STEP, fill: cycle[0] }), new Rect({ x: 8 * STEP, y: 3 * STEP, width: 2 * STEP, height: 2 * STEP, fill: cycle[1] }), ]) { layer.add(tile) }
let dragging: Shape | null = null let dx = 0 let dy = 0 stage.on('pointerdown', (e) => { if (e.target instanceof Rect) { dragging = e.target dx = e.worldPoint.x - dragging.x dy = e.worldPoint.y - dragging.y } }) stage.on('pointermove', (e) => { if (dragging) { dragging.x = Math.round((e.worldPoint.x - dx) / STEP) * STEP dragging.y = Math.round((e.worldPoint.y - dy) / STEP) * STEP } }) stage.on('pointerup', () => { dragging = null })
const bar = toolbar(host) const hint = document.createElement('span') hint.className = 'veyrajs-demo__hint' hint.textContent = 'Drag a tile — it snaps to the grid' bar.append(hint)
const off = onThemeChange(() => { const color = roles().panelStroke for (const l of lines) l.stroke = color }) return () => { off() disposeStage(stage) }}