@@ -45,6 +45,7 @@ import { t } from "../i18n";
|
|||||||
import { getNormalizedZoom } from "../scene";
|
import { getNormalizedZoom } from "../scene";
|
||||||
import { centerScrollOn } from "../scene/scroll";
|
import { centerScrollOn } from "../scene/scroll";
|
||||||
import { getStateForZoom } from "../scene/zoom";
|
import { getStateForZoom } from "../scene/zoom";
|
||||||
|
import { constrainScrollState } from "../scroll";
|
||||||
import { getShortcutKey } from "../shortcut";
|
import { getShortcutKey } from "../shortcut";
|
||||||
|
|
||||||
import { register } from "./register";
|
import { register } from "./register";
|
||||||
@@ -141,19 +142,20 @@ export const actionZoomIn = register({
|
|||||||
icon: ZoomInIcon,
|
icon: ZoomInIcon,
|
||||||
trackEvent: { category: "canvas" },
|
trackEvent: { category: "canvas" },
|
||||||
perform: (_elements, appState, _, app) => {
|
perform: (_elements, appState, _, app) => {
|
||||||
|
const nextState = {
|
||||||
|
...appState,
|
||||||
|
...getStateForZoom(
|
||||||
|
{
|
||||||
|
viewportX: appState.width / 2 + appState.offsetLeft,
|
||||||
|
viewportY: appState.height / 2 + appState.offsetTop,
|
||||||
|
nextZoom: getNormalizedZoom(appState.zoom.value + ZOOM_STEP),
|
||||||
|
},
|
||||||
|
appState,
|
||||||
|
),
|
||||||
|
userToFollow: null,
|
||||||
|
};
|
||||||
return {
|
return {
|
||||||
appState: {
|
appState: { ...nextState, ...constrainScrollState(nextState) },
|
||||||
...appState,
|
|
||||||
...getStateForZoom(
|
|
||||||
{
|
|
||||||
viewportX: appState.width / 2 + appState.offsetLeft,
|
|
||||||
viewportY: appState.height / 2 + appState.offsetTop,
|
|
||||||
nextZoom: getNormalizedZoom(appState.zoom.value + ZOOM_STEP),
|
|
||||||
},
|
|
||||||
appState,
|
|
||||||
),
|
|
||||||
userToFollow: null,
|
|
||||||
},
|
|
||||||
captureUpdate: CaptureUpdateAction.EVENTUALLY,
|
captureUpdate: CaptureUpdateAction.EVENTUALLY,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -182,19 +184,20 @@ export const actionZoomOut = register({
|
|||||||
viewMode: true,
|
viewMode: true,
|
||||||
trackEvent: { category: "canvas" },
|
trackEvent: { category: "canvas" },
|
||||||
perform: (_elements, appState, _, app) => {
|
perform: (_elements, appState, _, app) => {
|
||||||
|
const nextState = {
|
||||||
|
...appState,
|
||||||
|
...getStateForZoom(
|
||||||
|
{
|
||||||
|
viewportX: appState.width / 2 + appState.offsetLeft,
|
||||||
|
viewportY: appState.height / 2 + appState.offsetTop,
|
||||||
|
nextZoom: getNormalizedZoom(appState.zoom.value - ZOOM_STEP),
|
||||||
|
},
|
||||||
|
appState,
|
||||||
|
),
|
||||||
|
userToFollow: null,
|
||||||
|
};
|
||||||
return {
|
return {
|
||||||
appState: {
|
appState: { ...nextState, ...constrainScrollState(nextState) },
|
||||||
...appState,
|
|
||||||
...getStateForZoom(
|
|
||||||
{
|
|
||||||
viewportX: appState.width / 2 + appState.offsetLeft,
|
|
||||||
viewportY: appState.height / 2 + appState.offsetTop,
|
|
||||||
nextZoom: getNormalizedZoom(appState.zoom.value - ZOOM_STEP),
|
|
||||||
},
|
|
||||||
appState,
|
|
||||||
),
|
|
||||||
userToFollow: null,
|
|
||||||
},
|
|
||||||
captureUpdate: CaptureUpdateAction.EVENTUALLY,
|
captureUpdate: CaptureUpdateAction.EVENTUALLY,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -222,6 +225,8 @@ export const actionResetZoom = register({
|
|||||||
icon: ZoomResetIcon,
|
icon: ZoomResetIcon,
|
||||||
viewMode: true,
|
viewMode: true,
|
||||||
trackEvent: { category: "canvas" },
|
trackEvent: { category: "canvas" },
|
||||||
|
// resetting to 100% may violate active scroll constraints
|
||||||
|
predicate: (elements, appState) => !appState.scrollConstraints,
|
||||||
perform: (_elements, appState, _, app) => {
|
perform: (_elements, appState, _, app) => {
|
||||||
return {
|
return {
|
||||||
appState: {
|
appState: {
|
||||||
@@ -246,6 +251,8 @@ export const actionResetZoom = register({
|
|||||||
className="reset-zoom-button zoom-button"
|
className="reset-zoom-button zoom-button"
|
||||||
title={t("buttons.resetZoom")}
|
title={t("buttons.resetZoom")}
|
||||||
aria-label={t("buttons.resetZoom")}
|
aria-label={t("buttons.resetZoom")}
|
||||||
|
// keep the zoom level visible, but non-resettable while constrained
|
||||||
|
disabled={!!appState.scrollConstraints}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateData(null);
|
updateData(null);
|
||||||
}}
|
}}
|
||||||
@@ -254,7 +261,8 @@ export const actionResetZoom = register({
|
|||||||
</ToolButton>
|
</ToolButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
),
|
),
|
||||||
keyTest: (event) =>
|
keyTest: (event, appState) =>
|
||||||
|
!appState.scrollConstraints &&
|
||||||
(event.code === CODES.ZERO || event.code === CODES.NUM_ZERO) &&
|
(event.code === CODES.ZERO || event.code === CODES.NUM_ZERO) &&
|
||||||
(event[KEYS.CTRL_OR_CMD] || event.shiftKey),
|
(event[KEYS.CTRL_OR_CMD] || event.shiftKey),
|
||||||
});
|
});
|
||||||
@@ -399,6 +407,7 @@ export const actionZoomToFitSelectionInViewport = register({
|
|||||||
label: "labels.zoomToFitViewport",
|
label: "labels.zoomToFitViewport",
|
||||||
icon: zoomAreaIcon,
|
icon: zoomAreaIcon,
|
||||||
trackEvent: { category: "canvas" },
|
trackEvent: { category: "canvas" },
|
||||||
|
predicate: (elements, appState) => !appState.scrollConstraints,
|
||||||
perform: (elements, appState, _, app) => {
|
perform: (elements, appState, _, app) => {
|
||||||
const selectedElements = app.scene.getSelectedElements(appState);
|
const selectedElements = app.scene.getSelectedElements(appState);
|
||||||
return zoomToFit({
|
return zoomToFit({
|
||||||
@@ -413,7 +422,8 @@ export const actionZoomToFitSelectionInViewport = register({
|
|||||||
},
|
},
|
||||||
// NOTE shift-2 should have been assigned actionZoomToFitSelection.
|
// NOTE shift-2 should have been assigned actionZoomToFitSelection.
|
||||||
// TBD on how proceed
|
// TBD on how proceed
|
||||||
keyTest: (event) =>
|
keyTest: (event, appState) =>
|
||||||
|
!appState.scrollConstraints &&
|
||||||
event.code === CODES.TWO &&
|
event.code === CODES.TWO &&
|
||||||
event.shiftKey &&
|
event.shiftKey &&
|
||||||
!event.altKey &&
|
!event.altKey &&
|
||||||
@@ -425,6 +435,7 @@ export const actionZoomToFitSelection = register({
|
|||||||
label: "helpDialog.zoomToSelection",
|
label: "helpDialog.zoomToSelection",
|
||||||
icon: zoomAreaIcon,
|
icon: zoomAreaIcon,
|
||||||
trackEvent: { category: "canvas" },
|
trackEvent: { category: "canvas" },
|
||||||
|
predicate: (elements, appState) => !appState.scrollConstraints,
|
||||||
perform: (elements, appState, _, app) => {
|
perform: (elements, appState, _, app) => {
|
||||||
const selectedElements = app.scene.getSelectedElements(appState);
|
const selectedElements = app.scene.getSelectedElements(appState);
|
||||||
return zoomToFit({
|
return zoomToFit({
|
||||||
@@ -438,7 +449,8 @@ export const actionZoomToFitSelection = register({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
// NOTE this action should use shift-2 per figma, alas
|
// NOTE this action should use shift-2 per figma, alas
|
||||||
keyTest: (event) =>
|
keyTest: (event, appState) =>
|
||||||
|
!appState.scrollConstraints &&
|
||||||
event.code === CODES.THREE &&
|
event.code === CODES.THREE &&
|
||||||
event.shiftKey &&
|
event.shiftKey &&
|
||||||
!event.altKey &&
|
!event.altKey &&
|
||||||
@@ -451,6 +463,7 @@ export const actionZoomToFit = register({
|
|||||||
icon: zoomAreaIcon,
|
icon: zoomAreaIcon,
|
||||||
viewMode: true,
|
viewMode: true,
|
||||||
trackEvent: { category: "canvas" },
|
trackEvent: { category: "canvas" },
|
||||||
|
predicate: (elements, appState) => !appState.scrollConstraints,
|
||||||
perform: (elements, appState, _, app) =>
|
perform: (elements, appState, _, app) =>
|
||||||
zoomToFit({
|
zoomToFit({
|
||||||
targetElements: elements,
|
targetElements: elements,
|
||||||
@@ -461,7 +474,8 @@ export const actionZoomToFit = register({
|
|||||||
fitToViewport: false,
|
fitToViewport: false,
|
||||||
canvasOffsets: app.getEditorUIOffsets(),
|
canvasOffsets: app.getEditorUIOffsets(),
|
||||||
}),
|
}),
|
||||||
keyTest: (event) =>
|
keyTest: (event, appState) =>
|
||||||
|
!appState.scrollConstraints &&
|
||||||
event.code === CODES.ONE &&
|
event.code === CODES.ONE &&
|
||||||
event.shiftKey &&
|
event.shiftKey &&
|
||||||
!event.altKey &&
|
!event.altKey &&
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ export const getDefaultAppState = (): Omit<
|
|||||||
scrolledOutside: false,
|
scrolledOutside: false,
|
||||||
scrollX: 0,
|
scrollX: 0,
|
||||||
scrollY: 0,
|
scrollY: 0,
|
||||||
|
scrollConstraints: null,
|
||||||
selectedElementIds: {},
|
selectedElementIds: {},
|
||||||
hoveredElementIds: {},
|
hoveredElementIds: {},
|
||||||
selectedGroupIds: {},
|
selectedGroupIds: {},
|
||||||
@@ -226,6 +227,7 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
scrolledOutside: { browser: true, export: false, server: false },
|
scrolledOutside: { browser: true, export: false, server: false },
|
||||||
scrollX: { browser: true, export: false, server: false },
|
scrollX: { browser: true, export: false, server: false },
|
||||||
scrollY: { browser: true, export: false, server: false },
|
scrollY: { browser: true, export: false, server: false },
|
||||||
|
scrollConstraints: { browser: false, export: false, server: false },
|
||||||
selectedElementIds: { browser: true, export: false, server: false },
|
selectedElementIds: { browser: true, export: false, server: false },
|
||||||
hoveredElementIds: { browser: false, export: false, server: false },
|
hoveredElementIds: { browser: false, export: false, server: false },
|
||||||
selectedGroupIds: { browser: true, export: false, server: false },
|
selectedGroupIds: { browser: true, export: false, server: false },
|
||||||
|
|||||||
@@ -415,6 +415,7 @@ import {
|
|||||||
type ScrollToContentOptions,
|
type ScrollToContentOptions,
|
||||||
SCROLL_TO_CONTENT_ANIMATION_KEY,
|
SCROLL_TO_CONTENT_ANIMATION_KEY,
|
||||||
scrollToElements,
|
scrollToElements,
|
||||||
|
constrainScrollState,
|
||||||
} from "../scroll";
|
} from "../scroll";
|
||||||
import {
|
import {
|
||||||
setEraserCursor,
|
setEraserCursor,
|
||||||
@@ -498,6 +499,7 @@ import type {
|
|||||||
GenerateDiagramToCode,
|
GenerateDiagramToCode,
|
||||||
NullableGridSize,
|
NullableGridSize,
|
||||||
Offsets,
|
Offsets,
|
||||||
|
ScrollConstraints,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import type { RoughCanvas } from "roughjs/bin/canvas";
|
import type { RoughCanvas } from "roughjs/bin/canvas";
|
||||||
import type { Action, ActionResult } from "../actions/types";
|
import type { Action, ActionResult } from "../actions/types";
|
||||||
@@ -760,6 +762,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
clear: this.resetHistory,
|
clear: this.resetHistory,
|
||||||
},
|
},
|
||||||
scrollToContent: this.scrollToContent,
|
scrollToContent: this.scrollToContent,
|
||||||
|
setScrollConstraints: this.setScrollConstraints,
|
||||||
getSceneElements: this.getSceneElements,
|
getSceneElements: this.getSceneElements,
|
||||||
getAppState: () => this.state,
|
getAppState: () => this.state,
|
||||||
getFiles: () => this.files,
|
getFiles: () => this.files,
|
||||||
@@ -4373,6 +4376,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setScrollConstraints(null);
|
||||||
scrollToElements(this.state, elements, this.setState.bind(this), opts);
|
scrollToElements(this.state, elements, this.setState.bind(this), opts);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -4390,6 +4394,31 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
this.setState({ shouldCacheIgnoreZoom: false });
|
this.setState({ shouldCacheIgnoreZoom: false });
|
||||||
this.maybeUnfollowRemoteUser();
|
this.maybeUnfollowRemoteUser();
|
||||||
this.setState(state);
|
this.setState(state);
|
||||||
|
this.constrainViewportToScrollConstraints();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** clamps scroll/zoom back into `appState.scrollConstraints` (no-op when
|
||||||
|
* unconstrained). Runs as a queued update, so it sees the preceding change. */
|
||||||
|
private constrainViewportToScrollConstraints = () => {
|
||||||
|
this.setState((prevState) =>
|
||||||
|
prevState.scrollConstraints ? constrainScrollState(prevState) : null,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constrains pan & zoom to a scene-coordinate box, so the viewport can't be
|
||||||
|
* scrolled or zoomed out past it. Pass `null` to remove the constraint. When
|
||||||
|
* the box is smaller than the viewport, zoom is increased best-effort to fit.
|
||||||
|
*/
|
||||||
|
setScrollConstraints = (scrollConstraints: ScrollConstraints | null) => {
|
||||||
|
// apply the constraint and clamp the viewport in a single, synchronously
|
||||||
|
// flushed update so the new scroll/zoom is reflected immediately
|
||||||
|
flushSync(() => {
|
||||||
|
this.setState((prevState) => ({
|
||||||
|
scrollConstraints,
|
||||||
|
...constrainScrollState({ ...prevState, scrollConstraints }),
|
||||||
|
}));
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
setToast = (toast: AppState["toast"]) => {
|
setToast = (toast: AppState["toast"]) => {
|
||||||
@@ -5572,7 +5601,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
const initialScale = gesture.initialScale;
|
const initialScale = gesture.initialScale;
|
||||||
if (initialScale) {
|
if (initialScale) {
|
||||||
this.setState((state) => ({
|
this.translateCanvas((state) => ({
|
||||||
...getStateForZoom(
|
...getStateForZoom(
|
||||||
{
|
{
|
||||||
viewportX: this.lastViewportPosition.x,
|
viewportX: this.lastViewportPosition.x,
|
||||||
@@ -12867,6 +12896,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
cb && cb();
|
cb && cb();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
// a smaller viewport may push the min zoom up / shrink the pan range
|
||||||
|
this.constrainViewportToScrollConstraints();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
import { easeOut } from "@excalidraw/common";
|
import { easeOut, MAX_ZOOM, MIN_ZOOM } from "@excalidraw/common";
|
||||||
import { clamp } from "@excalidraw/math";
|
import { clamp } from "@excalidraw/math";
|
||||||
|
|
||||||
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
||||||
|
|
||||||
import { zoomToFit } from "./actions/actionCanvas";
|
import { zoomToFit } from "./actions/actionCanvas";
|
||||||
import { AnimationController } from "./renderer/animation";
|
import { AnimationController } from "./renderer/animation";
|
||||||
|
import { getNormalizedZoom } from "./scene";
|
||||||
import { calculateScrollCenter } from "./scene/scroll";
|
import { calculateScrollCenter } from "./scene/scroll";
|
||||||
|
|
||||||
import type { AppState, NormalizedZoomValue, Offsets } from "./types";
|
import type {
|
||||||
|
AppState,
|
||||||
|
NormalizedZoomValue,
|
||||||
|
Offsets,
|
||||||
|
ScrollConstraints,
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
export const SCROLL_TO_CONTENT_ANIMATION_KEY = "animateScrollToContent";
|
export const SCROLL_TO_CONTENT_ANIMATION_KEY = "animateScrollToContent";
|
||||||
|
|
||||||
@@ -39,6 +45,86 @@ export type ScrollToContentOptions = (
|
|||||||
|
|
||||||
type Viewport = Pick<AppState, "scrollX" | "scrollY" | "zoom">;
|
type Viewport = Pick<AppState, "scrollX" | "scrollY" | "zoom">;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The smallest zoom at which the constraint box still fully covers the viewport.
|
||||||
|
* Below this, the viewport would extend past the box on its binding dimension,
|
||||||
|
* so it becomes the enforced minimum zoom. When the box is smaller than the
|
||||||
|
* viewport this is > 1, forcing a zoom-in (best-effort fit).
|
||||||
|
*/
|
||||||
|
export const getMinZoomForConstraints = (
|
||||||
|
constraints: ScrollConstraints,
|
||||||
|
viewport: { width: number; height: number },
|
||||||
|
): number =>
|
||||||
|
clamp(
|
||||||
|
Math.max(
|
||||||
|
viewport.width / constraints.width,
|
||||||
|
viewport.height / constraints.height,
|
||||||
|
),
|
||||||
|
MIN_ZOOM,
|
||||||
|
MAX_ZOOM,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clamps a single scroll axis so the visible scene span stays inside the box.
|
||||||
|
* The visible span is `[-scroll, -scroll + visibleSize]`; we keep it within
|
||||||
|
* `[boxStart, boxStart + boxSize]`. When the box can't cover the viewport on
|
||||||
|
* this axis (only at the MAX_ZOOM cap for a tiny box) we center the box instead.
|
||||||
|
*/
|
||||||
|
const constrainScrollAxis = (
|
||||||
|
scroll: number,
|
||||||
|
boxStart: number,
|
||||||
|
boxSize: number,
|
||||||
|
visibleSize: number,
|
||||||
|
): number => {
|
||||||
|
const max = -boxStart;
|
||||||
|
const min = visibleSize - (boxStart + boxSize);
|
||||||
|
return min > max ? (min + max) / 2 : clamp(scroll, min, max);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clamps a proposed scroll/zoom so that, when `scrollConstraints` is set, the
|
||||||
|
* viewport cannot pan or zoom out of the box. Returns the input scroll/zoom
|
||||||
|
* unchanged when there are no constraints. Because the whole viewport is kept
|
||||||
|
* inside the box, any zoom anchor (which lives within the viewport) is also
|
||||||
|
* guaranteed to stay inside the box.
|
||||||
|
*/
|
||||||
|
export const constrainScrollState = (
|
||||||
|
state: Pick<
|
||||||
|
AppState,
|
||||||
|
"scrollX" | "scrollY" | "zoom" | "width" | "height" | "scrollConstraints"
|
||||||
|
>,
|
||||||
|
): Viewport => {
|
||||||
|
const { scrollConstraints, width, height } = state;
|
||||||
|
|
||||||
|
if (!scrollConstraints) {
|
||||||
|
return { scrollX: state.scrollX, scrollY: state.scrollY, zoom: state.zoom };
|
||||||
|
}
|
||||||
|
|
||||||
|
const minZoom = getMinZoomForConstraints(scrollConstraints, {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
});
|
||||||
|
const zoomValue = getNormalizedZoom(
|
||||||
|
clamp(state.zoom.value, minZoom, MAX_ZOOM),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
scrollX: constrainScrollAxis(
|
||||||
|
state.scrollX,
|
||||||
|
scrollConstraints.x,
|
||||||
|
scrollConstraints.width,
|
||||||
|
width / zoomValue,
|
||||||
|
),
|
||||||
|
scrollY: constrainScrollAxis(
|
||||||
|
state.scrollY,
|
||||||
|
scrollConstraints.y,
|
||||||
|
scrollConstraints.height,
|
||||||
|
height / zoomValue,
|
||||||
|
),
|
||||||
|
zoom: { value: zoomValue },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scrolls (and optionally zooms) the viewport so that the given target is in
|
* Scrolls (and optionally zooms) the viewport so that the given target is in
|
||||||
* view, optionally animating the transition.
|
* view, optionally animating the transition.
|
||||||
@@ -72,6 +158,8 @@ const getTargetViewport = (
|
|||||||
targetElements: readonly ExcalidrawElement[],
|
targetElements: readonly ExcalidrawElement[],
|
||||||
opts?: ScrollToContentOptions,
|
opts?: ScrollToContentOptions,
|
||||||
): Viewport => {
|
): Viewport => {
|
||||||
|
let viewport: Viewport;
|
||||||
|
|
||||||
if (opts?.fitToContent || opts?.fitToViewport) {
|
if (opts?.fitToContent || opts?.fitToViewport) {
|
||||||
const { appState } = zoomToFit({
|
const { appState } = zoomToFit({
|
||||||
canvasOffsets: opts.canvasOffsets,
|
canvasOffsets: opts.canvasOffsets,
|
||||||
@@ -83,17 +171,19 @@ const getTargetViewport = (
|
|||||||
maxZoom: opts.maxZoom,
|
maxZoom: opts.maxZoom,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
viewport = {
|
||||||
scrollX: appState.scrollX,
|
scrollX: appState.scrollX,
|
||||||
scrollY: appState.scrollY,
|
scrollY: appState.scrollY,
|
||||||
zoom: appState.zoom,
|
zoom: appState.zoom,
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
// keep the current zoom, only recenter the viewport on the target
|
||||||
|
const { scrollX, scrollY } = calculateScrollCenter(targetElements, state);
|
||||||
|
viewport = { scrollX, scrollY, zoom: state.zoom };
|
||||||
}
|
}
|
||||||
|
|
||||||
// keep the current zoom, only recenter the viewport on the target
|
// keep programmatic scrolling within the constraint box, if any
|
||||||
const { scrollX, scrollY } = calculateScrollCenter(targetElements, state);
|
return constrainScrollState({ ...state, ...viewport });
|
||||||
|
|
||||||
return { scrollX, scrollY, zoom: state.zoom };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Eases the viewport from its current position to `target` over `duration`,
|
/** Eases the viewport from its current position to `target` over `duration`,
|
||||||
|
|||||||
@@ -962,6 +962,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -1159,6 +1160,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": true,
|
"scrolledOutside": true,
|
||||||
@@ -1374,6 +1376,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -1706,6 +1709,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -2038,6 +2042,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": true,
|
"scrolledOutside": true,
|
||||||
@@ -2253,6 +2258,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -2495,6 +2501,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -2796,6 +2803,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
"id3": true,
|
"id3": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -3167,6 +3175,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -3688,6 +3697,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -4012,6 +4022,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -4338,6 +4349,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"id3": true,
|
"id3": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -5624,6 +5636,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -6842,6 +6855,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -7801,6 +7815,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -8799,6 +8814,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": true,
|
"scrolledOutside": true,
|
||||||
@@ -9794,6 +9810,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"id4": true,
|
"id4": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -721,6 +722,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
"id4": true,
|
"id4": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -1285,6 +1287,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -1646,6 +1649,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -2009,6 +2013,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -2270,6 +2275,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -2761,6 +2767,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -3065,6 +3072,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -3385,6 +3393,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -3680,6 +3689,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -3967,6 +3977,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -4203,6 +4214,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -4461,6 +4473,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -4733,6 +4746,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -4963,6 +4977,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -5193,6 +5208,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -5441,6 +5457,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -5698,6 +5715,7 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -5956,6 +5974,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"id1": true,
|
"id1": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -6286,6 +6305,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"id8": true,
|
"id8": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -6714,6 +6734,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
"id1": true,
|
"id1": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -7090,6 +7111,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -7400,6 +7422,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -7694,6 +7717,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -7925,6 +7949,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -8278,6 +8303,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -8634,6 +8660,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
"id3": true,
|
"id3": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -9038,6 +9065,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -9326,6 +9354,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -9591,6 +9620,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -9857,6 +9887,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -10093,6 +10124,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -10388,6 +10420,7 @@ exports[`history > multiplayer undo/redo > should override remotely added points
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -10707,6 +10740,7 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -10947,6 +10981,7 @@ exports[`history > multiplayer undo/redo > should redraw arrows on undo > [end o
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -11870,6 +11905,7 @@ exports[`history > multiplayer undo/redo > should update history entries after r
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -12131,6 +12167,7 @@ exports[`history > singleplayer undo/redo > remounting undo/redo buttons should
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -12367,6 +12404,7 @@ exports[`history > singleplayer undo/redo > should clear the redo stack on eleme
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -12605,6 +12643,7 @@ exports[`history > singleplayer undo/redo > should create entry when selecting f
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -13012,6 +13051,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on e
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -13220,6 +13260,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on e
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -13431,6 +13472,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on i
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -13730,6 +13772,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on i
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -14032,6 +14075,7 @@ exports[`history > singleplayer undo/redo > should create new history entry on s
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": -50,
|
"scrollX": -50,
|
||||||
"scrollY": -50,
|
"scrollY": -50,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -14275,6 +14319,7 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -14513,6 +14558,7 @@ exports[`history > singleplayer undo/redo > should end up with no history entry
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -14753,6 +14799,7 @@ exports[`history > singleplayer undo/redo > should iterate through the history w
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -14999,6 +15046,7 @@ exports[`history > singleplayer undo/redo > should not clear the redo stack on s
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -15334,6 +15382,7 @@ exports[`history > singleplayer undo/redo > should not collapse when applying co
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -15502,6 +15551,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -15790,6 +15840,7 @@ exports[`history > singleplayer undo/redo > should not end up with history entry
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -16054,6 +16105,7 @@ exports[`history > singleplayer undo/redo > should not modify anything on unrela
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -16207,6 +16259,7 @@ exports[`history > singleplayer undo/redo > should not override appstate changes
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -16491,6 +16544,7 @@ exports[`history > singleplayer undo/redo > should support appstate name or view
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -16653,6 +16707,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -17403,6 +17458,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -18051,6 +18107,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -18697,6 +18754,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -19450,6 +19508,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -20220,6 +20279,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements'
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -20701,6 +20761,7 @@ exports[`history > singleplayer undo/redo > should support duplication of groups
|
|||||||
"id1": true,
|
"id1": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -21213,6 +21274,7 @@ exports[`history > singleplayer undo/redo > should support element creation, del
|
|||||||
"id3": true,
|
"id3": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
@@ -21673,6 +21735,7 @@ exports[`history > singleplayer undo/redo > should support linear element creati
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"searchMatches": null,
|
"searchMatches": null,
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
|||||||
"id6": true,
|
"id6": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -516,6 +517,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
|||||||
"id6": true,
|
"id6": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -929,6 +931,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -1496,6 +1499,7 @@ exports[`regression tests > Drags selected element when hitting only bounding bo
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -1706,6 +1710,7 @@ exports[`regression tests > adjusts z order when grouping > [end of test] appSta
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -2091,6 +2096,7 @@ exports[`regression tests > alt-drag duplicates an element > [end of test] appSt
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -2335,6 +2341,7 @@ exports[`regression tests > arrow keys > [end of test] appState 1`] = `
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -2518,6 +2525,7 @@ exports[`regression tests > can drag element that covers another element, while
|
|||||||
"id6": true,
|
"id6": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -2842,6 +2850,7 @@ exports[`regression tests > change the properties of a shape > [end of test] app
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -3100,6 +3109,7 @@ exports[`regression tests > click on an element and drag it > [dragged] appState
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -3342,6 +3352,7 @@ exports[`regression tests > click on an element and drag it > [end of test] appS
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -3579,6 +3590,7 @@ exports[`regression tests > click to select a shape > [end of test] appState 1`]
|
|||||||
"id3": true,
|
"id3": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -3838,6 +3850,7 @@ exports[`regression tests > click-drag to select a group > [end of test] appStat
|
|||||||
"id6": true,
|
"id6": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -4151,6 +4164,7 @@ exports[`regression tests > deleting last but one element in editing group shoul
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -4591,6 +4605,7 @@ exports[`regression tests > deselects group of selected elements on pointer down
|
|||||||
"id3": true,
|
"id3": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -4875,6 +4890,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w
|
|||||||
"id3": true,
|
"id3": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -5151,6 +5167,7 @@ exports[`regression tests > deselects selected element on pointer down when poin
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -5360,6 +5377,7 @@ exports[`regression tests > deselects selected element, on pointer up, when clic
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -5559,6 +5577,7 @@ exports[`regression tests > double click to edit a group > [end of test] appStat
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -5956,6 +5975,7 @@ exports[`regression tests > drags selected elements from point inside common bou
|
|||||||
"id3": true,
|
"id3": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -6251,6 +6271,7 @@ exports[`regression tests > draw every type of shape > [end of test] appState 1`
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -7048,6 +7069,7 @@ exports[`regression tests > given a group of selected elements with an element t
|
|||||||
"id6": true,
|
"id6": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -7382,6 +7404,7 @@ exports[`regression tests > given a selected element A and a not selected elemen
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -7662,6 +7685,7 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -7898,6 +7922,7 @@ exports[`regression tests > given selected element A with lower z-index than uns
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -8137,6 +8162,7 @@ exports[`regression tests > key 2 selects rectangle tool > [end of test] appStat
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -8318,6 +8344,7 @@ exports[`regression tests > key 3 selects diamond tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -8499,6 +8526,7 @@ exports[`regression tests > key 4 selects ellipse tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -8680,6 +8708,7 @@ exports[`regression tests > key 5 selects arrow tool > [end of test] appState 1`
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -8914,6 +8943,7 @@ exports[`regression tests > key 6 selects line tool > [end of test] appState 1`]
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -9146,6 +9176,7 @@ exports[`regression tests > key 7 selects freedraw tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -9343,6 +9374,7 @@ exports[`regression tests > key a selects arrow tool > [end of test] appState 1`
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -9577,6 +9609,7 @@ exports[`regression tests > key d selects diamond tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -9758,6 +9791,7 @@ exports[`regression tests > key l selects line tool > [end of test] appState 1`]
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -9990,6 +10024,7 @@ exports[`regression tests > key o selects ellipse tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -10171,6 +10206,7 @@ exports[`regression tests > key p selects freedraw tool > [end of test] appState
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -10368,6 +10404,7 @@ exports[`regression tests > key r selects rectangle tool > [end of test] appStat
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -10553,6 +10590,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] appSta
|
|||||||
"id6": true,
|
"id6": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -11083,6 +11121,7 @@ exports[`regression tests > noop interaction after undo shouldn't create history
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -11362,6 +11401,7 @@ exports[`regression tests > pinch-to-zoom works > [end of test] appState 1`] = `
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": "-6.25000",
|
"scrollX": "-6.25000",
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -11488,6 +11528,7 @@ exports[`regression tests > shift click on selected element should deselect it o
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -11690,6 +11731,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test
|
|||||||
"id3": true,
|
"id3": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -12011,6 +12053,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes
|
|||||||
"id6": true,
|
"id6": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -12442,6 +12485,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh
|
|||||||
"id3": true,
|
"id3": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -13081,6 +13125,7 @@ exports[`regression tests > spacebar + drag scrolls the canvas > [end of test] a
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 60,
|
"scrollX": 60,
|
||||||
"scrollY": 60,
|
"scrollY": 60,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -13207,6 +13252,7 @@ exports[`regression tests > supports nested groups > [end of test] appState 1`]
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -13840,6 +13886,7 @@ exports[`regression tests > switches from group of selected elements to another
|
|||||||
"id6": true,
|
"id6": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -14179,6 +14226,7 @@ exports[`regression tests > switches selected element on pointer down > [end of
|
|||||||
"id3": true,
|
"id3": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -14442,6 +14490,7 @@ exports[`regression tests > two-finger scroll works > [end of test] appState 1`]
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 20,
|
"scrollX": 20,
|
||||||
"scrollY": "-18.53553",
|
"scrollY": "-18.53553",
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -14566,6 +14615,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] appStat
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -14932,6 +14982,7 @@ exports[`regression tests > updates fontSize & fontFamily appState > [end of tes
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@@ -15059,6 +15110,7 @@ exports[`regression tests > zoom hotkeys > [end of test] appState 1`] = `
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { KEYS, MAX_ZOOM } from "@excalidraw/common";
|
||||||
|
|
||||||
|
import { Excalidraw } from "../index";
|
||||||
|
import {
|
||||||
|
actionResetZoom,
|
||||||
|
actionZoomToFit,
|
||||||
|
actionZoomToFitSelection,
|
||||||
|
} from "../actions/actionCanvas";
|
||||||
|
import { getNormalizedZoom } from "../scene";
|
||||||
|
import { constrainScrollState, getMinZoomForConstraints } from "../scroll";
|
||||||
|
|
||||||
|
import { Keyboard } from "./helpers/ui";
|
||||||
|
import {
|
||||||
|
mockBoundingClientRect,
|
||||||
|
render,
|
||||||
|
restoreOriginalGetBoundingClientRect,
|
||||||
|
waitFor,
|
||||||
|
} from "./test-utils";
|
||||||
|
|
||||||
|
import type { AppState, ScrollConstraints } from "../types";
|
||||||
|
|
||||||
|
const { h } = window;
|
||||||
|
|
||||||
|
// `mockBoundingClientRect()` makes the viewport 200 x 100
|
||||||
|
const VIEWPORT = { width: 200, height: 100 };
|
||||||
|
|
||||||
|
const makeState = (
|
||||||
|
overrides: Partial<AppState> & {
|
||||||
|
scrollConstraints: ScrollConstraints | null;
|
||||||
|
},
|
||||||
|
): Pick<
|
||||||
|
AppState,
|
||||||
|
"scrollX" | "scrollY" | "zoom" | "width" | "height" | "scrollConstraints"
|
||||||
|
> => ({
|
||||||
|
scrollX: 0,
|
||||||
|
scrollY: 0,
|
||||||
|
zoom: { value: getNormalizedZoom(1) },
|
||||||
|
width: VIEWPORT.width,
|
||||||
|
height: VIEWPORT.height,
|
||||||
|
...overrides,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("constrainScrollState (pure)", () => {
|
||||||
|
it("returns scroll/zoom unchanged when there are no constraints", () => {
|
||||||
|
const state = makeState({
|
||||||
|
scrollX: 123,
|
||||||
|
scrollY: -45,
|
||||||
|
zoom: { value: getNormalizedZoom(0.5) },
|
||||||
|
scrollConstraints: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(constrainScrollState(state)).toEqual({
|
||||||
|
scrollX: 123,
|
||||||
|
scrollY: -45,
|
||||||
|
zoom: { value: getNormalizedZoom(0.5) },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("box larger than the viewport", () => {
|
||||||
|
const box: ScrollConstraints = { x: 0, y: 0, width: 1000, height: 1000 };
|
||||||
|
|
||||||
|
it("clamps scroll so the viewport can't leave the box", () => {
|
||||||
|
// pan past the box's top-left corner → clamp to the corner (scroll 0)
|
||||||
|
const corner = constrainScrollState(
|
||||||
|
makeState({ scrollX: 100, scrollY: 100, scrollConstraints: box }),
|
||||||
|
);
|
||||||
|
expect(corner.scrollX).toBeCloseTo(0);
|
||||||
|
expect(corner.scrollY).toBeCloseTo(0);
|
||||||
|
|
||||||
|
// pan past the box's far edges → clamp to width/height - boxSize
|
||||||
|
const farEdge = constrainScrollState(
|
||||||
|
makeState({ scrollX: -5000, scrollY: -5000, scrollConstraints: box }),
|
||||||
|
);
|
||||||
|
expect(farEdge.scrollX).toBeCloseTo(VIEWPORT.width - box.width); // -800
|
||||||
|
expect(farEdge.scrollY).toBeCloseTo(VIEWPORT.height - box.height); // -900
|
||||||
|
});
|
||||||
|
|
||||||
|
it("enforces a minimum zoom so the box keeps covering the viewport", () => {
|
||||||
|
const minZoom = getMinZoomForConstraints(box, VIEWPORT);
|
||||||
|
expect(minZoom).toBeCloseTo(0.2); // max(200/1000, 100/1000)
|
||||||
|
|
||||||
|
const { zoom } = constrainScrollState(
|
||||||
|
makeState({
|
||||||
|
zoom: { value: getNormalizedZoom(0.1) },
|
||||||
|
scrollConstraints: box,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(zoom.value).toBeCloseTo(minZoom);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("leaves an in-bounds viewport untouched", () => {
|
||||||
|
const inBounds = makeState({
|
||||||
|
scrollX: -100,
|
||||||
|
scrollY: -100,
|
||||||
|
zoom: { value: getNormalizedZoom(1) },
|
||||||
|
scrollConstraints: box,
|
||||||
|
});
|
||||||
|
expect(constrainScrollState(inBounds)).toMatchObject({
|
||||||
|
scrollX: -100,
|
||||||
|
scrollY: -100,
|
||||||
|
zoom: { value: getNormalizedZoom(1) },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("box smaller than the viewport (best-effort fit)", () => {
|
||||||
|
it("forces a zoom-in and removes pan room on the binding axis", () => {
|
||||||
|
const box: ScrollConstraints = { x: 0, y: 0, width: 50, height: 50 };
|
||||||
|
// minZoom = max(200/50, 100/50) = 4
|
||||||
|
expect(getMinZoomForConstraints(box, VIEWPORT)).toBeCloseTo(4);
|
||||||
|
|
||||||
|
const result = constrainScrollState(
|
||||||
|
makeState({
|
||||||
|
scrollX: 999,
|
||||||
|
scrollY: 999,
|
||||||
|
zoom: { value: getNormalizedZoom(1) },
|
||||||
|
scrollConstraints: box,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.zoom.value).toBeCloseTo(4);
|
||||||
|
// width binds: visible width (200/4=50) == box width → no x pan room
|
||||||
|
expect(result.scrollX).toBeCloseTo(0);
|
||||||
|
// height has slack (100/4=25 < 50) → clamped to the top edge here
|
||||||
|
expect(result.scrollY).toBeCloseTo(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("centers the box when it can't cover the viewport even at MAX_ZOOM", () => {
|
||||||
|
const box: ScrollConstraints = { x: 0, y: 0, width: 1, height: 1 };
|
||||||
|
expect(getMinZoomForConstraints(box, VIEWPORT)).toBe(MAX_ZOOM);
|
||||||
|
|
||||||
|
const result = constrainScrollState(
|
||||||
|
makeState({ scrollX: 1000, scrollY: 1000, scrollConstraints: box }),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.zoom.value).toBe(MAX_ZOOM);
|
||||||
|
// centered: (min + max) / 2, with min = w/zoom - (x+w), max = -x
|
||||||
|
const centeredX = (VIEWPORT.width / MAX_ZOOM - 1 + 0) / 2;
|
||||||
|
expect(result.scrollX).toBeCloseTo(centeredX);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("setScrollConstraints (integration)", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockBoundingClientRect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
restoreOriginalGetBoundingClientRect();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("snaps the current viewport inside the box when constraints are set", async () => {
|
||||||
|
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||||
|
await waitFor(() => expect(h.state.width).toBe(200));
|
||||||
|
|
||||||
|
// start well outside the future box
|
||||||
|
React.act(() => {
|
||||||
|
h.app.setScrollConstraints({ x: 0, y: 0, width: 1000, height: 1000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
// viewport must now be within [-800, 0] x [-900, 0]
|
||||||
|
expect(h.state.scrollX).toBeLessThanOrEqual(0);
|
||||||
|
expect(h.state.scrollX).toBeGreaterThanOrEqual(-800);
|
||||||
|
expect(h.state.scrollY).toBeLessThanOrEqual(0);
|
||||||
|
expect(h.state.scrollY).toBeGreaterThanOrEqual(-900);
|
||||||
|
expect(h.state.zoom.value).toBeGreaterThanOrEqual(0.2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prevents keyboard panning out of the box and restores freedom on clear", async () => {
|
||||||
|
await render(<Excalidraw handleKeyboardGlobally={true} />);
|
||||||
|
await waitFor(() => expect(h.state.width).toBe(200));
|
||||||
|
|
||||||
|
React.act(() => {
|
||||||
|
h.app.setScrollConstraints({ x: 0, y: 0, width: 300, height: 200 });
|
||||||
|
});
|
||||||
|
|
||||||
|
// hammer page-down (pans down) far past the box; scroll must stay clamped
|
||||||
|
for (let i = 0; i < 20; i++) {
|
||||||
|
Keyboard.keyPress(KEYS.PAGE_DOWN);
|
||||||
|
}
|
||||||
|
expect(h.state.scrollY).toBeGreaterThanOrEqual(h.state.height - 200);
|
||||||
|
|
||||||
|
// clearing constraints lets it scroll freely again
|
||||||
|
React.act(() => {
|
||||||
|
h.app.setScrollConstraints(null);
|
||||||
|
});
|
||||||
|
const before = h.state.scrollY;
|
||||||
|
Keyboard.keyPress(KEYS.PAGE_DOWN);
|
||||||
|
expect(h.state.scrollY).toBeLessThan(before);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disables reset-zoom and zoom-to-fit actions while constrained", async () => {
|
||||||
|
await render(<Excalidraw />);
|
||||||
|
await waitFor(() => expect(h.state.width).toBe(200));
|
||||||
|
|
||||||
|
expect(h.app.actionManager.isActionEnabled(actionResetZoom)).toBe(true);
|
||||||
|
expect(h.app.actionManager.isActionEnabled(actionZoomToFit)).toBe(true);
|
||||||
|
|
||||||
|
React.act(() => {
|
||||||
|
h.app.setScrollConstraints({ x: 0, y: 0, width: 500, height: 500 });
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(h.app.actionManager.isActionEnabled(actionResetZoom)).toBe(false);
|
||||||
|
expect(h.app.actionManager.isActionEnabled(actionZoomToFit)).toBe(false);
|
||||||
|
expect(h.app.actionManager.isActionEnabled(actionZoomToFitSelection)).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -273,6 +273,14 @@ export type ObservedElementsAppState = {
|
|||||||
|
|
||||||
export type BoxSelectionMode = "contain" | "overlap";
|
export type BoxSelectionMode = "contain" | "overlap";
|
||||||
|
|
||||||
|
/** A box, in scene coordinates, that pan & zoom are constrained to. */
|
||||||
|
export type ScrollConstraints = {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
contextMenu: {
|
contextMenu: {
|
||||||
items: ContextMenuItems;
|
items: ContextMenuItems;
|
||||||
@@ -380,6 +388,9 @@ export interface AppState {
|
|||||||
viewBackgroundColor: string;
|
viewBackgroundColor: string;
|
||||||
scrollX: number;
|
scrollX: number;
|
||||||
scrollY: number;
|
scrollY: number;
|
||||||
|
/** when set, pan & zoom are constrained so the viewport stays within this
|
||||||
|
* scene-coordinate box (see `setScrollConstraints`) */
|
||||||
|
scrollConstraints: ScrollConstraints | null;
|
||||||
cursorButton: "up" | "down";
|
cursorButton: "up" | "down";
|
||||||
scrolledOutside: boolean;
|
scrolledOutside: boolean;
|
||||||
name: string | null;
|
name: string | null;
|
||||||
@@ -971,6 +982,7 @@ export interface ExcalidrawImperativeAPI {
|
|||||||
getFiles: () => InstanceType<typeof App>["files"];
|
getFiles: () => InstanceType<typeof App>["files"];
|
||||||
getName: InstanceType<typeof App>["getName"];
|
getName: InstanceType<typeof App>["getName"];
|
||||||
scrollToContent: InstanceType<typeof App>["scrollToContent"];
|
scrollToContent: InstanceType<typeof App>["scrollToContent"];
|
||||||
|
setScrollConstraints: InstanceType<typeof App>["setScrollConstraints"];
|
||||||
registerAction: (action: Action) => void;
|
registerAction: (action: Action) => void;
|
||||||
refresh: InstanceType<typeof App>["refresh"];
|
refresh: InstanceType<typeof App>["refresh"];
|
||||||
setToast: InstanceType<typeof App>["setToast"];
|
setToast: InstanceType<typeof App>["setToast"];
|
||||||
|
|||||||
@@ -635,7 +635,7 @@ export const textWysiwyg = ({
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
app.actionManager.executeAction(actionZoomOut);
|
app.actionManager.executeAction(actionZoomOut);
|
||||||
updateWysiwygStyle();
|
updateWysiwygStyle();
|
||||||
} else if (!event.shiftKey && actionResetZoom.keyTest(event)) {
|
} else if (!event.shiftKey && actionResetZoom.keyTest(event, app.state)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
app.actionManager.executeAction(actionResetZoom);
|
app.actionManager.executeAction(actionResetZoom);
|
||||||
updateWysiwygStyle();
|
updateWysiwygStyle();
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ exports[`exportToSvg > with default arguments 1`] = `
|
|||||||
},
|
},
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
|
|||||||
Reference in New Issue
Block a user