Browse Source

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

David Luzar 1 year ago
parent
commit
00af35c692

+ 2 - 1
packages/excalidraw/components/Actions.tsx

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

+ 25 - 10
packages/excalidraw/components/App.tsx

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

+ 1 - 0
packages/excalidraw/constants.ts

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

+ 39 - 14
packages/excalidraw/element/textWysiwyg.tsx

@@ -11,7 +11,7 @@ import {
   isBoundToContainer,
   isTextElement,
 } from "./typeChecks";
-import { CLASSES, isSafari } from "../constants";
+import { CLASSES, isSafari, POINTER_BUTTON } from "../constants";
 import type {
   ExcalidrawElement,
   ExcalidrawLinearElement,
@@ -38,7 +38,11 @@ import {
   actionDecreaseFontSize,
   actionIncreaseFontSize,
 } from "../actions/actionProperties";
-import { actionZoomIn, actionZoomOut } from "../actions/actionCanvas";
+import {
+  actionResetZoom,
+  actionZoomIn,
+  actionZoomOut,
+} from "../actions/actionCanvas";
 import type App from "../components/App";
 import { LinearElementEditor } from "./linearElementEditor";
 import { parseClipboard } from "../clipboard";
@@ -379,6 +383,10 @@ export const textWysiwyg = ({
       event.preventDefault();
       app.actionManager.executeAction(actionZoomOut);
       updateWysiwygStyle();
+    } else if (!event.shiftKey && actionResetZoom.keyTest(event)) {
+      event.preventDefault();
+      app.actionManager.executeAction(actionResetZoom);
+      updateWysiwygStyle();
     } else if (actionDecreaseFontSize.keyTest(event)) {
       app.actionManager.executeAction(actionDecreaseFontSize);
     } else if (actionIncreaseFontSize.keyTest(event)) {
@@ -593,6 +601,7 @@ export const textWysiwyg = ({
     window.removeEventListener("blur", handleSubmit);
     window.removeEventListener("beforeunload", handleSubmit);
     unbindUpdate();
+    unbindOnScroll();
 
     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
   const onPointerDown = (event: MouseEvent) => {
     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 =
       target instanceof HTMLElement &&
       target.classList.contains("properties-trigger");
@@ -630,17 +658,14 @@ export const textWysiwyg = ({
     if (
       ((event.target instanceof HTMLElement ||
         event.target instanceof SVGElement) &&
-        event.target.closest(`.${CLASSES.SHAPE_ACTIONS_MENU}`) &&
+        event.target.closest(
+          `.${CLASSES.SHAPE_ACTIONS_MENU}, .${CLASSES.ZOOM_ACTIONS}`,
+        ) &&
         !isWritableElement(event.target)) ||
       isPropertiesTrigger
     ) {
-      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);
+      temporarilyDisableSubmit();
     } else if (
-      event.target instanceof HTMLElement &&
       event.target instanceof HTMLCanvasElement &&
       // Vitest simply ignores stopPropagation, capture-mode, or rAF
       // so without introducing crazier hacks, nothing we can do
@@ -659,7 +684,7 @@ export const textWysiwyg = ({
   };
 
   // handle updates of textElement properties of editing element
-  const unbindUpdate = Scene.getScene(element)!.onUpdate(() => {
+  const unbindUpdate = app.scene.onUpdate(() => {
     updateWysiwygStyle();
     const isPopupOpened = !!document.activeElement?.closest(
       ".properties-content",
@@ -669,6 +694,10 @@ export const textWysiwyg = ({
     }
   });
 
+  const unbindOnScroll = app.onScrollChangeEmitter.on(() => {
+    updateWysiwygStyle();
+  });
+
   // ---------------------------------------------------------------------------
 
   let isDestroyed = false;
@@ -699,10 +728,6 @@ export const textWysiwyg = ({
   requestAnimationFrame(() => {
     window.addEventListener("pointerdown", onPointerDown, { capture: true });
   });
-  window.addEventListener("wheel", stopEvent, {
-    passive: false,
-    capture: true,
-  });
   window.addEventListener("beforeunload", handleSubmit);
   excalidrawContainer
     ?.querySelector(".excalidraw-textEditorContainer")!