1
0
mirror of https://github.com/excalidraw/excalidraw.git synced 2024-11-10 11:35:52 +01:00

feat: enable panning/zoom while in wysiwyg (#8437)

This commit is contained in:
David Luzar 2024-08-29 00:42:46 +02:00 committed by GitHub
parent ea7c702cfc
commit 00af35c692
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 67 additions and 25 deletions

@ -49,6 +49,7 @@ import {
} from "./icons"; } from "./icons";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { useTunnels } from "../context/tunnels"; import { useTunnels } from "../context/tunnels";
import { CLASSES } from "../constants";
export const canChangeStrokeColor = ( export const canChangeStrokeColor = (
appState: UIAppState, appState: UIAppState,
@ -426,7 +427,7 @@ export const ZoomActions = ({
renderAction: ActionManager["renderAction"]; renderAction: ActionManager["renderAction"];
zoom: Zoom; zoom: Zoom;
}) => ( }) => (
<Stack.Col gap={1} className="zoom-actions"> <Stack.Col gap={1} className={CLASSES.ZOOM_ACTIONS}>
<Stack.Row align="center"> <Stack.Row align="center">
{renderAction("zoomOut")} {renderAction("zoomOut")}
{renderAction("resetZoom")} {renderAction("resetZoom")}

@ -2578,6 +2578,11 @@ class App extends React.Component<AppProps, AppState> {
addEventListener(window, EVENT.RESIZE, this.onResize, false), addEventListener(window, EVENT.RESIZE, this.onResize, false),
addEventListener(window, EVENT.UNLOAD, this.onUnload, false), addEventListener(window, EVENT.UNLOAD, this.onUnload, false),
addEventListener(window, EVENT.BLUR, this.onBlur, false), addEventListener(window, EVENT.BLUR, this.onBlur, false),
addEventListener(
this.excalidrawContainerRef.current,
EVENT.WHEEL,
this.handleWheel,
),
addEventListener( addEventListener(
this.excalidrawContainerRef.current, this.excalidrawContainerRef.current,
EVENT.DRAG_OVER, EVENT.DRAG_OVER,
@ -6384,8 +6389,8 @@ class App extends React.Component<AppProps, AppState> {
}; };
// Returns whether the event is a panning // Returns whether the event is a panning
private handleCanvasPanUsingWheelOrSpaceDrag = ( public handleCanvasPanUsingWheelOrSpaceDrag = (
event: React.PointerEvent<HTMLElement>, event: React.PointerEvent<HTMLElement> | MouseEvent,
): boolean => { ): boolean => {
if ( if (
!( !(
@ -6394,13 +6399,16 @@ class App extends React.Component<AppProps, AppState> {
(event.button === POINTER_BUTTON.MAIN && isHoldingSpace) || (event.button === POINTER_BUTTON.MAIN && isHoldingSpace) ||
isHandToolActive(this.state) || isHandToolActive(this.state) ||
this.state.viewModeEnabled) this.state.viewModeEnabled)
) || )
this.state.editingTextElement
) { ) {
return false; return false;
} }
isPanning = true; isPanning = true;
event.preventDefault();
if (!this.state.editingTextElement) {
// preventing defualt while text editing messes with cursor/focus
event.preventDefault();
}
let nextPastePrevented = false; let nextPastePrevented = false;
const isLinux = const isLinux =
@ -9472,7 +9480,6 @@ class App extends React.Component<AppProps, AppState> {
// NOTE wheel, touchstart, touchend events must be registered outside // NOTE wheel, touchstart, touchend events must be registered outside
// of react because react binds them them passively (so we can't prevent // of react because react binds them them passively (so we can't prevent
// default on them) // default on them)
this.interactiveCanvas.addEventListener(EVENT.WHEEL, this.handleWheel);
this.interactiveCanvas.addEventListener( this.interactiveCanvas.addEventListener(
EVENT.TOUCH_START, EVENT.TOUCH_START,
this.onTouchStart, this.onTouchStart,
@ -9480,10 +9487,6 @@ class App extends React.Component<AppProps, AppState> {
this.interactiveCanvas.addEventListener(EVENT.TOUCH_END, this.onTouchEnd); this.interactiveCanvas.addEventListener(EVENT.TOUCH_END, this.onTouchEnd);
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
} else { } else {
this.interactiveCanvas?.removeEventListener(
EVENT.WHEEL,
this.handleWheel,
);
this.interactiveCanvas?.removeEventListener( this.interactiveCanvas?.removeEventListener(
EVENT.TOUCH_START, EVENT.TOUCH_START,
this.onTouchStart, this.onTouchStart,
@ -10078,7 +10081,19 @@ class App extends React.Component<AppProps, AppState> {
( (
event: WheelEvent | React.WheelEvent<HTMLDivElement | HTMLCanvasElement>, event: WheelEvent | React.WheelEvent<HTMLDivElement | HTMLCanvasElement>,
) => { ) => {
// if not scrolling on canvas/wysiwyg, ignore
if (
!(
event.target instanceof HTMLCanvasElement ||
event.target instanceof HTMLTextAreaElement ||
event.target instanceof HTMLIFrameElement
)
) {
return;
}
event.preventDefault(); event.preventDefault();
if (isPanning) { if (isPanning) {
return; return;
} }

@ -112,6 +112,7 @@ export const ENV = {
export const CLASSES = { export const CLASSES = {
SHAPE_ACTIONS_MENU: "App-menu__left", SHAPE_ACTIONS_MENU: "App-menu__left",
ZOOM_ACTIONS: "zoom-actions",
}; };
/** /**

@ -11,7 +11,7 @@ import {
isBoundToContainer, isBoundToContainer,
isTextElement, isTextElement,
} from "./typeChecks"; } from "./typeChecks";
import { CLASSES, isSafari } from "../constants"; import { CLASSES, isSafari, POINTER_BUTTON } from "../constants";
import type { import type {
ExcalidrawElement, ExcalidrawElement,
ExcalidrawLinearElement, ExcalidrawLinearElement,
@ -38,7 +38,11 @@ import {
actionDecreaseFontSize, actionDecreaseFontSize,
actionIncreaseFontSize, actionIncreaseFontSize,
} from "../actions/actionProperties"; } from "../actions/actionProperties";
import { actionZoomIn, actionZoomOut } from "../actions/actionCanvas"; import {
actionResetZoom,
actionZoomIn,
actionZoomOut,
} from "../actions/actionCanvas";
import type App from "../components/App"; import type App from "../components/App";
import { LinearElementEditor } from "./linearElementEditor"; import { LinearElementEditor } from "./linearElementEditor";
import { parseClipboard } from "../clipboard"; import { parseClipboard } from "../clipboard";
@ -379,6 +383,10 @@ export const textWysiwyg = ({
event.preventDefault(); event.preventDefault();
app.actionManager.executeAction(actionZoomOut); app.actionManager.executeAction(actionZoomOut);
updateWysiwygStyle(); updateWysiwygStyle();
} else if (!event.shiftKey && actionResetZoom.keyTest(event)) {
event.preventDefault();
app.actionManager.executeAction(actionResetZoom);
updateWysiwygStyle();
} else if (actionDecreaseFontSize.keyTest(event)) { } else if (actionDecreaseFontSize.keyTest(event)) {
app.actionManager.executeAction(actionDecreaseFontSize); app.actionManager.executeAction(actionDecreaseFontSize);
} else if (actionIncreaseFontSize.keyTest(event)) { } else if (actionIncreaseFontSize.keyTest(event)) {
@ -593,6 +601,7 @@ export const textWysiwyg = ({
window.removeEventListener("blur", handleSubmit); window.removeEventListener("blur", handleSubmit);
window.removeEventListener("beforeunload", handleSubmit); window.removeEventListener("beforeunload", handleSubmit);
unbindUpdate(); unbindUpdate();
unbindOnScroll();
editable.remove(); editable.remove();
}; };
@ -619,10 +628,29 @@ export const textWysiwyg = ({
}); });
}; };
const temporarilyDisableSubmit = () => {
editable.onblur = null;
window.addEventListener("pointerup", bindBlurEvent);
// handle edge-case where pointerup doesn't fire e.g. due to user
// alt-tabbing away
window.addEventListener("blur", handleSubmit);
};
// prevent blur when changing properties from the menu // prevent blur when changing properties from the menu
const onPointerDown = (event: MouseEvent) => { const onPointerDown = (event: MouseEvent) => {
const target = event?.target; const target = event?.target;
// panning canvas
if (event.button === POINTER_BUTTON.WHEEL) {
// trying to pan by clicking inside text area itself -> handle here
if (target instanceof HTMLTextAreaElement) {
event.preventDefault();
app.handleCanvasPanUsingWheelOrSpaceDrag(event);
}
temporarilyDisableSubmit();
return;
}
const isPropertiesTrigger = const isPropertiesTrigger =
target instanceof HTMLElement && target instanceof HTMLElement &&
target.classList.contains("properties-trigger"); target.classList.contains("properties-trigger");
@ -630,17 +658,14 @@ export const textWysiwyg = ({
if ( if (
((event.target instanceof HTMLElement || ((event.target instanceof HTMLElement ||
event.target instanceof SVGElement) && event.target instanceof SVGElement) &&
event.target.closest(`.${CLASSES.SHAPE_ACTIONS_MENU}`) && event.target.closest(
`.${CLASSES.SHAPE_ACTIONS_MENU}, .${CLASSES.ZOOM_ACTIONS}`,
) &&
!isWritableElement(event.target)) || !isWritableElement(event.target)) ||
isPropertiesTrigger isPropertiesTrigger
) { ) {
editable.onblur = null; temporarilyDisableSubmit();
window.addEventListener("pointerup", bindBlurEvent);
// handle edge-case where pointerup doesn't fire e.g. due to user
// alt-tabbing away
window.addEventListener("blur", handleSubmit);
} else if ( } else if (
event.target instanceof HTMLElement &&
event.target instanceof HTMLCanvasElement && event.target instanceof HTMLCanvasElement &&
// Vitest simply ignores stopPropagation, capture-mode, or rAF // Vitest simply ignores stopPropagation, capture-mode, or rAF
// so without introducing crazier hacks, nothing we can do // so without introducing crazier hacks, nothing we can do
@ -659,7 +684,7 @@ export const textWysiwyg = ({
}; };
// handle updates of textElement properties of editing element // handle updates of textElement properties of editing element
const unbindUpdate = Scene.getScene(element)!.onUpdate(() => { const unbindUpdate = app.scene.onUpdate(() => {
updateWysiwygStyle(); updateWysiwygStyle();
const isPopupOpened = !!document.activeElement?.closest( const isPopupOpened = !!document.activeElement?.closest(
".properties-content", ".properties-content",
@ -669,6 +694,10 @@ export const textWysiwyg = ({
} }
}); });
const unbindOnScroll = app.onScrollChangeEmitter.on(() => {
updateWysiwygStyle();
});
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
let isDestroyed = false; let isDestroyed = false;
@ -699,10 +728,6 @@ export const textWysiwyg = ({
requestAnimationFrame(() => { requestAnimationFrame(() => {
window.addEventListener("pointerdown", onPointerDown, { capture: true }); window.addEventListener("pointerdown", onPointerDown, { capture: true });
}); });
window.addEventListener("wheel", stopEvent, {
passive: false,
capture: true,
});
window.addEventListener("beforeunload", handleSubmit); window.addEventListener("beforeunload", handleSubmit);
excalidrawContainer excalidrawContainer
?.querySelector(".excalidraw-textEditorContainer")! ?.querySelector(".excalidraw-textEditorContainer")!