| 
					
				 | 
			
			
				@@ -41,7 +41,7 @@ import { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } from "./scene"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import { renderScene } from "./renderer"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import { AppState, FlooredNumber } from "./types"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import { AppState, FlooredNumber, Gesture } from "./types"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import { ExcalidrawElement } from "./element/types"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import { 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -108,6 +108,7 @@ import useIsMobile, { IsMobileProvider } from "./is-mobile"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import { copyToAppClipboard, getClipboardContent } from "./clipboard"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import { normalizeScroll } from "./scene/data"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import { getCenter, getDistance } from "./gesture"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 let { elements } = createScene(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 const { history } = createHistory(); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -130,10 +131,11 @@ const CURSOR_TYPE = { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   CROSSHAIR: "crosshair", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   GRABBING: "grabbing", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-const MOUSE_BUTTON = { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+const POINTER_BUTTON = { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   MAIN: 0, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   WHEEL: 1, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   SECONDARY: 2, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  TOUCH: -1, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 // Block pinch-zooming on iOS outside of the content area 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -148,7 +150,13 @@ document.addEventListener( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   { passive: false }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-let lastMouseUp: ((e: any) => void) | null = null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+let lastPointerUp: ((e: any) => void) | null = null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+const gesture: Gesture = { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  pointers: [], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  lastCenter: null, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  initialDistance: null, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  initialScale: null, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+}; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 export function viewportCoordsToSceneCoords( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   { clientX, clientY }: { clientX: number; clientY: number }, 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -202,7 +210,6 @@ let cursorX = 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 let cursorY = 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 let isHoldingSpace: boolean = false; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 let isPanning: boolean = false; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-let isHoldingMouseButton: boolean = false; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 interface LayerUIProps { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   actionManager: ActionManager; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -279,17 +286,15 @@ const LayerUI = React.memo( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    function renderSelectedShapeActions( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      elements: readonly ExcalidrawElement[], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    ) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    const showSelectedShapeActions = 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      (appState.editingElement || getSelectedElements(elements).length) && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      appState.elementType === "selection"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    function renderSelectedShapeActions() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       const { elementType, editingElement } = appState; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       const targetElements = editingElement 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         ? [editingElement] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         : getSelectedElements(elements); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      if (!targetElements.length && elementType === "selection") { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        return null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       return ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         <div className="panelColumn"> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           {actionManager.renderAction("changeStrokeColor")} 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -331,8 +336,6 @@ const LayerUI = React.memo( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               {actionManager.renderAction("bringForward")} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             </div> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           </fieldset> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          {actionManager.renderAction("deleteSelectedElements")} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         </div> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -418,7 +421,7 @@ const LayerUI = React.memo( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               </Stack.Col> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             </div> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           </section> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        ) : appState.openedMenu === "shape" ? ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        ) : appState.openedMenu === "shape" && showSelectedShapeActions ? ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           <section 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             className="App-mobile-menu" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             aria-labelledby="selected-shape-title" 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -427,7 +430,7 @@ const LayerUI = React.memo( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               {t("headings.selectedShapeActions")} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             </h2> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             <div className="App-mobile-menu-scroller"> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              {renderSelectedShapeActions(elements)} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              {renderSelectedShapeActions()} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             </div> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           </section> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         ) : null} 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -444,6 +447,12 @@ const LayerUI = React.memo( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               </Stack.Row> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             </Stack.Col> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           </section> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          <HintViewer 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            elementType={appState.elementType} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            multiMode={appState.multiElement !== null} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            isResizing={appState.isResizing} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            elements={elements} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          /> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         </FixedSideContainer> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         <footer className="App-toolbar"> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           <div className="App-toolbar-content"> 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -459,7 +468,18 @@ const LayerUI = React.memo( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 })) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             /> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            <div 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              style={{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                visibility: isSomeElementSelected(elements) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  ? "visible" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  : "hidden", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              }} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            > 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              {" "} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              {actionManager.renderAction("deleteSelectedElements")} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            </div> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             {lockButton} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            {actionManager.renderAction("finalize")} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             <div 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               style={{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 visibility: isSomeElementSelected(elements) 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -482,12 +502,6 @@ const LayerUI = React.memo( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               /> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             </div> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            <HintViewer 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              elementType={appState.elementType} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              multiMode={appState.multiElement !== null} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              isResizing={appState.isResizing} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              elements={elements} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            /> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             {appState.scrolledOutside && ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               <button 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 className="scroll-back-to-content" 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -525,17 +539,17 @@ const LayerUI = React.memo( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   </Stack.Col> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 </Island> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               </section> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              <section 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                className="App-right-menu" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                aria-labelledby="selected-shape-title" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              > 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                <h2 className="visually-hidden" id="selected-shape-title"> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  {t("headings.selectedShapeActions")} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                </h2> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                <Island padding={4}> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  {renderSelectedShapeActions(elements)} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                </Island> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              </section> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              {showSelectedShapeActions ? ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                <section 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  className="App-right-menu" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  aria-labelledby="selected-shape-title" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                > 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  <h2 className="visually-hidden" id="selected-shape-title"> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    {t("headings.selectedShapeActions")} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  </h2> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  <Island padding={4}>{renderSelectedShapeActions()}</Island> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                </section> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              ) : null} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             </Stack.Col> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             <section aria-labelledby="shapes-title"> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               <Stack.Col gap={4} align="start"> 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -858,7 +872,7 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           this.setState({ ...data.appState }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    } else if (event.key === KEYS.SPACE && !isHoldingMouseButton) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } else if (event.key === KEYS.SPACE && gesture.pointers.length === 0) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       isHoldingSpace = true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       document.documentElement.style.cursor = CURSOR_TYPE.GRABBING; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -953,6 +967,10 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     this.setState({}); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  removePointer = (e: React.PointerEvent<HTMLElement>) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    gesture.pointers = gesture.pointers.filter(p => p.id !== e.pointerId); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   public render() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     const canvasDOMWidth = window.innerWidth; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     const canvasDOMHeight = window.innerHeight; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1055,12 +1073,12 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 left: e.clientX, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             }} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            onMouseDown={e => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              if (lastMouseUp !== null) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                // Unfortunately, sometimes we don't get a mouseup after a mousedown, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            onPointerDown={e => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              if (lastPointerUp !== null) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                // Unfortunately, sometimes we don't get a pointerup after a pointerdown, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 // this can happen when a contextual menu or alert is triggered. In order to avoid 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                // being in a weird state, we clean up on the next mousedown 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                lastMouseUp(e); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                // being in a weird state, we clean up on the next pointerdown 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                lastPointerUp(e); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               if (isPanning) { 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1069,15 +1087,14 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               // pan canvas on wheel button drag or space+drag 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               if ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                !isHoldingMouseButton && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                (e.button === MOUSE_BUTTON.WHEEL || 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  (e.button === MOUSE_BUTTON.MAIN && isHoldingSpace)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                gesture.pointers.length === 0 && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                (e.button === POINTER_BUTTON.WHEEL || 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  (e.button === POINTER_BUTTON.MAIN && isHoldingSpace)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               ) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                isHoldingMouseButton = true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 isPanning = true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 document.documentElement.style.cursor = CURSOR_TYPE.GRABBING; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 let { clientX: lastX, clientY: lastY } = e; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                const onMouseMove = (e: MouseEvent) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                const onPointerMove = (e: PointerEvent) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   const deltaX = lastX - e.clientX; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   const deltaY = lastY - e.clientY; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   lastX = e.clientX; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1092,30 +1109,44 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     ), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                const teardown = (lastMouseUp = () => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  lastMouseUp = null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                const teardown = (lastPointerUp = () => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  lastPointerUp = null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   isPanning = false; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  isHoldingMouseButton = false; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   if (!isHoldingSpace) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     setCursorForShape(this.state.elementType); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  window.removeEventListener("mousemove", onMouseMove); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  window.removeEventListener("mouseup", teardown); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  window.removeEventListener("pointermove", onPointerMove); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  window.removeEventListener("pointerup", teardown); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   window.removeEventListener("blur", teardown); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 window.addEventListener("blur", teardown); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                window.addEventListener("mousemove", onMouseMove, { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                window.addEventListener("pointermove", onPointerMove, { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   passive: true, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                window.addEventListener("mouseup", teardown); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                window.addEventListener("pointerup", teardown); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              // only handle left mouse button 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              if (e.button !== MOUSE_BUTTON.MAIN) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              // only handle left mouse button or touch 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              if ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                e.button !== POINTER_BUTTON.MAIN && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                e.button !== POINTER_BUTTON.TOUCH 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              ) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              // fixes mousemove causing selection of UI texts #32 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              gesture.pointers.push({ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                id: e.pointerId, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                x: e.clientX, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                y: e.clientY, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              if (gesture.pointers.length === 2) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                gesture.lastCenter = getCenter(gesture.pointers); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                gesture.initialScale = this.state.zoom; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                gesture.initialDistance = getDistance(gesture.pointers); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              // fixes pointermove causing selection of UI texts #32 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               e.preventDefault(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               // Preventing the event above disables default behavior 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               //  of defocusing potentially focused element, which is what we 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1124,6 +1155,11 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 document.activeElement.blur(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              // don't select while panning 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              if (gesture.pointers.length > 1) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               // Handle scrollbars dragging 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               const { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 isOverHorizontalScrollBar, 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1216,7 +1252,7 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                       elementIsAddedToSelection = true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    // We duplicate the selected element if alt is pressed on Mouse down 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    // We duplicate the selected element if alt is pressed on pointer down 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     if (e.altKey) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                       elements = [ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                         ...elements.map(element => ({ 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1352,8 +1388,8 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     p1: Point, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     deltaX: number, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     deltaY: number, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    mouseX: number, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    mouseY: number, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    pointerX: number, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    pointerY: number, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     perfect: boolean, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   ) => void) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 | null = null; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1363,8 +1399,8 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 p1: Point, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 deltaX: number, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 deltaY: number, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                mouseX: number, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                mouseY: number, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                pointerX: number, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                pointerY: number, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 perfect: boolean, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               ) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 if (perfect) { 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1373,8 +1409,8 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   const { width, height } = getPerfectElementSize( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     element.type, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    mouseX - element.x - p1[0], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    mouseY - element.y - p1[1], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    pointerX - element.x - p1[0], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    pointerY - element.y - p1[1], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   const dx = element.x + width + p1[0]; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1396,15 +1432,15 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 p1: Point, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 deltaX: number, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 deltaY: number, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                mouseX: number, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                mouseY: number, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                pointerX: number, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                pointerY: number, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 perfect: boolean, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               ) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 if (perfect) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   const { width, height } = getPerfectElementSize( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     element.type, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    mouseX - element.x, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    mouseY - element.y, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    pointerX - element.x, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    pointerY - element.y, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   p1[0] = width; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   p1[1] = height; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1414,7 +1450,7 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              const onMouseMove = (e: MouseEvent) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              const onPointerMove = (e: PointerEvent) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 const target = e.target; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 if (!(target instanceof HTMLElement)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   return; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1447,7 +1483,7 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 // for arrows, don't start dragging until a given threshold 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 //  to ensure we don't create a 2-point arrow by mistake when 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 //  user clicks mouse in a way that it moves a tiny bit (thus 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                //  triggering mousemove) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                //  triggering pointermove) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 if ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   !draggingOccurred && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   (this.state.elementType === "arrow" || 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1691,7 +1727,7 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 if (hitElement?.isSelected) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   // Marking that click was used for dragging to check 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  // if elements should be deselected on mouseup 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  // if elements should be deselected on pointerup 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   draggingOccurred = true; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   const selectedElements = getSelectedElements(elements); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   if (selectedElements.length > 0) { 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1790,7 +1826,7 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 this.setState({}); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              const onMouseUp = (e: MouseEvent) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              const onPointerUp = (e: PointerEvent) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 const { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   draggingElement, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   resizingElement, 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1806,10 +1842,9 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 resizeArrowFn = null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                lastMouseUp = null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                isHoldingMouseButton = false; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                window.removeEventListener("mousemove", onMouseMove); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                window.removeEventListener("mouseup", onMouseUp); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                lastPointerUp = null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                window.removeEventListener("pointermove", onPointerMove); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                window.removeEventListener("pointerup", onPointerUp); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 if (elementType === "arrow" || elementType === "line") { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   if (draggingElement!.points.length > 1) { 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1850,7 +1885,7 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   draggingElement && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   isInvisiblySmallElement(draggingElement) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 ) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  // remove invisible element which was added in onMouseDown 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  // remove invisible element which was added in onPointerDown 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   elements = elements.slice(0, -1); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   this.setState({ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     draggingElement: null, 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1882,7 +1917,7 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 // from hitted element 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 // 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 // If click occurred and elements were dragged or some element 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                // was added to selection (on mousedown phase) we need to keep 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                // was added to selection (on pointerdown phase) we need to keep 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 // selection unchanged 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 if ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   hitElement && 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1928,10 +1963,10 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              lastMouseUp = onMouseUp; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              lastPointerUp = onPointerUp; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              window.addEventListener("mousemove", onMouseMove); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              window.addEventListener("mouseup", onMouseUp); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              window.addEventListener("pointermove", onPointerMove); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              window.addEventListener("pointerup", onPointerUp); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             }} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             onDoubleClick={e => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               resetCursor(); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -2048,7 +2083,39 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             }} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            onMouseMove={e => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            onPointerMove={e => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              gesture.pointers = gesture.pointers.map(p => 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                p.id === e.pointerId 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  ? { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                      id: e.pointerId, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                      x: e.clientX, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                      y: e.clientY, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  : p, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              if (gesture.pointers.length === 2) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                const center = getCenter(gesture.pointers); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                const deltaX = center.x - gesture.lastCenter!.x; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                const deltaY = center.y - gesture.lastCenter!.y; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                gesture.lastCenter = center; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                const distance = getDistance(gesture.pointers); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                const scaleFactor = distance / gesture.initialDistance!; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                this.setState({ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  scrollX: normalizeScroll( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    this.state.scrollX + deltaX / this.state.zoom, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  ), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  scrollY: normalizeScroll( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    this.state.scrollY + deltaY / this.state.zoom, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  ), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  zoom: getNormalizedZoom(gesture.initialScale! * scaleFactor), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              } else { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                gesture.lastCenter = gesture.initialDistance = gesture.initialScale = null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               if (isHoldingSpace || isPanning) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 return; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               } 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -2101,6 +2168,8 @@ export class App extends React.Component<any, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               document.documentElement.style.cursor = hitElement ? "move" : ""; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             }} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            onPointerUp={this.removePointer} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            onPointerCancel={this.removePointer} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             onDrop={e => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               const file = e.dataTransfer.files[0]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               if (file?.type === "application/json") { 
			 |