浏览代码

fix: stop preventing canvas pointerdown/tapend events (#3207)

David Luzar 4 年之前
父节点
当前提交
e90e56452f
共有 4 个文件被更改,包括 36 次插入26 次删除
  1. 1 2
      public/index.html
  2. 4 12
      src/components/App.tsx
  3. 7 1
      src/css/styles.scss
  4. 24 11
      src/element/textWysiwyg.tsx

+ 1 - 2
public/index.html

@@ -112,8 +112,7 @@
           Roboto, Helvetica, Arial, sans-serif;
         font-family: var(--ui-font);
         -webkit-text-size-adjust: 100%;
-        -webkit-user-select: none;
-        user-select: none;
+
         width: 100vw;
         height: 100vh;
       }

+ 4 - 12
src/components/App.tsx

@@ -1105,7 +1105,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
   };
 
   private onTapEnd = (event: TouchEvent) => {
-    event.preventDefault();
     if (event.touches.length > 0) {
       this.setState({
         previousSelectedElementIds: {},
@@ -1631,10 +1630,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
           updateBoundElements(element);
         }
       }),
-      onSubmit: withBatchedUpdates((text) => {
+      onSubmit: withBatchedUpdates(({ text, viaKeyboard }) => {
         const isDeleted = !text.trim();
         updateElement(text, isDeleted);
-        if (!isDeleted) {
+        // select the created text element only if submitting via keyboard
+        // (when submitting via click it should act as signal to deselect)
+        if (!isDeleted && viaKeyboard) {
           this.setState((prevState) => ({
             selectedElementIds: {
               ...prevState.selectedElementIds,
@@ -2151,15 +2152,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
 
     this.updateGestureOnPointerDown(event);
 
-    // fixes pointermove causing selection of UI texts #32
-    event.preventDefault();
-    // Preventing the event above disables default behavior
-    // of defocusing potentially focused element, which is what we
-    // want when clicking inside the canvas.
-    if (document.activeElement instanceof HTMLElement) {
-      document.activeElement.blur();
-    }
-
     // don't select while panning
     if (gesture.pointers.size > 1) {
       return;

+ 7 - 1
src/css/styles.scss

@@ -17,6 +17,13 @@
   left: 0;
   right: 0;
 
+  // serves 2 purposes:
+  // 1. prevent selecting text outside the component when double-clicking or
+  //    dragging inside it (e.g. on canvas)
+  // 2. prevent selecting UI, both from the inside, and from outside the
+  //    component (e.g. if you select text in a sidebar)
+  user-select: none;
+
   a {
     font-weight: 500;
     text-decoration: none;
@@ -29,7 +36,6 @@
 
   canvas {
     touch-action: none;
-    user-select: none;
 
     // following props improve blurriness at certain devicePixelRatios.
     // AFAIK it doesn't affect export (in fact, export seems sharp either way).

+ 24 - 11
src/element/textWysiwyg.tsx

@@ -47,7 +47,7 @@ export const textWysiwyg = ({
   id: ExcalidrawElement["id"];
   appState: AppState;
   onChange?: (text: string) => void;
-  onSubmit: (text: string) => void;
+  onSubmit: (data: { text: string; viaKeyboard: boolean }) => void;
   getViewportCoords: (x: number, y: number) => [number, number];
   element: ExcalidrawElement;
   canvas: HTMLCanvasElement | null;
@@ -136,12 +136,14 @@ export const textWysiwyg = ({
   editable.onkeydown = (event) => {
     if (event.key === KEYS.ESCAPE) {
       event.preventDefault();
+      submittedViaKeyboard = true;
       handleSubmit();
     } else if (event.key === KEYS.ENTER && event[KEYS.CTRL_OR_CMD]) {
       event.preventDefault();
       if (event.isComposing || event.keyCode === 229) {
         return;
       }
+      submittedViaKeyboard = true;
       handleSubmit();
     } else if (event.key === KEYS.ENTER && !event.altKey) {
       event.stopPropagation();
@@ -153,8 +155,14 @@ export const textWysiwyg = ({
     event.stopPropagation();
   };
 
+  // using a state variable instead of passing it to the handleSubmit callback
+  // so that we don't need to create separate a callback for event handlers
+  let submittedViaKeyboard = false;
   const handleSubmit = () => {
-    onSubmit(normalizeText(editable.value));
+    onSubmit({
+      text: normalizeText(editable.value),
+      viaKeyboard: submittedViaKeyboard,
+    });
     cleanup();
   };
 
@@ -175,7 +183,7 @@ export const textWysiwyg = ({
     window.removeEventListener("resize", updateWysiwygStyle);
     window.removeEventListener("wheel", stopEvent, true);
     window.removeEventListener("pointerdown", onPointerDown);
-    window.removeEventListener("pointerup", rebindBlur);
+    window.removeEventListener("pointerup", bindBlurEvent);
     window.removeEventListener("blur", handleSubmit);
 
     unbindUpdate();
@@ -183,10 +191,12 @@ export const textWysiwyg = ({
     editable.remove();
   };
 
-  const rebindBlur = () => {
-    window.removeEventListener("pointerup", rebindBlur);
-    // deferred to guard against focus traps on various UIs that steal focus
-    // upon pointerUp
+  const bindBlurEvent = () => {
+    window.removeEventListener("pointerup", bindBlurEvent);
+    // Deferred so that the pointerdown that initiates the wysiwyg doesn't
+    // trigger the blur on ensuing pointerup.
+    // Also to handle cases such as picking a color which would trigger a blur
+    // in that same tick.
     setTimeout(() => {
       editable.onblur = handleSubmit;
       // case: clicking on the same property → no change → no update → no focus
@@ -202,7 +212,7 @@ export const textWysiwyg = ({
       !isWritableElement(event.target)
     ) {
       editable.onblur = null;
-      window.addEventListener("pointerup", rebindBlur);
+      window.addEventListener("pointerup", bindBlurEvent);
       // handle edge-case where pointerup doesn't fire e.g. due to user
       // alt-tabbing away
       window.addEventListener("blur", handleSubmit);
@@ -215,9 +225,14 @@ export const textWysiwyg = ({
     editable.focus();
   });
 
+  // ---------------------------------------------------------------------------
+
   let isDestroyed = false;
 
-  editable.onblur = handleSubmit;
+  // select on init (focusing is done separately inside the bindBlurEvent()
+  // because we need it to happen *after* the blur event from `pointerdown`)
+  editable.select();
+  bindBlurEvent();
 
   // reposition wysiwyg in case of canvas is resized. Using ResizeObserver
   // is preferred so we catch changes from host, where window may not resize.
@@ -239,6 +254,4 @@ export const textWysiwyg = ({
   document
     .querySelector(".excalidraw-textEditorContainer")!
     .appendChild(editable);
-  editable.focus();
-  editable.select();
 };