camox 0.76.0 → 0.76.1
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/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/ThreadMessages.js +5 -9
- package/dist/features/studio/showInCanvas.js +9 -19
- package/dist/features/studio/useEditorSelection.js +8 -19
- package/dist/package.js +1 -1
- package/dist/studio.css +1 -1
- package/package.json +4 -4
- package/dist/core/hooks/useFieldSelection.js +0 -67
- package/dist/core/hooks/useIsEditable.js +0 -21
- package/dist/core/hooks/useOverlayMessage.js +0 -70
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import { revealScrollDelta } from "../preview/selectionReveal.js";
|
|
2
|
+
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";
|
|
3
|
+
import { createCanvasFlight, createCanvasPan } from "./canvasFlight.js";
|
|
4
|
+
|
|
5
|
+
//#region src/features/canvas/canvasCameraController.ts
|
|
6
|
+
const GESTURE_IDLE_MS = 180;
|
|
7
|
+
/** A zoom ends once its input pauses this long; a stretch past a limit then springs back. */
|
|
8
|
+
const ZOOM_END_MS = 120;
|
|
9
|
+
/** The camera counts as moving until it has been still this long. */
|
|
10
|
+
const CAMERA_SETTLE_MS = 150;
|
|
11
|
+
const NO_INSETS = {
|
|
12
|
+
top: 0,
|
|
13
|
+
right: 0,
|
|
14
|
+
bottom: 0,
|
|
15
|
+
left: 0
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The camera's y at a new scale, keeping the document point at the viewport's
|
|
19
|
+
* top edge. Above the page top, the unscaled chrome keeps its screen position.
|
|
20
|
+
*/
|
|
21
|
+
function rescaleY(camera, scale) {
|
|
22
|
+
return camera.y > 0 ? camera.y : camera.y * scale / camera.scale;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The Canvas camera's policy: input, geometry and time in, camera and Selection changes out.
|
|
26
|
+
* It opens on the saved view if it still holds the initial page, else fitted to that page,
|
|
27
|
+
* else on the overview, and paints that first camera at once.
|
|
28
|
+
*/
|
|
29
|
+
function createCanvasCameraController(start, host) {
|
|
30
|
+
const { clock } = host;
|
|
31
|
+
let row = start.row;
|
|
32
|
+
let insets = row?.insets ?? NO_INSETS;
|
|
33
|
+
const page = start.initialPage;
|
|
34
|
+
const targets = () => row?.targets ?? (page ? [{
|
|
35
|
+
...page,
|
|
36
|
+
key: "initial"
|
|
37
|
+
}] : []);
|
|
38
|
+
const measure = () => {
|
|
39
|
+
const measured = host.measure();
|
|
40
|
+
insets = row?.insets ?? NO_INSETS;
|
|
41
|
+
const widths = targets().map((target) => target.width);
|
|
42
|
+
return {
|
|
43
|
+
viewport: {
|
|
44
|
+
width: Math.max(0, measured.viewport.width - insets.left - insets.right),
|
|
45
|
+
height: Math.max(0, measured.viewport.height - insets.top - insets.bottom)
|
|
46
|
+
},
|
|
47
|
+
content: {
|
|
48
|
+
width: row?.width ?? measured.content.width,
|
|
49
|
+
height: measured.content.height,
|
|
50
|
+
pageWidth: widths.length ? Math.min(...widths) : void 0
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
const measured = measure();
|
|
55
|
+
let viewportSize = measured.viewport;
|
|
56
|
+
let contentSize = measured.content;
|
|
57
|
+
let minimumScale = canvasMinimumScale(viewportSize, contentSize);
|
|
58
|
+
const updateGeometry = (next = measure()) => {
|
|
59
|
+
viewportSize = next.viewport;
|
|
60
|
+
contentSize = next.content;
|
|
61
|
+
minimumScale = canvasMinimumScale(viewportSize, contentSize);
|
|
62
|
+
};
|
|
63
|
+
const targetOf = (key) => key == null ? void 0 : targets().find((candidate) => candidate.key === key);
|
|
64
|
+
const toArea = (point) => ({
|
|
65
|
+
x: point.x - insets.left,
|
|
66
|
+
y: point.y - insets.top
|
|
67
|
+
});
|
|
68
|
+
let stretch;
|
|
69
|
+
let cancelZoomEnd;
|
|
70
|
+
const clearZoomEnd = () => {
|
|
71
|
+
cancelZoomEnd?.();
|
|
72
|
+
cancelZoomEnd = void 0;
|
|
73
|
+
};
|
|
74
|
+
const constrain = (next, stretched = !!stretch) => constrainCanvasCamera(next, viewportSize, contentSize, canvasMaximumScale(viewportSize, targets(), minimumScale), stretched);
|
|
75
|
+
const savedCamera = start.saved && !start.saved.fitted ? start.saved.camera : void 0;
|
|
76
|
+
let restoredCamera = savedCamera && (!page || isCanvasCameraSnapped(savedCamera, viewportSize, page)) ? savedCamera : void 0;
|
|
77
|
+
let fittingInitialPage = !!page && !restoredCamera;
|
|
78
|
+
const initialCamera = () => fittingInitialPage && page ? constrain(fitCanvasPage(viewportSize, page)) : canvasOverview(viewportSize, contentSize);
|
|
79
|
+
let camera = restoredCamera ? constrain(restoredCamera) : initialCamera();
|
|
80
|
+
let target = camera;
|
|
81
|
+
let animation;
|
|
82
|
+
let previousTime = 0;
|
|
83
|
+
const stopAnimation = () => {
|
|
84
|
+
animation?.();
|
|
85
|
+
animation = void 0;
|
|
86
|
+
previousTime = 0;
|
|
87
|
+
};
|
|
88
|
+
let flight;
|
|
89
|
+
let gesture;
|
|
90
|
+
let requested = row?.selected ?? null;
|
|
91
|
+
let selection = requested ?? (page && targets().find((candidate) => candidate.left === page.left)?.key) ?? null;
|
|
92
|
+
const select = (key) => {
|
|
93
|
+
if (key === selection) return;
|
|
94
|
+
selection = key;
|
|
95
|
+
if (key) row?.onSelect(key);
|
|
96
|
+
else row?.onDeselect?.();
|
|
97
|
+
};
|
|
98
|
+
const rest = (next) => {
|
|
99
|
+
if (!stretch) return next;
|
|
100
|
+
const maximumScale = canvasMaximumScale(viewportSize, targets(), minimumScale);
|
|
101
|
+
const scale = Math.min(maximumScale, Math.max(minimumScale, next.scale));
|
|
102
|
+
const settled = zoomCanvasAt(next, stretch.point, scale, minimumScale, maximumScale);
|
|
103
|
+
const held = targetOf(selection);
|
|
104
|
+
const fit = held && fitCanvasPage(viewportSize, held);
|
|
105
|
+
return constrain(fit && isCanvasPageScale(scale, fit.scale) ? {
|
|
106
|
+
...settled,
|
|
107
|
+
x: fit.x
|
|
108
|
+
} : settled, false);
|
|
109
|
+
};
|
|
110
|
+
const releaseSelection = () => {
|
|
111
|
+
const held = targetOf(selection);
|
|
112
|
+
if (selection && !(held && isCanvasCameraSnapped(rest(target), viewportSize, held))) select(null);
|
|
113
|
+
};
|
|
114
|
+
const pointers = /* @__PURE__ */ new Map();
|
|
115
|
+
const zoom = (base, point, factor, time, translation = {
|
|
116
|
+
x: 0,
|
|
117
|
+
y: 0
|
|
118
|
+
}) => {
|
|
119
|
+
const scale = base.scale;
|
|
120
|
+
const maximumScale = canvasMaximumScale(viewportSize, targets(), minimumScale);
|
|
121
|
+
const intendedScale = (gesture?.kind === "zoom" && time - gesture.time < GESTURE_IDLE_MS ? gesture.intendedScale : unstretchCanvasZoom(scale, minimumScale, maximumScale)) * factor;
|
|
122
|
+
gesture = {
|
|
123
|
+
kind: "zoom",
|
|
124
|
+
time,
|
|
125
|
+
intendedScale
|
|
126
|
+
};
|
|
127
|
+
const limited = Math.min(maximumScale, Math.max(minimumScale, intendedScale));
|
|
128
|
+
const eligible = targets().filter((candidate) => findCanvasSnapTarget(base, viewportSize, [candidate]));
|
|
129
|
+
const candidate = stretch && targetOf(selection) || findCanvasSnapTarget(camera, viewportSize, eligible);
|
|
130
|
+
const fit = candidate && fitCanvasPage(viewportSize, candidate);
|
|
131
|
+
const snapped = snapCanvasZoom(scale, limited, fit && fit.scale >= minimumScale ? fit.scale : void 0);
|
|
132
|
+
const stretched = limited !== intendedScale;
|
|
133
|
+
const nextScale = stretched ? stretchCanvasZoom(intendedScale, minimumScale, maximumScale) : snapped;
|
|
134
|
+
clearZoomEnd();
|
|
135
|
+
if (!pointers.size) cancelZoomEnd = clock.after(ZOOM_END_MS, endZoom);
|
|
136
|
+
stretch = stretched ? { point } : void 0;
|
|
137
|
+
const anchored = zoomCanvasAt(base, point, nextScale, minimumScale / Math.exp(ZOOM_STRETCH), maximumScale * Math.exp(ZOOM_STRETCH));
|
|
138
|
+
const next = {
|
|
139
|
+
...anchored,
|
|
140
|
+
x: anchored.x + translation.x,
|
|
141
|
+
y: anchored.y + translation.y
|
|
142
|
+
};
|
|
143
|
+
if (!candidate || !fit || !isCanvasPageScale(snapped, fit.scale)) return next;
|
|
144
|
+
select(candidate.key);
|
|
145
|
+
return {
|
|
146
|
+
...next,
|
|
147
|
+
x: (viewportSize.width - candidate.width * nextScale) / 2 - candidate.left * nextScale
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
const pan = (base, delta, time, source, rail = {
|
|
151
|
+
mode: "free",
|
|
152
|
+
samples: []
|
|
153
|
+
}) => {
|
|
154
|
+
const start = gesture?.kind === "pan" && gesture.source === source && time - gesture.time < GESTURE_IDLE_MS ? gesture.intendedX : base.x;
|
|
155
|
+
const intended = constrain({
|
|
156
|
+
...base,
|
|
157
|
+
x: start + delta.x,
|
|
158
|
+
y: base.y + delta.y
|
|
159
|
+
});
|
|
160
|
+
gesture = {
|
|
161
|
+
kind: "pan",
|
|
162
|
+
time,
|
|
163
|
+
rail,
|
|
164
|
+
intendedX: intended.x,
|
|
165
|
+
source
|
|
166
|
+
};
|
|
167
|
+
if (!delta.x) return {
|
|
168
|
+
...intended,
|
|
169
|
+
x: base.x
|
|
170
|
+
};
|
|
171
|
+
const candidate = findCanvasSnapTarget(intended, viewportSize, targets());
|
|
172
|
+
const fit = candidate && fitCanvasPage(viewportSize, candidate);
|
|
173
|
+
if (!candidate || !fit || fit.scale < minimumScale) return intended;
|
|
174
|
+
const held = isCanvasCameraSnapped(base, viewportSize, candidate);
|
|
175
|
+
if (!isInCanvasSnapWindow(intended, viewportSize, candidate, held)) return intended;
|
|
176
|
+
select(candidate.key);
|
|
177
|
+
gesture.intendedX += (candidate.left + candidate.width / 2) * (intended.scale - fit.scale);
|
|
178
|
+
return constrain({
|
|
179
|
+
x: fit.x,
|
|
180
|
+
y: rescaleY(intended, fit.scale),
|
|
181
|
+
scale: fit.scale
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
function endZoom() {
|
|
185
|
+
clearZoomEnd();
|
|
186
|
+
if (stretch) {
|
|
187
|
+
const next = rest(target);
|
|
188
|
+
stretch = void 0;
|
|
189
|
+
gesture = void 0;
|
|
190
|
+
markMoving();
|
|
191
|
+
applyCamera(next);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const isOverview = (view) => {
|
|
195
|
+
const overview = canvasOverview(viewportSize, contentSize);
|
|
196
|
+
return Math.abs(view.scale - overview.scale) < 1e-8 && Math.abs(view.x - overview.x) < .5 && Math.abs(view.y - overview.y) < .5;
|
|
197
|
+
};
|
|
198
|
+
const remember = () => {
|
|
199
|
+
if (restoredCamera || fittingInitialPage || !viewportSize.width || !viewportSize.height) return;
|
|
200
|
+
host.remember({
|
|
201
|
+
camera,
|
|
202
|
+
fitted: isOverview(camera)
|
|
203
|
+
});
|
|
204
|
+
};
|
|
205
|
+
let settle;
|
|
206
|
+
function markMoving() {
|
|
207
|
+
const wasMoving = !!settle;
|
|
208
|
+
settle?.();
|
|
209
|
+
settle = clock.after(CAMERA_SETTLE_MS, () => {
|
|
210
|
+
settle = void 0;
|
|
211
|
+
host.moving(controller);
|
|
212
|
+
});
|
|
213
|
+
if (!wasMoving) host.moving(controller);
|
|
214
|
+
}
|
|
215
|
+
const paint = () => {
|
|
216
|
+
host.paint(controller);
|
|
217
|
+
remember();
|
|
218
|
+
};
|
|
219
|
+
const animate = (time) => {
|
|
220
|
+
markMoving();
|
|
221
|
+
if (flight) {
|
|
222
|
+
flight.startedAt ??= time;
|
|
223
|
+
const progress = host.reducedMotion() ? 1 : Math.min(1, (time - flight.startedAt) / flight.path.duration);
|
|
224
|
+
camera = progress === 1 ? target : flight.path.at(progress);
|
|
225
|
+
paint();
|
|
226
|
+
if (progress < 1) {
|
|
227
|
+
animation = clock.frame(animate);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
flight = void 0;
|
|
231
|
+
animation = void 0;
|
|
232
|
+
previousTime = 0;
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const elapsed = previousTime ? Math.min(64, time - previousTime) : 16;
|
|
236
|
+
previousTime = time;
|
|
237
|
+
const amount = host.reducedMotion() ? 1 : 1 - Math.exp(-elapsed / 55);
|
|
238
|
+
const approach = (from, to, epsilon) => {
|
|
239
|
+
const next = from + (to - from) * amount;
|
|
240
|
+
return Math.abs(next - to) < epsilon ? to : next;
|
|
241
|
+
};
|
|
242
|
+
camera = constrain({
|
|
243
|
+
x: approach(camera.x, target.x, .05),
|
|
244
|
+
y: approach(camera.y, target.y, .05),
|
|
245
|
+
scale: approach(camera.scale, target.scale, 1e-4)
|
|
246
|
+
}, true);
|
|
247
|
+
const settled = Math.abs(camera.x - target.x) < .05 && Math.abs(camera.y - target.y) < .05 && Math.abs(camera.scale - target.scale) < 1e-4;
|
|
248
|
+
if (settled) camera = target;
|
|
249
|
+
paint();
|
|
250
|
+
animation = settled ? void 0 : clock.frame(animate);
|
|
251
|
+
if (settled) previousTime = 0;
|
|
252
|
+
};
|
|
253
|
+
function applyCamera(next, immediate = false) {
|
|
254
|
+
target = constrain(next);
|
|
255
|
+
if (immediate) {
|
|
256
|
+
stopAnimation();
|
|
257
|
+
camera = target;
|
|
258
|
+
paint();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (!animation) animation = clock.frame(animate);
|
|
262
|
+
}
|
|
263
|
+
const cancelCameraFlight = () => {
|
|
264
|
+
if (!flight) return;
|
|
265
|
+
flight = void 0;
|
|
266
|
+
stopAnimation();
|
|
267
|
+
target = camera;
|
|
268
|
+
gesture = void 0;
|
|
269
|
+
};
|
|
270
|
+
const startFlight = (key) => {
|
|
271
|
+
cancelCameraFlight();
|
|
272
|
+
const destination = targetOf(key);
|
|
273
|
+
if (!destination) return;
|
|
274
|
+
flyToCamera(() => fitCanvasPage(viewportSize, destination), destination);
|
|
275
|
+
};
|
|
276
|
+
const startOverviewFlight = () => {
|
|
277
|
+
cancelCameraFlight();
|
|
278
|
+
flyToCamera(() => canvasOverview(viewportSize, contentSize));
|
|
279
|
+
};
|
|
280
|
+
const startPan = (destination) => {
|
|
281
|
+
cancelCameraFlight();
|
|
282
|
+
flyToCamera(() => destination, void 0, true);
|
|
283
|
+
};
|
|
284
|
+
const flyToCamera = (destinationCamera, page, pan = false) => {
|
|
285
|
+
stopAnimation();
|
|
286
|
+
restoredCamera = void 0;
|
|
287
|
+
fittingInitialPage = false;
|
|
288
|
+
gesture = void 0;
|
|
289
|
+
clearZoomEnd();
|
|
290
|
+
stretch = void 0;
|
|
291
|
+
updateGeometry();
|
|
292
|
+
camera = constrain(camera, true);
|
|
293
|
+
target = constrain(destinationCamera());
|
|
294
|
+
const path = pan ? createCanvasPan(camera, target, viewportSize) : createCanvasFlight(camera, target, viewportSize, contentSize);
|
|
295
|
+
if (host.reducedMotion() || path.duration === 0) {
|
|
296
|
+
applyCamera(target, true);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
flight = {
|
|
300
|
+
page: page && { ...page },
|
|
301
|
+
pan,
|
|
302
|
+
path
|
|
303
|
+
};
|
|
304
|
+
animation = clock.frame(animate);
|
|
305
|
+
};
|
|
306
|
+
const move = (next, immediate = false) => {
|
|
307
|
+
markMoving();
|
|
308
|
+
cancelCameraFlight();
|
|
309
|
+
restoredCamera = void 0;
|
|
310
|
+
fittingInitialPage = false;
|
|
311
|
+
applyCamera(next, immediate);
|
|
312
|
+
releaseSelection();
|
|
313
|
+
};
|
|
314
|
+
let syncedTargets = targets();
|
|
315
|
+
const syncRow = () => {
|
|
316
|
+
const key = row?.selected ?? null;
|
|
317
|
+
const destination = targetOf(key);
|
|
318
|
+
const previousTargets = syncedTargets;
|
|
319
|
+
syncedTargets = targets();
|
|
320
|
+
if (key !== requested) {
|
|
321
|
+
requested = key;
|
|
322
|
+
selection = key;
|
|
323
|
+
if (destination && !isCanvasCameraSnapped(rest(target), viewportSize, destination)) startFlight(destination.key);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (flight?.page) {
|
|
327
|
+
const page = targetOf(flight.page.key);
|
|
328
|
+
if (!page) cancelCameraFlight();
|
|
329
|
+
else if (page.left !== flight.page.left || page.width !== flight.page.width) startFlight(page.key);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const previous = previousTargets[syncedTargets.findIndex((candidate) => candidate.key === key)];
|
|
333
|
+
if (destination && previous?.key === key && previous.left !== destination.left && previous.width === destination.width && selection === key && !flight && isCanvasCameraSnapped(rest(target), viewportSize, previous)) {
|
|
334
|
+
updateGeometry();
|
|
335
|
+
const fit = fitCanvasPage(viewportSize, destination);
|
|
336
|
+
const next = {
|
|
337
|
+
...fit,
|
|
338
|
+
y: rescaleY(restoredCamera ?? target, fit.scale)
|
|
339
|
+
};
|
|
340
|
+
if (restoredCamera || fittingInitialPage) restoredCamera = next;
|
|
341
|
+
fittingInitialPage = false;
|
|
342
|
+
camera = constrain(camera, true);
|
|
343
|
+
applyCamera(next, true);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (destination && selection === key && !flight && !fittingInitialPage && !isCanvasCameraSnapped(rest(target), viewportSize, destination)) startFlight(destination.key);
|
|
347
|
+
};
|
|
348
|
+
const resize = () => {
|
|
349
|
+
const previousInsets = insets;
|
|
350
|
+
const next = measure();
|
|
351
|
+
if (insets.left === previousInsets.left && insets.top === previousInsets.top && next.viewport.width === viewportSize.width && next.viewport.height === viewportSize.height && next.content.width === contentSize.width && next.content.height === contentSize.height && next.content.pageWidth === contentSize.pageWidth) return;
|
|
352
|
+
const wasFitted = isOverview(target);
|
|
353
|
+
gesture = void 0;
|
|
354
|
+
const held = next.viewport.width !== viewportSize.width && !flight && !fittingInitialPage ? targetOf(selection) : void 0;
|
|
355
|
+
updateGeometry(next);
|
|
356
|
+
if (held) {
|
|
357
|
+
const fit = fitCanvasPage(viewportSize, held);
|
|
358
|
+
const next = {
|
|
359
|
+
...fit,
|
|
360
|
+
y: rescaleY(restoredCamera ?? target, fit.scale)
|
|
361
|
+
};
|
|
362
|
+
if (restoredCamera) restoredCamera = next;
|
|
363
|
+
camera = constrain(camera, true);
|
|
364
|
+
applyCamera(next, true);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (flight) {
|
|
368
|
+
camera = constrain(camera, true);
|
|
369
|
+
if (flight.page) startFlight(flight.page.key);
|
|
370
|
+
else if (flight.pan) startPan(target);
|
|
371
|
+
else startOverviewFlight();
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (restoredCamera) {
|
|
375
|
+
applyCamera(restoredCamera, true);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (fittingInitialPage) {
|
|
379
|
+
applyCamera(initialCamera(), true);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
camera = constrain(camera, true);
|
|
383
|
+
applyCamera(wasFitted ? canvasOverview(viewportSize, contentSize) : target, true);
|
|
384
|
+
};
|
|
385
|
+
const wheel = (input) => {
|
|
386
|
+
const dx = canvasWheelDelta(input.deltaX, input.deltaMode, viewportSize.width);
|
|
387
|
+
const dy = canvasWheelDelta(input.deltaY, input.deltaMode, viewportSize.height);
|
|
388
|
+
if (!dx && !dy) return;
|
|
389
|
+
row?.onEditorMove?.();
|
|
390
|
+
cancelCameraFlight();
|
|
391
|
+
if (input.zoom) {
|
|
392
|
+
move(zoom(target, toArea(input.point), Math.exp(-dy * CANVAS_WHEEL_ZOOM_RATE), input.time));
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
endZoom();
|
|
396
|
+
const previous = gesture?.kind === "pan" && gesture.source === "wheel" && input.time - gesture.time < GESTURE_IDLE_MS ? gesture.rail : void 0;
|
|
397
|
+
const rail = !input.shift && !pointers.size && targets().length > 0 && isCanvasPageScale(target.scale, canvasMaximumScale(viewportSize, targets(), minimumScale)) ? canvasVerticalRail({
|
|
398
|
+
x: dx,
|
|
399
|
+
y: dy
|
|
400
|
+
}, input.time, previous) : {
|
|
401
|
+
mode: "free",
|
|
402
|
+
samples: []
|
|
403
|
+
};
|
|
404
|
+
const vertical = rail.mode === "vertical";
|
|
405
|
+
move(pan(target, {
|
|
406
|
+
x: -(vertical ? 0 : input.shift && dx === 0 ? dy : dx),
|
|
407
|
+
y: -(input.shift && dx === 0 ? 0 : dy)
|
|
408
|
+
}, input.time, "wheel", rail));
|
|
409
|
+
};
|
|
410
|
+
const pointerDown = (id, point) => {
|
|
411
|
+
gesture = void 0;
|
|
412
|
+
move(camera, true);
|
|
413
|
+
pointers.set(id, toArea(point));
|
|
414
|
+
};
|
|
415
|
+
const pointerMove = (id, point, time) => {
|
|
416
|
+
const previous = pointers.get(id);
|
|
417
|
+
if (!previous) return;
|
|
418
|
+
const next = toArea(point);
|
|
419
|
+
const other = [...pointers.entries()].find(([candidate]) => candidate !== id)?.[1];
|
|
420
|
+
pointers.set(id, next);
|
|
421
|
+
row?.onEditorMove?.();
|
|
422
|
+
if (!other) {
|
|
423
|
+
move(pan(camera, {
|
|
424
|
+
x: next.x - previous.x,
|
|
425
|
+
y: next.y - previous.y
|
|
426
|
+
}, time, "pointer"), true);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const oldCenter = {
|
|
430
|
+
x: (previous.x + other.x) / 2,
|
|
431
|
+
y: (previous.y + other.y) / 2
|
|
432
|
+
};
|
|
433
|
+
const center = {
|
|
434
|
+
x: (next.x + other.x) / 2,
|
|
435
|
+
y: (next.y + other.y) / 2
|
|
436
|
+
};
|
|
437
|
+
const oldDistance = Math.hypot(previous.x - other.x, previous.y - other.y);
|
|
438
|
+
const distance = Math.hypot(next.x - other.x, next.y - other.y);
|
|
439
|
+
const zoomed = zoom(camera, oldCenter, distance / Math.max(1, oldDistance), time, {
|
|
440
|
+
x: center.x - oldCenter.x,
|
|
441
|
+
y: center.y - oldCenter.y
|
|
442
|
+
});
|
|
443
|
+
move(zoomed, true);
|
|
444
|
+
};
|
|
445
|
+
const pointerUp = (id) => {
|
|
446
|
+
gesture = void 0;
|
|
447
|
+
pointers.delete(id);
|
|
448
|
+
if (pointers.size < 2) endZoom();
|
|
449
|
+
};
|
|
450
|
+
const directions = {
|
|
451
|
+
panLeft: {
|
|
452
|
+
x: 100,
|
|
453
|
+
y: 0
|
|
454
|
+
},
|
|
455
|
+
panRight: {
|
|
456
|
+
x: -100,
|
|
457
|
+
y: 0
|
|
458
|
+
},
|
|
459
|
+
panUp: {
|
|
460
|
+
x: 0,
|
|
461
|
+
y: 100
|
|
462
|
+
},
|
|
463
|
+
panDown: {
|
|
464
|
+
x: 0,
|
|
465
|
+
y: -100
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
const key = (key, time) => {
|
|
469
|
+
cancelCameraFlight();
|
|
470
|
+
const center = {
|
|
471
|
+
x: viewportSize.width / 2,
|
|
472
|
+
y: viewportSize.height / 2
|
|
473
|
+
};
|
|
474
|
+
const factor = key === "zoomIn" ? 1.25 : key === "zoomOut" ? .8 : key === "zoomToActualSize" ? 1 / target.scale : void 0;
|
|
475
|
+
if (factor) {
|
|
476
|
+
gesture = void 0;
|
|
477
|
+
row?.onEditorMove?.();
|
|
478
|
+
move(zoom(target, center, factor, time));
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (key === "overview") {
|
|
482
|
+
endZoom();
|
|
483
|
+
gesture = void 0;
|
|
484
|
+
row?.onEditorMove?.();
|
|
485
|
+
move(canvasOverview(viewportSize, contentSize));
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
const direction = directions[key];
|
|
489
|
+
if (!direction) return;
|
|
490
|
+
row?.onEditorMove?.();
|
|
491
|
+
endZoom();
|
|
492
|
+
move(pan(target, direction, time, "keyboard"));
|
|
493
|
+
};
|
|
494
|
+
const bringIntoView = (rect) => {
|
|
495
|
+
const bounds = {
|
|
496
|
+
left: insets.left,
|
|
497
|
+
top: insets.top,
|
|
498
|
+
right: insets.left + viewportSize.width,
|
|
499
|
+
bottom: insets.top + viewportSize.height
|
|
500
|
+
};
|
|
501
|
+
const dx = Math.max(0, bounds.left + 16 - rect.left) - Math.max(0, rect.right - bounds.right + 16);
|
|
502
|
+
const dy = Math.max(0, bounds.top + 16 - rect.top) - Math.max(0, rect.bottom - bounds.bottom + 16);
|
|
503
|
+
if (!dx && !dy) return;
|
|
504
|
+
gesture = void 0;
|
|
505
|
+
move({
|
|
506
|
+
...camera,
|
|
507
|
+
x: camera.x + dx,
|
|
508
|
+
y: camera.y + dy
|
|
509
|
+
}, true);
|
|
510
|
+
};
|
|
511
|
+
const reveal = (element, immediate) => {
|
|
512
|
+
if (flight && !flight.pan || pointers.size) return;
|
|
513
|
+
const worldTop = (element.frameTop - insets.top + element.top * camera.scale - camera.y) / camera.scale;
|
|
514
|
+
const delta = revealScrollDelta({
|
|
515
|
+
top: target.y + worldTop * target.scale,
|
|
516
|
+
bottom: target.y + (worldTop + element.height) * target.scale
|
|
517
|
+
}, {
|
|
518
|
+
top: 0,
|
|
519
|
+
bottom: viewportSize.height
|
|
520
|
+
});
|
|
521
|
+
if (!delta) return;
|
|
522
|
+
const next = {
|
|
523
|
+
...target,
|
|
524
|
+
y: target.y - delta
|
|
525
|
+
};
|
|
526
|
+
if (immediate) move(next, true);
|
|
527
|
+
else startPan(next);
|
|
528
|
+
};
|
|
529
|
+
const restore = (previous, place) => {
|
|
530
|
+
const destination = targetOf(selection);
|
|
531
|
+
if (!destination) return;
|
|
532
|
+
cancelCameraFlight();
|
|
533
|
+
updateGeometry();
|
|
534
|
+
const fit = fitCanvasPage(viewportSize, destination);
|
|
535
|
+
let next = previous ? {
|
|
536
|
+
...fit,
|
|
537
|
+
y: rescaleY(previous, fit.scale)
|
|
538
|
+
} : fit;
|
|
539
|
+
const placement = place(destination.key);
|
|
540
|
+
if (placement) next = placeCanvasPageAnchor(viewportSize, destination, placement.y, fit.scale, placement.frameOffsetY);
|
|
541
|
+
fittingInitialPage = false;
|
|
542
|
+
restoredCamera = next;
|
|
543
|
+
applyCamera(next, true);
|
|
544
|
+
};
|
|
545
|
+
const dispose = () => {
|
|
546
|
+
remember();
|
|
547
|
+
stopAnimation();
|
|
548
|
+
settle?.();
|
|
549
|
+
settle = void 0;
|
|
550
|
+
clearZoomEnd();
|
|
551
|
+
host.moving(controller);
|
|
552
|
+
};
|
|
553
|
+
const controller = {
|
|
554
|
+
get camera() {
|
|
555
|
+
return camera;
|
|
556
|
+
},
|
|
557
|
+
get insets() {
|
|
558
|
+
return insets;
|
|
559
|
+
},
|
|
560
|
+
get moving() {
|
|
561
|
+
return !!settle;
|
|
562
|
+
},
|
|
563
|
+
get dragging() {
|
|
564
|
+
return pointers.size > 0;
|
|
565
|
+
},
|
|
566
|
+
setRow: (next) => {
|
|
567
|
+
row = next;
|
|
568
|
+
syncRow();
|
|
569
|
+
},
|
|
570
|
+
resize,
|
|
571
|
+
wheel,
|
|
572
|
+
pointerDown,
|
|
573
|
+
pointerMove,
|
|
574
|
+
pointerUp,
|
|
575
|
+
key,
|
|
576
|
+
bringIntoView,
|
|
577
|
+
flyTo: startFlight,
|
|
578
|
+
fitAll: startOverviewFlight,
|
|
579
|
+
reveal,
|
|
580
|
+
capture: () => {
|
|
581
|
+
const held = flight ? void 0 : targetOf(selection);
|
|
582
|
+
return {
|
|
583
|
+
camera: { ...camera },
|
|
584
|
+
page: held?.key
|
|
585
|
+
};
|
|
586
|
+
},
|
|
587
|
+
restore,
|
|
588
|
+
dispose
|
|
589
|
+
};
|
|
590
|
+
paint();
|
|
591
|
+
return controller;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
//#endregion
|
|
595
|
+
export { createCanvasCameraController };
|