feat: add possibility to limit scroll area
This commit is contained in:
parent
2e46e27490
commit
75f8e904cc
@ -98,6 +98,7 @@ export const getDefaultAppState = (): Omit<
|
||||
pendingImageElementId: null,
|
||||
showHyperlinkPopup: false,
|
||||
selectedLinearElement: null,
|
||||
scrollConstraints: null,
|
||||
};
|
||||
};
|
||||
|
||||
@ -204,6 +205,7 @@ const APP_STATE_STORAGE_CONF = (<
|
||||
pendingImageElementId: { browser: false, export: false, server: false },
|
||||
showHyperlinkPopup: { browser: false, export: false, server: false },
|
||||
selectedLinearElement: { browser: true, export: false, server: false },
|
||||
scrollConstraints: { browser: true, export: false, server: false },
|
||||
});
|
||||
|
||||
const _clearAppStateForStorage = <
|
||||
|
@ -506,6 +506,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
resetCursor: this.resetCursor,
|
||||
updateFrameRendering: this.updateFrameRendering,
|
||||
toggleSidebar: this.toggleSidebar,
|
||||
setScrollConstraints: this.setScrollConstraints,
|
||||
} as const;
|
||||
if (typeof excalidrawRef === "function") {
|
||||
excalidrawRef(api);
|
||||
@ -1728,7 +1729,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
const scrolledOutside =
|
||||
// hide when editing text
|
||||
isTextElement(this.state.editingElement)
|
||||
isTextElement(this.state.editingElement) ||
|
||||
this.state.scrollConstraints
|
||||
? false
|
||||
: !atLeastOneVisibleElement && renderingElements.length > 0;
|
||||
if (this.state.scrolledOutside !== scrolledOutside) {
|
||||
@ -2369,7 +2371,25 @@ class App extends React.Component<AppProps, AppState> {
|
||||
state,
|
||||
) => {
|
||||
this.cancelInProgresAnimation?.();
|
||||
this.setState(state);
|
||||
|
||||
// When there are no scroll constraints, update the state directly
|
||||
if (!this.state.scrollConstraints) {
|
||||
this.setState(state);
|
||||
}
|
||||
|
||||
// If state is a function, we generate the new state and then apply scroll constraints
|
||||
if (typeof state === "function") {
|
||||
this.setState((prevState, props) => {
|
||||
// Generate new state
|
||||
const newState = state(prevState, props);
|
||||
|
||||
// Apply scroll constraints to the new state
|
||||
return this.constrainScroll(newState);
|
||||
});
|
||||
} else {
|
||||
// If state is not a function, apply scroll constraints directly before updating the state
|
||||
this.setState(this.constrainScroll(state));
|
||||
}
|
||||
};
|
||||
|
||||
setToast = (
|
||||
@ -7607,6 +7627,74 @@ class App extends React.Component<AppProps, AppState> {
|
||||
await setLanguage(currentLang);
|
||||
this.setAppState({});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scroll constraints of the application state.
|
||||
*
|
||||
* @param scrollConstraints - The new scroll constraints.
|
||||
*/
|
||||
public setScrollConstraints = (
|
||||
scrollConstraints: AppState["scrollConstraints"],
|
||||
) => {
|
||||
this.setState({
|
||||
scrollConstraints,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Constrains the scroll position of the app state within the defined scroll constraints.
|
||||
* The scroll position is adjusted based on the application's current state including zoom level and viewport dimensions.
|
||||
*
|
||||
* @param nextState - The next state of the application, or a subset of the application state.
|
||||
* @returns The modified next state with scrollX and scrollY constrained to the scroll constraints.
|
||||
*/
|
||||
private constrainScroll = <K extends keyof AppState>(
|
||||
nextState: AppState | Pick<AppState, K> | null,
|
||||
) => {
|
||||
const { scrollX, scrollY, scrollConstraints, width, height, zoom } =
|
||||
this.state;
|
||||
|
||||
// When no scroll constraints are set, return the nextState as is
|
||||
if (!scrollConstraints || !nextState) {
|
||||
return nextState;
|
||||
}
|
||||
|
||||
// Calculate scaled width and height
|
||||
const scaledWidth = width / zoom.value;
|
||||
const scaledHeight = height / zoom.value;
|
||||
|
||||
// Set default constrainedScrollX and constrainedScrollY values
|
||||
let constrainedScrollX = scrollX;
|
||||
let constrainedScrollY = scrollY;
|
||||
|
||||
// If scrollX is part of the nextState, constrain it within the scroll constraints
|
||||
if ("scrollX" in nextState) {
|
||||
constrainedScrollX = Math.min(
|
||||
scrollConstraints.x,
|
||||
Math.max(
|
||||
nextState.scrollX,
|
||||
scrollConstraints.x - scrollConstraints.width + scaledWidth,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// If scrollY is part of the nextState, constrain it within the scroll constraints
|
||||
if ("scrollY" in nextState) {
|
||||
constrainedScrollY = Math.min(
|
||||
scrollConstraints.y,
|
||||
Math.max(
|
||||
nextState.scrollY,
|
||||
scrollConstraints.y - scrollConstraints.height + scaledHeight,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...nextState,
|
||||
scrollX: constrainedScrollX,
|
||||
scrollY: constrainedScrollY,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
@ -223,6 +223,12 @@ export type AppState = {
|
||||
pendingImageElementId: ExcalidrawImageElement["id"] | null;
|
||||
showHyperlinkPopup: false | "info" | "editor";
|
||||
selectedLinearElement: LinearElementEditor | null;
|
||||
scrollConstraints: {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type UIAppState = Omit<
|
||||
@ -549,6 +555,7 @@ export type ExcalidrawImperativeAPI = {
|
||||
* used in conjunction with view mode (props.viewModeEnabled).
|
||||
*/
|
||||
updateFrameRendering: InstanceType<typeof App>["updateFrameRendering"];
|
||||
setScrollConstraints: InstanceType<typeof App>["setScrollConstraints"];
|
||||
};
|
||||
|
||||
export type Device = Readonly<{
|
||||
|
Loading…
x
Reference in New Issue
Block a user