2.9 KiB
@blinksgg/canvas — Core Principles
These principles guide every design decision in the canvas library. When in doubt, refer here. When principles conflict, earlier ones win.
1. Touch-first, mouse-compatible
Design for fingers and stylus first, then ensure mouse works well. Every interaction has device-aware thresholds — 44px hit targets for fingers, 3px drag thresholds for pencil, palm rejection when stylus is active. Mouse is not the default; it is one of three equal input sources.
"Pencil draws, fingers navigate, mouse does both." — input-classifier.ts
2. Headless core, optional UI
All state lives in Jotai atoms with zero React dependency. The core/
layer is a pure state machine: atoms in, atoms out. Components in
components/ are one possible UI — consumers can build entirely
custom UIs by importing only @blinksgg/canvas/core and
@blinksgg/canvas/hooks.
3. Local-first, sync-optional
Every operation (drag, resize, copy, paste, undo, redo) works without
a network connection. The clipboard is in-memory. Undo/redo uses
delta-based snapshots in local state. The database layer (db/) is
an optional add-on with an adapter interface — not a requirement.
4. Events are data, actions are configurable
Canvas interactions produce typed events (NodeDoubleClick, BackgroundLongPress, etc.). What happens in response is a mapping in a settings store, not hardcoded logic. Users remap actions via presets or the SettingsPanel. This separates "what happened" from "what to do about it."
5. Atoms over context
Prefer Jotai atoms over React context for shared state. Atoms are composable, testable without React, and don't cause provider waterfall re-renders. Use React context only for non-reactive configuration (style themes, callback refs).
6. Fully controllable headless API
Every canvas operation must be executable from the core/ layer
without React. If a hook can do it, an atom or pure function in
core/ can do it too. The React hooks are convenience wrappers,
not the only path. External tools, tests, and non-React integrations
should have full control via store.set(atom, value).
7. Performance by default
Virtualization is on by default (only render visible nodes). The React Compiler handles memoization automatically. Edge paths only recompute when their connected nodes move. Delta-based history is O(1) for moves instead of O(N) full-graph snapshots.
8. Composition over configuration
Provide small, focused hooks (useNodeDrag, useNodeResize,
useTapGesture) that compose into complex behavior. The Canvas
component is a convenient composition — not the only way to use the
library. Advanced users compose hooks directly.
9. Backend-agnostic persistence
The CanvasStorageAdapter interface defines CRUD + subscriptions.
Implement it for any backend. The simplest path is callback props
(onNodePersist) with no adapter at all.