import type { IMAGE_MIME_TYPES, UserIdleState, throttleRAF, MIME_TYPES, ColorPaletteCustom } from "@excalidraw/common"; import type { SuggestedBinding } from "@excalidraw/element"; import type { LinearElementEditor } from "@excalidraw/element"; import type { MaybeTransformHandleType } from "@excalidraw/element"; import type { PointerType, ExcalidrawLinearElement, NonDeletedExcalidrawElement, NonDeleted, TextAlign, ExcalidrawElement, GroupId, ExcalidrawBindableElement, Arrowhead, ChartType, FontFamilyValues, ExcalidrawTextElement, FileId, Theme, StrokeRoundness, ExcalidrawEmbeddableElement, ExcalidrawMagicFrameElement, ExcalidrawFrameLikeElement, ExcalidrawElementType, ExcalidrawIframeLikeElement, OrderedExcalidrawElement, ExcalidrawNonSelectionElement } from "@excalidraw/element/types"; import type { Merge, MaybePromise, ValueOf, MakeBrand } from "@excalidraw/common/utility-types"; import type { CaptureUpdateActionType, DurableIncrement, EphemeralIncrement } from "@excalidraw/element"; import type { Action } from "./actions/types"; import type { Spreadsheet } from "./charts"; import type { ClipboardData } from "./clipboard"; import type App from "./components/App"; import type Library from "./data/library"; import type { FileSystemHandle } from "./data/filesystem"; import type { ContextMenuItems } from "./components/ContextMenu"; import type { SnapLine } from "./snapping"; import type { ImportedDataState } from "./data/types"; import type { Language } from "./i18n"; import type { isOverScrollBars } from "./scene/scrollbars"; import type React from "react"; import type { JSX } from "react"; export type SocketId = string & { _brand: "SocketId"; }; export type Collaborator = Readonly<{ pointer?: CollaboratorPointer; button?: "up" | "down"; selectedElementIds?: AppState["selectedElementIds"]; username?: string | null; userState?: UserIdleState; color?: { background: string; stroke: string; }; avatarUrl?: string; id?: string; socketId?: SocketId; isCurrentUser?: boolean; isInCall?: boolean; isSpeaking?: boolean; isMuted?: boolean; }>; export type CollaboratorPointer = { x: number; y: number; tool: "pointer" | "laser"; /** * Whether to render cursor + username. Useful when you only want to render * laser trail. * * @default true */ renderCursor?: boolean; /** * Explicit laser color. * * @default string collaborator's cursor color */ laserColor?: string; }; export type DataURL = string & { _brand: "DataURL"; }; export type BinaryFileData = { mimeType: ValueOf | typeof MIME_TYPES.binary; id: FileId; dataURL: DataURL; /** * Epoch timestamp in milliseconds */ created: number; /** * Indicates when the file was last retrieved from storage to be loaded * onto the scene. We use this flag to determine whether to delete unused * files from storage. * * Epoch timestamp in milliseconds. */ lastRetrieved?: number; /** * indicates the version of the file. This can be used to determine whether * the file dataURL has changed e.g. as part of restore due to schema update. */ version?: number; }; export type BinaryFileMetadata = Omit; export type BinaryFiles = Record; export type ToolType = "selection" | "lasso" | "rectangle" | "diamond" | "ellipse" | "arrow" | "line" | "freedraw" | "text" | "image" | "eraser" | "hand" | "frame" | "magicframe" | "embeddable" | "laser" | "mermaid"; export type ElementOrToolType = ExcalidrawElementType | ToolType | "custom"; export type ActiveTool = { type: ToolType; customType: null; } | { type: "custom"; customType: string; }; export type SidebarName = string; export type SidebarTabName = string; export type UserToFollow = { socketId: SocketId; username: string; }; type _CommonCanvasAppState = { zoom: AppState["zoom"]; scrollX: AppState["scrollX"]; scrollY: AppState["scrollY"]; width: AppState["width"]; height: AppState["height"]; viewModeEnabled: AppState["viewModeEnabled"]; openDialog: AppState["openDialog"]; editingGroupId: AppState["editingGroupId"]; selectedElementIds: AppState["selectedElementIds"]; frameToHighlight: AppState["frameToHighlight"]; offsetLeft: AppState["offsetLeft"]; offsetTop: AppState["offsetTop"]; theme: AppState["theme"]; }; export type StaticCanvasAppState = Readonly<_CommonCanvasAppState & { shouldCacheIgnoreZoom: AppState["shouldCacheIgnoreZoom"]; /** null indicates transparent bg */ viewBackgroundColor: AppState["viewBackgroundColor"] | null; exportScale: AppState["exportScale"]; selectedElementsAreBeingDragged: AppState["selectedElementsAreBeingDragged"]; gridSize: AppState["gridSize"]; gridStep: AppState["gridStep"]; frameRendering: AppState["frameRendering"]; linkOpacity: AppState["linkOpacity"]; gridColor: AppState["gridColor"]; gridDirection: AppState["gridDirection"]; frameColor: AppState["frameColor"]; currentHoveredFontFamily: AppState["currentHoveredFontFamily"]; hoveredElementIds: AppState["hoveredElementIds"]; croppingElementId: AppState["croppingElementId"]; }>; export type InteractiveCanvasAppState = Readonly<_CommonCanvasAppState & { activeEmbeddable: AppState["activeEmbeddable"]; selectionElement: AppState["selectionElement"]; selectedGroupIds: AppState["selectedGroupIds"]; selectedLinearElement: AppState["selectedLinearElement"]; multiElement: AppState["multiElement"]; isBindingEnabled: AppState["isBindingEnabled"]; suggestedBindings: AppState["suggestedBindings"]; isRotating: AppState["isRotating"]; elementsToHighlight: AppState["elementsToHighlight"]; collaborators: AppState["collaborators"]; snapLines: AppState["snapLines"]; zenModeEnabled: AppState["zenModeEnabled"]; editingTextElement: AppState["editingTextElement"]; gridColor: AppState["gridColor"]; gridDirection: AppState["gridDirection"]; highlightSearchResult: AppState["highlightSearchResult"]; isCropping: AppState["isCropping"]; croppingElementId: AppState["croppingElementId"]; searchMatches: AppState["searchMatches"]; activeLockedId: AppState["activeLockedId"]; }>; export type ObservedAppState = ObservedStandaloneAppState & ObservedElementsAppState; export type ObservedStandaloneAppState = { name: AppState["name"]; viewBackgroundColor: AppState["viewBackgroundColor"]; }; export type ObservedElementsAppState = { editingGroupId: AppState["editingGroupId"]; selectedElementIds: AppState["selectedElementIds"]; selectedGroupIds: AppState["selectedGroupIds"]; selectedLinearElementId: LinearElementEditor["elementId"] | null; selectedLinearElementIsEditing: boolean | null; croppingElementId: AppState["croppingElementId"]; lockedMultiSelections: AppState["lockedMultiSelections"]; activeLockedId: AppState["activeLockedId"]; }; export interface AppState { contextMenu: { items: ContextMenuItems; top: number; left: number; } | null; showWelcomeScreen: boolean; isLoading: boolean; errorMessage: React.ReactNode; activeEmbeddable: { element: NonDeletedExcalidrawElement; state: "hover" | "active"; } | null; /** * for a newly created element * - set on pointer down, updated during pointer move, used on pointer up */ newElement: NonDeleted | null; /** * for a single element that's being resized * - set on pointer down when it's selected and the active tool is selection */ resizingElement: NonDeletedExcalidrawElement | null; /** * multiElement is for multi-point linear element that's created by clicking as opposed to dragging * - when set and present, the editor will handle linear element creation logic accordingly */ multiElement: NonDeleted | null; /** * decoupled from newElement, dragging selection only creates selectionElement * - set on pointer down, updated during pointer move */ selectionElement: NonDeletedExcalidrawElement | null; isBindingEnabled: boolean; startBoundElement: NonDeleted | null; suggestedBindings: SuggestedBinding[]; frameToHighlight: NonDeleted | null; frameRendering: { enabled: boolean; name: boolean; outline: boolean; clip: boolean; }; editingFrame: string | null; elementsToHighlight: NonDeleted[] | null; /** * set when a new text is created or when an existing text is being edited */ editingTextElement: NonDeletedExcalidrawElement | null; activeTool: { /** * indicates a previous tool we should revert back to if we deselect the * currently active tool. At the moment applies to `eraser` and `hand` tool. */ lastActiveTool: ActiveTool | null; locked: boolean; fromSelection: boolean; } & ActiveTool; penMode: boolean; penDetected: boolean; exportBackground: boolean; exportEmbedScene: boolean; exportWithDarkMode: boolean; exportScale: number; currentItemStrokeColor: string; currentItemBackgroundColor: string; currentItemFillStyle: ExcalidrawElement["fillStyle"]; currentItemStrokeWidth: number; currentItemStrokeStyle: ExcalidrawElement["strokeStyle"]; currentItemRoughness: number; currentItemOpacity: number; currentItemFontFamily: FontFamilyValues; currentItemFontSize: number; currentItemTextAlign: TextAlign; currentItemStartArrowhead: Arrowhead | null; currentItemEndArrowhead: Arrowhead | null; currentHoveredFontFamily: FontFamilyValues | null; currentItemRoundness: StrokeRoundness; currentItemArrowType: "sharp" | "round" | "elbow"; viewBackgroundColor: string; scrollX: number; scrollY: number; cursorButton: "up" | "down"; scrolledOutside: boolean; name: string | null; isResizing: boolean; isRotating: boolean; zoom: Zoom; openMenu: "canvas" | "shape" | null; openPopup: "canvasBackground" | "elementBackground" | "elementStroke" | "fontFamily" | null; openSidebar: { name: SidebarName; tab?: SidebarTabName; } | null; openDialog: null | { name: "imageExport" | "help" | "jsonExport"; } | { name: "ttd"; tab: "text-to-diagram" | "mermaid"; } | { name: "commandPalette"; } | { name: "elementLinkSelector"; sourceElementId: ExcalidrawElement["id"]; }; /** * Reflects user preference for whether the default sidebar should be docked. * * NOTE this is only a user preference and does not reflect the actual docked * state of the sidebar, because the host apps can override this through * a DefaultSidebar prop, which is not reflected back to the appState. */ defaultSidebarDockedPreference: boolean; lastPointerDownWith: PointerType; selectedElementIds: Readonly<{ [id: string]: true; }>; hoveredElementIds: Readonly<{ [id: string]: true; }>; previousSelectedElementIds: { [id: string]: true; }; selectedElementsAreBeingDragged: boolean; shouldCacheIgnoreZoom: boolean; toast: { message: string; closable?: boolean; duration?: number; } | null; zenModeEnabled: boolean; theme: Theme; /** grid cell px size */ gridSize: number; gridStep: number; gridModeEnabled: boolean; viewModeEnabled: boolean; /** top-most selected groups (i.e. does not include nested groups) */ selectedGroupIds: { [groupId: string]: boolean; }; /** group being edited when you drill down to its constituent element (e.g. when you double-click on a group's element) */ editingGroupId: GroupId | null; width: number; height: number; offsetTop: number; offsetLeft: number; fileHandle: FileSystemHandle | null; collaborators: Map; stats: { open: boolean; /** bitmap. Use `STATS_PANELS` bit values */ panels: number; }; currentChartType: ChartType; pasteDialog: { shown: false; data: null; } | { shown: true; data: Spreadsheet; }; showHyperlinkPopup: false | "info" | "editor"; linkOpacity: number; trayModeEnabled: boolean; colorPalette?: { canvasBackground: ColorPaletteCustom; elementBackground: ColorPaletteCustom; elementStroke: ColorPaletteCustom; topPicks: { canvasBackground: [string, string, string, string, string]; elementStroke: [string, string, string, string, string]; elementBackground: [string, string, string, string, string]; }; }; allowWheelZoom?: boolean; allowPinchZoom?: boolean; pinnedScripts?: string[]; customPens?: any[]; currentStrokeOptions?: any; resetCustomPen?: any; gridColor: { Bold: string; Regular: string; }; gridDirection: { horizontal: boolean; vertical: boolean; }; highlightSearchResult: boolean; dynamicStyle: { [x: string]: string; }; frameColor: { stroke: string; fill: string; nameColor: string; }; invertBindingBehaviour: boolean; selectedLinearElement: LinearElementEditor | null; snapLines: readonly SnapLine[]; originSnapOffset: { x: number; y: number; } | null; objectsSnapModeEnabled: boolean; /** the user's socket id & username who is being followed on the canvas */ userToFollow: UserToFollow | null; /** the socket ids of the users following the current user */ followedBy: Set; /** image cropping */ isCropping: boolean; croppingElementId: ExcalidrawElement["id"] | null; /** null if no search matches found / search closed */ searchMatches: Readonly<{ focusedId: ExcalidrawElement["id"] | null; matches: readonly SearchMatch[]; }> | null; /** the locked element/group that's active and shows unlock popup */ activeLockedId: string | null; lockedMultiSelections: { [groupId: string]: true; }; } export type SearchMatch = { id: string; focus: boolean; matchedLines: { offsetX: number; offsetY: number; width: number; height: number; showOnCanvas: boolean; }[]; }; export type UIAppState = Omit; export type NormalizedZoomValue = number & { _brand: "normalizedZoom"; }; export type Zoom = Readonly<{ value: NormalizedZoomValue; }>; export type PointerCoords = Readonly<{ x: number; y: number; }>; export type Gesture = { pointers: Map; lastCenter: { x: number; y: number; } | null; initialDistance: number | null; initialScale: number | null; }; export declare class GestureEvent extends UIEvent { readonly rotation: number; readonly scale: number; } /** @deprecated legacy: do not use outside of migration paths */ export type LibraryItem_v1 = readonly NonDeleted[]; /** @deprecated legacy: do not use outside of migration paths */ type LibraryItems_v1 = readonly LibraryItem_v1[]; /** v2 library item */ export type LibraryItem = { id: string; status: "published" | "unpublished"; elements: readonly NonDeleted[]; /** timestamp in epoch (ms) */ created: number; name?: string; error?: string; }; export type LibraryItems = readonly LibraryItem[]; export type LibraryItems_anyVersion = LibraryItems | LibraryItems_v1; export type LibraryItemsSource = ((currentLibraryItems: LibraryItems) => MaybePromise) | MaybePromise; export type ExcalidrawInitialDataState = Merge["libraryItems"]>; }>; export type OnUserFollowedPayload = { userToFollow: UserToFollow; action: "FOLLOW" | "UNFOLLOW"; }; export interface ExcalidrawProps { onChange?: (elements: readonly OrderedExcalidrawElement[], appState: AppState, files: BinaryFiles) => void; onIncrement?: (event: DurableIncrement | EphemeralIncrement) => void; initialData?: (() => MaybePromise) | MaybePromise; excalidrawAPI?: (api: ExcalidrawImperativeAPI) => void; isCollaborating?: boolean; onPointerUpdate?: (payload: { pointer: { x: number; y: number; tool: "pointer" | "laser"; }; button: "down" | "up"; pointersMap: Gesture["pointers"]; }) => void; onPaste?: (data: ClipboardData, event: ClipboardEvent | null) => Promise | boolean; onDrop?: (event: React.DragEvent) => Promise | boolean; /** * Called when element(s) are duplicated so you can listen or modify as * needed. * * Called when duplicating via mouse-drag, keyboard, paste, library insert * etc. * * Returned elements will be used in place of the next elements * (you should return all elements, including deleted, and not mutate * the element if changes are made) */ onDuplicate?: (nextElements: readonly ExcalidrawElement[], /** excludes the duplicated elements */ prevElements: readonly ExcalidrawElement[]) => ExcalidrawElement[] | void; renderTopRightUI?: (isMobile: boolean, appState: UIAppState) => JSX.Element | null; langCode?: Language["code"]; viewModeEnabled?: boolean; zenModeEnabled?: boolean; gridModeEnabled?: boolean; objectsSnapModeEnabled?: boolean; libraryReturnUrl?: string; initState?: AppState; theme?: Theme; name?: string; renderCustomStats?: (elements: readonly NonDeletedExcalidrawElement[], appState: UIAppState) => JSX.Element; UIOptions?: Partial; detectScroll?: boolean; handleKeyboardGlobally?: boolean; onLibraryChange?: (libraryItems: LibraryItems) => void | Promise; autoFocus?: boolean; onBeforeTextEdit?: (textElement: ExcalidrawTextElement, isExistingElement: boolean) => string; onBeforeTextSubmit?: (textElement: ExcalidrawTextElement, nextText: string, //wrapped nextOriginalText: string, isDeleted: boolean) => { updatedNextOriginalText: string; nextLink: string; }; generateIdForFile?: (file: File) => string | Promise; onThemeChange?: (newTheme: string) => void; onViewModeChange?: (isViewModeEnabled: boolean) => void; generateLinkForSelection?: (id: string, type: "element" | "group") => string; onLinkOpen?: (element: NonDeletedExcalidrawElement, event: CustomEvent<{ nativeEvent: MouseEvent | React.PointerEvent; }>) => void; onLinkHover?: (element: NonDeletedExcalidrawElement, event: React.PointerEvent) => void; onPointerDown?: (activeTool: AppState["activeTool"], pointerDownState: PointerDownState) => void; onPointerUp?: (activeTool: AppState["activeTool"], pointerDownState: PointerDownState) => void; onScrollChange?: (scrollX: number, scrollY: number, zoom: Zoom) => void; onUserFollow?: (payload: OnUserFollowedPayload) => void; children?: React.ReactNode; validateEmbeddable?: boolean | string[] | RegExp | RegExp[] | ((link: string) => boolean | undefined); renderEmbeddable?: (element: NonDeleted, appState: AppState) => JSX.Element | null; renderWebview?: boolean; renderEmbeddableMenu?: (appState: AppState) => JSX.Element | null; renderMermaid?: boolean; onContextMenu?: (element: readonly NonDeletedExcalidrawElement[], appState: AppState, onClose: (callback?: () => void) => void) => JSX.Element | null; aiEnabled?: boolean; showDeprecatedFonts?: boolean; insertLinkAction?: (linkVal: string) => void; renderScrollbars?: boolean; } export type SceneData = { elements?: ImportedDataState["elements"]; appState?: ImportedDataState["appState"]; collaborators?: Map; captureUpdate?: CaptureUpdateActionType; }; export type ExportOpts = { saveFileToDisk?: boolean; onExportToBackend?: (exportedElements: readonly NonDeletedExcalidrawElement[], appState: UIAppState, files: BinaryFiles) => void; renderCustomUI?: (exportedElements: readonly NonDeletedExcalidrawElement[], appState: UIAppState, files: BinaryFiles, canvas: HTMLCanvasElement) => JSX.Element; }; export type CanvasActions = Partial<{ changeViewBackgroundColor: boolean; clearCanvas: boolean; export: false | ExportOpts; loadScene: boolean; saveToActiveFile: boolean; toggleTheme: boolean | null; saveAsImage: boolean; }>; export type UIOptions = Partial<{ dockedSidebarBreakpoint: number; canvasActions: CanvasActions; tools: { image: boolean; }; /** @deprecated does nothing. Will be removed in 0.15 */ welcomeScreen?: boolean; }>; export type AppProps = Merge & { export: ExportOpts; }; }>; detectScroll: boolean; handleKeyboardGlobally: boolean; isCollaborating: boolean; children?: React.ReactNode; aiEnabled: boolean; }>; /** A subset of App class properties that we need to use elsewhere * in the app, eg Manager. Factored out into a separate type to keep DRY. */ export type AppClassProperties = { props: AppProps; state: AppState; interactiveCanvas: HTMLCanvasElement | null; /** static canvas */ canvas: HTMLCanvasElement; focusContainer(): void; library: Library; imageCache: Map; mimeType: ValueOf; }>; files: BinaryFiles; device: App["device"]; scene: App["scene"]; syncActionResult: App["syncActionResult"]; fonts: App["fonts"]; pasteFromClipboard: App["pasteFromClipboard"]; id: App["id"]; onInsertElements: App["onInsertElements"]; onExportImage: App["onExportImage"]; lastViewportPosition: App["lastViewportPosition"]; scrollToContent: App["scrollToContent"]; addFiles: App["addFiles"]; addElementsFromPasteOrLibrary: App["addElementsFromPasteOrLibrary"]; setSelection: App["setSelection"]; togglePenMode: App["togglePenMode"]; toggleLock: App["toggleLock"]; setActiveTool: App["setActiveTool"]; setOpenDialog: App["setOpenDialog"]; insertEmbeddableElement: App["insertEmbeddableElement"]; onMagicframeToolSelect: App["onMagicframeToolSelect"]; getName: App["getName"]; dismissLinearEditor: App["dismissLinearEditor"]; flowChartCreator: App["flowChartCreator"]; getEffectiveGridSize: App["getEffectiveGridSize"]; setPlugins: App["setPlugins"]; plugins: App["plugins"]; getEditorUIOffsets: App["getEditorUIOffsets"]; visibleElements: App["visibleElements"]; excalidrawContainerValue: App["excalidrawContainerValue"]; onPointerUpEmitter: App["onPointerUpEmitter"]; updateEditorAtom: App["updateEditorAtom"]; }; export type PointerDownState = Readonly<{ origin: Readonly<{ x: number; y: number; }>; originInGrid: Readonly<{ x: number; y: number; }>; scrollbars: ReturnType; lastCoords: { x: number; y: number; }; originalElements: Map>; resize: { handleType: MaybeTransformHandleType; isResizing: boolean; offset: { x: number; y: number; }; arrowDirection: "origin" | "end"; center: { x: number; y: number; }; }; hit: { element: NonDeleted | null; allHitElements: NonDeleted[]; wasAddedToSelection: boolean; hasBeenDuplicated: boolean; hasHitCommonBoundingBoxOfSelectedElements: boolean; }; withCmdOrCtrl: boolean; drag: { hasOccurred: boolean; offset: { x: number; y: number; } | null; origin: { x: number; y: number; }; }; eventListeners: { onMove: null | ReturnType; onUp: null | ((event: PointerEvent) => void); onKeyDown: null | ((event: KeyboardEvent) => void); onKeyUp: null | ((event: KeyboardEvent) => void); }; boxSelection: { hasOccurred: boolean; }; }>; export type UnsubscribeCallback = () => void; export interface ExcalidrawImperativeAPI { updateScene: InstanceType["updateScene"]; mutateElement: InstanceType["mutateElement"]; updateLibrary: InstanceType["updateLibrary"]; resetScene: InstanceType["resetScene"]; getSceneElementsIncludingDeleted: InstanceType["getSceneElementsIncludingDeleted"]; getSceneElementsMapIncludingDeleted: InstanceType["getSceneElementsMapIncludingDeleted"]; history: { clear: InstanceType["resetHistory"]; }; setForceRenderAllEmbeddables: InstanceType["setForceRenderAllEmbeddables"]; zoomToFit: InstanceType["zoomToFit"]; getColorAtScenePoint: InstanceType["getColorAtScenePoint"]; startLineEditor: InstanceType["startLineEditor"]; getSceneElements: InstanceType["getSceneElements"]; getAppState: () => InstanceType["state"]; getFiles: () => InstanceType["files"]; getName: InstanceType["getName"]; scrollToContent: InstanceType["scrollToContent"]; registerAction: (action: Action) => void; refresh: InstanceType["refresh"]; setToast: InstanceType["setToast"]; addFiles: (data: BinaryFileData[]) => void; updateContainerSize: InstanceType["updateContainerSize"]; id: string; selectElements: (elements: readonly ExcalidrawElement[], highlightSearchResult?: boolean) => void; sendBackward: (elements: readonly ExcalidrawElement[]) => void; bringForward: (elements: readonly ExcalidrawElement[]) => void; sendToBack: (elements: readonly ExcalidrawElement[]) => void; bringToFront: (elements: readonly ExcalidrawElement[]) => void; setMobileModeAllowed: (allow: boolean) => void; setActiveTool: InstanceType["setActiveTool"]; setCursor: InstanceType["setCursor"]; resetCursor: InstanceType["resetCursor"]; toggleSidebar: InstanceType["toggleSidebar"]; getHTMLIFrameElement: InstanceType["getHTMLIFrameElement"]; /** * Disables rendering of frames (including element clipping), but currently * the frames are still interactive in edit mode. As such, this API should be * used in conjunction with view mode (props.viewModeEnabled). */ updateFrameRendering: InstanceType["updateFrameRendering"]; onChange: (callback: (elements: readonly ExcalidrawElement[], appState: AppState, files: BinaryFiles) => void) => UnsubscribeCallback; onIncrement: (callback: (event: DurableIncrement | EphemeralIncrement) => void) => UnsubscribeCallback; onPointerDown: (callback: (activeTool: AppState["activeTool"], pointerDownState: PointerDownState, event: React.PointerEvent) => void) => UnsubscribeCallback; onPointerUp: (callback: (activeTool: AppState["activeTool"], pointerDownState: PointerDownState, event: PointerEvent) => void) => UnsubscribeCallback; onScrollChange: (callback: (scrollX: number, scrollY: number, zoom: Zoom) => void) => UnsubscribeCallback; onUserFollow: (callback: (payload: OnUserFollowedPayload) => void) => UnsubscribeCallback; } export type Device = Readonly<{ viewport: { isMobile: boolean; isLandscape: boolean; }; editor: { isMobile: boolean; canFitSidebar: boolean; }; isTouchScreen: boolean; }>; export type FrameNameBounds = { x: number; y: number; width: number; height: number; angle: number; }; export type FrameNameBoundsCache = { get: (frameElement: ExcalidrawFrameLikeElement | ExcalidrawMagicFrameElement) => FrameNameBounds | null; _cache: Map; }; export type KeyboardModifiersObject = { ctrlKey: boolean; shiftKey: boolean; altKey: boolean; metaKey: boolean; }; export type Primitive = number | string | boolean | bigint | symbol | null | undefined; export type JSONValue = string | number | boolean | null | object; export type EmbedsValidationStatus = Map; export type ElementsPendingErasure = Set; export type PendingExcalidrawElements = ExcalidrawElement[]; /** Runtime gridSize value. Null indicates disabled grid. */ export type NullableGridSize = (AppState["gridSize"] & MakeBrand<"NullableGridSize">) | null; export type GenerateDiagramToCode = (props: { frame: ExcalidrawMagicFrameElement; children: readonly ExcalidrawElement[]; }) => MaybePromise<{ html: string; }>; export type Offsets = Partial<{ top: number; right: number; bottom: number; left: number; }>; export {};