@canvas-harness/react 0.1.2 → 0.1.4
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 +39 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +40 -15
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -131,6 +131,24 @@ type CanvasProps = {
|
|
|
131
131
|
* <Canvas selectionColor="#10b981" />
|
|
132
132
|
*/
|
|
133
133
|
selectionColor?: string;
|
|
134
|
+
/**
|
|
135
|
+
* Cap on the canvas backing-store DPR. Defaults to `1`.
|
|
136
|
+
*
|
|
137
|
+
* At native device-pixel ratio on hi-DPI displays (Mac Retina ≈ 2,
|
|
138
|
+
* Windows 4K @ 175% ≈ 1.75), the canvas backing buffer can hit
|
|
139
|
+
* 20-30 megapixels per frame — the per-frame GPU-upload cost alone
|
|
140
|
+
* eats a sizable slice of the frame budget. Capping DPR at 1 keeps
|
|
141
|
+
* perf consistent across hardware at the cost of slightly softer
|
|
142
|
+
* shape outlines on hi-DPI displays. Text remains crisp regardless
|
|
143
|
+
* (the text bitmap cache handles its own DPR).
|
|
144
|
+
*
|
|
145
|
+
* Bump to `2` (or `window.devicePixelRatio`) when crispness matters
|
|
146
|
+
* more than FPS — e.g. presentation slides, print-export views.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* <Canvas maxDpr={2} /> // pixel-crisp at the cost of FPS on hi-DPI
|
|
150
|
+
*/
|
|
151
|
+
maxDpr?: number;
|
|
134
152
|
/**
|
|
135
153
|
* Render a custom node's React subtree. Called once per
|
|
136
154
|
* library-mounted custom-node id; positioning is handled by the
|
package/dist/index.d.ts
CHANGED
|
@@ -131,6 +131,24 @@ type CanvasProps = {
|
|
|
131
131
|
* <Canvas selectionColor="#10b981" />
|
|
132
132
|
*/
|
|
133
133
|
selectionColor?: string;
|
|
134
|
+
/**
|
|
135
|
+
* Cap on the canvas backing-store DPR. Defaults to `1`.
|
|
136
|
+
*
|
|
137
|
+
* At native device-pixel ratio on hi-DPI displays (Mac Retina ≈ 2,
|
|
138
|
+
* Windows 4K @ 175% ≈ 1.75), the canvas backing buffer can hit
|
|
139
|
+
* 20-30 megapixels per frame — the per-frame GPU-upload cost alone
|
|
140
|
+
* eats a sizable slice of the frame budget. Capping DPR at 1 keeps
|
|
141
|
+
* perf consistent across hardware at the cost of slightly softer
|
|
142
|
+
* shape outlines on hi-DPI displays. Text remains crisp regardless
|
|
143
|
+
* (the text bitmap cache handles its own DPR).
|
|
144
|
+
*
|
|
145
|
+
* Bump to `2` (or `window.devicePixelRatio`) when crispness matters
|
|
146
|
+
* more than FPS — e.g. presentation slides, print-export views.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* <Canvas maxDpr={2} /> // pixel-crisp at the cost of FPS on hi-DPI
|
|
150
|
+
*/
|
|
151
|
+
maxDpr?: number;
|
|
134
152
|
/**
|
|
135
153
|
* Render a custom node's React subtree. Called once per
|
|
136
154
|
* library-mounted custom-node id; positioning is handled by the
|
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, useSyncExternalStore, useRef,
|
|
2
|
+
import { createContext, useContext, useSyncExternalStore, useRef, useEffect, useState } from 'react';
|
|
3
3
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
5
|
// src/Canvas.tsx
|
|
@@ -813,12 +813,27 @@ var usePanZoom = (ref, store, tool) => {
|
|
|
813
813
|
scheduled = false;
|
|
814
814
|
rafId = 0;
|
|
815
815
|
if (pendingZoomFactor !== 1 && pendingZoomAnchor) {
|
|
816
|
+
const MAX_PER_FRAME = 2;
|
|
817
|
+
const MIN_PER_FRAME = 0.5;
|
|
818
|
+
let appliedFactor = pendingZoomFactor;
|
|
819
|
+
let drained = true;
|
|
820
|
+
if (appliedFactor > MAX_PER_FRAME) {
|
|
821
|
+
appliedFactor = MAX_PER_FRAME;
|
|
822
|
+
pendingZoomFactor = pendingZoomFactor / MAX_PER_FRAME;
|
|
823
|
+
drained = false;
|
|
824
|
+
} else if (appliedFactor < MIN_PER_FRAME) {
|
|
825
|
+
appliedFactor = MIN_PER_FRAME;
|
|
826
|
+
pendingZoomFactor = pendingZoomFactor / MIN_PER_FRAME;
|
|
827
|
+
drained = false;
|
|
828
|
+
} else {
|
|
829
|
+
pendingZoomFactor = 1;
|
|
830
|
+
}
|
|
816
831
|
const camera = store.getCamera();
|
|
817
832
|
store.setCamera(
|
|
818
|
-
zoomAtScreenPoint(camera, clampZoom(camera.z *
|
|
833
|
+
zoomAtScreenPoint(camera, clampZoom(camera.z * appliedFactor), pendingZoomAnchor)
|
|
819
834
|
);
|
|
820
|
-
|
|
821
|
-
|
|
835
|
+
if (drained) pendingZoomAnchor = null;
|
|
836
|
+
else schedule();
|
|
822
837
|
}
|
|
823
838
|
if (pendingDx !== 0 || pendingDy !== 0) {
|
|
824
839
|
const camera = store.getCamera();
|
|
@@ -855,7 +870,7 @@ var usePanZoom = (ref, store, tool) => {
|
|
|
855
870
|
if (isEditing()) return;
|
|
856
871
|
e.preventDefault();
|
|
857
872
|
if (e.ctrlKey || e.metaKey) {
|
|
858
|
-
const factor = Math.exp(-e.deltaY * 0.01);
|
|
873
|
+
const factor = Math.abs(e.deltaY) >= 100 ? e.deltaY > 0 ? 1 / 1.1 : 1.1 : Math.exp(-e.deltaY * 0.01);
|
|
859
874
|
pendingZoomFactor *= factor;
|
|
860
875
|
pendingZoomAnchor = screenFromClient(e.clientX, e.clientY);
|
|
861
876
|
pulseMotion("zooming");
|
|
@@ -1030,6 +1045,7 @@ function CanvasSurface({
|
|
|
1030
1045
|
arrowDefaults,
|
|
1031
1046
|
background,
|
|
1032
1047
|
selectionColor,
|
|
1048
|
+
maxDpr,
|
|
1033
1049
|
renderCustomNodeView,
|
|
1034
1050
|
children
|
|
1035
1051
|
}) {
|
|
@@ -1047,8 +1063,15 @@ function CanvasSurface({
|
|
|
1047
1063
|
const interactionMode = useInteractionMode();
|
|
1048
1064
|
useArrowTool(wrapRef, store, tool === "arrow", arrowDefaults);
|
|
1049
1065
|
const { mountedIds, setMountedIds } = useOverlayHost();
|
|
1050
|
-
|
|
1051
|
-
|
|
1066
|
+
useEffect(() => {
|
|
1067
|
+
const el = overlayRef.current;
|
|
1068
|
+
if (!el) return;
|
|
1069
|
+
const apply = (c) => {
|
|
1070
|
+
el.style.transform = `translate(${-c.x * c.z}px, ${-c.y * c.z}px) scale(${c.z})`;
|
|
1071
|
+
};
|
|
1072
|
+
apply(store.getCamera());
|
|
1073
|
+
return store.subscribe("camera", apply);
|
|
1074
|
+
}, [store]);
|
|
1052
1075
|
useEffect(() => {
|
|
1053
1076
|
if (!staticRef.current || !interactiveRef.current || w === 0 || h === 0) return;
|
|
1054
1077
|
if (rendererRef.current) {
|
|
@@ -1064,6 +1087,7 @@ function CanvasSurface({
|
|
|
1064
1087
|
height: h,
|
|
1065
1088
|
background,
|
|
1066
1089
|
selectionColor,
|
|
1090
|
+
maxDpr,
|
|
1067
1091
|
onOverlayChange: (ids) => setMountedIds(ids)
|
|
1068
1092
|
});
|
|
1069
1093
|
r.start();
|
|
@@ -1073,7 +1097,7 @@ function CanvasSurface({
|
|
|
1073
1097
|
r.dispose();
|
|
1074
1098
|
rendererRef.current = null;
|
|
1075
1099
|
};
|
|
1076
|
-
}, [store, theme, w, h, onRenderer, setMountedIds]);
|
|
1100
|
+
}, [store, theme, w, h, maxDpr, onRenderer, setMountedIds]);
|
|
1077
1101
|
useEffect(() => {
|
|
1078
1102
|
rendererRef.current?.setBackground(background);
|
|
1079
1103
|
}, [background]);
|
|
@@ -1095,9 +1119,9 @@ function CanvasSurface({
|
|
|
1095
1119
|
if (toolRef.current === "select") {
|
|
1096
1120
|
const rect = el.getBoundingClientRect();
|
|
1097
1121
|
const screen = { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
|
1098
|
-
const
|
|
1099
|
-
const world = screenToWorld(screen,
|
|
1100
|
-
const hit = hitTestAny(store, world,
|
|
1122
|
+
const camera = store.getCamera();
|
|
1123
|
+
const world = screenToWorld(screen, camera);
|
|
1124
|
+
const hit = hitTestAny(store, world, camera.z);
|
|
1101
1125
|
if (hit && hit.kind === "body" && "nodeId" in hit) {
|
|
1102
1126
|
store.beginEdit(hit.nodeId);
|
|
1103
1127
|
} else if (hit && hit.kind === "body" && "edgeId" in hit) {
|
|
@@ -1135,9 +1159,9 @@ function CanvasSurface({
|
|
|
1135
1159
|
if (e.button !== 0) return;
|
|
1136
1160
|
if (!isShapeTool(toolRef.current)) return;
|
|
1137
1161
|
if (store.getInteractionState().mode === "editing") return;
|
|
1138
|
-
const
|
|
1139
|
-
const world = screenToWorld(screenFromEvent(e),
|
|
1140
|
-
if (hitTestAny(store, world,
|
|
1162
|
+
const camera = store.getCamera();
|
|
1163
|
+
const world = screenToWorld(screenFromEvent(e), camera);
|
|
1164
|
+
if (hitTestAny(store, world, camera.z)) return;
|
|
1141
1165
|
startWorld = world;
|
|
1142
1166
|
startScreen = screenFromEvent(e);
|
|
1143
1167
|
activePointerId = e.pointerId;
|
|
@@ -1244,7 +1268,8 @@ function CanvasSurface({
|
|
|
1244
1268
|
window.addEventListener("keydown", onKey);
|
|
1245
1269
|
return () => window.removeEventListener("keydown", onKey);
|
|
1246
1270
|
}, [store]);
|
|
1247
|
-
const
|
|
1271
|
+
const initialCamera = store.getCamera();
|
|
1272
|
+
const overlayTransform = `translate(${-initialCamera.x * initialCamera.z}px, ${-initialCamera.y * initialCamera.z}px) scale(${initialCamera.z})`;
|
|
1248
1273
|
return /* @__PURE__ */ jsxs(
|
|
1249
1274
|
"div",
|
|
1250
1275
|
{
|