@agent-native/core 0.175.2-nightly-20260826150235 → 0.175.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/corpus/README.md +1 -1
- package/corpus/templates/clips/app/components/player/comments-panel.tsx +1 -1
- package/corpus/templates/clips/app/components/player/video-player.tsx +5 -3
- package/corpus/templates/clips/desktop/src/app.tsx +171 -51
- package/corpus/templates/clips/desktop/src/lib/pill-completion.ts +111 -0
- package/corpus/templates/clips/desktop/src/lib/pill-session.ts +34 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +181 -79
- package/corpus/templates/clips/desktop/src/lib/rewind-recording-start.ts +28 -4
- package/corpus/templates/clips/desktop/src/main.tsx +4 -4
- package/corpus/templates/clips/desktop/src/overlays/record-pill.tsx +1320 -0
- package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +1 -1
- package/corpus/templates/clips/desktop/src/styles.css +20 -308
- package/corpus/templates/clips/desktop/src/tailwind.css +85 -0
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +153 -70
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +15 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +25 -7
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +3 -3
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +375 -24
- package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +22 -30
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +5 -0
- package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +134 -14
- package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +233 -4
- package/corpus/templates/clips/desktop/src-tauri/src/util.rs +8 -5
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +1 -1
- package/corpus/templates/design/.generated/bridge/embedded-wheel.generated.ts +3 -2
- package/corpus/templates/design/.generated/bridge/zoom.generated.ts +8 -2
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +46 -10
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +89 -63
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +3 -1
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +8 -2
- package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +11 -2
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -4
- package/corpus/templates/design/app/components/design/multi-screen/zoom-gesture.ts +19 -63
- package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +2 -1
- package/corpus/templates/design/shared/canvas-math.ts +11 -1
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +44 -18
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +88 -23
- package/corpus/templates/slides/app/context/DeckContext.tsx +155 -86
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +7 -5
- package/corpus/templates/slides/changelog/2026-08-26-thumbnail-arrow-keys-now-navigate-slides-after-selection.md +6 -0
- package/dist/client/hooks/index.d.ts +1 -0
- package/dist/client/hooks/index.js +1 -0
- package/dist/client/use-pinch-zoom.js +17 -4
- package/dist/client/zoom-gesture.d.ts +74 -0
- package/dist/client/zoom-gesture.js +100 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
- package/dist/provider-api/actions/provider-api.d.ts +13 -13
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/builder-browser.d.ts +13 -13
- package/dist/server/builder-browser.js +46 -15
- package/dist/server/builder-oauth.d.ts +12 -0
- package/dist/server/builder-oauth.js +13 -2
- package/dist/server/core-routes-plugin.js +54 -19
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/vite/client.js +2 -0
- package/docs/content/template-chat-developers.mdx +100 -13
- package/docs/content/template-chat-first-edits.mdx +5 -5
- package/docs/content/template-chat.mdx +84 -36
- package/package.json +4 -3
- package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +0 -458
|
@@ -5,6 +5,13 @@ import {
|
|
|
5
5
|
} from "@agent-native/core/client/host";
|
|
6
6
|
import { useT } from "@agent-native/core/client/i18n";
|
|
7
7
|
import { type ReviewThread } from "@agent-native/core/client/review";
|
|
8
|
+
import {
|
|
9
|
+
clampZoomFactor,
|
|
10
|
+
normalizeWheelDeltaPx,
|
|
11
|
+
resolveZoomGestureDevice,
|
|
12
|
+
zoomFactorForWheelDelta,
|
|
13
|
+
type ZoomGestureDevice,
|
|
14
|
+
} from "@agent-native/core/client/zoom-gesture";
|
|
8
15
|
import type { ReviewComment } from "@agent-native/core/review";
|
|
9
16
|
import { injectDocumentMarkup } from "@agent-native/core/shared";
|
|
10
17
|
import { isLoopbackPreviewAllowed } from "@shared/builder-preview-url";
|
|
@@ -1239,6 +1246,7 @@ export function DesignCanvas({
|
|
|
1239
1246
|
// Queue them here until ready fires (or the iframe finishes loading, as a
|
|
1240
1247
|
// fallback for older/interact-mode documents that never inject the chrome
|
|
1241
1248
|
// bridge and thus never post ready) and flush in order.
|
|
1249
|
+
const pinchZoomDeviceRef = useRef<ZoomGestureDevice | null>(null);
|
|
1242
1250
|
const bridgeReadyRef = useRef(false);
|
|
1243
1251
|
const [readyIframeDocumentIdentity, setReadyIframeDocumentIdentity] =
|
|
1244
1252
|
useState<string | null>(null);
|
|
@@ -1517,6 +1525,19 @@ export function DesignCanvas({
|
|
|
1517
1525
|
.replace("__EDITING_SAFETY_ENABLED__", interactMode ? "false" : "true"),
|
|
1518
1526
|
[interactMode],
|
|
1519
1527
|
);
|
|
1528
|
+
// srcdoc is rebuilt per document, so unlike the keyed live-edit bundle above
|
|
1529
|
+
// it can carry the real first-paint value: forwarding that arrives only by
|
|
1530
|
+
// postMessage stays dead until the handshake, and is stranded by a swap.
|
|
1531
|
+
const embeddedGestureBridgeForSrcdoc = useMemo(
|
|
1532
|
+
() =>
|
|
1533
|
+
EMBEDDED_WHEEL_BRIDGE_SCRIPT.replace(
|
|
1534
|
+
"__EMBEDDED_WHEEL_FORWARDING_ENABLED__",
|
|
1535
|
+
isEmbeddedFrame && !interactMode ? "true" : "false",
|
|
1536
|
+
)
|
|
1537
|
+
.replace("__EMBEDDED_SPACE_KEY_FORWARDING_ENABLED__", "false")
|
|
1538
|
+
.replace("__EDITING_SAFETY_ENABLED__", interactMode ? "false" : "true"),
|
|
1539
|
+
[interactMode, isEmbeddedFrame],
|
|
1540
|
+
);
|
|
1520
1541
|
const includeLiveEditEditorChrome = !interactMode && !readOnly;
|
|
1521
1542
|
const liveEditBridgeScript = useMemo(
|
|
1522
1543
|
() =>
|
|
@@ -2420,7 +2441,7 @@ export function DesignCanvas({
|
|
|
2420
2441
|
ZOOM_BRIDGE_SCRIPT +
|
|
2421
2442
|
NAV_BRIDGE_SCRIPT +
|
|
2422
2443
|
LIGHTWEIGHT_HIT_TEST_BRIDGE_SCRIPT +
|
|
2423
|
-
|
|
2444
|
+
embeddedGestureBridgeForSrcdoc +
|
|
2424
2445
|
editorChromeBridge +
|
|
2425
2446
|
imageDiagBridge;
|
|
2426
2447
|
const frameContent = getEmbeddedFrameDocumentContent({
|
|
@@ -2468,7 +2489,7 @@ export function DesignCanvas({
|
|
|
2468
2489
|
interactMode,
|
|
2469
2490
|
isEmbeddedFrame,
|
|
2470
2491
|
embeddedFrameBackground,
|
|
2471
|
-
|
|
2492
|
+
embeddedGestureBridgeForSrcdoc,
|
|
2472
2493
|
iframeRenderContent,
|
|
2473
2494
|
transparentBackground,
|
|
2474
2495
|
]);
|
|
@@ -3147,6 +3168,10 @@ export function DesignCanvas({
|
|
|
3147
3168
|
return;
|
|
3148
3169
|
}
|
|
3149
3170
|
if (e.data.type === "pinch-zoom-wheel") {
|
|
3171
|
+
// An embedded frame's zoom belongs to the parent canvas, which already
|
|
3172
|
+
// gets the same gesture as embedded-canvas-wheel. Both bridges are
|
|
3173
|
+
// installed in every document, so without this the two apply twice.
|
|
3174
|
+
if (isEmbeddedFrame) return;
|
|
3150
3175
|
if (!onZoomChange) return;
|
|
3151
3176
|
const iframe = iframeRef.current;
|
|
3152
3177
|
const scroll = scrollContainerRef.current;
|
|
@@ -3158,15 +3183,26 @@ export function DesignCanvas({
|
|
|
3158
3183
|
// handler above applies to its own wheel deltas/coordinates).
|
|
3159
3184
|
const rawDeltaY = Number(e.data.deltaY);
|
|
3160
3185
|
if (!Number.isFinite(rawDeltaY)) return;
|
|
3161
|
-
//
|
|
3162
|
-
//
|
|
3163
|
-
//
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3186
|
+
// A synthetic WheelEvent cannot reliably reach usePinchZoom's listener,
|
|
3187
|
+
// so this path computes the factor itself — from the same module, never
|
|
3188
|
+
// re-derived by hand, or the two curves drift apart again.
|
|
3189
|
+
const forwardedMode = Number(e.data.deltaMode);
|
|
3190
|
+
const deltaMode = Number.isFinite(forwardedMode) ? forwardedMode : 0;
|
|
3191
|
+
pinchZoomDeviceRef.current = resolveZoomGestureDevice({
|
|
3192
|
+
deltaY: rawDeltaY,
|
|
3193
|
+
deltaMode,
|
|
3194
|
+
ctrlKey: Boolean(e.data.ctrlKey),
|
|
3195
|
+
metaKey: Boolean(e.data.metaKey),
|
|
3196
|
+
atMs: performance.now(),
|
|
3197
|
+
previous: pinchZoomDeviceRef.current,
|
|
3198
|
+
});
|
|
3167
3199
|
const currentZoom = zoomRef.current;
|
|
3168
|
-
const
|
|
3169
|
-
|
|
3200
|
+
const factor = clampZoomFactor(
|
|
3201
|
+
zoomFactorForWheelDelta(
|
|
3202
|
+
normalizeWheelDeltaPx(rawDeltaY, deltaMode),
|
|
3203
|
+
pinchZoomDeviceRef.current.pinch,
|
|
3204
|
+
),
|
|
3205
|
+
);
|
|
3170
3206
|
const nextZoom = Math.max(
|
|
3171
3207
|
DEFAULT_CANVAS_MIN_ZOOM,
|
|
3172
3208
|
Math.min(DEFAULT_CANVAS_MAX_ZOOM, currentZoom * factor),
|
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
} from "@agent-native/core/client/host";
|
|
5
5
|
import { useT } from "@agent-native/core/client/i18n";
|
|
6
6
|
import {
|
|
7
|
+
CANVAS_FIT_PADDING_PX,
|
|
7
8
|
DEFAULT_CANVAS_MAX_ZOOM,
|
|
9
|
+
DEFAULT_CANVAS_AUTOFIT_MIN_ZOOM,
|
|
8
10
|
DEFAULT_CANVAS_MIN_ZOOM,
|
|
9
11
|
DEFAULT_SNAP_THRESHOLD_SCREEN_PX,
|
|
10
12
|
canvasToScreenPoint,
|
|
@@ -178,7 +180,10 @@ const EMPTY_SCREEN_IDS: readonly string[] = [];
|
|
|
178
180
|
// redeclared locally and drifting from the shared constant.
|
|
179
181
|
const MIN_ZOOM = DEFAULT_CANVAS_MIN_ZOOM;
|
|
180
182
|
const MAX_ZOOM = DEFAULT_CANVAS_MAX_ZOOM;
|
|
181
|
-
const
|
|
183
|
+
const AUTOFIT_MIN_ZOOM = DEFAULT_CANVAS_AUTOFIT_MIN_ZOOM;
|
|
184
|
+
// Must match embedded-wheel.bridge.ts's payload bound, or every fast swipe
|
|
185
|
+
// arriving over a screen loses its tail to the tighter of the two clamps.
|
|
186
|
+
const MAX_WHEEL_PAN_DELTA = 240;
|
|
182
187
|
/** Live counter-scale for constant-size frame chrome, written on the world
|
|
183
188
|
* element every gesture frame by applyViewToDom. React supplies the same
|
|
184
189
|
* number as the var's fallback, so first paint and SSR are unaffected. */
|
|
@@ -379,9 +384,12 @@ import {
|
|
|
379
384
|
import type { AlignmentGuide, CanvasFrameEntry } from "./multi-screen/types";
|
|
380
385
|
import { vectorEditCanvasToLocalPoint } from "./multi-screen/vector-edit-geometry";
|
|
381
386
|
import {
|
|
382
|
-
|
|
387
|
+
accumulateZoomFactor,
|
|
388
|
+
clampZoomFactor,
|
|
389
|
+
normalizeWheelDeltaPx,
|
|
383
390
|
resolveExternalZoomAnchor,
|
|
384
|
-
|
|
391
|
+
resolveZoomGestureDevice,
|
|
392
|
+
type ZoomGestureDevice,
|
|
385
393
|
} from "./multi-screen/zoom-gesture";
|
|
386
394
|
|
|
387
395
|
/**
|
|
@@ -1003,7 +1011,18 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
1003
1011
|
* reply overwrites the selection the user actually made. */
|
|
1004
1012
|
const drillInRequestRef = useRef(0);
|
|
1005
1013
|
const feedbackTimerRef = useRef<number | null>(null);
|
|
1006
|
-
|
|
1014
|
+
// Two slots, not one: Cmd pressed or released mid-scroll turns a single
|
|
1015
|
+
// physical stream into interleaved zoom and pan ticks, and a shared slot
|
|
1016
|
+
// drops whichever kind the other overwrote.
|
|
1017
|
+
const pendingZoomGestureRef = useRef<Extract<
|
|
1018
|
+
PendingWheelGesture,
|
|
1019
|
+
{ mode: "zoom" }
|
|
1020
|
+
> | null>(null);
|
|
1021
|
+
const pendingPanGestureRef = useRef<Extract<
|
|
1022
|
+
PendingWheelGesture,
|
|
1023
|
+
{ mode: "pan" }
|
|
1024
|
+
> | null>(null);
|
|
1025
|
+
const zoomGestureDeviceRef = useRef<ZoomGestureDevice | null>(null);
|
|
1007
1026
|
const wheelGestureFrameRef = useRef<number | null>(null);
|
|
1008
1027
|
const worldRef = useRef<HTMLDivElement>(null);
|
|
1009
1028
|
const pixelGridRef = useRef<HTMLDivElement>(null);
|
|
@@ -1649,12 +1668,15 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
1649
1668
|
const boundsTop = bounds?.top ?? 0;
|
|
1650
1669
|
// Leave a Figma-like board gutter beside the last frame for quick drops/draws,
|
|
1651
1670
|
// and fit tall single frames so lower canvas interactions remain reachable.
|
|
1671
|
+
const minFitScale = AUTOFIT_MIN_ZOOM / 100;
|
|
1652
1672
|
const widthFitScale =
|
|
1653
1673
|
renderedScreens.length > 1 && totalWidth > 0
|
|
1654
|
-
? Math.max(
|
|
1674
|
+
? Math.max(minFitScale, (rect.width - 180) / totalWidth)
|
|
1655
1675
|
: scale;
|
|
1656
1676
|
const heightFitScale =
|
|
1657
|
-
totalHeight > 0
|
|
1677
|
+
totalHeight > 0
|
|
1678
|
+
? Math.max(minFitScale, (rect.height - 96) / totalHeight)
|
|
1679
|
+
: scale;
|
|
1658
1680
|
const nextScale = Math.min(scale, widthFitScale, heightFitScale);
|
|
1659
1681
|
if (nextScale < scale) {
|
|
1660
1682
|
const nextZoom = nextScale * 100;
|
|
@@ -1662,8 +1684,10 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
1662
1684
|
setCanvasZoom(nextZoom);
|
|
1663
1685
|
onZoomChange?.(nextZoom);
|
|
1664
1686
|
}
|
|
1665
|
-
|
|
1666
|
-
|
|
1687
|
+
// Genuinely centred, including when content overflows: flooring these
|
|
1688
|
+
// pins an oversized lineup against one edge and runs it off the other.
|
|
1689
|
+
const visualLeft = (rect.width - totalWidth * nextScale) / 2;
|
|
1690
|
+
const visualTop = (rect.height - totalHeight * nextScale) / 2;
|
|
1667
1691
|
const nextPan = {
|
|
1668
1692
|
x: visualLeft - (SURFACE_PADDING + boundsLeft) * nextScale,
|
|
1669
1693
|
y: visualTop - (SURFACE_PADDING + boundsTop) * nextScale,
|
|
@@ -7050,7 +7074,8 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
7050
7074
|
cameraCommand.fitBounds,
|
|
7051
7075
|
{ width: rect.width, height: rect.height },
|
|
7052
7076
|
{
|
|
7053
|
-
paddingScreenPx:
|
|
7077
|
+
paddingScreenPx:
|
|
7078
|
+
cameraCommand.paddingScreenPx ?? CANVAS_FIT_PADDING_PX,
|
|
7054
7079
|
// Frame geometry is canvas-space, while every frame DOM node lives
|
|
7055
7080
|
// inside the padded world. Include that shared origin so fitting a
|
|
7056
7081
|
// small drawn/preset frame actually centers its painted card.
|
|
@@ -7147,70 +7172,65 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
7147
7172
|
|
|
7148
7173
|
const flushPendingWheelGesture = useCallback(() => {
|
|
7149
7174
|
wheelGestureFrameRef.current = null;
|
|
7150
|
-
const
|
|
7151
|
-
|
|
7152
|
-
|
|
7175
|
+
const zoomGesture = pendingZoomGestureRef.current;
|
|
7176
|
+
const panGesture = pendingPanGestureRef.current;
|
|
7177
|
+
pendingZoomGestureRef.current = null;
|
|
7178
|
+
pendingPanGestureRef.current = null;
|
|
7179
|
+
|
|
7180
|
+
let nextPan = panRef.current;
|
|
7181
|
+
let moved = false;
|
|
7182
|
+
let settleChrome = false;
|
|
7153
7183
|
|
|
7154
|
-
if (
|
|
7184
|
+
if (zoomGesture) {
|
|
7155
7185
|
const currentZoom = zoomRef.current;
|
|
7156
7186
|
const nextZoom = clamp(
|
|
7157
|
-
currentZoom *
|
|
7187
|
+
currentZoom * clampZoomFactor(zoomGesture.factor),
|
|
7158
7188
|
MIN_ZOOM,
|
|
7159
7189
|
MAX_ZOOM,
|
|
7160
7190
|
);
|
|
7161
|
-
if (nextZoom
|
|
7191
|
+
if (nextZoom !== currentZoom) {
|
|
7192
|
+
nextPan = getPanForZoomToCursor({
|
|
7193
|
+
pan: nextPan,
|
|
7194
|
+
cursor: zoomGesture.cursor,
|
|
7195
|
+
oldZoom: currentZoom,
|
|
7196
|
+
nextZoom,
|
|
7197
|
+
});
|
|
7198
|
+
zoomRef.current = nextZoom;
|
|
7199
|
+
moved = true;
|
|
7200
|
+
settleChrome = true;
|
|
7201
|
+
}
|
|
7202
|
+
}
|
|
7162
7203
|
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
zoomRef.current = nextZoom;
|
|
7170
|
-
panRef.current = nextPan;
|
|
7171
|
-
markWheelGestureActive();
|
|
7172
|
-
applyViewToDom();
|
|
7173
|
-
scheduleViewCommit({ settleChrome: true });
|
|
7174
|
-
return;
|
|
7204
|
+
if (panGesture) {
|
|
7205
|
+
nextPan = {
|
|
7206
|
+
x: nextPan.x - panGesture.deltaX,
|
|
7207
|
+
y: nextPan.y - panGesture.deltaY,
|
|
7208
|
+
};
|
|
7209
|
+
moved = true;
|
|
7175
7210
|
}
|
|
7176
7211
|
|
|
7177
|
-
|
|
7178
|
-
x: panRef.current.x - gesture.deltaX,
|
|
7179
|
-
y: panRef.current.y - gesture.deltaY,
|
|
7180
|
-
};
|
|
7212
|
+
if (!moved) return;
|
|
7181
7213
|
panRef.current = nextPan;
|
|
7182
7214
|
markWheelGestureActive();
|
|
7183
7215
|
applyViewToDom();
|
|
7184
|
-
scheduleViewCommit();
|
|
7216
|
+
scheduleViewCommit(settleChrome ? { settleChrome: true } : undefined);
|
|
7185
7217
|
}, [applyViewToDom, markWheelGestureActive, scheduleViewCommit]);
|
|
7186
7218
|
|
|
7187
7219
|
const enqueueWheelGesture = useCallback(
|
|
7188
7220
|
(gesture: PendingWheelGesture) => {
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
pending?.mode === "zoom" &&
|
|
7195
|
-
gesture.mode === "zoom" &&
|
|
7196
|
-
pending.pinch === gesture.pinch
|
|
7197
|
-
) {
|
|
7198
|
-
pendingWheelGestureRef.current = {
|
|
7199
|
-
mode: "zoom",
|
|
7200
|
-
deltaY: pending.deltaY + gesture.deltaY,
|
|
7201
|
-
pinch: gesture.pinch,
|
|
7202
|
-
cursor: gesture.cursor,
|
|
7203
|
-
clientX: gesture.clientX,
|
|
7204
|
-
clientY: gesture.clientY,
|
|
7221
|
+
if (gesture.mode === "zoom") {
|
|
7222
|
+
const pending = pendingZoomGestureRef.current;
|
|
7223
|
+
pendingZoomGestureRef.current = {
|
|
7224
|
+
...gesture,
|
|
7225
|
+
factor: (pending?.factor ?? 1) * gesture.factor,
|
|
7205
7226
|
};
|
|
7206
|
-
} else
|
|
7207
|
-
|
|
7227
|
+
} else {
|
|
7228
|
+
const pending = pendingPanGestureRef.current;
|
|
7229
|
+
pendingPanGestureRef.current = {
|
|
7208
7230
|
mode: "pan",
|
|
7209
|
-
deltaX: pending
|
|
7210
|
-
deltaY: pending
|
|
7231
|
+
deltaX: (pending?.deltaX ?? 0) + gesture.deltaX,
|
|
7232
|
+
deltaY: (pending?.deltaY ?? 0) + gesture.deltaY,
|
|
7211
7233
|
};
|
|
7212
|
-
} else {
|
|
7213
|
-
pendingWheelGestureRef.current = gesture;
|
|
7214
7234
|
}
|
|
7215
7235
|
|
|
7216
7236
|
if (wheelGestureFrameRef.current !== null) return;
|
|
@@ -7244,13 +7264,18 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
7244
7264
|
// no forced style/layout read on the hot pan path.
|
|
7245
7265
|
const rect = surfaceRef.current?.getBoundingClientRect();
|
|
7246
7266
|
if (!rect) return;
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7267
|
+
const device = resolveZoomGestureDevice({
|
|
7268
|
+
deltaY: args.deltaY,
|
|
7269
|
+
deltaMode: args.deltaMode,
|
|
7270
|
+
ctrlKey: args.ctrlKey,
|
|
7271
|
+
metaKey: args.metaKey,
|
|
7272
|
+
atMs: performance.now(),
|
|
7273
|
+
previous: zoomGestureDeviceRef.current,
|
|
7274
|
+
});
|
|
7275
|
+
zoomGestureDeviceRef.current = device;
|
|
7250
7276
|
enqueueWheelGesture({
|
|
7251
7277
|
mode: "zoom",
|
|
7252
|
-
|
|
7253
|
-
pinch: args.deltaMode === 0 && isPinchZoomDelta(args.deltaY),
|
|
7278
|
+
factor: accumulateZoomFactor(1, delta.y, device.pinch),
|
|
7254
7279
|
cursor: {
|
|
7255
7280
|
x: args.clientX - rect.left,
|
|
7256
7281
|
y: args.clientY - rect.top,
|
|
@@ -7289,7 +7314,9 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
|
|
|
7289
7314
|
if (!event.isTrusted) return;
|
|
7290
7315
|
// Pan/zoom mid-drag invalidates the gesture's cached snap viewport.
|
|
7291
7316
|
invalidateSnapGesture();
|
|
7292
|
-
|
|
7317
|
+
// Chrome sends non-cancelable wheel events during a fling; cancelling one
|
|
7318
|
+
// is a no-op that logs an Intervention per event and still scrolls.
|
|
7319
|
+
if (event.cancelable) event.preventDefault();
|
|
7293
7320
|
event.stopPropagation();
|
|
7294
7321
|
enqueueWheelGestureFromClient({
|
|
7295
7322
|
deltaX: event.deltaX,
|
|
@@ -11586,10 +11613,9 @@ function getWheelDeltaFromValues(
|
|
|
11586
11613
|
deltaY: number,
|
|
11587
11614
|
deltaMode: number,
|
|
11588
11615
|
) {
|
|
11589
|
-
const multiplier = deltaMode === 1 ? 16 : deltaMode === 2 ? 800 : 1;
|
|
11590
11616
|
return {
|
|
11591
|
-
x: deltaX
|
|
11592
|
-
y: deltaY
|
|
11617
|
+
x: normalizeWheelDeltaPx(deltaX, deltaMode),
|
|
11618
|
+
y: normalizeWheelDeltaPx(deltaY, deltaMode),
|
|
11593
11619
|
};
|
|
11594
11620
|
}
|
|
11595
11621
|
|
|
@@ -5717,7 +5717,9 @@ declare var __INITIAL_SOURCE_HEAD__: string;
|
|
|
5717
5717
|
}
|
|
5718
5718
|
|
|
5719
5719
|
function stopNativeInteraction(e: Event): void {
|
|
5720
|
-
|
|
5720
|
+
// A fling's wheel events are not cancelable; cancelling one logs a browser
|
|
5721
|
+
// Intervention per event and scrolls anyway.
|
|
5722
|
+
if (e.cancelable) e.preventDefault();
|
|
5721
5723
|
e.stopPropagation();
|
|
5722
5724
|
if (e.stopImmediatePropagation) e.stopImmediatePropagation();
|
|
5723
5725
|
}
|
|
@@ -96,7 +96,9 @@ declare var __EDITING_SAFETY_ENABLED__: boolean;
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
function stopNativeInteraction(e: Event): void {
|
|
99
|
-
|
|
99
|
+
// A fling's wheel events are not cancelable; cancelling one logs a browser
|
|
100
|
+
// Intervention per event and scrolls anyway.
|
|
101
|
+
if (e.cancelable) e.preventDefault();
|
|
100
102
|
e.stopPropagation();
|
|
101
103
|
if (e.stopImmediatePropagation) e.stopImmediatePropagation();
|
|
102
104
|
}
|
|
@@ -120,7 +122,11 @@ declare var __EDITING_SAFETY_ENABLED__: boolean;
|
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
function onWheel(e: WheelEvent): void {
|
|
123
|
-
|
|
125
|
+
// Zoom forwards whatever the pan flag says: the flag only arrives by
|
|
126
|
+
// postMessage and a document swap strands it, while the zoom bridge cancels
|
|
127
|
+
// ctrl/meta wheel here regardless — so gating it makes the gesture vanish.
|
|
128
|
+
var zoomIntent = !!(e.ctrlKey || e.metaKey);
|
|
129
|
+
if (!wheelEnabled && !zoomIntent) return;
|
|
124
130
|
stopNativeInteraction(e);
|
|
125
131
|
postToParent({
|
|
126
132
|
type: "embedded-canvas-wheel",
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Protocol (iframe → parent):
|
|
10
10
|
*
|
|
11
|
-
* { type: 'pinch-zoom-wheel', deltaY, clientX, clientY
|
|
11
|
+
* { type: 'pinch-zoom-wheel', deltaY, deltaMode, clientX, clientY,
|
|
12
|
+
* ctrlKey, metaKey }
|
|
12
13
|
*
|
|
13
14
|
* Rules:
|
|
14
15
|
* • No import/require of any module (DOM globals only).
|
|
@@ -23,7 +24,9 @@
|
|
|
23
24
|
document.documentElement || document.body || document;
|
|
24
25
|
function onWheel(e: WheelEvent): void {
|
|
25
26
|
if (!(e.ctrlKey || e.metaKey)) return;
|
|
26
|
-
|
|
27
|
+
// A fling's wheel events are not cancelable; cancelling one logs a browser
|
|
28
|
+
// Intervention per event and scrolls anyway.
|
|
29
|
+
if (e.cancelable) e.preventDefault();
|
|
27
30
|
try {
|
|
28
31
|
(window.parent as Window).postMessage(
|
|
29
32
|
{
|
|
@@ -31,6 +34,12 @@
|
|
|
31
34
|
deltaY: e.deltaY,
|
|
32
35
|
clientX: e.clientX,
|
|
33
36
|
clientY: e.clientY,
|
|
37
|
+
// The parent classifies and scales from these three. Dropping the
|
|
38
|
+
// modifiers puts every pinch on the notch curve; dropping the mode
|
|
39
|
+
// makes a Firefox line tick read as 3px of travel.
|
|
40
|
+
deltaMode: e.deltaMode,
|
|
41
|
+
ctrlKey: !!e.ctrlKey,
|
|
42
|
+
metaKey: !!e.metaKey,
|
|
34
43
|
},
|
|
35
44
|
"*",
|
|
36
45
|
);
|
|
@@ -857,10 +857,9 @@ export type DragState =
|
|
|
857
857
|
export type PendingWheelGesture =
|
|
858
858
|
| {
|
|
859
859
|
mode: "zoom";
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
pinch: boolean;
|
|
860
|
+
/** Already-curved multiplier, not a raw delta: the two devices' curves
|
|
861
|
+
* differ, so only the factor is safe to accumulate across events. */
|
|
862
|
+
factor: number;
|
|
864
863
|
cursor: Point;
|
|
865
864
|
clientX: number;
|
|
866
865
|
clientY: number;
|
|
@@ -1,69 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Wheel/pinch
|
|
2
|
+
* Wheel/pinch zoom for the overview canvas.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* far higher rate. Feeding both through one `exp(-deltaY * 0.01)` meant a
|
|
8
|
-
* single mouse notch multiplied zoom by e¹ ≈ 2.72× — which is what made canvas
|
|
9
|
-
* zoom feel uncontrollable next to Figma's ~1.1× per notch.
|
|
10
|
-
*
|
|
11
|
-
* The clamp applies to the resulting FACTOR, not to the accumulated delta.
|
|
12
|
-
* Clamping the delta made the step frame-rate dependent: deltas accumulate
|
|
13
|
-
* between animation frames, so a dropped frame silently doubled the distance
|
|
14
|
-
* travelled. Clamping the factor means a dropped frame costs smoothness, never
|
|
15
|
-
* distance.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/** One mouse-wheel detent in `deltaMode === 0` pixels (Chrome/Safari). */
|
|
19
|
-
export const MOUSE_WHEEL_NOTCH_PX = 100;
|
|
20
|
-
/** Zoom multiplier for one full mouse-wheel notch, matching Figma's feel. */
|
|
21
|
-
export const ZOOM_STEP_PER_NOTCH = 1.1;
|
|
22
|
-
/** Multiplier exponent per pixel of trackpad pinch delta. */
|
|
23
|
-
export const PINCH_ZOOM_SENSITIVITY = 0.0075;
|
|
24
|
-
/** Ceiling on a single frame's zoom change, in either direction. */
|
|
25
|
-
export const MAX_ZOOM_FACTOR_PER_FRAME = 1.6;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Whether a ctrl/meta-modified wheel delta came from a trackpad pinch rather
|
|
29
|
-
* than a mouse wheel. Browsers report macOS pinch as a synthetic ctrl+wheel
|
|
30
|
-
* with small, frequently fractional pixel deltas, while a real wheel detent is
|
|
31
|
-
* a whole number near the notch size. Only meaningful for `deltaMode === 0`;
|
|
32
|
-
* line/page modes are always discrete wheels and callers must not ask.
|
|
4
|
+
* The device split and both curves are shared, because a surface that
|
|
5
|
+
* re-implements either half reproduces the same mouse-reads-as-pinch bug.
|
|
6
|
+
* Only the anchor below belongs to this surface.
|
|
33
7
|
*/
|
|
34
|
-
export
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
? Math.exp(-deltaY * PINCH_ZOOM_SENSITIVITY)
|
|
50
|
-
: Math.pow(ZOOM_STEP_PER_NOTCH, -deltaY / MOUSE_WHEEL_NOTCH_PX);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Bounds one frame's zoom change symmetrically in multiplicative space, so
|
|
54
|
-
* zooming in and back out by the same clamped amount is a round trip. */
|
|
55
|
-
export function clampZoomFactor(factor: number): number {
|
|
56
|
-
if (!Number.isFinite(factor) || factor <= 0) return 1;
|
|
57
|
-
return Math.min(
|
|
58
|
-
MAX_ZOOM_FACTOR_PER_FRAME,
|
|
59
|
-
Math.max(1 / MAX_ZOOM_FACTOR_PER_FRAME, factor),
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** The zoom multiplier to apply for one flushed gesture frame. */
|
|
64
|
-
export function resolveZoomFactor(deltaY: number, pinch: boolean): number {
|
|
65
|
-
return clampZoomFactor(zoomFactorForWheelDelta(deltaY, pinch));
|
|
66
|
-
}
|
|
8
|
+
export {
|
|
9
|
+
accumulateZoomFactor,
|
|
10
|
+
clampZoomFactor,
|
|
11
|
+
isPinchZoomDelta,
|
|
12
|
+
MAX_PINCH_DELTA_PX,
|
|
13
|
+
MAX_ZOOM_FACTOR_PER_FRAME,
|
|
14
|
+
MOUSE_WHEEL_NOTCH_PX,
|
|
15
|
+
normalizeWheelDeltaPx,
|
|
16
|
+
PINCH_ZOOM_SENSITIVITY,
|
|
17
|
+
resolveZoomGestureDevice,
|
|
18
|
+
ZOOM_GESTURE_IDLE_RESET_MS,
|
|
19
|
+
ZOOM_STEP_PER_NOTCH,
|
|
20
|
+
zoomFactorForWheelDelta,
|
|
21
|
+
type ZoomGestureDevice,
|
|
22
|
+
} from "@agent-native/core/client/zoom-gesture";
|
|
67
23
|
|
|
68
24
|
/** Anchor for an externally driven zoom change. Only hold the frame centre when
|
|
69
25
|
* it is on screen: a frame taller than the viewport has its centre off-screen,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CanvasFrameGeometryById } from "@shared/canvas-frames";
|
|
2
2
|
import {
|
|
3
|
+
CANVAS_FIT_PADDING_PX,
|
|
3
4
|
DEFAULT_CANVAS_MAX_ZOOM,
|
|
4
5
|
DEFAULT_CANVAS_MIN_ZOOM,
|
|
5
6
|
getCameraForBounds,
|
|
@@ -169,7 +170,7 @@ export function computeFitCameraForFrames(
|
|
|
169
170
|
const bounds = getFrameGroupBounds(frames);
|
|
170
171
|
if (!bounds) return null;
|
|
171
172
|
return getCameraForBounds(bounds, viewport, {
|
|
172
|
-
paddingScreenPx: options?.paddingScreenPx ??
|
|
173
|
+
paddingScreenPx: options?.paddingScreenPx ?? CANVAS_FIT_PADDING_PX,
|
|
173
174
|
minZoom: DEFAULT_CANVAS_MIN_ZOOM,
|
|
174
175
|
maxZoom: DEFAULT_CANVAS_MAX_ZOOM,
|
|
175
176
|
fallbackZoom: 100,
|
|
@@ -282,6 +282,16 @@ export const DEFAULT_ASSIGNED_REGION_MAX_COLUMNS = 3;
|
|
|
282
282
|
export const DEFAULT_CANVAS_MIN_ZOOM = 2;
|
|
283
283
|
export const DEFAULT_CANVAS_MAX_ZOOM = 25600;
|
|
284
284
|
|
|
285
|
+
/**
|
|
286
|
+
* Floor for the AUTOMATIC shrink-to-fit that runs on a screen-count change —
|
|
287
|
+
* never for an explicit Zoom to fit, which must be free to reach MIN_ZOOM or a
|
|
288
|
+
* board of more than ~9 screens in a row stops fitting.
|
|
289
|
+
*/
|
|
290
|
+
export const DEFAULT_CANVAS_AUTOFIT_MIN_ZOOM = 10;
|
|
291
|
+
|
|
292
|
+
/** Breathing room a fit leaves around the fitted bounds, in screen px. */
|
|
293
|
+
export const CANVAS_FIT_PADDING_PX = 64;
|
|
294
|
+
|
|
285
295
|
export function screenToCanvasPoint(
|
|
286
296
|
point: CanvasPoint,
|
|
287
297
|
camera: CanvasCamera,
|
|
@@ -511,7 +521,7 @@ export function getCameraForBounds(
|
|
|
511
521
|
bounds: FrameBounds | FrameGeometry | null,
|
|
512
522
|
viewport: CanvasSize,
|
|
513
523
|
{
|
|
514
|
-
paddingScreenPx =
|
|
524
|
+
paddingScreenPx = CANVAS_FIT_PADDING_PX,
|
|
515
525
|
canvasPadding = 0,
|
|
516
526
|
minZoom = 10,
|
|
517
527
|
maxZoom = 400,
|