Compare commits
32 Commits
master
...
arnost/scr
Author | SHA1 | Date | |
---|---|---|---|
|
b0cdd00c2a | ||
|
6711735b27 | ||
|
803e14ada1 | ||
|
4469c02191 | ||
|
04e23e1d29 | ||
|
d24a032dbb | ||
|
76d3930983 | ||
|
af6e64ffc2 | ||
|
4e9039e850 | ||
|
132750f753 | ||
|
71eb3023b2 | ||
|
6d165971fc | ||
|
9562e4309f | ||
|
e8e391e465 | ||
|
92be92071a | ||
|
71918e57a8 | ||
|
c0bd9027cb | ||
|
7336b1c276 | ||
|
7fb6c23715 | ||
|
82014fe670 | ||
|
bc44c3f947 | ||
|
19ba107041 | ||
|
381ef93956 | ||
|
f82363aae9 | ||
|
485c57fd59 | ||
|
35b43c14d8 | ||
|
f7e8056abe | ||
|
71f7960606 | ||
|
2998573e79 | ||
|
209934c90a | ||
|
a8158691b7 | ||
|
75f8e904cc |
@ -16,7 +16,7 @@ export type ActionResult =
|
|||||||
elements?: readonly ExcalidrawElement[] | null;
|
elements?: readonly ExcalidrawElement[] | null;
|
||||||
appState?: MarkOptional<
|
appState?: MarkOptional<
|
||||||
AppState,
|
AppState,
|
||||||
"offsetTop" | "offsetLeft" | "width" | "height"
|
"offsetTop" | "offsetLeft" | "width" | "height" | "scrollConstraints"
|
||||||
> | null;
|
> | null;
|
||||||
files?: BinaryFiles | null;
|
files?: BinaryFiles | null;
|
||||||
commitToHistory: boolean;
|
commitToHistory: boolean;
|
||||||
|
@ -17,7 +17,7 @@ const defaultExportScale = EXPORT_SCALES.includes(devicePixelRatio)
|
|||||||
|
|
||||||
export const getDefaultAppState = (): Omit<
|
export const getDefaultAppState = (): Omit<
|
||||||
AppState,
|
AppState,
|
||||||
"offsetTop" | "offsetLeft" | "width" | "height"
|
"offsetTop" | "offsetLeft" | "width" | "height" | "scrollConstraints"
|
||||||
> => {
|
> => {
|
||||||
return {
|
return {
|
||||||
showWelcomeScreen: false,
|
showWelcomeScreen: false,
|
||||||
@ -206,6 +206,7 @@ const APP_STATE_STORAGE_CONF = (<
|
|||||||
pendingImageElementId: { browser: false, export: false, server: false },
|
pendingImageElementId: { browser: false, export: false, server: false },
|
||||||
showHyperlinkPopup: { browser: false, export: false, server: false },
|
showHyperlinkPopup: { browser: false, export: false, server: false },
|
||||||
selectedLinearElement: { browser: true, export: false, server: false },
|
selectedLinearElement: { browser: true, export: false, server: false },
|
||||||
|
scrollConstraints: { browser: false, export: false, server: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
const _clearAppStateForStorage = <
|
const _clearAppStateForStorage = <
|
||||||
|
@ -209,7 +209,11 @@ import {
|
|||||||
isSomeElementSelected,
|
isSomeElementSelected,
|
||||||
} from "../scene";
|
} from "../scene";
|
||||||
import Scene from "../scene/Scene";
|
import Scene from "../scene/Scene";
|
||||||
import { RenderConfig, ScrollBars } from "../scene/types";
|
import {
|
||||||
|
RenderConfig,
|
||||||
|
ScrollBars,
|
||||||
|
ConstrainedScrollValues,
|
||||||
|
} from "../scene/types";
|
||||||
import { getStateForZoom } from "../scene/zoom";
|
import { getStateForZoom } from "../scene/zoom";
|
||||||
import { findShapeByKey, SHAPES } from "../shapes";
|
import { findShapeByKey, SHAPES } from "../shapes";
|
||||||
import {
|
import {
|
||||||
@ -229,6 +233,7 @@ import {
|
|||||||
FrameNameBoundsCache,
|
FrameNameBoundsCache,
|
||||||
SidebarName,
|
SidebarName,
|
||||||
SidebarTabName,
|
SidebarTabName,
|
||||||
|
ScrollConstraints,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import {
|
import {
|
||||||
debounce,
|
debounce,
|
||||||
@ -257,6 +262,7 @@ import {
|
|||||||
muteFSAbortError,
|
muteFSAbortError,
|
||||||
isTestEnv,
|
isTestEnv,
|
||||||
easeOut,
|
easeOut,
|
||||||
|
isShallowEqual,
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
import {
|
import {
|
||||||
embeddableURLValidator,
|
embeddableURLValidator,
|
||||||
@ -380,6 +386,7 @@ const ExcalidrawAppStateContext = React.createContext<AppState>({
|
|||||||
height: 0,
|
height: 0,
|
||||||
offsetLeft: 0,
|
offsetLeft: 0,
|
||||||
offsetTop: 0,
|
offsetTop: 0,
|
||||||
|
scrollConstraints: null,
|
||||||
});
|
});
|
||||||
ExcalidrawAppStateContext.displayName = "ExcalidrawAppStateContext";
|
ExcalidrawAppStateContext.displayName = "ExcalidrawAppStateContext";
|
||||||
|
|
||||||
@ -478,6 +485,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
lastPointerDown: React.PointerEvent<HTMLElement> | null = null;
|
lastPointerDown: React.PointerEvent<HTMLElement> | null = null;
|
||||||
lastPointerUp: React.PointerEvent<HTMLElement> | PointerEvent | null = null;
|
lastPointerUp: React.PointerEvent<HTMLElement> | PointerEvent | null = null;
|
||||||
lastViewportPosition = { x: 0, y: 0 };
|
lastViewportPosition = { x: 0, y: 0 };
|
||||||
|
private memoizedScrollConstraints: ReturnType<
|
||||||
|
App["calculateConstraints"]
|
||||||
|
> | null = null;
|
||||||
|
|
||||||
constructor(props: AppProps) {
|
constructor(props: AppProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -489,7 +499,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
gridModeEnabled = false,
|
gridModeEnabled = false,
|
||||||
theme = defaultAppState.theme,
|
theme = defaultAppState.theme,
|
||||||
name = defaultAppState.name,
|
name = defaultAppState.name,
|
||||||
|
scrollConstraints,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
...defaultAppState,
|
...defaultAppState,
|
||||||
theme,
|
theme,
|
||||||
@ -501,6 +513,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
name,
|
name,
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
|
scrollConstraints: scrollConstraints ?? null,
|
||||||
};
|
};
|
||||||
this.id = nanoid();
|
this.id = nanoid();
|
||||||
this.library = new Library(this);
|
this.library = new Library(this);
|
||||||
@ -532,6 +545,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
resetCursor: this.resetCursor,
|
resetCursor: this.resetCursor,
|
||||||
updateFrameRendering: this.updateFrameRendering,
|
updateFrameRendering: this.updateFrameRendering,
|
||||||
toggleSidebar: this.toggleSidebar,
|
toggleSidebar: this.toggleSidebar,
|
||||||
|
setScrollConstraints: this.setScrollConstraints,
|
||||||
} as const;
|
} as const;
|
||||||
if (typeof excalidrawRef === "function") {
|
if (typeof excalidrawRef === "function") {
|
||||||
excalidrawRef(api);
|
excalidrawRef(api);
|
||||||
@ -1546,7 +1560,15 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
isLoading: false,
|
isLoading: false,
|
||||||
toast: this.state.toast,
|
toast: this.state.toast,
|
||||||
};
|
};
|
||||||
if (initialData?.scrollToContent) {
|
if (this.props.scrollConstraints) {
|
||||||
|
scene.appState = {
|
||||||
|
...scene.appState,
|
||||||
|
...this.calculateConstrainedScrollCenter(
|
||||||
|
this.props.scrollConstraints,
|
||||||
|
scene.appState,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
} else if (initialData?.scrollToContent) {
|
||||||
scene.appState = {
|
scene.appState = {
|
||||||
...scene.appState,
|
...scene.appState,
|
||||||
...calculateScrollCenter(
|
...calculateScrollCenter(
|
||||||
@ -1557,6 +1579,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
height: this.state.height,
|
height: this.state.height,
|
||||||
offsetTop: this.state.offsetTop,
|
offsetTop: this.state.offsetTop,
|
||||||
offsetLeft: this.state.offsetLeft,
|
offsetLeft: this.state.offsetLeft,
|
||||||
|
scrollConstraints: this.state.scrollConstraints,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
),
|
),
|
||||||
@ -1843,10 +1866,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
componentDidUpdate(prevProps: AppProps, prevState: AppState) {
|
componentDidUpdate(prevProps: AppProps, prevState: AppState) {
|
||||||
this.updateEmbeddables();
|
this.updateEmbeddables();
|
||||||
if (
|
const elementsIncludingDeleted = this.scene.getElementsIncludingDeleted();
|
||||||
!this.state.showWelcomeScreen &&
|
if (!this.state.showWelcomeScreen && !elementsIncludingDeleted.length) {
|
||||||
!this.scene.getElementsIncludingDeleted().length
|
|
||||||
) {
|
|
||||||
this.setState({ showWelcomeScreen: true });
|
this.setState({ showWelcomeScreen: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1973,7 +1994,73 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.renderScene();
|
|
||||||
|
let constraintedScrollState;
|
||||||
|
if (
|
||||||
|
this.state.scrollConstraints &&
|
||||||
|
!this.state.scrollConstraints.isAnimating
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
scrollX,
|
||||||
|
scrollY,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
scrollConstraints,
|
||||||
|
zoom,
|
||||||
|
cursorButton,
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const canUseMemoizedConstraints =
|
||||||
|
isShallowEqual(scrollConstraints, prevState.scrollConstraints ?? {}) &&
|
||||||
|
isShallowEqual(
|
||||||
|
{ width, height, zoom: zoom.value, cursorButton },
|
||||||
|
{
|
||||||
|
width: prevState.width,
|
||||||
|
height: prevState.height,
|
||||||
|
zoom: prevState.zoom.value,
|
||||||
|
cursorButton: prevState.cursorButton,
|
||||||
|
} ?? {},
|
||||||
|
);
|
||||||
|
|
||||||
|
const calculatedConstraints =
|
||||||
|
canUseMemoizedConstraints && !!this.memoizedScrollConstraints
|
||||||
|
? this.memoizedScrollConstraints
|
||||||
|
: this.calculateConstraints({
|
||||||
|
scrollConstraints,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
zoom,
|
||||||
|
cursorButton,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.memoizedScrollConstraints = calculatedConstraints;
|
||||||
|
|
||||||
|
const constrainedScrollValues = this.constrainScrollValues({
|
||||||
|
...calculatedConstraints,
|
||||||
|
scrollX,
|
||||||
|
scrollY,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isViewportOutsideOfConstrainedArea =
|
||||||
|
this.isViewportOutsideOfConstrainedArea({
|
||||||
|
scrollX,
|
||||||
|
scrollY,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
scrollConstraints,
|
||||||
|
});
|
||||||
|
|
||||||
|
constraintedScrollState = this.handleConstrainedScrollStateChange({
|
||||||
|
...constrainedScrollValues,
|
||||||
|
shouldAnimate:
|
||||||
|
isViewportOutsideOfConstrainedArea &&
|
||||||
|
this.state.cursorButton !== "down" &&
|
||||||
|
prevState.zoom.value === this.state.zoom.value &&
|
||||||
|
elementsIncludingDeleted.length > 0, // Do not animate when app is initialized but scene is empty - this would cause flickering
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.renderScene(constraintedScrollState);
|
||||||
this.history.record(this.state, this.scene.getElementsIncludingDeleted());
|
this.history.record(this.state, this.scene.getElementsIncludingDeleted());
|
||||||
|
|
||||||
// Do not notify consumers if we're still loading the scene. Among other
|
// Do not notify consumers if we're still loading the scene. Among other
|
||||||
@ -1989,7 +2076,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderScene = () => {
|
private renderScene = (constrainedScroll?: ConstrainedScrollValues) => {
|
||||||
const cursorButton: {
|
const cursorButton: {
|
||||||
[id: string]: string | undefined;
|
[id: string]: string | undefined;
|
||||||
} = {};
|
} = {};
|
||||||
@ -2060,10 +2147,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
canvas: this.canvas!,
|
canvas: this.canvas!,
|
||||||
renderConfig: {
|
renderConfig: {
|
||||||
selectionColor,
|
selectionColor,
|
||||||
scrollX: this.state.scrollX,
|
scrollX: constrainedScroll?.scrollX ?? this.state.scrollX,
|
||||||
scrollY: this.state.scrollY,
|
scrollY: constrainedScroll?.scrollY ?? this.state.scrollY,
|
||||||
viewBackgroundColor: this.state.viewBackgroundColor,
|
viewBackgroundColor: this.state.viewBackgroundColor,
|
||||||
zoom: this.state.zoom,
|
zoom: constrainedScroll?.zoom ?? this.state.zoom,
|
||||||
remotePointerViewportCoords: pointerViewportCoords,
|
remotePointerViewportCoords: pointerViewportCoords,
|
||||||
remotePointerButton: cursorButton,
|
remotePointerButton: cursorButton,
|
||||||
remoteSelectedElementIds,
|
remoteSelectedElementIds,
|
||||||
@ -2081,7 +2168,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
const scrolledOutside =
|
const scrolledOutside =
|
||||||
// hide when editing text
|
// hide when editing text
|
||||||
isTextElement(this.state.editingElement)
|
isTextElement(this.state.editingElement) ||
|
||||||
|
this.state.scrollConstraints
|
||||||
? false
|
? false
|
||||||
: !atLeastOneVisibleElement && renderingElements.length > 0;
|
: !atLeastOneVisibleElement && renderingElements.length > 0;
|
||||||
if (this.state.scrolledOutside !== scrolledOutside) {
|
if (this.state.scrolledOutside !== scrolledOutside) {
|
||||||
@ -2619,8 +2707,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
/** decimal fraction between 0.1 (10% zoom) and 30 (3000% zoom) */
|
/** decimal fraction between 0.1 (10% zoom) and 30 (3000% zoom) */
|
||||||
value: number,
|
value: number,
|
||||||
) => {
|
) => {
|
||||||
this.setState({
|
this.setState(
|
||||||
...getStateForZoom(
|
getStateForZoom(
|
||||||
{
|
{
|
||||||
viewportX: this.state.width / 2 + this.state.offsetLeft,
|
viewportX: this.state.width / 2 + this.state.offsetLeft,
|
||||||
viewportY: this.state.height / 2 + this.state.offsetTop,
|
viewportY: this.state.height / 2 + this.state.offsetTop,
|
||||||
@ -2628,7 +2716,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
},
|
},
|
||||||
this.state,
|
this.state,
|
||||||
),
|
),
|
||||||
});
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
private cancelInProgresAnimation: (() => void) | null = null;
|
private cancelInProgresAnimation: (() => void) | null = null;
|
||||||
@ -8153,6 +8241,424 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
await setLanguage(currentLang);
|
await setLanguage(currentLang);
|
||||||
this.setAppState({});
|
this.setAppState({});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the scroll constraints of the application state.
|
||||||
|
*
|
||||||
|
* @param scrollConstraints - The new scroll constraints.
|
||||||
|
*/
|
||||||
|
public setScrollConstraints = (
|
||||||
|
scrollConstraints: ScrollConstraints | null,
|
||||||
|
) => {
|
||||||
|
if (scrollConstraints) {
|
||||||
|
const { scrollX, scrollY, width, height, zoom, cursorButton } =
|
||||||
|
this.state;
|
||||||
|
const constrainedScrollValues = this.constrainScrollValues({
|
||||||
|
...this.calculateConstraints({
|
||||||
|
scrollConstraints,
|
||||||
|
zoom,
|
||||||
|
cursorButton,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}),
|
||||||
|
scrollX,
|
||||||
|
scrollY,
|
||||||
|
});
|
||||||
|
this.animateConstrainedScroll({
|
||||||
|
...constrainedScrollValues,
|
||||||
|
opts: {
|
||||||
|
onEndCallback: () => {
|
||||||
|
this.setState({
|
||||||
|
scrollConstraints,
|
||||||
|
viewModeEnabled: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
scrollConstraints: null,
|
||||||
|
viewModeEnabled: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the scroll center coordinates and the optimal zoom level to fit the constrained scrollable area within the viewport.
|
||||||
|
*
|
||||||
|
* This method first calculates the necessary zoom level to fit the entire constrained scrollable area within the viewport.
|
||||||
|
* Then it calculates the constraints for the viewport given the new zoom level and the current scrollable area dimensions.
|
||||||
|
* The function returns an object containing the optimal scroll positions and zoom level.
|
||||||
|
*
|
||||||
|
* @param scrollConstraints - The constraints of the scrollable area including width, height, and position.
|
||||||
|
* @param appState - An object containing the current horizontal and vertical scroll positions.
|
||||||
|
* @returns An object containing the calculated optimal horizontal and vertical scroll positions and zoom level.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* const { scrollX, scrollY, zoom } = this.calculateConstrainedScrollCenter(scrollConstraints, { scrollX, scrollY });
|
||||||
|
*/
|
||||||
|
public calculateConstrainedScrollCenter = (
|
||||||
|
scrollConstraints: AppState["scrollConstraints"],
|
||||||
|
{ scrollX, scrollY }: Pick<AppState, "scrollX" | "scrollY">,
|
||||||
|
): {
|
||||||
|
scrollX: AppState["scrollX"];
|
||||||
|
scrollY: AppState["scrollY"];
|
||||||
|
zoom: AppState["zoom"];
|
||||||
|
} => {
|
||||||
|
const { width, height, zoom } = this.state;
|
||||||
|
|
||||||
|
if (!scrollConstraints) {
|
||||||
|
return { scrollX, scrollY, zoom };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { zoomLevelX, zoomLevelY, maxZoomLevel } = this.calculateZoomLevel(
|
||||||
|
scrollConstraints,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
);
|
||||||
|
|
||||||
|
// The zoom level to contain the whole constrained area in view
|
||||||
|
const _zoom = {
|
||||||
|
value: getNormalizedZoom(
|
||||||
|
maxZoomLevel ?? Math.min(zoomLevelX, zoomLevelY),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const constraints = this.calculateConstraints({
|
||||||
|
scrollConstraints,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
zoom: _zoom,
|
||||||
|
cursorButton: "up",
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
scrollX: constraints.minScrollX,
|
||||||
|
scrollY: constraints.minScrollY,
|
||||||
|
zoom: constraints.constrainedZoom,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the zoom levels necessary to fit the constrained scrollable area within the viewport on the X and Y axes.
|
||||||
|
*
|
||||||
|
* The function considers the dimensions of the scrollable area, the dimensions of the viewport, the viewport zoom factor,
|
||||||
|
* and whether the zoom should be locked. It then calculates the necessary zoom levels for the X and Y axes separately.
|
||||||
|
* If the zoom should be locked, it calculates the maximum zoom level that fits the scrollable area within the viewport,
|
||||||
|
* factoring in the viewport zoom factor. If the zoom should not be locked, the maximum zoom level is set to null.
|
||||||
|
*
|
||||||
|
* @param scrollConstraints - The constraints of the scrollable area including width, height, and position.
|
||||||
|
* @param width - The width of the viewport.
|
||||||
|
* @param height - The height of the viewport.
|
||||||
|
* @returns An object containing the calculated zoom levels for the X and Y axes, and the maximum zoom level if applicable.
|
||||||
|
*/
|
||||||
|
private calculateZoomLevel = (
|
||||||
|
scrollConstraints: ScrollConstraints,
|
||||||
|
width: AppState["width"],
|
||||||
|
height: AppState["height"],
|
||||||
|
) => {
|
||||||
|
const DEFAULT_VIEWPORT_ZOOM_FACTOR = 0.7;
|
||||||
|
|
||||||
|
const lockZoom = scrollConstraints.lockZoom ?? false;
|
||||||
|
const viewportZoomFactor = scrollConstraints.viewportZoomFactor
|
||||||
|
? Math.min(1, Math.max(scrollConstraints.viewportZoomFactor, 0.1))
|
||||||
|
: DEFAULT_VIEWPORT_ZOOM_FACTOR;
|
||||||
|
|
||||||
|
const scrollableWidth = scrollConstraints.width;
|
||||||
|
const scrollableHeight = scrollConstraints.height;
|
||||||
|
const zoomLevelX = width / scrollableWidth;
|
||||||
|
const zoomLevelY = height / scrollableHeight;
|
||||||
|
const maxZoomLevel = lockZoom
|
||||||
|
? getNormalizedZoom(Math.min(zoomLevelX, zoomLevelY) * viewportZoomFactor)
|
||||||
|
: null;
|
||||||
|
return { zoomLevelX, zoomLevelY, maxZoomLevel };
|
||||||
|
};
|
||||||
|
|
||||||
|
private calculateConstraints = ({
|
||||||
|
scrollConstraints,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
zoom,
|
||||||
|
cursorButton,
|
||||||
|
}: {
|
||||||
|
scrollConstraints: ScrollConstraints;
|
||||||
|
width: AppState["width"];
|
||||||
|
height: AppState["height"];
|
||||||
|
zoom: AppState["zoom"];
|
||||||
|
cursorButton: AppState["cursorButton"];
|
||||||
|
}) => {
|
||||||
|
// Set the overscroll allowance percentage
|
||||||
|
const OVERSCROLL_ALLOWANCE_PERCENTAGE = 0.2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the center position of the constrained scroll area.
|
||||||
|
* @returns The X and Y coordinates of the center position.
|
||||||
|
*/
|
||||||
|
const calculateConstrainedScrollCenter = (zoom: number) => {
|
||||||
|
const constrainedScrollCenterX =
|
||||||
|
scrollConstraints.x + (scrollConstraints.width - width / zoom) / -2;
|
||||||
|
const constrainedScrollCenterY =
|
||||||
|
scrollConstraints.y + (scrollConstraints.height - height / zoom) / -2;
|
||||||
|
return { constrainedScrollCenterX, constrainedScrollCenterY };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the overscroll allowance values for the constrained area.
|
||||||
|
* @returns The overscroll allowance values for the X and Y axes.
|
||||||
|
*/
|
||||||
|
const calculateOverscrollAllowance = () => {
|
||||||
|
const overscrollAllowanceX =
|
||||||
|
OVERSCROLL_ALLOWANCE_PERCENTAGE * scrollConstraints.width;
|
||||||
|
const overscrollAllowanceY =
|
||||||
|
OVERSCROLL_ALLOWANCE_PERCENTAGE * scrollConstraints.height;
|
||||||
|
return { overscrollAllowanceX, overscrollAllowanceY };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the minimum and maximum scroll values based on the current state.
|
||||||
|
* @param shouldAdjustForCenteredView - Whether the view should be adjusted for centered view - when constrained area fits the viewport.
|
||||||
|
* @param overscrollAllowanceX - The overscroll allowance value for the X axis.
|
||||||
|
* @param overscrollAllowanceY - The overscroll allowance value for the Y axis.
|
||||||
|
* @param constrainedScrollCenterX - The X coordinate of the constrained scroll area center.
|
||||||
|
* @param constrainedScrollCenterY - The Y coordinate of the constrained scroll area center.
|
||||||
|
* @returns The minimum and maximum scroll values for the X and Y axes.
|
||||||
|
*/
|
||||||
|
const calculateMinMaxScrollValues = (
|
||||||
|
shouldAdjustForCenteredView: boolean,
|
||||||
|
overscrollAllowanceX: number,
|
||||||
|
overscrollAllowanceY: number,
|
||||||
|
constrainedScrollCenterX: number,
|
||||||
|
constrainedScrollCenterY: number,
|
||||||
|
zoom: number,
|
||||||
|
) => {
|
||||||
|
let maxScrollX;
|
||||||
|
let minScrollX;
|
||||||
|
let maxScrollY;
|
||||||
|
let minScrollY;
|
||||||
|
|
||||||
|
if (cursorButton === "down" && shouldAdjustForCenteredView) {
|
||||||
|
maxScrollX = constrainedScrollCenterX + overscrollAllowanceX;
|
||||||
|
minScrollX = constrainedScrollCenterX - overscrollAllowanceX;
|
||||||
|
maxScrollY = constrainedScrollCenterY + overscrollAllowanceY;
|
||||||
|
minScrollY = constrainedScrollCenterY - overscrollAllowanceY;
|
||||||
|
} else if (cursorButton === "down" && !shouldAdjustForCenteredView) {
|
||||||
|
maxScrollX = scrollConstraints.x + overscrollAllowanceX;
|
||||||
|
minScrollX =
|
||||||
|
scrollConstraints.x -
|
||||||
|
scrollConstraints.width +
|
||||||
|
width / zoom -
|
||||||
|
overscrollAllowanceX;
|
||||||
|
maxScrollY = scrollConstraints.y + overscrollAllowanceY;
|
||||||
|
minScrollY =
|
||||||
|
scrollConstraints.y -
|
||||||
|
scrollConstraints.height +
|
||||||
|
height / zoom -
|
||||||
|
overscrollAllowanceY;
|
||||||
|
} else if (cursorButton !== "down" && shouldAdjustForCenteredView) {
|
||||||
|
maxScrollX = constrainedScrollCenterX;
|
||||||
|
minScrollX = constrainedScrollCenterX;
|
||||||
|
maxScrollY = constrainedScrollCenterY;
|
||||||
|
minScrollY = constrainedScrollCenterY;
|
||||||
|
} else {
|
||||||
|
maxScrollX = scrollConstraints.x;
|
||||||
|
minScrollX =
|
||||||
|
scrollConstraints.x - scrollConstraints.width + width / zoom;
|
||||||
|
maxScrollY = scrollConstraints.y;
|
||||||
|
minScrollY =
|
||||||
|
scrollConstraints.y - scrollConstraints.height + height / zoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { maxScrollX, minScrollX, maxScrollY, minScrollY };
|
||||||
|
};
|
||||||
|
|
||||||
|
const { zoomLevelX, zoomLevelY, maxZoomLevel } = this.calculateZoomLevel(
|
||||||
|
scrollConstraints,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
);
|
||||||
|
|
||||||
|
const constrainedZoom = getNormalizedZoom(
|
||||||
|
maxZoomLevel ? Math.max(maxZoomLevel, zoom.value) : zoom.value,
|
||||||
|
);
|
||||||
|
const { constrainedScrollCenterX, constrainedScrollCenterY } =
|
||||||
|
calculateConstrainedScrollCenter(constrainedZoom);
|
||||||
|
const { overscrollAllowanceX, overscrollAllowanceY } =
|
||||||
|
calculateOverscrollAllowance();
|
||||||
|
const shouldAdjustForCenteredView =
|
||||||
|
constrainedZoom <= zoomLevelX || constrainedZoom <= zoomLevelY;
|
||||||
|
const { maxScrollX, minScrollX, maxScrollY, minScrollY } =
|
||||||
|
calculateMinMaxScrollValues(
|
||||||
|
shouldAdjustForCenteredView,
|
||||||
|
overscrollAllowanceX,
|
||||||
|
overscrollAllowanceY,
|
||||||
|
constrainedScrollCenterX,
|
||||||
|
constrainedScrollCenterY,
|
||||||
|
constrainedZoom,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
maxScrollX,
|
||||||
|
minScrollX,
|
||||||
|
maxScrollY,
|
||||||
|
minScrollY,
|
||||||
|
constrainedZoom: {
|
||||||
|
value: constrainedZoom,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constrains the scroll values within the constrained area.
|
||||||
|
* @param maxScrollX - The maximum scroll value for the X axis.
|
||||||
|
* @param minScrollX - The minimum scroll value for the X axis.
|
||||||
|
* @param maxScrollY - The maximum scroll value for the Y axis.
|
||||||
|
* @param minScrollY - The minimum scroll value for the Y axis.
|
||||||
|
* @returns The constrained scroll values for the X and Y axes.
|
||||||
|
*/
|
||||||
|
private constrainScrollValues = ({
|
||||||
|
scrollX,
|
||||||
|
scrollY,
|
||||||
|
maxScrollX,
|
||||||
|
minScrollX,
|
||||||
|
maxScrollY,
|
||||||
|
minScrollY,
|
||||||
|
constrainedZoom,
|
||||||
|
}: {
|
||||||
|
scrollX: number;
|
||||||
|
scrollY: number;
|
||||||
|
maxScrollX: number;
|
||||||
|
minScrollX: number;
|
||||||
|
maxScrollY: number;
|
||||||
|
minScrollY: number;
|
||||||
|
constrainedZoom: AppState["zoom"];
|
||||||
|
}) => {
|
||||||
|
const constrainedScrollX = Math.min(
|
||||||
|
maxScrollX,
|
||||||
|
Math.max(scrollX, minScrollX),
|
||||||
|
);
|
||||||
|
const constrainedScrollY = Math.min(
|
||||||
|
maxScrollY,
|
||||||
|
Math.max(scrollY, minScrollY),
|
||||||
|
);
|
||||||
|
return { constrainedScrollX, constrainedScrollY, constrainedZoom };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animate the scroll values to the constrained area
|
||||||
|
*/
|
||||||
|
private animateConstrainedScroll = ({
|
||||||
|
constrainedScrollX,
|
||||||
|
constrainedScrollY,
|
||||||
|
opts,
|
||||||
|
}: {
|
||||||
|
constrainedScrollX: number;
|
||||||
|
constrainedScrollY: number;
|
||||||
|
opts?: {
|
||||||
|
onStartCallback?: () => void;
|
||||||
|
onEndCallback?: () => void;
|
||||||
|
};
|
||||||
|
}) => {
|
||||||
|
const { scrollX, scrollY, scrollConstraints } = this.state;
|
||||||
|
|
||||||
|
const { onStartCallback, onEndCallback } = opts || {};
|
||||||
|
|
||||||
|
if (!scrollConstraints) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
easeToValuesRAF({
|
||||||
|
fromValues: { scrollX, scrollY },
|
||||||
|
toValues: {
|
||||||
|
scrollX: constrainedScrollX,
|
||||||
|
scrollY: constrainedScrollY,
|
||||||
|
},
|
||||||
|
onStep: ({ scrollX, scrollY }) => {
|
||||||
|
this.setState({ scrollX, scrollY });
|
||||||
|
},
|
||||||
|
onStart: () => {
|
||||||
|
this.setState({
|
||||||
|
scrollConstraints: { ...scrollConstraints, isAnimating: true },
|
||||||
|
});
|
||||||
|
onStartCallback && onStartCallback();
|
||||||
|
},
|
||||||
|
onEnd: () => {
|
||||||
|
this.setState({
|
||||||
|
scrollConstraints: { ...scrollConstraints, isAnimating: false },
|
||||||
|
});
|
||||||
|
onEndCallback && onEndCallback();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private isViewportOutsideOfConstrainedArea = ({
|
||||||
|
scrollX,
|
||||||
|
scrollY,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
scrollConstraints,
|
||||||
|
}: {
|
||||||
|
scrollX: AppState["scrollX"];
|
||||||
|
scrollY: AppState["scrollY"];
|
||||||
|
width: AppState["width"];
|
||||||
|
height: AppState["height"];
|
||||||
|
scrollConstraints: AppState["scrollConstraints"];
|
||||||
|
}) => {
|
||||||
|
if (!scrollConstraints) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
scrollX < scrollConstraints.x ||
|
||||||
|
scrollX + width > scrollConstraints.x + scrollConstraints.width ||
|
||||||
|
scrollY < scrollConstraints.y ||
|
||||||
|
scrollY + height > scrollConstraints.y + scrollConstraints.height
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the state change based on the constrained scroll values.
|
||||||
|
* Also handles the animation to the constrained area when the viewport is outside of constrained area.
|
||||||
|
* @param constrainedScrollX - The constrained scroll value for the X axis.
|
||||||
|
* @param constrainedScrollY - The constrained scroll value for the Y axis.
|
||||||
|
* @returns The constrained state if the state has changed, when needs to be passed into render function, otherwise null.
|
||||||
|
*/
|
||||||
|
private handleConstrainedScrollStateChange = ({
|
||||||
|
constrainedScrollX,
|
||||||
|
constrainedScrollY,
|
||||||
|
constrainedZoom,
|
||||||
|
shouldAnimate,
|
||||||
|
}: {
|
||||||
|
constrainedScrollX: number;
|
||||||
|
constrainedScrollY: number;
|
||||||
|
constrainedZoom: AppState["zoom"];
|
||||||
|
shouldAnimate?: boolean;
|
||||||
|
}) => {
|
||||||
|
const { scrollX, scrollY } = this.state;
|
||||||
|
const isStateChanged =
|
||||||
|
constrainedScrollX !== scrollX || constrainedScrollY !== scrollY;
|
||||||
|
|
||||||
|
if (isStateChanged) {
|
||||||
|
if (shouldAnimate) {
|
||||||
|
this.animateConstrainedScroll({
|
||||||
|
constrainedScrollX,
|
||||||
|
constrainedScrollY,
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const constrainedState = {
|
||||||
|
scrollX: constrainedScrollX,
|
||||||
|
scrollY: constrainedScrollY,
|
||||||
|
zoom: constrainedZoom,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setState(constrainedState);
|
||||||
|
return constrainedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
@ -46,7 +46,7 @@ import { normalizeLink } from "./url";
|
|||||||
|
|
||||||
type RestoredAppState = Omit<
|
type RestoredAppState = Omit<
|
||||||
AppState,
|
AppState,
|
||||||
"offsetTop" | "offsetLeft" | "width" | "height"
|
"offsetTop" | "offsetLeft" | "width" | "height" | "scrollConstraints"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const AllowedExcalidrawActiveTools: Record<
|
export const AllowedExcalidrawActiveTools: Record<
|
||||||
|
@ -731,6 +731,13 @@ const ExcalidrawWrapper = () => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
scrollConstraints={{
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 2560,
|
||||||
|
height: 1300,
|
||||||
|
lockZoom: true,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<AppMainMenu
|
<AppMainMenu
|
||||||
setCollabDialogShown={setCollabDialogShown}
|
setCollabDialogShown={setCollabDialogShown}
|
||||||
|
@ -65,6 +65,7 @@ const canvas = exportToCanvas(
|
|||||||
offsetLeft: 0,
|
offsetLeft: 0,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
|
scrollConstraints: null,
|
||||||
},
|
},
|
||||||
{}, // files
|
{}, // files
|
||||||
{
|
{
|
||||||
|
@ -42,6 +42,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
|
|||||||
onPointerDown,
|
onPointerDown,
|
||||||
onScrollChange,
|
onScrollChange,
|
||||||
children,
|
children,
|
||||||
|
scrollConstraints,
|
||||||
validateEmbeddable,
|
validateEmbeddable,
|
||||||
renderEmbeddable,
|
renderEmbeddable,
|
||||||
} = props;
|
} = props;
|
||||||
@ -100,7 +101,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
|
|||||||
onPointerUpdate={onPointerUpdate}
|
onPointerUpdate={onPointerUpdate}
|
||||||
renderTopRightUI={renderTopRightUI}
|
renderTopRightUI={renderTopRightUI}
|
||||||
langCode={langCode}
|
langCode={langCode}
|
||||||
viewModeEnabled={viewModeEnabled}
|
viewModeEnabled={viewModeEnabled /* || !!scrollConstraints */}
|
||||||
zenModeEnabled={zenModeEnabled}
|
zenModeEnabled={zenModeEnabled}
|
||||||
gridModeEnabled={gridModeEnabled}
|
gridModeEnabled={gridModeEnabled}
|
||||||
libraryReturnUrl={libraryReturnUrl}
|
libraryReturnUrl={libraryReturnUrl}
|
||||||
@ -117,6 +118,7 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
|
|||||||
onLinkOpen={onLinkOpen}
|
onLinkOpen={onLinkOpen}
|
||||||
onPointerDown={onPointerDown}
|
onPointerDown={onPointerDown}
|
||||||
onScrollChange={onScrollChange}
|
onScrollChange={onScrollChange}
|
||||||
|
scrollConstraints={scrollConstraints}
|
||||||
validateEmbeddable={validateEmbeddable}
|
validateEmbeddable={validateEmbeddable}
|
||||||
renderEmbeddable={renderEmbeddable}
|
renderEmbeddable={renderEmbeddable}
|
||||||
>
|
>
|
||||||
|
@ -64,7 +64,14 @@ export const exportToCanvas = ({
|
|||||||
const { exportBackground, viewBackgroundColor } = restoredAppState;
|
const { exportBackground, viewBackgroundColor } = restoredAppState;
|
||||||
return _exportToCanvas(
|
return _exportToCanvas(
|
||||||
passElementsSafely(restoredElements),
|
passElementsSafely(restoredElements),
|
||||||
{ ...restoredAppState, offsetTop: 0, offsetLeft: 0, width: 0, height: 0 },
|
{
|
||||||
|
...restoredAppState,
|
||||||
|
offsetTop: 0,
|
||||||
|
offsetLeft: 0,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
scrollConstraints: null,
|
||||||
|
},
|
||||||
files || {},
|
files || {},
|
||||||
{ exportBackground, exportPadding, viewBackgroundColor },
|
{ exportBackground, exportPadding, viewBackgroundColor },
|
||||||
(width: number, height: number) => {
|
(width: number, height: number) => {
|
||||||
|
@ -60,3 +60,8 @@ export type ScrollBars = {
|
|||||||
height: number;
|
height: number;
|
||||||
} | null;
|
} | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ConstrainedScrollValues = Pick<
|
||||||
|
AppState,
|
||||||
|
"scrollX" | "scrollY" | "zoom"
|
||||||
|
> | null;
|
||||||
|
@ -346,6 +346,7 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -539,6 +540,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -738,6 +740,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -1111,6 +1114,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -1484,6 +1488,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -1683,6 +1688,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -1919,6 +1925,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -2222,6 +2229,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
|||||||
"id1": true,
|
"id1": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -2609,6 +2617,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -3488,6 +3497,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -3861,6 +3871,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -4236,6 +4247,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
|||||||
"id1": true,
|
"id1": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -4968,6 +4980,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,
|
||||||
@ -5548,6 +5561,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,
|
||||||
@ -6050,6 +6064,7 @@ exports[`contextMenu element > shows context menu for canvas > [end of test] app
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -6446,6 +6461,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -6820,6 +6836,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] ap
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
|
@ -74,6 +74,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
|||||||
"id2": true,
|
"id2": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -527,6 +528,7 @@ exports[`given element A and group of elements B and given both are selected whe
|
|||||||
"id2": true,
|
"id2": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -978,6 +980,7 @@ exports[`regression tests > Cmd/Ctrl-click exclusively select element under poin
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -1808,6 +1811,7 @@ exports[`regression tests > Drags selected element when hitting only bounding bo
|
|||||||
"id0": true,
|
"id0": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -2018,6 +2022,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,
|
||||||
@ -2469,6 +2474,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,
|
||||||
@ -2706,6 +2712,7 @@ exports[`regression tests > arrow keys > [end of test] appState 1`] = `
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -2873,6 +2880,7 @@ exports[`regression tests > can drag element that covers another element, while
|
|||||||
"id2": true,
|
"id2": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -3312,6 +3320,7 @@ exports[`regression tests > change the properties of a shape > [end of test] app
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -3608,6 +3617,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,
|
||||||
@ -3850,6 +3860,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,
|
||||||
@ -4103,6 +4114,7 @@ exports[`regression tests > click to select a shape > [end of test] appState 1`]
|
|||||||
"id1": true,
|
"id1": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -4342,6 +4354,7 @@ exports[`regression tests > click-drag to select a group > [end of test] appStat
|
|||||||
"id2": true,
|
"id2": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -4712,6 +4725,7 @@ exports[`regression tests > deselects group of selected elements on pointer down
|
|||||||
"id1": true,
|
"id1": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -5006,6 +5020,7 @@ exports[`regression tests > deselects group of selected elements on pointer up w
|
|||||||
"id1": true,
|
"id1": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -5271,6 +5286,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,
|
||||||
@ -5464,6 +5480,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,
|
||||||
@ -5627,6 +5644,7 @@ exports[`regression tests > double click to edit a group > [end of test] appStat
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -6079,6 +6097,7 @@ exports[`regression tests > drags selected elements from point inside common bou
|
|||||||
"id1": true,
|
"id1": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -6390,6 +6409,7 @@ exports[`regression tests > draw every type of shape > [end of test] appState 1`
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -8457,6 +8477,7 @@ exports[`regression tests > given a group of selected elements with an element t
|
|||||||
"id2": true,
|
"id2": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -8797,6 +8818,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,
|
||||||
@ -9037,6 +9059,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,
|
||||||
@ -9233,6 +9256,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,
|
||||||
@ -9499,6 +9523,7 @@ exports[`regression tests > key 2 selects rectangle tool > [end of test] appStat
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -9664,6 +9689,7 @@ exports[`regression tests > key 3 selects diamond tool > [end of test] appState
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -9829,6 +9855,7 @@ exports[`regression tests > key 4 selects ellipse tool > [end of test] appState
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -9994,6 +10021,7 @@ exports[`regression tests > key 5 selects arrow tool > [end of test] appState 1`
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -10197,6 +10225,7 @@ exports[`regression tests > key 6 selects line tool > [end of test] appState 1`]
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -10400,6 +10429,7 @@ exports[`regression tests > key 7 selects freedraw tool > [end of test] appState
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -10583,6 +10613,7 @@ exports[`regression tests > key a selects arrow tool > [end of test] appState 1`
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -10786,6 +10817,7 @@ exports[`regression tests > key d selects diamond tool > [end of test] appState
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -10951,6 +10983,7 @@ exports[`regression tests > key l selects line tool > [end of test] appState 1`]
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -11154,6 +11187,7 @@ exports[`regression tests > key o selects ellipse tool > [end of test] appState
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -11319,6 +11353,7 @@ exports[`regression tests > key p selects freedraw tool > [end of test] appState
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -11502,6 +11537,7 @@ exports[`regression tests > key r selects rectangle tool > [end of test] appStat
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -11671,6 +11707,7 @@ exports[`regression tests > make a group and duplicate it > [end of test] appSta
|
|||||||
"id2": true,
|
"id2": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -12330,6 +12367,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,
|
||||||
@ -12567,6 +12605,7 @@ exports[`regression tests > pinch-to-zoom works > [end of test] appState 1`] = `
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": -2.916666666666668,
|
"scrollX": -2.916666666666668,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -12687,6 +12726,7 @@ exports[`regression tests > rerenders UI on language change > [end of test] appS
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -12809,6 +12849,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,
|
||||||
@ -12975,6 +13016,7 @@ exports[`regression tests > shift-click to multiselect, then drag > [end of test
|
|||||||
"id1": true,
|
"id1": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -13290,6 +13332,7 @@ exports[`regression tests > should group elements and ungroup them > [end of tes
|
|||||||
"id2": true,
|
"id2": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -13844,6 +13887,7 @@ exports[`regression tests > should show fill icons when element has non transpar
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -14057,6 +14101,7 @@ exports[`regression tests > single-clicking on a subgroup of a selected group sh
|
|||||||
"id6": true,
|
"id6": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -14905,6 +14950,7 @@ exports[`regression tests > spacebar + drag scrolls the canvas > [end of test] a
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 60,
|
"scrollX": 60,
|
||||||
"scrollY": 60,
|
"scrollY": 60,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -15027,6 +15073,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,
|
||||||
@ -15840,6 +15887,7 @@ exports[`regression tests > switches from group of selected elements to another
|
|||||||
"id2": true,
|
"id2": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -16236,6 +16284,7 @@ exports[`regression tests > switches selected element on pointer down > [end of
|
|||||||
"id1": true,
|
"id1": true,
|
||||||
},
|
},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -16501,6 +16550,7 @@ exports[`regression tests > two-finger scroll works > [end of test] appState 1`]
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 10,
|
"scrollX": 10,
|
||||||
"scrollY": -10,
|
"scrollY": -10,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -16621,6 +16671,7 @@ exports[`regression tests > undo/redo drawing an element > [end of test] appStat
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -17101,6 +17152,7 @@ exports[`regression tests > updates fontSize & fontFamily appState > [end of tes
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
@ -17221,6 +17273,7 @@ exports[`regression tests > zoom hotkeys > [end of test] appState 1`] = `
|
|||||||
"pendingImageElementId": null,
|
"pendingImageElementId": null,
|
||||||
"previousSelectedElementIds": {},
|
"previousSelectedElementIds": {},
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
|
"scrollConstraints": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
|
13
src/types.ts
13
src/types.ts
@ -238,6 +238,7 @@ export type AppState = {
|
|||||||
pendingImageElementId: ExcalidrawImageElement["id"] | null;
|
pendingImageElementId: ExcalidrawImageElement["id"] | null;
|
||||||
showHyperlinkPopup: false | "info" | "editor";
|
showHyperlinkPopup: false | "info" | "editor";
|
||||||
selectedLinearElement: LinearElementEditor | null;
|
selectedLinearElement: LinearElementEditor | null;
|
||||||
|
scrollConstraints: ScrollConstraints | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UIAppState = Omit<
|
export type UIAppState = Omit<
|
||||||
@ -376,6 +377,7 @@ export interface ExcalidrawProps {
|
|||||||
) => void;
|
) => void;
|
||||||
onScrollChange?: (scrollX: number, scrollY: number) => void;
|
onScrollChange?: (scrollX: number, scrollY: number) => void;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
scrollConstraints?: AppState["scrollConstraints"];
|
||||||
validateEmbeddable?:
|
validateEmbeddable?:
|
||||||
| boolean
|
| boolean
|
||||||
| string[]
|
| string[]
|
||||||
@ -574,6 +576,7 @@ export type ExcalidrawImperativeAPI = {
|
|||||||
* used in conjunction with view mode (props.viewModeEnabled).
|
* used in conjunction with view mode (props.viewModeEnabled).
|
||||||
*/
|
*/
|
||||||
updateFrameRendering: InstanceType<typeof App>["updateFrameRendering"];
|
updateFrameRendering: InstanceType<typeof App>["updateFrameRendering"];
|
||||||
|
setScrollConstraints: InstanceType<typeof App>["setScrollConstraints"];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Device = Readonly<{
|
export type Device = Readonly<{
|
||||||
@ -602,3 +605,13 @@ export type FrameNameBoundsCache = {
|
|||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ScrollConstraints = {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
isAnimating?: boolean;
|
||||||
|
viewportZoomFactor?: number;
|
||||||
|
lockZoom?: boolean;
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user