canvas/docs/migration-v1.md
2026-03-11 18:42:08 -07:00

4.7 KiB

Migrating to @blinksgg/canvas v1.0

This guide covers breaking changes and migration steps from v0.x to v1.0.

Requirements

  • React 19.0+ — React 18 is not supported (React Compiler, context-as-JSX)
  • Jotai 2.6+ — Required for atom families and store API
  • Node 18+ — For ESM support

Breaking Changes Summary

From v0.14+ (Gesture System v2)

onAction callback type changed: GestureEventInputEvent

The Canvas component's onAction prop now receives InputEvent (union of pointer + keyboard events) instead of GestureEvent (pointer only).

// Before (v0.x)
onAction={(event: GestureEvent, resolution) => {
  const { subject, worldPosition } = event;
}}

// After (v1.0)
onAction={(event: InputEvent, resolution) => {
  if (event.kind === 'key') return; // skip keyboard events
  const { subject, worldPosition } = event; // narrowed to pointer
}}

Old gesture config replaced: The v1 gesture config shape (event-to-action mapping objects) was replaced by the v2 specificity-based binding system.

// Before (v0.x) — removed
<Canvas gestureConfig={{ onNodeDoubleTap: 'fit-to-view' }} />

// After (v1.0) — use binding contexts
import { gesturesV2 } from '@blinksgg/canvas';
// Bindings are configured via contexts, not flat config

From v0.23+ (Removed Exports)

These were deprecated in v0.16 and removed in v0.23:

Removed Replacement
findPortAtPosition() Use hitTestPort() from @blinksgg/canvas/utils
resolveGestureIntent() Use gesturesV2.resolve()
isBackgroundGesture() Check event.subject.kind === 'background'
useSupabaseClient() Use CanvasDbProvider with storage adapter

From v0.6+ (React 19)

  • Context.Provider syntax → <Context> (React 19 style)
  • Manual React.memo() / useCallback() / useMemo() removed — React Compiler handles memoization
  • Ref cleanup functions used instead of useEffect return for pointer capture

Import Path Changes

All import paths are stable and unchanged since v0.14:

import { Canvas, useCanvasSelection, graphAtom } from '@blinksgg/canvas';
import { commandRegistry } from '@blinksgg/canvas/commands';
import { graphAtom, selectedNodeIdsAtom } from '@blinksgg/canvas/core';
import { useNodeDrag, useFitToBounds } from '@blinksgg/canvas/hooks';
import { gesturesV2 } from '@blinksgg/canvas'; // or from '@blinksgg/canvas/gestures'
import { CanvasStorageAdapter } from '@blinksgg/canvas/db';

Storage Adapter Migration

If you were using the old Supabase provider props:

// Before (v0.x) — Supabase-specific props
<Canvas supabaseUrl="..." supabaseKey="..." />

// After (v1.0) — adapter pattern
import { CanvasDbProvider, SupabaseStorageAdapter } from '@blinksgg/canvas/db';

const adapter = new SupabaseStorageAdapter(supabaseClient);
<CanvasDbProvider adapter={adapter}>
  <Canvas ... />
</CanvasDbProvider>

Or use callback props for simple persistence (no adapter needed):

<Canvas
  onNodePersist={async (nodeId, graphId, uiProps) => {
    await saveToYourBackend(nodeId, graphId, uiProps);
  }}
/>

Peer Dependencies

Required:

  • react ^19.0.0
  • react-dom ^19.0.0
  • jotai ^2.6.0

Optional (install only if using the feature):

  • d3-force ^3.0.0 — for useForceLayout hook
  • @tanstack/react-query ^5.17.0 — for query-based atoms
  • jotai-tanstack-query — for atomsWithQuery integration
  • @blocknote/core, @blocknote/react, @blocknote/shadcn ^0.45.0 — for NoteNode

Gesture System v1 → v2 Mapping

v1 Gesture Config v2 Equivalent
onNodeDoubleTap: 'fit-to-view' Default binding: double-tap node → fit-to-view
onBackgroundDoubleTap: 'create-node' Default binding: double-tap background → create-node
onNodeRightClick: 'context-menu' Default binding: right-click node → context-menu
onNodeLongPress: 'toggle-lock' Default binding: long-press node → toggle-lock

All default bindings are pre-configured. Override via gestureConfig prop or addGestureRuleAtom.

Quick Start (v1.0)

import { Provider as JotaiProvider } from 'jotai';
import { Canvas, CanvasStyleProvider, graphAtom } from '@blinksgg/canvas';

function App() {
  return (
    <JotaiProvider>
      <CanvasStyleProvider isDark={true}>
        <Canvas
          renderNode={({ node, isSelected }) => (
            <div>{node.label}</div>
          )}
          onAction={(event, resolution) => {
            if (event.kind === 'key') return;
            console.log(resolution.actionId, event.subject);
          }}
        />
      </CanvasStyleProvider>
    </JotaiProvider>
  );
}