@canvas-harness/react 0.0.2 → 0.0.3

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.
package/dist/index.d.cts CHANGED
@@ -464,7 +464,7 @@ declare function useCanRedo(): boolean;
464
464
  * zoom; this hook handles primary-button gestures only.
465
465
  */
466
466
 
467
- type InteractionTool = 'select' | 'rect' | 'ellipse' | 'diamond' | 'capsule' | 'arrow' | 'text';
467
+ type InteractionTool = 'select' | 'pan' | 'rect' | 'ellipse' | 'diamond' | 'capsule' | 'arrow' | 'text';
468
468
 
469
469
  /**
470
470
  * @canvas-harness/react
package/dist/index.d.ts CHANGED
@@ -464,7 +464,7 @@ declare function useCanRedo(): boolean;
464
464
  * zoom; this hook handles primary-button gestures only.
465
465
  */
466
466
 
467
- type InteractionTool = 'select' | 'rect' | 'ellipse' | 'diamond' | 'capsule' | 'arrow' | 'text';
467
+ type InteractionTool = 'select' | 'pan' | 'rect' | 'ellipse' | 'diamond' | 'capsule' | 'arrow' | 'text';
468
468
 
469
469
  /**
470
470
  * @canvas-harness/react
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createRenderer, DEFAULT_MINIMAP_MAX_NODES, renderMinimapContent, sceneBounds, drawMinimapViewport, worldViewportFromCamera, screenToWorld, hitTestAny, copy, cut, paste, createPalmRejectionState, createDefaultTextareaEditor, minimapScreenToWorld, notePenActive, shouldRejectTouch, notePenInactive, asEdgeId, midpointToCubicControls, projectToNodeBoundary, marqueeNodes, shouldAutoFit, computeAutoFitHeight, hitTestPoint, worldToNodeLocal, edgeLabelBoundsWorld, asNodeId, zoomAtScreenPoint, clampZoom, panByScreen } from '@canvas-harness/core';
2
- import { createContext, useContext, useRef, useState, useEffect, useSyncExternalStore } from 'react';
2
+ import { createContext, useContext, useSyncExternalStore, useRef, useState, useEffect } from 'react';
3
3
  import { jsx, jsxs } from 'react/jsx-runtime';
4
4
 
5
5
  // src/Canvas.tsx
@@ -16,6 +16,54 @@ function useCanvasStore() {
16
16
  }
17
17
  return store;
18
18
  }
19
+ function useInteractionState() {
20
+ const store = useCanvasStore();
21
+ return useSyncExternalStore(
22
+ (cb) => store.subscribe("interaction", cb),
23
+ () => store.getInteractionState()
24
+ );
25
+ }
26
+ function useInteractionMode() {
27
+ const store = useCanvasStore();
28
+ return useSyncExternalStore(
29
+ (cb) => {
30
+ let lastMode = store.getInteractionState().mode;
31
+ return store.subscribe("interaction", (state) => {
32
+ if (state.mode !== lastMode) {
33
+ lastMode = state.mode;
34
+ cb();
35
+ }
36
+ });
37
+ },
38
+ () => store.getInteractionState().mode
39
+ );
40
+ }
41
+ function useCursor() {
42
+ const store = useCanvasStore();
43
+ return useSyncExternalStore(
44
+ (cb) => store.subscribe("interaction", cb),
45
+ () => store.getInteractionState().pointer
46
+ );
47
+ }
48
+ function useIsMoving() {
49
+ const mode = useInteractionMode();
50
+ return mode === "panning" || mode === "zooming" || mode === "dragging" || mode === "resizing" || mode === "rotating";
51
+ }
52
+ var EMPTY_DRAGGED = [];
53
+ function useDraggedIds() {
54
+ const store = useCanvasStore();
55
+ return useSyncExternalStore(
56
+ (cb) => store.subscribe("interaction", cb),
57
+ () => {
58
+ const state = store.getInteractionState();
59
+ return state.draggedIds.length === 0 ? EMPTY_DRAGGED : state.draggedIds;
60
+ }
61
+ );
62
+ }
63
+ function useIsPenActive() {
64
+ const cursor = useCursor();
65
+ return cursor?.pointerType === "pen";
66
+ }
19
67
  function EditorMount({
20
68
  store,
21
69
  factory = createDefaultTextareaEditor
@@ -711,7 +759,9 @@ var useOverlayHost = () => {
711
759
  }, []);
712
760
  return { mountedIds, setMountedIds };
713
761
  };
714
- var usePanZoom = (ref, store) => {
762
+ var usePanZoom = (ref, store, tool) => {
763
+ const toolRef = useRef(tool);
764
+ toolRef.current = tool;
715
765
  useEffect(() => {
716
766
  const el = ref.current;
717
767
  if (!el) return;
@@ -849,7 +899,8 @@ var usePanZoom = (ref, store) => {
849
899
  }
850
900
  return;
851
901
  }
852
- if (e.button === 1 || e.button === 0 && panActivatedBySpace) {
902
+ const handToolActive = toolRef.current === "pan";
903
+ if (e.button === 1 || e.button === 0 && (panActivatedBySpace || handToolActive)) {
853
904
  panning = true;
854
905
  lastX = e.clientX;
855
906
  lastY = e.clientY;
@@ -992,8 +1043,9 @@ function CanvasSurface({
992
1043
  const toolRef = useRef(tool);
993
1044
  toolRef.current = tool;
994
1045
  const { w, h } = useResizeObserver(wrapRef);
995
- usePanZoom(wrapRef, store);
1046
+ usePanZoom(wrapRef, store, tool);
996
1047
  useInteractionGesture(wrapRef, store, tool);
1048
+ const interactionMode = useInteractionMode();
997
1049
  useArrowTool(wrapRef, store, tool === "arrow", arrowDefaults);
998
1050
  const { mountedIds, setMountedIds } = useOverlayHost();
999
1051
  const [camera, setCamera] = useState(() => store.getCamera());
@@ -1204,7 +1256,7 @@ function CanvasSurface({
1204
1256
  inset: 0,
1205
1257
  background: "#f8fafc",
1206
1258
  overflow: "hidden",
1207
- cursor: tool === "select" ? "default" : "crosshair",
1259
+ cursor: tool === "pan" ? interactionMode === "panning" ? "grabbing" : "grab" : tool === "select" ? "default" : "crosshair",
1208
1260
  touchAction: "none"
1209
1261
  },
1210
1262
  children: [
@@ -1554,54 +1606,6 @@ function useCamera() {
1554
1606
  () => store.getCamera()
1555
1607
  );
1556
1608
  }
1557
- function useInteractionState() {
1558
- const store = useCanvasStore();
1559
- return useSyncExternalStore(
1560
- (cb) => store.subscribe("interaction", cb),
1561
- () => store.getInteractionState()
1562
- );
1563
- }
1564
- function useInteractionMode() {
1565
- const store = useCanvasStore();
1566
- return useSyncExternalStore(
1567
- (cb) => {
1568
- let lastMode = store.getInteractionState().mode;
1569
- return store.subscribe("interaction", (state) => {
1570
- if (state.mode !== lastMode) {
1571
- lastMode = state.mode;
1572
- cb();
1573
- }
1574
- });
1575
- },
1576
- () => store.getInteractionState().mode
1577
- );
1578
- }
1579
- function useCursor() {
1580
- const store = useCanvasStore();
1581
- return useSyncExternalStore(
1582
- (cb) => store.subscribe("interaction", cb),
1583
- () => store.getInteractionState().pointer
1584
- );
1585
- }
1586
- function useIsMoving() {
1587
- const mode = useInteractionMode();
1588
- return mode === "panning" || mode === "zooming" || mode === "dragging" || mode === "resizing" || mode === "rotating";
1589
- }
1590
- var EMPTY_DRAGGED = [];
1591
- function useDraggedIds() {
1592
- const store = useCanvasStore();
1593
- return useSyncExternalStore(
1594
- (cb) => store.subscribe("interaction", cb),
1595
- () => {
1596
- const state = store.getInteractionState();
1597
- return state.draggedIds.length === 0 ? EMPTY_DRAGGED : state.draggedIds;
1598
- }
1599
- );
1600
- }
1601
- function useIsPenActive() {
1602
- const cursor = useCursor();
1603
- return cursor?.pointerType === "pen";
1604
- }
1605
1609
  function useLocalPresence() {
1606
1610
  const store = useCanvasStore();
1607
1611
  return useSyncExternalStore(