From 3d617958cc194b0e994f65c04bd81427fabc7345 Mon Sep 17 00:00:00 2001
From: David Luzar <luzar.david@gmail.com>
Date: Tue, 19 Sep 2023 16:01:40 +0200
Subject: [PATCH] fix: improperly disabling UI pointer-events on canvas
 interaction (#7005)

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
---
 src/components/App.tsx                        | 21 +++++++++++++++----
 src/components/FixedSideContainer.scss        |  2 +-
 src/components/LayerUI.scss                   | 10 ++++++---
 src/components/LayerUI.tsx                    | 10 ++-------
 src/components/Sidebar/Sidebar.scss           |  2 ++
 src/components/Stats.scss                     |  2 +-
 src/components/UserList.scss                  |  2 +-
 src/components/footer/Footer.tsx              |  2 +-
 src/components/footer/FooterCenter.scss       |  3 ++-
 .../welcome-screen/WelcomeScreen.scss         |  6 +++---
 src/constants.ts                              |  8 +++++++
 src/css/styles.scss                           | 14 ++++++-------
 .../components/AppWelcomeScreen.tsx           |  3 ++-
 13 files changed, 54 insertions(+), 31 deletions(-)

diff --git a/src/components/App.tsx b/src/components/App.tsx
index 373ff7b66f..367c8f7f67 100644
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -85,6 +85,7 @@ import {
   VERTICAL_ALIGN,
   YOUTUBE_STATES,
   ZOOM_STEP,
+  POINTER_EVENTS,
 } from "../constants";
 import { exportCanvas, loadFromBlob } from "../data";
 import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library";
@@ -857,7 +858,9 @@ class App extends React.Component<AppProps, AppState> {
                   width: isVisible ? `${el.width}px` : 0,
                   height: isVisible ? `${el.height}px` : 0,
                   transform: isVisible ? `rotate(${el.angle}rad)` : "none",
-                  pointerEvents: isActive ? "auto" : "none",
+                  pointerEvents: isActive
+                    ? POINTER_EVENTS.enabled
+                    : POINTER_EVENTS.disabled,
                 }}
               >
                 {isHovered && (
@@ -1081,9 +1084,9 @@ class App extends React.Component<AppProps, AppState> {
             whiteSpace: "nowrap",
             textOverflow: "ellipsis",
             cursor: CURSOR_TYPE.MOVE,
-            // disable all interaction (e.g. cursor change) when in view
-            // mode
-            pointerEvents: this.state.viewModeEnabled ? "none" : "all",
+            pointerEvents: this.state.viewModeEnabled
+              ? POINTER_EVENTS.disabled
+              : POINTER_EVENTS.inheritFromUI,
           }}
           onPointerDown={(event) => this.handleCanvasPointerDown(event)}
           onWheel={(event) => this.handleWheel(event)}
@@ -1125,6 +1128,16 @@ class App extends React.Component<AppProps, AppState> {
           "excalidraw--view-mode": this.state.viewModeEnabled,
           "excalidraw--mobile": this.device.isMobile,
         })}
+        style={{
+          ["--ui-pointerEvents" as any]:
+            this.state.selectionElement ||
+            this.state.draggingElement ||
+            this.state.resizingElement ||
+            (this.state.editingElement &&
+              !isTextElement(this.state.editingElement))
+              ? POINTER_EVENTS.disabled
+              : POINTER_EVENTS.enabled,
+        }}
         ref={this.excalidrawContainerRef}
         onDrop={this.handleAppOnDrop}
         tabIndex={0}
diff --git a/src/components/FixedSideContainer.scss b/src/components/FixedSideContainer.scss
index a485d6d252..62d77d5056 100644
--- a/src/components/FixedSideContainer.scss
+++ b/src/components/FixedSideContainer.scss
@@ -7,7 +7,7 @@
   }
 
   .FixedSideContainer > * {
-    pointer-events: all;
+    pointer-events: var(--ui-pointerEvents);
   }
 
   .FixedSideContainer_side_top {
diff --git a/src/components/LayerUI.scss b/src/components/LayerUI.scss
index 73e3922945..8898b0f83d 100644
--- a/src/components/LayerUI.scss
+++ b/src/components/LayerUI.scss
@@ -91,13 +91,17 @@
         visibility: visible;
         transition: visibility 0s linear 300ms, opacity 0.5s;
         transition-delay: 0.8s;
+
+        pointer-events: var(--ui-pointerEvents);
       }
     }
 
     .layer-ui__wrapper__footer-left,
