| 
					
				 | 
			
			
				@@ -64,6 +64,8 @@ import { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   MQ_MAX_HEIGHT_LANDSCAPE, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   MQ_MAX_WIDTH_LANDSCAPE, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   MQ_MAX_WIDTH_PORTRAIT, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  MQ_RIGHT_SIDEBAR_MIN_WIDTH, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  MQ_SM_MAX_WIDTH, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   POINTER_BUTTON, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   SCROLL_TIMEOUT, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   TAP_TWICE_TIMEOUT, 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -194,7 +196,7 @@ import { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   LibraryItems, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   PointerDownState, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   SceneData, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  DeviceType, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  Device, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } from "../types"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   debounce, 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -220,7 +222,6 @@ import { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } from "../utils"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import ContextMenu, { ContextMenuOption } from "./ContextMenu"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import LayerUI from "./LayerUI"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import { Stats } from "./Stats"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import { Toast } from "./Toast"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import { actionToggleViewMode } from "../actions/actionToggleViewMode"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import { 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -259,12 +260,14 @@ import { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   isLocalLink, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } from "../element/Hyperlink"; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-const defaultDeviceTypeContext: DeviceType = { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+const deviceContextInitialValue = { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  isSmScreen: false, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   isMobile: false, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   isTouchScreen: false, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  canDeviceFitSidebar: false, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-const DeviceTypeContext = React.createContext(defaultDeviceTypeContext); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-export const useDeviceType = () => useContext(DeviceTypeContext); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+const DeviceContext = React.createContext<Device>(deviceContextInitialValue); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+export const useDevice = () => useContext<Device>(DeviceContext); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 const ExcalidrawContainerContext = React.createContext<{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   container: HTMLDivElement | null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   id: string | null; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -296,10 +299,7 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   rc: RoughCanvas | null = null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   unmounted: boolean = false; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   actionManager: ActionManager; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  deviceType: DeviceType = { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    isMobile: false, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    isTouchScreen: false, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  device: Device = deviceContextInitialValue; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   detachIsMobileMqHandler?: () => void; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   private excalidrawContainerRef = React.createRef<HTMLDivElement>(); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -353,12 +353,12 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       width: window.innerWidth, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       height: window.innerHeight, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       showHyperlinkPopup: false, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      isLibraryMenuDocked: false, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     this.id = nanoid(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     this.library = new Library(this); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     if (excalidrawRef) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       const readyPromise = 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         ("current" in excalidrawRef && excalidrawRef.current?.readyPromise) || 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -485,7 +485,7 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       <div 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         className={clsx("excalidraw excalidraw-container", { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           "excalidraw--view-mode": viewModeEnabled, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          "excalidraw--mobile": this.deviceType.isMobile, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          "excalidraw--mobile": this.device.isMobile, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         })} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         ref={this.excalidrawContainerRef} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         onDrop={this.handleAppOnDrop} 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -497,7 +497,7 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         <ExcalidrawContainerContext.Provider 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           value={this.excalidrawContainerValue} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         > 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          <DeviceTypeContext.Provider value={this.deviceType}> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          <DeviceContext.Provider value={this.device}> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             <LayerUI 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               canvas={this.canvas} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               appState={this.state} 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -521,6 +521,7 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               isCollaborating={this.props.isCollaborating} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               renderTopRightUI={renderTopRightUI} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               renderCustomFooter={renderFooter} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              renderCustomStats={renderCustomStats} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               viewModeEnabled={viewModeEnabled} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               showExitZenModeBtn={ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 typeof this.props?.zenModeEnabled === "undefined" && 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -548,15 +549,6 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 onLinkOpen={this.props.onLinkOpen} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               /> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             )} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            {this.state.showStats && ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              <Stats 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                appState={this.state} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                setAppState={this.setAppState} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                elements={this.scene.getNonDeletedElements()} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                onClose={this.toggleStats} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                renderCustomStats={renderCustomStats} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              /> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            )} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             {this.state.toastMessage !== null && ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               <Toast 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 message={this.state.toastMessage} 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -564,7 +556,7 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               /> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             )} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             <main>{this.renderCanvas()}</main> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          </DeviceTypeContext.Provider> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          </DeviceContext.Provider> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         </ExcalidrawContainerContext.Provider> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       </div> 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     ); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -763,7 +755,12 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     const scene = restore(initialData, null, null); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     scene.appState = { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       ...scene.appState, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      isLibraryOpen: this.state.isLibraryOpen, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      // we're falling back to current (pre-init) state when deciding 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      // whether to open the library, to handle a case where we 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      // update the state outside of initialData (e.g. when loading the app 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      // with a library install link, which should auto-open the library) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      isLibraryOpen: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        initialData?.appState?.isLibraryOpen || this.state.isLibraryOpen, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       activeTool: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         scene.appState.activeTool.type === "image" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           ? { ...scene.appState.activeTool, type: "selection" } 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -794,6 +791,21 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  private refreshDeviceState = (container: HTMLDivElement) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    const { width, height } = container.getBoundingClientRect(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    const sidebarBreakpoint = 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      this.props.UIOptions.dockedSidebarBreakpoint != null 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        ? this.props.UIOptions.dockedSidebarBreakpoint 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        : MQ_RIGHT_SIDEBAR_MIN_WIDTH; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    this.device = updateObject(this.device, { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      isSmScreen: width < MQ_SM_MAX_WIDTH, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      isMobile: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        width < MQ_MAX_WIDTH_PORTRAIT || 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        (height < MQ_MAX_HEIGHT_LANDSCAPE && width < MQ_MAX_WIDTH_LANDSCAPE), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      canDeviceFitSidebar: width > sidebarBreakpoint, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   public async componentDidMount() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     this.unmounted = false; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     this.excalidrawContainerValue.container = 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -835,34 +847,53 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       this.focusContainer(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      this.excalidrawContainerRef.current && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      // bounding rects don't work in tests so updating 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      // the state on init would result in making the test enviro run 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      // in mobile breakpoint (0 width/height), making everything fail 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      process.env.NODE_ENV !== "test" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    ) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      this.refreshDeviceState(this.excalidrawContainerRef.current); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     if ("ResizeObserver" in window && this.excalidrawContainerRef?.current) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       this.resizeObserver = new ResizeObserver(() => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        // compute isMobile state 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        // recompute device dimensions state 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         // --------------------------------------------------------------------- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        const { width, height } = 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          this.excalidrawContainerRef.current!.getBoundingClientRect(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        this.deviceType = updateObject(this.deviceType, { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          isMobile: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            width < MQ_MAX_WIDTH_PORTRAIT || 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            (height < MQ_MAX_HEIGHT_LANDSCAPE && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              width < MQ_MAX_WIDTH_LANDSCAPE), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        this.refreshDeviceState(this.excalidrawContainerRef.current!); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         // refresh offsets 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         // --------------------------------------------------------------------- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         this.updateDOMRect(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       this.resizeObserver?.observe(this.excalidrawContainerRef.current); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } else if (window.matchMedia) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      const mediaQuery = window.matchMedia( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      const mdScreenQuery = window.matchMedia( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         `(max-width: ${MQ_MAX_WIDTH_PORTRAIT}px), (max-height: ${MQ_MAX_HEIGHT_LANDSCAPE}px) and (max-width: ${MQ_MAX_WIDTH_LANDSCAPE}px)`, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      const smScreenQuery = window.matchMedia( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        `(max-width: ${MQ_SM_MAX_WIDTH}px)`, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      const canDeviceFitSidebarMediaQuery = window.matchMedia( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        `(min-width: ${ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          // NOTE this won't update if a different breakpoint is supplied 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          // after mount 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          this.props.UIOptions.dockedSidebarBreakpoint != null 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ? this.props.UIOptions.dockedSidebarBreakpoint 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            : MQ_RIGHT_SIDEBAR_MIN_WIDTH 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        }px)`, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       const handler = () => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        this.deviceType = updateObject(this.deviceType, { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          isMobile: mediaQuery.matches, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        this.excalidrawContainerRef.current!.getBoundingClientRect(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        this.device = updateObject(this.device, { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          isSmScreen: smScreenQuery.matches, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          isMobile: mdScreenQuery.matches, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          canDeviceFitSidebar: canDeviceFitSidebarMediaQuery.matches, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      mediaQuery.addListener(handler); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      this.detachIsMobileMqHandler = () => mediaQuery.removeListener(handler); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      mdScreenQuery.addListener(handler); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      this.detachIsMobileMqHandler = () => 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        mdScreenQuery.removeListener(handler); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     const searchParams = new URLSearchParams(window.location.search.slice(1)); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1004,6 +1035,14 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   componentDidUpdate(prevProps: AppProps, prevState: AppState) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     if ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      this.excalidrawContainerRef.current && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      prevProps.UIOptions.dockedSidebarBreakpoint !== 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        this.props.UIOptions.dockedSidebarBreakpoint 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    ) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      this.refreshDeviceState(this.excalidrawContainerRef.current); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       prevState.scrollX !== this.state.scrollX || 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       prevState.scrollY !== this.state.scrollY 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     ) { 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1175,7 +1214,7 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         theme: this.state.theme, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         imageCache: this.imageCache, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         isExporting: false, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        renderScrollbars: !this.deviceType.isMobile, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        renderScrollbars: !this.device.isMobile, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1453,11 +1492,15 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     this.scene.replaceAllElements(nextElements); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     this.history.resumeRecording(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     this.setState( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       selectGroupsForSelectedElements( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           ...this.state, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          isLibraryOpen: false, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          isLibraryOpen: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            this.state.isLibraryOpen && this.device.canDeviceFitSidebar 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              ? this.state.isLibraryMenuDocked 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              : false, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           selectedElementIds: newElements.reduce((map, element) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             if (!isBoundToContainer(element)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               map[element.id] = true; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1529,7 +1572,7 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       trackEvent( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         "toolbar", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         "toggleLock", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        `${source} (${this.deviceType.isMobile ? "mobile" : "desktop"})`, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        `${source} (${this.device.isMobile ? "mobile" : "desktop"})`, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     this.setState((prevState) => { 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1560,10 +1603,6 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     this.actionManager.executeAction(actionToggleZenMode); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  toggleStats = () => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    this.actionManager.executeAction(actionToggleStats); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   scrollToContent = ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     target: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       | ExcalidrawElement 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1721,7 +1760,16 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       if (event.code === CODES.ZERO) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        this.setState({ isLibraryOpen: !this.state.isLibraryOpen }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        const nextState = !this.state.isLibraryOpen; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        this.setState({ isLibraryOpen: nextState }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        // track only openings 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if (nextState) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          trackEvent( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            "library", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            "toggleLibrary (open)", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            `keyboard (${this.device.isMobile ? "mobile" : "desktop"})`, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       if (isArrowKey(event.key)) { 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -1815,7 +1863,7 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             trackEvent( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               "toolbar", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               shape, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              `keyboard (${this.deviceType.isMobile ? "mobile" : "desktop"})`, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              `keyboard (${this.device.isMobile ? "mobile" : "desktop"})`, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           this.setActiveTool({ type: shape }); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -2440,7 +2488,7 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           element, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           this.state, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           [scenePointer.x, scenePointer.y], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          this.deviceType.isMobile, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          this.device.isMobile, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     }); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -2472,7 +2520,7 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       this.hitLinkElement, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       this.state, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       [lastPointerDownCoords.x, lastPointerDownCoords.y], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      this.deviceType.isMobile, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      this.device.isMobile, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     const lastPointerUpCoords = viewportCoordsToSceneCoords( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       this.lastPointerUp!, 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -2482,7 +2530,7 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       this.hitLinkElement, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       this.state, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       [lastPointerUpCoords.x, lastPointerUpCoords.y], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      this.deviceType.isMobile, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      this.device.isMobile, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     if (lastPointerDownHittingLinkIcon && lastPointerUpHittingLinkIcon) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       const url = this.hitLinkElement.link; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -2921,10 +2969,10 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     if ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      !this.deviceType.isTouchScreen && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      !this.device.isTouchScreen && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       ["pen", "touch"].includes(event.pointerType) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     ) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      this.deviceType = updateObject(this.deviceType, { isTouchScreen: true }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      this.device = updateObject(this.device, { isTouchScreen: true }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     if (isPanning) { 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -3066,7 +3114,7 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     event: React.PointerEvent<HTMLCanvasElement>, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   ) => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     this.lastPointerUp = event; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    if (this.deviceType.isTouchScreen) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if (this.device.isTouchScreen) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       const scenePointer = viewportCoordsToSceneCoords( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         { clientX: event.clientX, clientY: event.clientY }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         this.state, 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -3084,7 +3132,7 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       this.hitLinkElement && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       !this.state.selectedElementIds[this.hitLinkElement.id] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     ) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      this.redirectToLink(event, this.deviceType.isTouchScreen); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      this.redirectToLink(event, this.device.isTouchScreen); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     this.removePointer(event); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -3456,7 +3504,7 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               pointerDownState.hit.element, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               this.state, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               [pointerDownState.origin.x, pointerDownState.origin.y], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              this.deviceType.isMobile, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              this.device.isMobile, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           ) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             return false; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -5563,7 +5611,7 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       } else { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         ContextMenu.push({ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           options: [ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            this.deviceType.isMobile && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            this.device.isMobile && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               navigator.clipboard && { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 trackEvent: false, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 name: "paste", 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -5575,7 +5623,7 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 contextItemLabel: "labels.paste", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            this.deviceType.isMobile && navigator.clipboard && separator, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            this.device.isMobile && navigator.clipboard && separator, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             probablySupportsClipboardBlob && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               elements.length > 0 && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               actionCopyAsPng, 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -5620,9 +5668,9 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       } else { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         ContextMenu.push({ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           options: [ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            this.deviceType.isMobile && actionCut, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            this.deviceType.isMobile && navigator.clipboard && actionCopy, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            this.deviceType.isMobile && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            this.device.isMobile && actionCut, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            this.device.isMobile && navigator.clipboard && actionCopy, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            this.device.isMobile && 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               navigator.clipboard && { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 name: "paste", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 trackEvent: false, 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -5634,7 +5682,7 @@ class App extends React.Component<AppProps, AppState> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 contextItemLabel: "labels.paste", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               }, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            this.deviceType.isMobile && separator, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            this.device.isMobile && separator, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             ...options, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             separator, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             actionCopyStyles, 
			 |