@canvas-harness/react 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,462 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as _canvas_harness_core from '@canvas-harness/core';
3
+ import { Node, CanvasStore, EditorAdapterFactory, Renderer, CanvasBackground, NodeId, EdgeId, Edge, CameraState, PointerInfo, InteractionMode, InteractionState, PresenceState, ClientId } from '@canvas-harness/core';
4
+ import { ReactNode, CSSProperties } from 'react';
5
+
6
+ /**
7
+ * Defaults applied to every edge the arrow tool creates. Lets a
8
+ * consumer (e.g. a style-memory hook) inject the user's last-used
9
+ * pathStyle / arrowheads / style without forcing every edge through
10
+ * a custom factory.
11
+ */
12
+ type ArrowToolDefaults = {
13
+ pathStyle?: _canvas_harness_core.PathStyle;
14
+ style?: _canvas_harness_core.EdgeStyle;
15
+ };
16
+
17
+ /**
18
+ * Theme resolver — see ARCHITECTURE.md §13.10. Returns a color string
19
+ * (or undefined to fall back to the built-in defaults) for a given
20
+ * design-system token + context.
21
+ */
22
+ type ThemeResolver = (token: string, ctx?: {
23
+ node?: Node;
24
+ state?: 'idle' | 'hover' | 'selected' | 'drag';
25
+ }) => string | undefined;
26
+
27
+ /**
28
+ * Pointer info passed to `onClick` / `onDoubleClick`. Includes the
29
+ * point in both screen space and world space so consumers don't have
30
+ * to convert.
31
+ */
32
+ type CanvasPointerEvent = {
33
+ /** Position relative to the canvas element. */
34
+ screen: {
35
+ x: number;
36
+ y: number;
37
+ };
38
+ /** Position in scene coordinates (camera-adjusted). */
39
+ world: {
40
+ x: number;
41
+ y: number;
42
+ };
43
+ /** Tool active when the event fired. */
44
+ tool: string;
45
+ /** The native MouseEvent — read modifiers, button, etc. */
46
+ native: MouseEvent;
47
+ };
48
+ /**
49
+ * Fired on pointerup after a drag-to-create gesture (non-select tool,
50
+ * drag larger than ~5px). The consumer maps the rect into a new node
51
+ * (defaults, type, style — all consumer policy).
52
+ */
53
+ type CanvasCreateDragEvent = {
54
+ /** Bounding rect of the drag, in world coordinates. */
55
+ rect: {
56
+ x: number;
57
+ y: number;
58
+ w: number;
59
+ h: number;
60
+ };
61
+ /** Tool active when the gesture ended. */
62
+ tool: string;
63
+ native: PointerEvent;
64
+ };
65
+ type CanvasProps = {
66
+ /**
67
+ * Optional — when omitted, the component reads the store from
68
+ * `<CanvasProvider>` context. Pass directly for tests or
69
+ * standalone-canvas use.
70
+ */
71
+ store?: CanvasStore;
72
+ /**
73
+ * Current tool. The library handles `'select'` and `'arrow'`
74
+ * internally; any other string passes through to `onClick` /
75
+ * `onCreateDrag` so consumers can wire their own shape-create /
76
+ * text-tool / lasso / ... logic.
77
+ */
78
+ tool: string;
79
+ /** Theme resolver — see ARCHITECTURE.md §13.10 for the token catalog. */
80
+ theme?: ThemeResolver;
81
+ /**
82
+ * Pluggable in-place editor factory; defaults to the built-in
83
+ * `<textarea>`. Implement to swap in Lexical / ProseMirror / TipTap.
84
+ */
85
+ editorAdapter?: EditorAdapterFactory;
86
+ /** Called once when the renderer is mounted. Useful for perf overlays. */
87
+ onRenderer?: (r: Renderer) => void;
88
+ /** Click on the canvas surface (not over a node handle). */
89
+ onClick?: (e: CanvasPointerEvent) => void;
90
+ /** Double-click on the surface. The library has already triggered
91
+ * `beginEdit` if the click landed on a node body. */
92
+ onDoubleClick?: (e: CanvasPointerEvent) => void;
93
+ /**
94
+ * Drag-to-create — fires on pointerup when the user dragged with a
95
+ * non-select tool. Sub-threshold drags fall through to `onClick`.
96
+ *
97
+ * @example
98
+ * onCreateDrag={({ rect, tool }) => {
99
+ * if (tool === 'rect') store.addNode({ ...rect, type: 'rect', ... })
100
+ * }}
101
+ */
102
+ onCreateDrag?: (e: CanvasCreateDragEvent) => void;
103
+ /**
104
+ * Defaults applied to every edge the built-in arrow tool creates.
105
+ * Lets a consumer remember the user's last-used pathStyle / style /
106
+ * arrowheads. Shape + text tools route through `onClick` /
107
+ * `onCreateDrag` so consumer controls those defaults directly.
108
+ */
109
+ arrowDefaults?: ArrowToolDefaults;
110
+ /**
111
+ * Page background + optional infinite dot/grid pattern. Local-only
112
+ * (not part of the synced scene). Update by changing the prop —
113
+ * `<Canvas>` calls `renderer.setBackground` and forces a repaint.
114
+ *
115
+ * @example
116
+ * <Canvas background={{ color: '#fffaf3', pattern: 'dots', gap: 24 }} />
117
+ */
118
+ background?: CanvasBackground;
119
+ /**
120
+ * Render a custom node's React subtree. Called once per
121
+ * library-mounted custom-node id; positioning is handled by the
122
+ * overlay container (consumer fills the slot).
123
+ *
124
+ * @example
125
+ * renderCustomNodeView={id => {
126
+ * const node = store.getNode(id)
127
+ * if (node?.type === 'chart-card') return <ChartCardView node={node} />
128
+ * return null
129
+ * }}
130
+ */
131
+ renderCustomNodeView?: (id: NodeId) => ReactNode;
132
+ /** Extra content rendered inside the canvas absolute container. */
133
+ children?: ReactNode;
134
+ };
135
+ /**
136
+ * Mounts the canvas surface (static + interactive layers + DOM overlay
137
+ * for custom-node views + in-place editor mount). Owns the renderer
138
+ * lifecycle, gesture hooks, resize observer.
139
+ *
140
+ * Use inside a {@link CanvasProvider} (or pass `store` directly).
141
+ *
142
+ * @example
143
+ * function App() {
144
+ * const store = useRef(createCanvasStore()).current
145
+ * const [tool, setTool] = useState('select')
146
+ * return (
147
+ * <CanvasProvider store={store}>
148
+ * <Canvas
149
+ * tool={tool}
150
+ * onClick={e => console.log('click at', e.world)}
151
+ * onCreateDrag={e => {
152
+ * store.addNode({ id: asNodeId(store.generateId()), type: e.tool, ...e.rect, angle: 0, z: 0, groups: [] })
153
+ * }}
154
+ * />
155
+ * <Toolbar onSelect={setTool} />
156
+ * </CanvasProvider>
157
+ * )
158
+ * }
159
+ */
160
+ declare function Canvas(props: CanvasProps): react_jsx_runtime.JSX.Element;
161
+
162
+ /**
163
+ * Bird's-eye overview of the entire scene + a viewport rectangle
164
+ * showing where the camera is. Click or drag inside to pan.
165
+ *
166
+ * Perf model — see IMPROVEMENTS.md and core/render/minimap.ts:
167
+ * - Scene content is rendered into an offscreen-canvas cache **once
168
+ * per committed batch** (`'change'` event). Cost: O(N) per
169
+ * commit, not per frame.
170
+ * - On camera changes (pan/zoom), only the viewport rectangle is
171
+ * redrawn over the cached image. Cost: O(1) per frame.
172
+ * - Above `maxNodes`, the content render is skipped and a small
173
+ * placeholder text is shown instead.
174
+ *
175
+ * @example
176
+ * <Minimap width={200} height={150} position="bottom-right" />
177
+ */
178
+ type MinimapProps = {
179
+ /** Map width in CSS px. Default 200. */
180
+ width?: number;
181
+ /** Map height in CSS px. Default 150. */
182
+ height?: number;
183
+ /** Above this many nodes, content render is skipped (placeholder
184
+ * shown instead). Default 5000. */
185
+ maxNodes?: number;
186
+ /** Fixed-position corner shortcut. Use `style` for custom placement. */
187
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
188
+ /** Override container styles entirely (skip `position`). */
189
+ style?: CSSProperties;
190
+ /** Color of the viewport rect overlay. Default brand blue. */
191
+ viewportColor?: string;
192
+ /** Background color drawn behind the cached content + applied to the
193
+ * default container background. Default white. */
194
+ backgroundColor?: string;
195
+ /** Container border color (the surrounding chip). Default light slate. */
196
+ borderColor?: string;
197
+ /** Fallback node color when a node has no `style.backgroundColor`.
198
+ * Default neutral slate. */
199
+ defaultNodeColor?: string;
200
+ };
201
+ declare function Minimap({ width, height, maxNodes, position, style, viewportColor, backgroundColor, borderColor, defaultNodeColor, }: MinimapProps): react_jsx_runtime.JSX.Element;
202
+
203
+ type CanvasProviderProps = {
204
+ store: CanvasStore;
205
+ children: ReactNode;
206
+ };
207
+ /**
208
+ * Provides a {@link CanvasStore} to descendant hooks via context.
209
+ * Wrap your app (or just the canvas + its panels) once at the top
210
+ * level. `<Canvas>` reads the same store from context.
211
+ *
212
+ * @example
213
+ * const store = useRef(createCanvasStore()).current
214
+ * <CanvasProvider store={store}>
215
+ * <Toolbar />
216
+ * <Canvas tool="select" />
217
+ * <Sidebar />
218
+ * </CanvasProvider>
219
+ */
220
+ declare function CanvasProvider({ store, children }: CanvasProviderProps): react_jsx_runtime.JSX.Element;
221
+ /**
222
+ * Returns the {@link CanvasStore} from context. Use this when you need
223
+ * to mutate the store from event handlers (e.g. tool buttons, side
224
+ * panels). For reactive reads, prefer the more specific hooks
225
+ * (`useNode`, `useSelection`, `useCamera`, ...).
226
+ *
227
+ * Throws if called outside a `<CanvasProvider>`.
228
+ *
229
+ * @example
230
+ * function ClearButton() {
231
+ * const store = useCanvasStore()
232
+ * return <button onClick={() => store.batch(() => {
233
+ * for (const n of store.getAllNodes()) store.removeNode(n.id)
234
+ * })}>Clear</button>
235
+ * }
236
+ */
237
+ declare function useCanvasStore(): CanvasStore;
238
+
239
+ /**
240
+ * Subscribes to a single node. Re-renders **only** when that node
241
+ * changes — moves on other nodes are free.
242
+ *
243
+ * The recommended hook for custom-node React views, layer panels keyed
244
+ * by id, and any per-node UI.
245
+ *
246
+ * @example
247
+ * function StickyView({ id }: { id: NodeId }) {
248
+ * const node = useNode(id)
249
+ * if (!node) return null
250
+ * return <div>{node.content ?? 'empty'}</div>
251
+ * }
252
+ */
253
+ declare function useNode(id: NodeId): Node | undefined;
254
+ /**
255
+ * Returns every node (optionally filtered). Re-renders on **every**
256
+ * committed batch — expensive. Use for sidebars / minimaps / layer
257
+ * panels that legitimately see all nodes; never inside per-node
258
+ * components.
259
+ *
260
+ * @example
261
+ * // Layer panel: list every node grouped by type.
262
+ * function Layers() {
263
+ * const nodes = useNodes()
264
+ * return <ul>{nodes.map(n => <li key={n.id}>{n.type}</li>)}</ul>
265
+ * }
266
+ *
267
+ * @example
268
+ * // Filtered: only text nodes.
269
+ * const textNodes = useNodes(n => n.type === 'text')
270
+ */
271
+ declare function useNodes(predicate?: (n: Node) => boolean): Node[];
272
+
273
+ /**
274
+ * Subscribes to a single edge. Re-renders only when that edge mutates
275
+ * (style change, endpoint reconnect, etc.). Use inside per-edge UI
276
+ * like a label component or an inspector panel.
277
+ *
278
+ * @example
279
+ * function EdgeLabel({ id }: { id: EdgeId }) {
280
+ * const edge = useEdge(id)
281
+ * return <span>{edge?.style?.strokeColor ?? 'default'}</span>
282
+ * }
283
+ */
284
+ declare function useEdge(id: EdgeId): Edge | undefined;
285
+ /**
286
+ * Returns every edge (optionally filtered). Re-renders on every
287
+ * committed batch — expensive. Use for inspector panels or minimaps;
288
+ * never inside per-edge components.
289
+ *
290
+ * @example
291
+ * const dashed = useEdges(e => e.style?.strokeStyle === 'dashed')
292
+ */
293
+ declare function useEdges(predicate?: (e: Edge) => boolean): Edge[];
294
+
295
+ /**
296
+ * Returns the current selection — an array of node and/or edge ids.
297
+ * Re-renders only when the selection changes.
298
+ *
299
+ * @example
300
+ * function DeleteButton() {
301
+ * const store = useCanvasStore()
302
+ * const selection = useSelection()
303
+ * return (
304
+ * <button disabled={selection.length === 0} onClick={() => {
305
+ * for (const id of selection) {
306
+ * if (store.getNode(id as NodeId)) store.removeNode(id as NodeId)
307
+ * else store.removeEdge(id as EdgeId)
308
+ * }
309
+ * }}>
310
+ * Delete ({selection.length})
311
+ * </button>
312
+ * )
313
+ * }
314
+ */
315
+ declare function useSelection(): (NodeId | EdgeId)[];
316
+
317
+ /**
318
+ * Returns the current camera (`{ x, y, z }`). Re-renders on every
319
+ * camera change — pan / zoom / `store.setCamera(...)`.
320
+ *
321
+ * Useful for status bars, minimaps, or positioning overlays in world
322
+ * coordinates.
323
+ *
324
+ * @example
325
+ * function ZoomReadout() {
326
+ * const { z } = useCamera()
327
+ * return <span>{Math.round(z * 100)}%</span>
328
+ * }
329
+ */
330
+ declare function useCamera(): CameraState;
331
+
332
+ /**
333
+ * Full interaction state. Fires on **any** change — mode flips,
334
+ * pointermoves, drag delta updates, marquee rect updates, ...
335
+ *
336
+ * Most consumers want a narrower hook (`useInteractionMode`,
337
+ * `useCursor`, `useIsMoving`, `useDraggedIds`). Reach for this one only
338
+ * if you need multiple fields together.
339
+ *
340
+ * @example
341
+ * const state = useInteractionState()
342
+ * if (state.mode === 'marqueeing') drawMarqueeOverlay(state.marqueeRect)
343
+ */
344
+ declare function useInteractionState(): InteractionState;
345
+ /**
346
+ * Just the interaction mode. Re-renders only on mode transitions,
347
+ * never on pointermove.
348
+ *
349
+ * Use to gate heavy effects ("only run X when mode === 'idle'") or
350
+ * disable UI affordances during a drag.
351
+ *
352
+ * @example
353
+ * const mode = useInteractionMode()
354
+ * <button disabled={mode !== 'idle'}>Run AI suggestion</button>
355
+ */
356
+ declare function useInteractionMode(): InteractionMode;
357
+ /**
358
+ * Latest pointer info — `worldX/Y`, `screenX/Y`, `pointerType`,
359
+ * optional `pressure` (for pens). Updated on every pointermove.
360
+ *
361
+ * @example
362
+ * const cursor = useCursor()
363
+ * <div>x: {cursor?.worldX.toFixed(1)}</div>
364
+ */
365
+ declare function useCursor(): PointerInfo | null;
366
+ /**
367
+ * `true` while the user is panning, zooming, dragging, resizing, or
368
+ * rotating. Derived from {@link useInteractionMode}.
369
+ *
370
+ * Useful for skipping expensive renders during motion (the library
371
+ * does this internally for the bitmap cache; consumers can do the same
372
+ * for custom-node React views).
373
+ *
374
+ * @example
375
+ * const isMoving = useIsMoving()
376
+ * return isMoving ? <Skeleton /> : <ExpensiveChart />
377
+ */
378
+ declare function useIsMoving(): boolean;
379
+ declare function useDraggedIds(): readonly (NodeId | EdgeId)[];
380
+ /**
381
+ * `true` when the most recent pointer was a stylus. Falls back to
382
+ * `false` before any pointer event has fired.
383
+ *
384
+ * Use to surface pen-specific UI (pressure-aware tools, ink hints).
385
+ *
386
+ * @example
387
+ * const isPen = useIsPenActive()
388
+ * {isPen && <PressureToolbar />}
389
+ */
390
+ declare function useIsPenActive(): boolean;
391
+
392
+ /**
393
+ * This client's own presence — cursor / selection / editing / color /
394
+ * name. Re-renders when local presence updates.
395
+ *
396
+ * Set local presence via `store.presence.setLocal({...})`. The library
397
+ * forwards it through the attached `SyncAdapter` automatically.
398
+ *
399
+ * @example
400
+ * const me = useLocalPresence()
401
+ * <div>signed in as {me.name}</div>
402
+ */
403
+ declare function useLocalPresence(): PresenceState;
404
+ /**
405
+ * Reads remote presence.
406
+ *
407
+ * - `usePresence(clientId)` — one remote client's state, or
408
+ * `undefined` if they've left.
409
+ * - `usePresence()` — map of every remote client. Re-renders on every
410
+ * remote update (join / leave / cursor move). Use sparingly.
411
+ *
412
+ * @example
413
+ * // Paint every remote cursor.
414
+ * const remotes = usePresence()
415
+ * for (const p of remotes.values()) drawCursor(p)
416
+ *
417
+ * @example
418
+ * // Just one peer.
419
+ * const peer = usePresence(asClientId('alice'))
420
+ */
421
+ declare function usePresence(clientId: ClientId): PresenceState | undefined;
422
+ declare function usePresence(): ReadonlyMap<ClientId, PresenceState>;
423
+
424
+ /**
425
+ * `true` when there's something to undo. Updates after every committed
426
+ * batch (the only thing that changes the stack).
427
+ *
428
+ * @example
429
+ * const canUndo = useCanUndo()
430
+ * <button disabled={!canUndo} onClick={() => store.undo()}>Undo</button>
431
+ */
432
+ declare function useCanUndo(): boolean;
433
+ /**
434
+ * `true` when there's something to redo. See {@link useCanUndo}.
435
+ *
436
+ * @example
437
+ * const canRedo = useCanRedo()
438
+ * <button disabled={!canRedo} onClick={() => store.redo()}>Redo</button>
439
+ */
440
+ declare function useCanRedo(): boolean;
441
+
442
+ /**
443
+ * useInteraction — owns the gesture state machine for the Select tool.
444
+ *
445
+ * Handles click-select, shift-toggle, marquee, drag, resize. Talks to the
446
+ * store via setInteractionState (transient) and applyOp/updateNode (commits).
447
+ *
448
+ * The pan/zoom hook (usePanZoom) handles middle-button pan and wheel/pinch
449
+ * zoom; this hook handles primary-button gestures only.
450
+ */
451
+
452
+ type InteractionTool = 'select' | 'rect' | 'ellipse' | 'diamond' | 'capsule' | 'arrow' | 'text';
453
+
454
+ /**
455
+ * @canvas-harness/react
456
+ *
457
+ * React bindings: `<Canvas>` component + data/interaction/presence/history hooks.
458
+ * See ARCHITECTURE.md §13 and IMPLEMENTATION.md Phase 9.
459
+ */
460
+ declare const VERSION = "0.0.0";
461
+
462
+ export { type ArrowToolDefaults, Canvas, type CanvasCreateDragEvent, type CanvasPointerEvent, type CanvasProps, CanvasProvider, type CanvasProviderProps, type InteractionTool, Minimap, type MinimapProps, type ThemeResolver, VERSION, useCamera, useCanRedo, useCanUndo, useCanvasStore, useCursor, useDraggedIds, useEdge, useEdges, useInteractionMode, useInteractionState, useIsMoving, useIsPenActive, useLocalPresence, useNode, useNodes, usePresence, useSelection };