@canvas-harness/react 0.0.1 → 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.cjs +62 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +63 -54
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -116,6 +116,21 @@ type CanvasProps = {
|
|
|
116
116
|
* <Canvas background={{ color: '#fffaf3', pattern: 'dots', gap: 24 }} />
|
|
117
117
|
*/
|
|
118
118
|
background?: CanvasBackground;
|
|
119
|
+
/**
|
|
120
|
+
* Color for all selection chrome: outline, resize + rotate handles,
|
|
121
|
+
* edge endpoint + midpoint handles, marquee, drag-create preview,
|
|
122
|
+
* and the draft edge during creation. Defaults to `#3b82f6`. Update
|
|
123
|
+
* by changing the prop — `<Canvas>` calls
|
|
124
|
+
* `renderer.setSelectionColor` without recreating the renderer.
|
|
125
|
+
*
|
|
126
|
+
* Accepts any CSS color literal (hex, rgb(), named). Typically you
|
|
127
|
+
* also want to pass the same value to `<Minimap viewportColor={...} />`
|
|
128
|
+
* so the two stay visually in sync.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* <Canvas selectionColor="#10b981" />
|
|
132
|
+
*/
|
|
133
|
+
selectionColor?: string;
|
|
119
134
|
/**
|
|
120
135
|
* Render a custom node's React subtree. Called once per
|
|
121
136
|
* library-mounted custom-node id; positioning is handled by the
|
|
@@ -449,7 +464,7 @@ declare function useCanRedo(): boolean;
|
|
|
449
464
|
* zoom; this hook handles primary-button gestures only.
|
|
450
465
|
*/
|
|
451
466
|
|
|
452
|
-
type InteractionTool = 'select' | 'rect' | 'ellipse' | 'diamond' | 'capsule' | 'arrow' | 'text';
|
|
467
|
+
type InteractionTool = 'select' | 'pan' | 'rect' | 'ellipse' | 'diamond' | 'capsule' | 'arrow' | 'text';
|
|
453
468
|
|
|
454
469
|
/**
|
|
455
470
|
* @canvas-harness/react
|
package/dist/index.d.ts
CHANGED
|
@@ -116,6 +116,21 @@ type CanvasProps = {
|
|
|
116
116
|
* <Canvas background={{ color: '#fffaf3', pattern: 'dots', gap: 24 }} />
|
|
117
117
|
*/
|
|
118
118
|
background?: CanvasBackground;
|
|
119
|
+
/**
|
|
120
|
+
* Color for all selection chrome: outline, resize + rotate handles,
|
|
121
|
+
* edge endpoint + midpoint handles, marquee, drag-create preview,
|
|
122
|
+
* and the draft edge during creation. Defaults to `#3b82f6`. Update
|
|
123
|
+
* by changing the prop — `<Canvas>` calls
|
|
124
|
+
* `renderer.setSelectionColor` without recreating the renderer.
|
|
125
|
+
*
|
|
126
|
+
* Accepts any CSS color literal (hex, rgb(), named). Typically you
|
|
127
|
+
* also want to pass the same value to `<Minimap viewportColor={...} />`
|
|
128
|
+
* so the two stay visually in sync.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* <Canvas selectionColor="#10b981" />
|
|
132
|
+
*/
|
|
133
|
+
selectionColor?: string;
|
|
119
134
|
/**
|
|
120
135
|
* Render a custom node's React subtree. Called once per
|
|
121
136
|
* library-mounted custom-node id; positioning is handled by the
|
|
@@ -449,7 +464,7 @@ declare function useCanRedo(): boolean;
|
|
|
449
464
|
* zoom; this hook handles primary-button gestures only.
|
|
450
465
|
*/
|
|
451
466
|
|
|
452
|
-
type InteractionTool = 'select' | 'rect' | 'ellipse' | 'diamond' | 'capsule' | 'arrow' | 'text';
|
|
467
|
+
type InteractionTool = 'select' | 'pan' | 'rect' | 'ellipse' | 'diamond' | 'capsule' | 'arrow' | 'text';
|
|
453
468
|
|
|
454
469
|
/**
|
|
455
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
|
|
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
|
-
|
|
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;
|
|
@@ -979,6 +1030,7 @@ function CanvasSurface({
|
|
|
979
1030
|
onCreateDrag,
|
|
980
1031
|
arrowDefaults,
|
|
981
1032
|
background,
|
|
1033
|
+
selectionColor,
|
|
982
1034
|
renderCustomNodeView,
|
|
983
1035
|
children
|
|
984
1036
|
}) {
|
|
@@ -991,8 +1043,9 @@ function CanvasSurface({
|
|
|
991
1043
|
const toolRef = useRef(tool);
|
|
992
1044
|
toolRef.current = tool;
|
|
993
1045
|
const { w, h } = useResizeObserver(wrapRef);
|
|
994
|
-
usePanZoom(wrapRef, store);
|
|
1046
|
+
usePanZoom(wrapRef, store, tool);
|
|
995
1047
|
useInteractionGesture(wrapRef, store, tool);
|
|
1048
|
+
const interactionMode = useInteractionMode();
|
|
996
1049
|
useArrowTool(wrapRef, store, tool === "arrow", arrowDefaults);
|
|
997
1050
|
const { mountedIds, setMountedIds } = useOverlayHost();
|
|
998
1051
|
const [camera, setCamera] = useState(() => store.getCamera());
|
|
@@ -1011,6 +1064,7 @@ function CanvasSurface({
|
|
|
1011
1064
|
width: w,
|
|
1012
1065
|
height: h,
|
|
1013
1066
|
background,
|
|
1067
|
+
selectionColor,
|
|
1014
1068
|
onOverlayChange: (ids) => setMountedIds(ids)
|
|
1015
1069
|
});
|
|
1016
1070
|
r.start();
|
|
@@ -1024,6 +1078,9 @@ function CanvasSurface({
|
|
|
1024
1078
|
useEffect(() => {
|
|
1025
1079
|
rendererRef.current?.setBackground(background);
|
|
1026
1080
|
}, [background]);
|
|
1081
|
+
useEffect(() => {
|
|
1082
|
+
if (selectionColor !== void 0) rendererRef.current?.setSelectionColor(selectionColor);
|
|
1083
|
+
}, [selectionColor]);
|
|
1027
1084
|
useEffect(() => {
|
|
1028
1085
|
const el = wrapRef.current;
|
|
1029
1086
|
if (!el) return;
|
|
@@ -1061,6 +1118,7 @@ function CanvasSurface({
|
|
|
1061
1118
|
el.removeEventListener("dblclick", onDoubleClickHandler);
|
|
1062
1119
|
};
|
|
1063
1120
|
}, [store, onClick, onDoubleClick]);
|
|
1121
|
+
const justCommittedRef = useRef(false);
|
|
1064
1122
|
useEffect(() => {
|
|
1065
1123
|
const el = wrapRef.current;
|
|
1066
1124
|
if (!el || !onCreateDrag) return;
|
|
@@ -1068,7 +1126,6 @@ function CanvasSurface({
|
|
|
1068
1126
|
let startScreen = null;
|
|
1069
1127
|
let activePointerId = null;
|
|
1070
1128
|
let committed = false;
|
|
1071
|
-
const justCommittedRef = { current: false };
|
|
1072
1129
|
const screenFromEvent = (e) => {
|
|
1073
1130
|
const rect = el.getBoundingClientRect();
|
|
1074
1131
|
return { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
|
@@ -1199,7 +1256,7 @@ function CanvasSurface({
|
|
|
1199
1256
|
inset: 0,
|
|
1200
1257
|
background: "#f8fafc",
|
|
1201
1258
|
overflow: "hidden",
|
|
1202
|
-
cursor: tool === "select" ? "default" : "crosshair",
|
|
1259
|
+
cursor: tool === "pan" ? interactionMode === "panning" ? "grabbing" : "grab" : tool === "select" ? "default" : "crosshair",
|
|
1203
1260
|
touchAction: "none"
|
|
1204
1261
|
},
|
|
1205
1262
|
children: [
|
|
@@ -1549,54 +1606,6 @@ function useCamera() {
|
|
|
1549
1606
|
() => store.getCamera()
|
|
1550
1607
|
);
|
|
1551
1608
|
}
|
|
1552
|
-
function useInteractionState() {
|
|
1553
|
-
const store = useCanvasStore();
|
|
1554
|
-
return useSyncExternalStore(
|
|
1555
|
-
(cb) => store.subscribe("interaction", cb),
|
|
1556
|
-
() => store.getInteractionState()
|
|
1557
|
-
);
|
|
1558
|
-
}
|
|
1559
|
-
function useInteractionMode() {
|
|
1560
|
-
const store = useCanvasStore();
|
|
1561
|
-
return useSyncExternalStore(
|
|
1562
|
-
(cb) => {
|
|
1563
|
-
let lastMode = store.getInteractionState().mode;
|
|
1564
|
-
return store.subscribe("interaction", (state) => {
|
|
1565
|
-
if (state.mode !== lastMode) {
|
|
1566
|
-
lastMode = state.mode;
|
|
1567
|
-
cb();
|
|
1568
|
-
}
|
|
1569
|
-
});
|
|
1570
|
-
},
|
|
1571
|
-
() => store.getInteractionState().mode
|
|
1572
|
-
);
|
|
1573
|
-
}
|
|
1574
|
-
function useCursor() {
|
|
1575
|
-
const store = useCanvasStore();
|
|
1576
|
-
return useSyncExternalStore(
|
|
1577
|
-
(cb) => store.subscribe("interaction", cb),
|
|
1578
|
-
() => store.getInteractionState().pointer
|
|
1579
|
-
);
|
|
1580
|
-
}
|
|
1581
|
-
function useIsMoving() {
|
|
1582
|
-
const mode = useInteractionMode();
|
|
1583
|
-
return mode === "panning" || mode === "zooming" || mode === "dragging" || mode === "resizing" || mode === "rotating";
|
|
1584
|
-
}
|
|
1585
|
-
var EMPTY_DRAGGED = [];
|
|
1586
|
-
function useDraggedIds() {
|
|
1587
|
-
const store = useCanvasStore();
|
|
1588
|
-
return useSyncExternalStore(
|
|
1589
|
-
(cb) => store.subscribe("interaction", cb),
|
|
1590
|
-
() => {
|
|
1591
|
-
const state = store.getInteractionState();
|
|
1592
|
-
return state.draggedIds.length === 0 ? EMPTY_DRAGGED : state.draggedIds;
|
|
1593
|
-
}
|
|
1594
|
-
);
|
|
1595
|
-
}
|
|
1596
|
-
function useIsPenActive() {
|
|
1597
|
-
const cursor = useCursor();
|
|
1598
|
-
return cursor?.pointerType === "pen";
|
|
1599
|
-
}
|
|
1600
1609
|
function useLocalPresence() {
|
|
1601
1610
|
const store = useCanvasStore();
|
|
1602
1611
|
return useSyncExternalStore(
|