-    .layer-ui__wrapper__footer-right,
-    .disable-zen-mode--visible {
-      pointer-events: all;
+    .footer-center,
+    .layer-ui__wrapper__footer-right {
+      & > * {
+        pointer-events: var(--ui-pointerEvents);
+      }
     }
 
     .layer-ui__wrapper__footer-right {
diff --git a/src/components/LayerUI.tsx b/src/components/LayerUI.tsx
index 26be77aefa..02f360e255 100644
--- a/src/components/LayerUI.tsx
+++ b/src/components/LayerUI.tsx
@@ -2,7 +2,7 @@ import clsx from "clsx";
 import React from "react";
 import { ActionManager } from "../actions/manager";
 import { CLASSES, DEFAULT_SIDEBAR, LIBRARY_SIDEBAR_WIDTH } from "../constants";
-import { isTextElement, showSelectedShapeActions } from "../element";
+import { showSelectedShapeActions } from "../element";
 import { NonDeletedExcalidrawElement } from "../element/types";
 import { Language, t } from "../i18n";
 import { calculateScrollCenter } from "../scene";
@@ -427,13 +427,7 @@ const LayerUI = ({
       {!device.isMobile && (
         <>
           <div
-            className={clsx("layer-ui__wrapper", {
-              "disable-pointerEvents":
-                appState.draggingElement ||
-                appState.resizingElement ||
-                (appState.editingElement &&
-                  !isTextElement(appState.editingElement)),
-            })}
+            className="layer-ui__wrapper"
             style={
               appState.openSidebar &&
               isSidebarDocked &&
diff --git a/src/components/Sidebar/Sidebar.scss b/src/components/Sidebar/Sidebar.scss
index 200e94ef46..abb6f00959 100644
--- a/src/components/Sidebar/Sidebar.scss
+++ b/src/components/Sidebar/Sidebar.scss
@@ -17,6 +17,8 @@
     background-color: var(--sidebar-bg-color);
     box-shadow: var(--sidebar-shadow);
 
+    pointer-events: var(--ui-pointerEvents);
+
     :root[dir="rtl"] & {
       left: 0;
       right: auto;
diff --git a/src/components/Stats.scss b/src/components/Stats.scss
index 0a2f6b62dc..187a47a418 100644
--- a/src/components/Stats.scss
+++ b/src/components/Stats.scss
@@ -7,7 +7,7 @@
     right: 12px;
     font-size: 12px;
     z-index: 10;
-    pointer-events: all;
+    pointer-events: var(--ui-pointerEvents);
 
     h3 {
       margin: 0 24px 8px 0;
diff --git a/src/components/UserList.scss b/src/components/UserList.scss
index e224673c08..bafa7ad3ae 100644
--- a/src/components/UserList.scss
+++ b/src/components/UserList.scss
@@ -26,7 +26,7 @@
   }
 
   .UserList > * {
-    pointer-events: all;
+    pointer-events: var(--ui-pointerEvents);
   }
 
   .UserList_mobile {
diff --git a/src/components/footer/Footer.tsx b/src/components/footer/Footer.tsx
index 173ea451ad..85844aad6d 100644
--- a/src/components/footer/Footer.tsx
+++ b/src/components/footer/Footer.tsx
@@ -73,7 +73,7 @@ const Footer = ({
       <FooterCenterTunnel.Out />
       <div
         className={clsx("layer-ui__wrapper__footer-right zen-mode-transition", {
-          "transition-right disable-pointerEvents": appState.zenModeEnabled,
+          "transition-right": appState.zenModeEnabled,
         })}
       >
         <div style={{ position: "relative" }}>
diff --git a/src/components/footer/FooterCenter.scss b/src/components/footer/FooterCenter.scss
index 1e17db6c56..ce65659227 100644
--- a/src/components/footer/FooterCenter.scss
+++ b/src/components/footer/FooterCenter.scss
@@ -1,10 +1,11 @@
 .footer-center {
   pointer-events: none;
   & > * {
-    pointer-events: all;
+    pointer-events: var(--ui-pointerEvents);
   }
 
   display: flex;
   width: 100%;
   justify-content: flex-start;
+  margin-inline-end: 0.6rem;
 }
diff --git a/src/components/welcome-screen/WelcomeScreen.scss b/src/components/welcome-screen/WelcomeScreen.scss
index a1b88d8bde..8f1a09bf31 100644
--- a/src/components/welcome-screen/WelcomeScreen.scss
+++ b/src/components/welcome-screen/WelcomeScreen.scss
@@ -161,7 +161,7 @@
   .welcome-screen-menu-item {
     box-sizing: border-box;
 
-    pointer-events: all;
+    pointer-events: var(--ui-pointerEvents);
 
     color: var(--color-gray-50);
     font-size: 0.875rem;
@@ -202,7 +202,7 @@
     }
   }
 
-  &:not(:active) .welcome-screen-menu-item:hover {
+  .welcome-screen-menu-item:hover {
     text-decoration: none;
     background: var(--color-gray-10);
 
@@ -246,7 +246,7 @@
       }
     }
 
-    &:not(:active) .welcome-screen-menu-item:hover {
+    .welcome-screen-menu-item:hover {
       background: var(--color-gray-85);
 
       .welcome-screen-menu-item__shortcut {
diff --git a/src/constants.ts b/src/constants.ts
index 46ae25b401..90fc667581 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -41,6 +41,14 @@ export const POINTER_BUTTON = {
   TOUCH: -1,
 } as const;
 
+export const POINTER_EVENTS = {
+  enabled: "all",
+  disabled: "none",
+  // asserted as any so it can be freely assigned to React Element
+  // "pointerEnvets" CSS prop
+  inheritFromUI: "var(--ui-pointerEvents)" as any,
+} as const;
+
 export enum EVENT {
   COPY = "copy",
   PASTE = "paste",
diff --git a/src/css/styles.scss b/src/css/styles.scss
index bb90633201..4ca58af4f7 100644
--- a/src/css/styles.scss
+++ b/src/css/styles.scss
@@ -253,7 +253,7 @@
       max-height: 100%;
       display: flex;
       flex-direction: column;
-      pointer-events: initial;
+      pointer-events: var(--ui-pointerEvents);
 
       .panelColumn {
         padding: 8px 8px 0 8px;
@@ -301,7 +301,7 @@
     pointer-events: none !important;
 
     & > * {
-      pointer-events: all;
+      pointer-events: var(--ui-pointerEvents);
     }
   }
 
@@ -312,16 +312,16 @@
     cursor: default;
     pointer-events: none !important;
 
+    & > * {
+      pointer-events: var(--ui-pointerEvents);
+    }
+
     @media (min-width: 1536px) {
       grid-template-columns: 1fr 1fr 1fr;
       grid-gap: 3rem;
     }
   }
 
-  .layer-ui__wrapper:not(.disable-pointerEvents) .App-menu_top > * {
-    pointer-events: all;
-  }
-
   .App-menu_top > *:first-child {
     justify-self: flex-start;
   }
@@ -436,7 +436,7 @@
     left: 50%;
     bottom: 30px;
     transform: translateX(-50%);
-    pointer-events: all;
+    pointer-events: var(--ui-pointerEvents);
     font-family: inherit;
 
     &:hover {
diff --git a/src/excalidraw-app/components/AppWelcomeScreen.tsx b/src/excalidraw-app/components/AppWelcomeScreen.tsx
index 699c3ba88d..72b688d9bd 100644
--- a/src/excalidraw-app/components/AppWelcomeScreen.tsx
+++ b/src/excalidraw-app/components/AppWelcomeScreen.tsx
@@ -3,6 +3,7 @@ import { PlusPromoIcon } from "../../components/icons";
 import { useI18n } from "../../i18n";
 import { WelcomeScreen } from "../../packages/excalidraw/index";
 import { isExcalidrawPlusSignedUser } from "../app_constants";
+import { POINTER_EVENTS } from "../../constants";
 
 export const AppWelcomeScreen: React.FC<{
   setCollabDialogShown: (toggle: boolean) => any;
@@ -18,7 +19,7 @@ export const AppWelcomeScreen: React.FC<{
         if (bit === "Excalidraw+") {
           return (
             <a
-              style={{ pointerEvents: "all" }}
+              style={{ pointerEvents: POINTER_EVENTS.inheritFromUI }}
               href={`${
                 import.meta.env.VITE_APP_PLUS_APP
               }?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenSignedInUser`}