camox 0.76.0 → 0.77.0
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/core/editing/createEditableBlock.js +1077 -1681
- package/dist/core/editing/fieldSlot.js +251 -0
- package/dist/core/editing/useFieldSlot.js +159 -0
- package/dist/core/layoutContext.d.ts +2 -0
- package/dist/core/layoutContext.js +27 -1
- package/dist/core/lib/artifactTheme.js +43 -6
- package/dist/features/canvas/CanvasPageFrame.js +2 -2
- package/dist/features/canvas/CanvasPreviewPageSlot.js +1 -1
- package/dist/features/canvas/canvasCameraController.js +595 -0
- package/dist/features/canvas/useCanvasCamera.js +141 -587
- package/dist/features/canvas/useCanvasFrame.js +1 -1
- package/dist/features/page/DerivedPageContent.js +4 -8
- package/dist/features/preview/components/AssetLightbox.js +6 -65
- package/dist/features/preview/components/CanvasPreview.js +2 -2
- package/dist/features/preview/components/Frame.js +3 -3
- package/dist/features/preview/components/ItemFieldsEditor.js +376 -460
- package/dist/features/preview/components/Overlays.js +4 -9
- package/dist/features/preview/components/PreviewPanel.js +3 -3
- package/dist/features/preview/components/RemovedBlockPlaceholder.js +2 -2
- package/dist/features/preview/components/SelectionEditor.js +285 -339
- package/dist/features/preview/components/ZoomableImage.js +105 -0
- package/dist/features/preview/components/contentFieldSchema.js +3 -1
- package/dist/features/preview/components/useFieldWriter.js +30 -51
- package/dist/features/preview/components/useRecordView.js +3 -3
- package/dist/features/preview/overlayMessages.js +20 -58
- package/dist/features/preview/previewStore.js +6 -123
- package/dist/features/preview/repeatableItemPath.js +47 -0
- package/dist/features/renderer/RendererApp.js +6 -6
- package/dist/features/renderer/RendererFrames.js +2 -2
- package/dist/features/renderer/rendererHost.js +1 -1
- package/dist/features/routes/pageRuntime.d.ts +2 -8
- package/dist/features/routes/pageRuntime.js +21 -105
- package/dist/features/runtime/pageApp.js +1 -1
- package/dist/features/runtime/pageRead.js +105 -0
- package/dist/features/runtime/reader.d.ts +25 -0
- package/dist/features/runtime/reader.js +142 -0
- package/dist/features/runtime/runtime.d.ts +9 -3
- package/dist/features/runtime/runtime.js +608 -776
- package/dist/features/sdkBundle/buildSdkBundle.d.ts +5 -0
- package/dist/features/sdkBundle/buildSdkBundle.js +9 -3
- package/dist/features/studio/components/DraftsSheet.js +124 -65
- package/dist/features/studio/components/LocalePicker.js +518 -134
- package/dist/features/studio/components/ThreadMessages.js +5 -9
- package/dist/features/studio/showInCanvas.js +9 -19
- package/dist/features/studio/useEditorSelection.js +8 -19
- package/dist/lib/locale-path.js +2 -2
- package/dist/lib/queries.js +5 -1
- package/dist/package.js +1 -1
- package/dist/studio.css +1 -1
- package/package.json +4 -4
- package/skills/camox/references/artifacts.md +7 -0
- package/dist/core/hooks/useFieldSelection.js +0 -67
- package/dist/core/hooks/useIsEditable.js +0 -21
- package/dist/core/hooks/useOverlayMessage.js +0 -70
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import { canvasStore } from "./canvasStore.js";
|
|
2
|
-
import { canvasPageIframe } from "../preview/previewPageFrame.js";
|
|
3
2
|
import { ViewportContinuityContext, captureViewportAnchor, resolveViewportAnchor } from "../preview/viewportContinuity.js";
|
|
3
|
+
import { canvasPageIframe } from "../preview/previewPageFrame.js";
|
|
4
4
|
import { canvasSeamCover } from "./canvasFrameDocument.js";
|
|
5
|
-
import {
|
|
6
|
-
import { CANVAS_WHEEL_ZOOM_RATE, ZOOM_STRETCH, canvasMaximumScale, canvasMinimumScale, canvasOverview, canvasVerticalRail, canvasWheelDelta, constrainCanvasCamera, findCanvasSnapTarget, fitCanvasPage, isCanvasCameraSnapped, isCanvasPageScale, isInCanvasSnapWindow, placeCanvasPageAnchor, snapCanvasZoom, stretchCanvasZoom, unstretchCanvasZoom, zoomCanvasAt } from "./canvasCamera.js";
|
|
7
|
-
import { createCanvasFlight, createCanvasPan } from "./canvasFlight.js";
|
|
5
|
+
import { createCanvasCameraController } from "./canvasCameraController.js";
|
|
8
6
|
import * as React from "react";
|
|
9
7
|
|
|
10
8
|
//#region src/features/canvas/useCanvasCamera.ts
|
|
11
|
-
const GESTURE_IDLE_MS = 180;
|
|
12
|
-
/** A zoom ends once its input pauses this long; a stretch past a limit then springs back. */
|
|
13
|
-
const ZOOM_END_MS = 120;
|
|
14
9
|
const CAMERA_PROPERTIES = [
|
|
15
10
|
"--canvas-x",
|
|
16
11
|
"--canvas-y",
|
|
@@ -18,8 +13,6 @@ const CAMERA_PROPERTIES = [
|
|
|
18
13
|
"--canvas-rest-zoom",
|
|
19
14
|
"--canvas-pixel-ratio"
|
|
20
15
|
];
|
|
21
|
-
/** The camera counts as moving until it has been still this long. */
|
|
22
|
-
const CAMERA_SETTLE_MS = 150;
|
|
23
16
|
/**
|
|
24
17
|
* At rest, zooms where layers that scale with the camera stay promoted
|
|
25
18
|
* (`data-canvas-promoted`, for `will-change: transform`), so a movement starts
|
|
@@ -38,20 +31,27 @@ function isControl(target) {
|
|
|
38
31
|
function isTextEntry(target) {
|
|
39
32
|
return target instanceof Element && (!!target.closest("input, select, textarea") || target instanceof HTMLElement && target.isContentEditable);
|
|
40
33
|
}
|
|
34
|
+
/** The camera's keyboard commands, by key: a zoom key wins over Shift+1, which types "+" on some layouts. */
|
|
35
|
+
function keyCommand(event) {
|
|
36
|
+
switch (event.key) {
|
|
37
|
+
case "+":
|
|
38
|
+
case "=": return "zoomIn";
|
|
39
|
+
case "-": return "zoomOut";
|
|
40
|
+
case "0": return "zoomToActualSize";
|
|
41
|
+
}
|
|
42
|
+
if (event.key === "Home" || event.shiftKey && event.code === "Digit1") return "overview";
|
|
43
|
+
switch (event.key) {
|
|
44
|
+
case "ArrowLeft": return "panLeft";
|
|
45
|
+
case "ArrowRight": return "panRight";
|
|
46
|
+
case "ArrowUp": return "panUp";
|
|
47
|
+
case "ArrowDown": return "panDown";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
41
50
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
51
|
+
* Adapts the Canvas camera controller to the DOM: its input events, sizes, animation
|
|
52
|
+
* frames and timers, and the canvas store. Animate only the camera's CSS properties;
|
|
53
|
+
* page trees never rerender on pan/zoom.
|
|
44
54
|
*/
|
|
45
|
-
function rescaleY(camera, scale) {
|
|
46
|
-
return camera.y > 0 ? camera.y : camera.y * scale / camera.scale;
|
|
47
|
-
}
|
|
48
|
-
const NO_INSETS = {
|
|
49
|
-
top: 0,
|
|
50
|
-
right: 0,
|
|
51
|
-
bottom: 0,
|
|
52
|
-
left: 0
|
|
53
|
-
};
|
|
54
|
-
/** Animate only the camera's CSS properties; page trees never rerender on pan/zoom. */
|
|
55
55
|
function useCanvasCamera(workspaceKey, initialTarget, row) {
|
|
56
56
|
const continuity = React.useContext(ViewportContinuityContext);
|
|
57
57
|
const mirrors = React.useRef(/* @__PURE__ */ new Set());
|
|
@@ -59,9 +59,9 @@ function useCanvasCamera(workspaceKey, initialTarget, row) {
|
|
|
59
59
|
const contentRef = React.useRef(null);
|
|
60
60
|
const initialTargetRef = React.useRef(initialTarget);
|
|
61
61
|
const rowRef = React.useRef(row);
|
|
62
|
-
const
|
|
62
|
+
const controllerRef = React.useRef(null);
|
|
63
63
|
const flyTo = React.useCallback((key) => {
|
|
64
|
-
|
|
64
|
+
controllerRef.current?.flyTo(key);
|
|
65
65
|
}, []);
|
|
66
66
|
const paintMirror = React.useRef(null);
|
|
67
67
|
/** Paints the camera onto an element outside the viewport until the returned cleanup. */
|
|
@@ -81,15 +81,24 @@ function useCanvasCamera(workspaceKey, initialTarget, row) {
|
|
|
81
81
|
* unless enough of it already shows. A page flight or a drag in progress keeps the camera.
|
|
82
82
|
*/
|
|
83
83
|
const reveal = React.useCallback((element, immediate = false) => {
|
|
84
|
-
|
|
84
|
+
const controller = controllerRef.current;
|
|
85
|
+
const viewport = viewportRef.current;
|
|
86
|
+
const frame = element.ownerDocument.defaultView?.frameElement;
|
|
87
|
+
if (!controller || !viewport || !frame) return;
|
|
88
|
+
const rect = element.getBoundingClientRect();
|
|
89
|
+
controller.reveal({
|
|
90
|
+
frameTop: frame.getBoundingClientRect().top - viewport.getBoundingClientRect().top,
|
|
91
|
+
top: rect.top,
|
|
92
|
+
height: rect.height
|
|
93
|
+
}, immediate);
|
|
85
94
|
}, []);
|
|
86
95
|
/** Zoom out to the whole Canvas, keeping the selection: its caller decides what it becomes. */
|
|
87
96
|
const fitAll = React.useCallback(() => {
|
|
88
|
-
|
|
97
|
+
controllerRef.current?.fitAll();
|
|
89
98
|
}, []);
|
|
90
99
|
React.useLayoutEffect(() => {
|
|
91
100
|
rowRef.current = row;
|
|
92
|
-
|
|
101
|
+
controllerRef.current?.setRow(row);
|
|
93
102
|
});
|
|
94
103
|
React.useLayoutEffect(() => {
|
|
95
104
|
const viewport = viewportRef.current;
|
|
@@ -108,187 +117,23 @@ function useCanvasCamera(workspaceKey, initialTarget, row) {
|
|
|
108
117
|
capture: true,
|
|
109
118
|
passive: false
|
|
110
119
|
});
|
|
111
|
-
let insets = rowRef.current?.insets ?? NO_INSETS;
|
|
112
|
-
const measureViewport = () => {
|
|
113
|
-
insets = rowRef.current?.insets ?? NO_INSETS;
|
|
114
|
-
return {
|
|
115
|
-
width: Math.max(0, viewport.clientWidth - insets.left - insets.right),
|
|
116
|
-
height: Math.max(0, viewport.clientHeight - insets.top - insets.bottom)
|
|
117
|
-
};
|
|
118
|
-
};
|
|
119
|
-
let viewportSize = measureViewport();
|
|
120
|
-
const page = initialTargetRef.current;
|
|
121
|
-
const targets = () => rowRef.current?.targets ?? (page ? [{
|
|
122
|
-
...page,
|
|
123
|
-
key: "initial"
|
|
124
|
-
}] : []);
|
|
125
|
-
const measureContent = () => {
|
|
126
|
-
const widths = targets().map((target) => target.width);
|
|
127
|
-
return {
|
|
128
|
-
width: rowRef.current?.width ?? content.offsetWidth,
|
|
129
|
-
height: content.offsetHeight,
|
|
130
|
-
pageWidth: widths.length ? Math.min(...widths) : void 0
|
|
131
|
-
};
|
|
132
|
-
};
|
|
133
|
-
let contentSize = measureContent();
|
|
134
|
-
let minimumScale = canvasMinimumScale(viewportSize, contentSize);
|
|
135
|
-
const updateGeometry = (nextViewport = measureViewport(), nextContent = measureContent()) => {
|
|
136
|
-
viewportSize = nextViewport;
|
|
137
|
-
contentSize = nextContent;
|
|
138
|
-
minimumScale = canvasMinimumScale(viewportSize, contentSize);
|
|
139
|
-
};
|
|
140
|
-
const targetOf = (key) => key == null ? void 0 : targets().find((candidate) => candidate.key === key);
|
|
141
|
-
let stretch;
|
|
142
|
-
let zoomEnd = 0;
|
|
143
|
-
const constrain = (next, stretched = !!stretch) => constrainCanvasCamera(next, viewportSize, contentSize, canvasMaximumScale(viewportSize, targets(), minimumScale), stretched);
|
|
144
|
-
const saved = canvasStore.getSnapshot().context.views[workspaceKey];
|
|
145
|
-
const savedCamera = saved && !saved.fitted ? saved.camera : void 0;
|
|
146
|
-
let restoredCamera = savedCamera && (!page || isCanvasCameraSnapped(savedCamera, viewportSize, page)) ? savedCamera : void 0;
|
|
147
|
-
let fittingInitialPage = !!page && !restoredCamera;
|
|
148
|
-
const initialCamera = () => fittingInitialPage && page ? constrain(fitCanvasPage(viewportSize, page)) : canvasOverview(viewportSize, contentSize);
|
|
149
|
-
let camera = restoredCamera ? constrain(restoredCamera) : initialCamera();
|
|
150
|
-
let target = camera;
|
|
151
|
-
let animation = 0;
|
|
152
|
-
let previousTime = 0;
|
|
153
|
-
let flight;
|
|
154
|
-
let gesture;
|
|
155
|
-
let requested = rowRef.current?.selected ?? null;
|
|
156
|
-
let selection = requested ?? (page && targets().find((candidate) => candidate.left === page.left)?.key) ?? null;
|
|
157
|
-
const select = (key) => {
|
|
158
|
-
if (key === selection) return;
|
|
159
|
-
selection = key;
|
|
160
|
-
if (key) rowRef.current?.onSelect(key);
|
|
161
|
-
else rowRef.current?.onDeselect?.();
|
|
162
|
-
};
|
|
163
|
-
const rest = (next) => {
|
|
164
|
-
if (!stretch) return next;
|
|
165
|
-
const maximumScale = canvasMaximumScale(viewportSize, targets(), minimumScale);
|
|
166
|
-
const scale = Math.min(maximumScale, Math.max(minimumScale, next.scale));
|
|
167
|
-
const settled = zoomCanvasAt(next, stretch.point, scale, minimumScale, maximumScale);
|
|
168
|
-
const held = targetOf(selection);
|
|
169
|
-
const fit = held && fitCanvasPage(viewportSize, held);
|
|
170
|
-
return constrain(fit && isCanvasPageScale(scale, fit.scale) ? {
|
|
171
|
-
...settled,
|
|
172
|
-
x: fit.x
|
|
173
|
-
} : settled, false);
|
|
174
|
-
};
|
|
175
|
-
const releaseSelection = () => {
|
|
176
|
-
const held = targetOf(selection);
|
|
177
|
-
if (selection && !(held && isCanvasCameraSnapped(rest(target), viewportSize, held))) select(null);
|
|
178
|
-
};
|
|
179
|
-
const zoom = (base, point, factor, time, translation = {
|
|
180
|
-
x: 0,
|
|
181
|
-
y: 0
|
|
182
|
-
}) => {
|
|
183
|
-
const scale = base.scale;
|
|
184
|
-
const maximumScale = canvasMaximumScale(viewportSize, targets(), minimumScale);
|
|
185
|
-
const intendedScale = (gesture?.kind === "zoom" && time - gesture.time < GESTURE_IDLE_MS ? gesture.intendedScale : unstretchCanvasZoom(scale, minimumScale, maximumScale)) * factor;
|
|
186
|
-
gesture = {
|
|
187
|
-
kind: "zoom",
|
|
188
|
-
time,
|
|
189
|
-
intendedScale
|
|
190
|
-
};
|
|
191
|
-
const limited = Math.min(maximumScale, Math.max(minimumScale, intendedScale));
|
|
192
|
-
const eligible = targets().filter((candidate) => findCanvasSnapTarget(base, viewportSize, [candidate]));
|
|
193
|
-
const candidate = stretch && targetOf(selection) || findCanvasSnapTarget(camera, viewportSize, eligible);
|
|
194
|
-
const fit = candidate && fitCanvasPage(viewportSize, candidate);
|
|
195
|
-
const snapped = snapCanvasZoom(scale, limited, fit && fit.scale >= minimumScale ? fit.scale : void 0);
|
|
196
|
-
const stretched = limited !== intendedScale;
|
|
197
|
-
const nextScale = stretched ? stretchCanvasZoom(intendedScale, minimumScale, maximumScale) : snapped;
|
|
198
|
-
ownerWindow.clearTimeout(zoomEnd);
|
|
199
|
-
if (!pointers.size) zoomEnd = ownerWindow.setTimeout(endZoom, ZOOM_END_MS);
|
|
200
|
-
stretch = stretched ? { point } : void 0;
|
|
201
|
-
const anchored = zoomCanvasAt(base, point, nextScale, minimumScale / Math.exp(ZOOM_STRETCH), maximumScale * Math.exp(ZOOM_STRETCH));
|
|
202
|
-
const next = {
|
|
203
|
-
...anchored,
|
|
204
|
-
x: anchored.x + translation.x,
|
|
205
|
-
y: anchored.y + translation.y
|
|
206
|
-
};
|
|
207
|
-
if (!candidate || !fit || !isCanvasPageScale(snapped, fit.scale)) return next;
|
|
208
|
-
select(candidate.key);
|
|
209
|
-
return {
|
|
210
|
-
...next,
|
|
211
|
-
x: (viewportSize.width - candidate.width * nextScale) / 2 - candidate.left * nextScale
|
|
212
|
-
};
|
|
213
|
-
};
|
|
214
|
-
const pan = (base, delta, time, source, rail = {
|
|
215
|
-
mode: "free",
|
|
216
|
-
samples: []
|
|
217
|
-
}) => {
|
|
218
|
-
const start = gesture?.kind === "pan" && gesture.source === source && time - gesture.time < GESTURE_IDLE_MS ? gesture.intendedX : base.x;
|
|
219
|
-
const intended = constrain({
|
|
220
|
-
...base,
|
|
221
|
-
x: start + delta.x,
|
|
222
|
-
y: base.y + delta.y
|
|
223
|
-
});
|
|
224
|
-
gesture = {
|
|
225
|
-
kind: "pan",
|
|
226
|
-
time,
|
|
227
|
-
rail,
|
|
228
|
-
intendedX: intended.x,
|
|
229
|
-
source
|
|
230
|
-
};
|
|
231
|
-
if (!delta.x) return {
|
|
232
|
-
...intended,
|
|
233
|
-
x: base.x
|
|
234
|
-
};
|
|
235
|
-
const candidate = findCanvasSnapTarget(intended, viewportSize, targets());
|
|
236
|
-
const fit = candidate && fitCanvasPage(viewportSize, candidate);
|
|
237
|
-
if (!candidate || !fit || fit.scale < minimumScale) return intended;
|
|
238
|
-
const held = isCanvasCameraSnapped(base, viewportSize, candidate);
|
|
239
|
-
if (!isInCanvasSnapWindow(intended, viewportSize, candidate, held)) return intended;
|
|
240
|
-
select(candidate.key);
|
|
241
|
-
gesture.intendedX += (candidate.left + candidate.width / 2) * (intended.scale - fit.scale);
|
|
242
|
-
return constrain({
|
|
243
|
-
x: fit.x,
|
|
244
|
-
y: rescaleY(intended, fit.scale),
|
|
245
|
-
scale: fit.scale
|
|
246
|
-
});
|
|
247
|
-
};
|
|
248
|
-
const endZoom = () => {
|
|
249
|
-
ownerWindow.clearTimeout(zoomEnd);
|
|
250
|
-
if (stretch) {
|
|
251
|
-
const next = rest(target);
|
|
252
|
-
stretch = void 0;
|
|
253
|
-
gesture = void 0;
|
|
254
|
-
markMoving();
|
|
255
|
-
applyCamera(next);
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
const pointers = /* @__PURE__ */ new Map();
|
|
259
120
|
let spaceHeld = false;
|
|
260
|
-
const paintPanState = () => {
|
|
261
|
-
for (const element of [viewport, ...mirrors.current]) if (
|
|
121
|
+
const paintPanState = (view) => {
|
|
122
|
+
for (const element of [viewport, ...mirrors.current]) if (view.dragging) element.dataset.canvasPan = "dragging";
|
|
262
123
|
else if (spaceHeld) element.dataset.canvasPan = "ready";
|
|
263
124
|
else delete element.dataset.canvasPan;
|
|
264
125
|
};
|
|
265
126
|
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
return Math.abs(view.scale - overview.scale) < 1e-8 && Math.abs(view.x - overview.x) < .5 && Math.abs(view.y - overview.y) < .5;
|
|
269
|
-
};
|
|
270
|
-
const remember = () => {
|
|
271
|
-
if (restoredCamera || fittingInitialPage || !viewportSize.width || !viewportSize.height) return;
|
|
272
|
-
canvasStore.send({
|
|
273
|
-
type: "rememberView",
|
|
274
|
-
workspaceKey,
|
|
275
|
-
view: {
|
|
276
|
-
camera,
|
|
277
|
-
fitted: isOverview(camera)
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
};
|
|
281
|
-
let settle = 0;
|
|
282
|
-
const paintPromotion = () => {
|
|
283
|
-
const promoted = !!settle || camera.scale >= PROMOTED_ZOOM_RANGE.min && camera.scale <= PROMOTED_ZOOM_RANGE.max;
|
|
127
|
+
const paintPromotion = ({ camera, moving }) => {
|
|
128
|
+
const promoted = moving || camera.scale >= PROMOTED_ZOOM_RANGE.min && camera.scale <= PROMOTED_ZOOM_RANGE.max;
|
|
284
129
|
for (const element of [viewport, ...mirrors.current]) {
|
|
285
130
|
if (promoted === "canvasPromoted" in element.dataset) continue;
|
|
286
131
|
if (promoted) element.dataset.canvasPromoted = "";
|
|
287
132
|
else delete element.dataset.canvasPromoted;
|
|
288
133
|
}
|
|
289
134
|
};
|
|
290
|
-
const paintAtRest = () => {
|
|
291
|
-
if (
|
|
135
|
+
const paintAtRest = ({ camera, moving }) => {
|
|
136
|
+
if (moving) return;
|
|
292
137
|
const seamCover = String(canvasSeamCover(camera.scale, ownerWindow.devicePixelRatio));
|
|
293
138
|
const zoom = String(camera.scale);
|
|
294
139
|
for (const element of [viewport, ...mirrors.current]) {
|
|
@@ -296,358 +141,131 @@ function useCanvasCamera(workspaceKey, initialTarget, row) {
|
|
|
296
141
|
if (element.style.getPropertyValue("--canvas-rest-zoom") !== zoom) element.style.setProperty("--canvas-rest-zoom", zoom);
|
|
297
142
|
}
|
|
298
143
|
};
|
|
299
|
-
const
|
|
300
|
-
paintPromotion();
|
|
301
|
-
paintAtRest();
|
|
302
|
-
canvasStore.send({
|
|
303
|
-
type: "setCameraMoving",
|
|
304
|
-
workspaceKey,
|
|
305
|
-
moving
|
|
306
|
-
});
|
|
307
|
-
};
|
|
308
|
-
const markMoving = () => {
|
|
309
|
-
const wasMoving = !!settle;
|
|
310
|
-
ownerWindow.clearTimeout(settle);
|
|
311
|
-
settle = ownerWindow.setTimeout(() => {
|
|
312
|
-
settle = 0;
|
|
313
|
-
setMoving(false);
|
|
314
|
-
}, CAMERA_SETTLE_MS);
|
|
315
|
-
if (!wasMoving) setMoving(true);
|
|
316
|
-
};
|
|
317
|
-
const paintElement = (element) => {
|
|
144
|
+
const paintElement = (element, { camera, insets }) => {
|
|
318
145
|
element.style.setProperty("--canvas-x", `${camera.x + insets.left}px`);
|
|
319
146
|
element.style.setProperty("--canvas-y", `${camera.y + insets.top}px`);
|
|
320
147
|
element.style.setProperty("--canvas-zoom", String(camera.scale));
|
|
321
148
|
element.style.setProperty("--canvas-pixel-ratio", String(ownerWindow.devicePixelRatio));
|
|
322
149
|
};
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
markMoving();
|
|
338
|
-
if (flight) {
|
|
339
|
-
flight.startedAt ??= time;
|
|
340
|
-
const progress = reducedMotion.matches ? 1 : Math.min(1, (time - flight.startedAt) / flight.path.duration);
|
|
341
|
-
camera = progress === 1 ? target : flight.path.at(progress);
|
|
342
|
-
paint();
|
|
343
|
-
if (progress < 1) {
|
|
344
|
-
animation = requestAnimationFrame(animate);
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
flight = void 0;
|
|
348
|
-
animation = 0;
|
|
349
|
-
previousTime = 0;
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
const elapsed = previousTime ? Math.min(64, time - previousTime) : 16;
|
|
353
|
-
previousTime = time;
|
|
354
|
-
const amount = reducedMotion.matches ? 1 : 1 - Math.exp(-elapsed / 55);
|
|
355
|
-
const approach = (from, to, epsilon) => {
|
|
356
|
-
const next = from + (to - from) * amount;
|
|
357
|
-
return Math.abs(next - to) < epsilon ? to : next;
|
|
358
|
-
};
|
|
359
|
-
camera = constrain({
|
|
360
|
-
x: approach(camera.x, target.x, .05),
|
|
361
|
-
y: approach(camera.y, target.y, .05),
|
|
362
|
-
scale: approach(camera.scale, target.scale, 1e-4)
|
|
363
|
-
}, true);
|
|
364
|
-
const settled = Math.abs(camera.x - target.x) < .05 && Math.abs(camera.y - target.y) < .05 && Math.abs(camera.scale - target.scale) < 1e-4;
|
|
365
|
-
if (settled) camera = target;
|
|
366
|
-
paint();
|
|
367
|
-
animation = settled ? 0 : requestAnimationFrame(animate);
|
|
368
|
-
if (settled) previousTime = 0;
|
|
369
|
-
};
|
|
370
|
-
const applyCamera = (next, immediate = false) => {
|
|
371
|
-
target = constrain(next);
|
|
372
|
-
if (immediate) {
|
|
373
|
-
cancelAnimationFrame(animation);
|
|
374
|
-
animation = 0;
|
|
375
|
-
previousTime = 0;
|
|
376
|
-
camera = target;
|
|
377
|
-
paint();
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
if (!animation) animation = requestAnimationFrame(animate);
|
|
381
|
-
};
|
|
382
|
-
const cancelCameraFlight = () => {
|
|
383
|
-
if (!flight) return;
|
|
384
|
-
flight = void 0;
|
|
385
|
-
cancelAnimationFrame(animation);
|
|
386
|
-
animation = 0;
|
|
387
|
-
previousTime = 0;
|
|
388
|
-
target = camera;
|
|
389
|
-
gesture = void 0;
|
|
390
|
-
};
|
|
391
|
-
const startFlight = (key) => {
|
|
392
|
-
cancelCameraFlight();
|
|
393
|
-
const destination = targetOf(key);
|
|
394
|
-
if (!destination) return;
|
|
395
|
-
flyToCamera(() => fitCanvasPage(viewportSize, destination), destination);
|
|
396
|
-
};
|
|
397
|
-
const startOverviewFlight = () => {
|
|
398
|
-
cancelCameraFlight();
|
|
399
|
-
flyToCamera(() => canvasOverview(viewportSize, contentSize));
|
|
400
|
-
};
|
|
401
|
-
const startPan = (destination) => {
|
|
402
|
-
cancelCameraFlight();
|
|
403
|
-
flyToCamera(() => destination, void 0, true);
|
|
404
|
-
};
|
|
405
|
-
const flyToCamera = (destinationCamera, page, pan = false) => {
|
|
406
|
-
cancelAnimationFrame(animation);
|
|
407
|
-
animation = 0;
|
|
408
|
-
previousTime = 0;
|
|
409
|
-
restoredCamera = void 0;
|
|
410
|
-
fittingInitialPage = false;
|
|
411
|
-
gesture = void 0;
|
|
412
|
-
ownerWindow.clearTimeout(zoomEnd);
|
|
413
|
-
stretch = void 0;
|
|
414
|
-
updateGeometry();
|
|
415
|
-
camera = constrain(camera, true);
|
|
416
|
-
target = constrain(destinationCamera());
|
|
417
|
-
const path = pan ? createCanvasPan(camera, target, viewportSize) : createCanvasFlight(camera, target, viewportSize, contentSize);
|
|
418
|
-
if (reducedMotion.matches || path.duration === 0) {
|
|
419
|
-
applyCamera(target, true);
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
flight = {
|
|
423
|
-
page: page && { ...page },
|
|
424
|
-
pan,
|
|
425
|
-
path
|
|
426
|
-
};
|
|
427
|
-
animation = requestAnimationFrame(animate);
|
|
428
|
-
};
|
|
429
|
-
let syncedTargets = targets();
|
|
430
|
-
flightControls.current = {
|
|
431
|
-
flyTo: startFlight,
|
|
432
|
-
reveal: (element, immediate) => {
|
|
433
|
-
const frame = element.ownerDocument.defaultView?.frameElement;
|
|
434
|
-
if (!frame || flight && !flight.pan || pointers.size) return;
|
|
435
|
-
const rect = element.getBoundingClientRect();
|
|
436
|
-
const worldTop = (frame.getBoundingClientRect().top - viewport.getBoundingClientRect().top - insets.top + rect.top * camera.scale - camera.y) / camera.scale;
|
|
437
|
-
const delta = revealScrollDelta({
|
|
438
|
-
top: target.y + worldTop * target.scale,
|
|
439
|
-
bottom: target.y + (worldTop + rect.height) * target.scale
|
|
440
|
-
}, {
|
|
441
|
-
top: 0,
|
|
442
|
-
bottom: viewportSize.height
|
|
443
|
-
});
|
|
444
|
-
if (!delta) return;
|
|
445
|
-
const next = {
|
|
446
|
-
...target,
|
|
447
|
-
y: target.y - delta
|
|
448
|
-
};
|
|
449
|
-
if (immediate) move(next, true);
|
|
450
|
-
else startPan(next);
|
|
451
|
-
},
|
|
452
|
-
syncRow: () => {
|
|
453
|
-
const key = rowRef.current?.selected ?? null;
|
|
454
|
-
const destination = targetOf(key);
|
|
455
|
-
const previousTargets = syncedTargets;
|
|
456
|
-
syncedTargets = targets();
|
|
457
|
-
if (key !== requested) {
|
|
458
|
-
requested = key;
|
|
459
|
-
selection = key;
|
|
460
|
-
if (destination && !isCanvasCameraSnapped(rest(target), viewportSize, destination)) startFlight(destination.key);
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
if (flight?.page) {
|
|
464
|
-
const page = targetOf(flight.page.key);
|
|
465
|
-
if (!page) cancelCameraFlight();
|
|
466
|
-
else if (page.left !== flight.page.left || page.width !== flight.page.width) startFlight(page.key);
|
|
467
|
-
return;
|
|
150
|
+
const saved = canvasStore.getSnapshot().context.views[workspaceKey];
|
|
151
|
+
const controller = createCanvasCameraController({
|
|
152
|
+
initialPage: initialTargetRef.current,
|
|
153
|
+
saved,
|
|
154
|
+
row: rowRef.current
|
|
155
|
+
}, {
|
|
156
|
+
measure: () => ({
|
|
157
|
+
viewport: {
|
|
158
|
+
width: viewport.clientWidth,
|
|
159
|
+
height: viewport.clientHeight
|
|
160
|
+
},
|
|
161
|
+
content: {
|
|
162
|
+
width: content.offsetWidth,
|
|
163
|
+
height: content.offsetHeight
|
|
468
164
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
fittingInitialPage = false;
|
|
479
|
-
camera = constrain(camera, true);
|
|
480
|
-
applyCamera(next, true);
|
|
481
|
-
return;
|
|
165
|
+
}),
|
|
166
|
+
clock: {
|
|
167
|
+
frame: (callback) => {
|
|
168
|
+
const id = requestAnimationFrame(callback);
|
|
169
|
+
return () => cancelAnimationFrame(id);
|
|
170
|
+
},
|
|
171
|
+
after: (ms, callback) => {
|
|
172
|
+
const id = ownerWindow.setTimeout(callback, ms);
|
|
173
|
+
return () => ownerWindow.clearTimeout(id);
|
|
482
174
|
}
|
|
483
|
-
if (destination && selection === key && !flight && !fittingInitialPage && !isCanvasCameraSnapped(rest(target), viewportSize, destination)) startFlight(destination.key);
|
|
484
175
|
},
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
176
|
+
reducedMotion: () => reducedMotion.matches,
|
|
177
|
+
paint: (view) => {
|
|
178
|
+
paintElement(viewport, view);
|
|
179
|
+
for (const element of mirrors.current) paintElement(element, view);
|
|
180
|
+
paintPromotion(view);
|
|
181
|
+
paintAtRest(view);
|
|
182
|
+
},
|
|
183
|
+
moving: (view) => {
|
|
184
|
+
paintPromotion(view);
|
|
185
|
+
paintAtRest(view);
|
|
186
|
+
canvasStore.send({
|
|
187
|
+
type: "setCameraMoving",
|
|
188
|
+
workspaceKey,
|
|
189
|
+
moving: view.moving
|
|
190
|
+
});
|
|
191
|
+
},
|
|
192
|
+
remember: (view) => canvasStore.send({
|
|
193
|
+
type: "rememberView",
|
|
194
|
+
workspaceKey,
|
|
195
|
+
view
|
|
196
|
+
})
|
|
197
|
+
});
|
|
198
|
+
controllerRef.current = controller;
|
|
199
|
+
paintMirror.current = (element) => {
|
|
200
|
+
paintElement(element, controller);
|
|
201
|
+
paintPanState(controller);
|
|
202
|
+
paintPromotion(controller);
|
|
203
|
+
paintAtRest(controller);
|
|
494
204
|
};
|
|
205
|
+
const pageElement = (key) => Array.from(content.querySelectorAll("[data-canvas-page]")).find((element) => element.dataset.canvasPage === key);
|
|
495
206
|
const continuityBinding = {
|
|
496
207
|
capture: () => {
|
|
497
|
-
const
|
|
498
|
-
const element =
|
|
208
|
+
const { camera, page } = controller.capture();
|
|
209
|
+
const element = page ? pageElement(page) : void 0;
|
|
499
210
|
const doc = element ? canvasPageIframe(element)?.contentDocument : void 0;
|
|
500
|
-
const anchor = doc && element ? captureViewportAnchor(doc, Math.max(0, (viewport.getBoundingClientRect().top + insets.top - element.getBoundingClientRect().top) / camera.scale)) : void 0;
|
|
211
|
+
const anchor = doc && element ? captureViewportAnchor(doc, Math.max(0, (viewport.getBoundingClientRect().top + controller.insets.top - element.getBoundingClientRect().top) / camera.scale)) : void 0;
|
|
501
212
|
return {
|
|
502
|
-
camera
|
|
213
|
+
camera,
|
|
503
214
|
pathname: element ? element.dataset.canvasPathname : void 0,
|
|
504
215
|
anchor
|
|
505
216
|
};
|
|
506
217
|
},
|
|
507
218
|
/** Snap to the selected page, at the anchor's reading point or the previous camera's. */
|
|
508
219
|
restore: (anchor, previous) => {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
} : fit;
|
|
520
|
-
if (anchor && doc) {
|
|
521
|
-
const y = resolveViewportAnchor(doc, anchor);
|
|
522
|
-
const frameOffsetY = page ? (page.getBoundingClientRect().top - viewport.getBoundingClientRect().top - insets.top - camera.y) / camera.scale : 0;
|
|
523
|
-
next = placeCanvasPageAnchor(viewportSize, destination, y, fit.scale, frameOffsetY);
|
|
524
|
-
}
|
|
525
|
-
fittingInitialPage = false;
|
|
526
|
-
restoredCamera = next;
|
|
527
|
-
applyCamera(next, true);
|
|
220
|
+
controller.restore(previous, (key) => {
|
|
221
|
+
const page = pageElement(key);
|
|
222
|
+
const doc = page ? canvasPageIframe(page)?.contentDocument : void 0;
|
|
223
|
+
if (!anchor || !doc || !page) return void 0;
|
|
224
|
+
const { camera, insets } = controller;
|
|
225
|
+
return {
|
|
226
|
+
y: resolveViewportAnchor(doc, anchor),
|
|
227
|
+
frameOffsetY: (page.getBoundingClientRect().top - viewport.getBoundingClientRect().top - insets.top - camera.y) / camera.scale
|
|
228
|
+
};
|
|
229
|
+
});
|
|
528
230
|
}
|
|
529
231
|
};
|
|
530
232
|
if (continuity) continuity.canvas = continuityBinding;
|
|
531
|
-
const resize = new ResizeObserver(() =>
|
|
532
|
-
const previousInsets = insets;
|
|
533
|
-
const nextViewport = measureViewport();
|
|
534
|
-
const nextContent = measureContent();
|
|
535
|
-
if (insets.left === previousInsets.left && insets.top === previousInsets.top && nextViewport.width === viewportSize.width && nextViewport.height === viewportSize.height && nextContent.width === contentSize.width && nextContent.height === contentSize.height && nextContent.pageWidth === contentSize.pageWidth) return;
|
|
536
|
-
const wasFitted = isOverview(target);
|
|
537
|
-
gesture = void 0;
|
|
538
|
-
const held = nextViewport.width !== viewportSize.width && !flight && !fittingInitialPage ? targetOf(selection) : void 0;
|
|
539
|
-
updateGeometry(nextViewport, nextContent);
|
|
540
|
-
if (held) {
|
|
541
|
-
const fit = fitCanvasPage(viewportSize, held);
|
|
542
|
-
const next = {
|
|
543
|
-
...fit,
|
|
544
|
-
y: rescaleY(restoredCamera ?? target, fit.scale)
|
|
545
|
-
};
|
|
546
|
-
if (restoredCamera) restoredCamera = next;
|
|
547
|
-
camera = constrain(camera, true);
|
|
548
|
-
applyCamera(next, true);
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
if (flight) {
|
|
552
|
-
camera = constrain(camera, true);
|
|
553
|
-
if (flight.page) startFlight(flight.page.key);
|
|
554
|
-
else if (flight.pan) startPan(target);
|
|
555
|
-
else startOverviewFlight();
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
if (restoredCamera) {
|
|
559
|
-
applyCamera(restoredCamera, true);
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
if (fittingInitialPage) {
|
|
563
|
-
applyCamera(initialCamera(), true);
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
camera = constrain(camera, true);
|
|
567
|
-
applyCamera(wasFitted ? canvasOverview(viewportSize, contentSize) : target, true);
|
|
568
|
-
});
|
|
233
|
+
const resize = new ResizeObserver(() => controller.resize());
|
|
569
234
|
resize.observe(viewport);
|
|
570
235
|
resize.observe(content);
|
|
571
236
|
const localPoint = (x, y) => {
|
|
572
237
|
const rect = viewport.getBoundingClientRect();
|
|
573
238
|
return {
|
|
574
|
-
x: x - rect.left
|
|
575
|
-
y: y - rect.top
|
|
239
|
+
x: x - rect.left,
|
|
240
|
+
y: y - rect.top
|
|
576
241
|
};
|
|
577
242
|
};
|
|
578
243
|
const onWheel = (event) => {
|
|
579
244
|
event.preventDefault();
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
}
|
|
590
|
-
endZoom();
|
|
591
|
-
const previous = gesture?.kind === "pan" && gesture.source === "wheel" && event.timeStamp - gesture.time < GESTURE_IDLE_MS ? gesture.rail : void 0;
|
|
592
|
-
const rail = !event.shiftKey && !pointers.size && targets().length > 0 && isCanvasPageScale(target.scale, canvasMaximumScale(viewportSize, targets(), minimumScale)) ? canvasVerticalRail({
|
|
593
|
-
x: dx,
|
|
594
|
-
y: dy
|
|
595
|
-
}, event.timeStamp, previous) : {
|
|
596
|
-
mode: "free",
|
|
597
|
-
samples: []
|
|
598
|
-
};
|
|
599
|
-
const vertical = rail.mode === "vertical";
|
|
600
|
-
move(pan(target, {
|
|
601
|
-
x: -(vertical ? 0 : event.shiftKey && dx === 0 ? dy : dx),
|
|
602
|
-
y: -(event.shiftKey && dx === 0 ? 0 : dy)
|
|
603
|
-
}, event.timeStamp, "wheel", rail));
|
|
245
|
+
controller.wheel({
|
|
246
|
+
deltaX: event.deltaX,
|
|
247
|
+
deltaY: event.deltaY,
|
|
248
|
+
deltaMode: event.deltaMode,
|
|
249
|
+
point: localPoint(event.clientX, event.clientY),
|
|
250
|
+
zoom: event.ctrlKey || event.metaKey,
|
|
251
|
+
shift: event.shiftKey,
|
|
252
|
+
time: event.timeStamp
|
|
253
|
+
});
|
|
604
254
|
};
|
|
605
255
|
const onPointerDown = (event) => {
|
|
606
256
|
if (!(event.button === 1 || event.button === 0 && (spaceHeld || event.pointerType === "touch")) || !spaceHeld && isControl(event.target)) return;
|
|
607
|
-
gesture = void 0;
|
|
608
257
|
event.preventDefault();
|
|
609
258
|
viewport.focus({ preventScroll: true });
|
|
610
|
-
|
|
611
|
-
pointers.set(event.pointerId, localPoint(event.clientX, event.clientY));
|
|
259
|
+
controller.pointerDown(event.pointerId, localPoint(event.clientX, event.clientY));
|
|
612
260
|
viewport.setPointerCapture(event.pointerId);
|
|
613
|
-
paintPanState();
|
|
261
|
+
paintPanState(controller);
|
|
614
262
|
};
|
|
615
263
|
const onPointerMove = (event) => {
|
|
616
|
-
|
|
617
|
-
if (!previous) return;
|
|
618
|
-
const next = localPoint(event.clientX, event.clientY);
|
|
619
|
-
const other = [...pointers.entries()].find(([id]) => id !== event.pointerId)?.[1];
|
|
620
|
-
pointers.set(event.pointerId, next);
|
|
621
|
-
rowRef.current?.onEditorMove?.();
|
|
622
|
-
if (!other) {
|
|
623
|
-
move(pan(camera, {
|
|
624
|
-
x: next.x - previous.x,
|
|
625
|
-
y: next.y - previous.y
|
|
626
|
-
}, event.timeStamp, "pointer"), true);
|
|
627
|
-
return;
|
|
628
|
-
}
|
|
629
|
-
const oldCenter = {
|
|
630
|
-
x: (previous.x + other.x) / 2,
|
|
631
|
-
y: (previous.y + other.y) / 2
|
|
632
|
-
};
|
|
633
|
-
const center = {
|
|
634
|
-
x: (next.x + other.x) / 2,
|
|
635
|
-
y: (next.y + other.y) / 2
|
|
636
|
-
};
|
|
637
|
-
const oldDistance = Math.hypot(previous.x - other.x, previous.y - other.y);
|
|
638
|
-
const distance = Math.hypot(next.x - other.x, next.y - other.y);
|
|
639
|
-
const zoomed = zoom(camera, oldCenter, distance / Math.max(1, oldDistance), event.timeStamp, {
|
|
640
|
-
x: center.x - oldCenter.x,
|
|
641
|
-
y: center.y - oldCenter.y
|
|
642
|
-
});
|
|
643
|
-
move(zoomed, true);
|
|
264
|
+
controller.pointerMove(event.pointerId, localPoint(event.clientX, event.clientY), event.timeStamp);
|
|
644
265
|
};
|
|
645
266
|
const onPointerEnd = (event) => {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
if (pointers.size < 2) endZoom();
|
|
649
|
-
if (pointers.size) return;
|
|
650
|
-
paintPanState();
|
|
267
|
+
controller.pointerUp(event.pointerId);
|
|
268
|
+
if (!controller.dragging) paintPanState(controller);
|
|
651
269
|
};
|
|
652
270
|
const ownsSpace = (target) => {
|
|
653
271
|
const doc = viewport.ownerDocument;
|
|
@@ -661,96 +279,37 @@ function useCanvasCamera(workspaceKey, initialTarget, row) {
|
|
|
661
279
|
event.preventDefault();
|
|
662
280
|
if (spaceHeld) return;
|
|
663
281
|
spaceHeld = true;
|
|
664
|
-
paintPanState();
|
|
282
|
+
paintPanState(controller);
|
|
665
283
|
};
|
|
666
284
|
const onSpaceUp = (event) => {
|
|
667
285
|
if (event.code !== "Space" || !spaceHeld) return;
|
|
668
286
|
event.preventDefault();
|
|
669
287
|
spaceHeld = false;
|
|
670
|
-
paintPanState();
|
|
288
|
+
paintPanState(controller);
|
|
671
289
|
};
|
|
672
290
|
const releaseSpace = () => {
|
|
673
291
|
spaceHeld = false;
|
|
674
|
-
paintPanState();
|
|
292
|
+
paintPanState(controller);
|
|
675
293
|
};
|
|
676
294
|
const onFocusIn = (event) => {
|
|
677
295
|
if (!isControl(event.target)) return;
|
|
678
296
|
const rect = event.target.getBoundingClientRect();
|
|
679
|
-
const
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
const dy = Math.max(0, bounds.top + 16 - rect.top) - Math.max(0, rect.bottom - bounds.bottom + 16);
|
|
688
|
-
if (!dx && !dy) return;
|
|
689
|
-
gesture = void 0;
|
|
690
|
-
move({
|
|
691
|
-
...camera,
|
|
692
|
-
x: camera.x + dx,
|
|
693
|
-
y: camera.y + dy
|
|
694
|
-
}, true);
|
|
297
|
+
const { x: left, y: top } = localPoint(rect.left, rect.top);
|
|
298
|
+
const { x: right, y: bottom } = localPoint(rect.right, rect.bottom);
|
|
299
|
+
controller.bringIntoView({
|
|
300
|
+
left,
|
|
301
|
+
top,
|
|
302
|
+
right,
|
|
303
|
+
bottom
|
|
304
|
+
});
|
|
695
305
|
};
|
|
696
306
|
const onKeyDown = (event) => {
|
|
697
307
|
if (isControl(event.target) || event.altKey || event.ctrlKey || event.metaKey) return;
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
"=",
|
|
701
|
-
"-",
|
|
702
|
-
"0",
|
|
703
|
-
"Home",
|
|
704
|
-
"ArrowLeft",
|
|
705
|
-
"ArrowRight",
|
|
706
|
-
"ArrowUp",
|
|
707
|
-
"ArrowDown"
|
|
708
|
-
].includes(event.key) || event.shiftKey && event.code === "Digit1") cancelCameraFlight();
|
|
709
|
-
const center = {
|
|
710
|
-
x: viewportSize.width / 2,
|
|
711
|
-
y: viewportSize.height / 2
|
|
712
|
-
};
|
|
713
|
-
const factor = event.key === "+" || event.key === "=" ? 1.25 : event.key === "-" ? .8 : event.key === "0" ? 1 / target.scale : void 0;
|
|
714
|
-
if (factor) {
|
|
715
|
-
gesture = void 0;
|
|
716
|
-
event.preventDefault();
|
|
717
|
-
rowRef.current?.onEditorMove?.();
|
|
718
|
-
move(zoom(target, center, factor, event.timeStamp));
|
|
719
|
-
return;
|
|
720
|
-
}
|
|
721
|
-
if (event.key === "Home" || event.shiftKey && event.code === "Digit1") {
|
|
722
|
-
endZoom();
|
|
723
|
-
gesture = void 0;
|
|
724
|
-
event.preventDefault();
|
|
725
|
-
rowRef.current?.onEditorMove?.();
|
|
726
|
-
move(canvasOverview(viewportSize, contentSize));
|
|
727
|
-
return;
|
|
728
|
-
}
|
|
729
|
-
const direction = {
|
|
730
|
-
ArrowLeft: {
|
|
731
|
-
x: 100,
|
|
732
|
-
y: 0
|
|
733
|
-
},
|
|
734
|
-
ArrowRight: {
|
|
735
|
-
x: -100,
|
|
736
|
-
y: 0
|
|
737
|
-
},
|
|
738
|
-
ArrowUp: {
|
|
739
|
-
x: 0,
|
|
740
|
-
y: 100
|
|
741
|
-
},
|
|
742
|
-
ArrowDown: {
|
|
743
|
-
x: 0,
|
|
744
|
-
y: -100
|
|
745
|
-
}
|
|
746
|
-
}[event.key];
|
|
747
|
-
if (!direction) return;
|
|
308
|
+
const command = keyCommand(event);
|
|
309
|
+
if (!command) return;
|
|
748
310
|
event.preventDefault();
|
|
749
|
-
|
|
750
|
-
endZoom();
|
|
751
|
-
move(pan(target, direction, event.timeStamp, "keyboard"));
|
|
311
|
+
controller.key(command, event.timeStamp);
|
|
752
312
|
};
|
|
753
|
-
paint();
|
|
754
313
|
viewport.addEventListener("wheel", onWheel, { passive: false });
|
|
755
314
|
viewport.addEventListener("pointerdown", onPointerDown);
|
|
756
315
|
viewport.addEventListener("pointermove", onPointerMove);
|
|
@@ -764,16 +323,11 @@ function useCanvasCamera(workspaceKey, initialTarget, row) {
|
|
|
764
323
|
ownerWindow.addEventListener("blur", releaseSpace);
|
|
765
324
|
return () => {
|
|
766
325
|
if (continuity?.canvas === continuityBinding) continuity.canvas = null;
|
|
767
|
-
|
|
326
|
+
controllerRef.current = null;
|
|
768
327
|
ownerWindow.removeEventListener("wheel", preventHistorySwipe, { capture: true });
|
|
769
328
|
if (overscrollX) rootStyle.setProperty("overscroll-behavior-x", overscrollX, overscrollPriority);
|
|
770
329
|
else rootStyle.removeProperty("overscroll-behavior-x");
|
|
771
|
-
|
|
772
|
-
cancelAnimationFrame(animation);
|
|
773
|
-
ownerWindow.clearTimeout(settle);
|
|
774
|
-
settle = 0;
|
|
775
|
-
ownerWindow.clearTimeout(zoomEnd);
|
|
776
|
-
setMoving(false);
|
|
330
|
+
controller.dispose();
|
|
777
331
|
resize.disconnect();
|
|
778
332
|
viewport.removeEventListener("wheel", onWheel);
|
|
779
333
|
viewport.removeEventListener("pointerdown", onPointerDown);
|