74 lines
2.9 KiB
Markdown
74 lines
2.9 KiB
Markdown
|
|
# @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.
|