types.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758
  1. import React from "react";
  2. import {
  3. PointerType,
  4. ExcalidrawLinearElement,
  5. NonDeletedExcalidrawElement,
  6. NonDeleted,
  7. TextAlign,
  8. ExcalidrawElement,
  9. GroupId,
  10. ExcalidrawBindableElement,
  11. Arrowhead,
  12. ChartType,
  13. FontFamilyValues,
  14. FileId,
  15. ExcalidrawImageElement,
  16. Theme,
  17. StrokeRoundness,
  18. ExcalidrawEmbeddableElement,
  19. ExcalidrawMagicFrameElement,
  20. ExcalidrawFrameLikeElement,
  21. ExcalidrawElementType,
  22. ExcalidrawIframeLikeElement,
  23. } from "./element/types";
  24. import { Action } from "./actions/types";
  25. import { Point as RoughPoint } from "roughjs/bin/geometry";
  26. import { LinearElementEditor } from "./element/linearElementEditor";
  27. import { SuggestedBinding } from "./element/binding";
  28. import { ImportedDataState } from "./data/types";
  29. import type App from "./components/App";
  30. import type { throttleRAF } from "./utils";
  31. import { Spreadsheet } from "./charts";
  32. import { Language } from "./i18n";
  33. import { ClipboardData } from "./clipboard";
  34. import { isOverScrollBars } from "./scene/scrollbars";
  35. import { MaybeTransformHandleType } from "./element/transformHandles";
  36. import Library from "./data/library";
  37. import type { FileSystemHandle } from "./data/filesystem";
  38. import type { IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
  39. import { ContextMenuItems } from "./components/ContextMenu";
  40. import { SnapLine } from "./snapping";
  41. import { Merge, ValueOf } from "./utility-types";
  42. export type Point = Readonly<RoughPoint>;
  43. export type SocketId = string & { _brand: "SocketId" };
  44. export type Collaborator = Readonly<{
  45. pointer?: CollaboratorPointer;
  46. button?: "up" | "down";
  47. selectedElementIds?: AppState["selectedElementIds"];
  48. username?: string | null;
  49. userState?: UserIdleState;
  50. color?: {
  51. background: string;
  52. stroke: string;
  53. };
  54. // The url of the collaborator's avatar, defaults to username initials
  55. // if not present
  56. avatarUrl?: string;
  57. // user id. If supplied, we'll filter out duplicates when rendering user avatars.
  58. id?: string;
  59. socketId?: SocketId;
  60. isCurrentUser?: boolean;
  61. }>;
  62. export type CollaboratorPointer = {
  63. x: number;
  64. y: number;
  65. tool: "pointer" | "laser";
  66. };
  67. export type DataURL = string & { _brand: "DataURL" };
  68. export type BinaryFileData = {
  69. mimeType:
  70. | ValueOf<typeof IMAGE_MIME_TYPES>
  71. // future user or unknown file type
  72. | typeof MIME_TYPES.binary;
  73. id: FileId;
  74. dataURL: DataURL;
  75. /**
  76. * Epoch timestamp in milliseconds
  77. */
  78. created: number;
  79. /**
  80. * Indicates when the file was last retrieved from storage to be loaded
  81. * onto the scene. We use this flag to determine whether to delete unused
  82. * files from storage.
  83. *
  84. * Epoch timestamp in milliseconds.
  85. */
  86. lastRetrieved?: number;
  87. };
  88. export type BinaryFileMetadata = Omit<BinaryFileData, "dataURL">;
  89. export type BinaryFiles = Record<ExcalidrawElement["id"], BinaryFileData>;
  90. export type ToolType =
  91. | "selection"
  92. | "rectangle"
  93. | "diamond"
  94. | "ellipse"
  95. | "arrow"
  96. | "line"
  97. | "freedraw"
  98. | "text"
  99. | "image"
  100. | "eraser"
  101. | "hand"
  102. | "frame"
  103. | "magicframe"
  104. | "embeddable"
  105. | "laser";
  106. export type ElementOrToolType = ExcalidrawElementType | ToolType | "custom";
  107. export type ActiveTool =
  108. | {
  109. type: ToolType;
  110. customType: null;
  111. }
  112. | {
  113. type: "custom";
  114. customType: string;
  115. };
  116. export type SidebarName = string;
  117. export type SidebarTabName = string;
  118. export type UserToFollow = {
  119. socketId: SocketId;
  120. username: string;
  121. };
  122. type _CommonCanvasAppState = {
  123. zoom: AppState["zoom"];
  124. scrollX: AppState["scrollX"];
  125. scrollY: AppState["scrollY"];
  126. width: AppState["width"];
  127. height: AppState["height"];
  128. viewModeEnabled: AppState["viewModeEnabled"];
  129. editingGroupId: AppState["editingGroupId"]; // TODO: move to interactive canvas if possible
  130. selectedElementIds: AppState["selectedElementIds"]; // TODO: move to interactive canvas if possible
  131. frameToHighlight: AppState["frameToHighlight"]; // TODO: move to interactive canvas if possible
  132. offsetLeft: AppState["offsetLeft"];
  133. offsetTop: AppState["offsetTop"];
  134. theme: AppState["theme"];
  135. pendingImageElementId: AppState["pendingImageElementId"];
  136. };
  137. export type StaticCanvasAppState = Readonly<
  138. _CommonCanvasAppState & {
  139. shouldCacheIgnoreZoom: AppState["shouldCacheIgnoreZoom"];
  140. /** null indicates transparent bg */
  141. viewBackgroundColor: AppState["viewBackgroundColor"] | null;
  142. exportScale: AppState["exportScale"];
  143. selectedElementsAreBeingDragged: AppState["selectedElementsAreBeingDragged"];
  144. gridSize: AppState["gridSize"];
  145. frameRendering: AppState["frameRendering"];
  146. }
  147. >;
  148. export type InteractiveCanvasAppState = Readonly<
  149. _CommonCanvasAppState & {
  150. // renderInteractiveScene
  151. activeEmbeddable: AppState["activeEmbeddable"];
  152. editingLinearElement: AppState["editingLinearElement"];
  153. selectionElement: AppState["selectionElement"];
  154. selectedGroupIds: AppState["selectedGroupIds"];
  155. selectedLinearElement: AppState["selectedLinearElement"];
  156. multiElement: AppState["multiElement"];
  157. isBindingEnabled: AppState["isBindingEnabled"];
  158. suggestedBindings: AppState["suggestedBindings"];
  159. isRotating: AppState["isRotating"];
  160. elementsToHighlight: AppState["elementsToHighlight"];
  161. // Collaborators
  162. collaborators: AppState["collaborators"];
  163. // SnapLines
  164. snapLines: AppState["snapLines"];
  165. zenModeEnabled: AppState["zenModeEnabled"];
  166. }
  167. >;
  168. export interface AppState {
  169. contextMenu: {
  170. items: ContextMenuItems;
  171. top: number;
  172. left: number;
  173. } | null;
  174. showWelcomeScreen: boolean;
  175. isLoading: boolean;
  176. errorMessage: React.ReactNode;
  177. activeEmbeddable: {
  178. element: NonDeletedExcalidrawElement;
  179. state: "hover" | "active";
  180. } | null;
  181. draggingElement: NonDeletedExcalidrawElement | null;
  182. resizingElement: NonDeletedExcalidrawElement | null;
  183. multiElement: NonDeleted<ExcalidrawLinearElement> | null;
  184. selectionElement: NonDeletedExcalidrawElement | null;
  185. isBindingEnabled: boolean;
  186. startBoundElement: NonDeleted<ExcalidrawBindableElement> | null;
  187. suggestedBindings: SuggestedBinding[];
  188. frameToHighlight: NonDeleted<ExcalidrawFrameLikeElement> | null;
  189. frameRendering: {
  190. enabled: boolean;
  191. name: boolean;
  192. outline: boolean;
  193. clip: boolean;
  194. };
  195. editingFrame: string | null;
  196. elementsToHighlight: NonDeleted<ExcalidrawElement>[] | null;
  197. // element being edited, but not necessarily added to elements array yet
  198. // (e.g. text element when typing into the input)
  199. editingElement: NonDeletedExcalidrawElement | null;
  200. editingLinearElement: LinearElementEditor | null;
  201. activeTool: {
  202. /**
  203. * indicates a previous tool we should revert back to if we deselect the
  204. * currently active tool. At the moment applies to `eraser` and `hand` tool.
  205. */
  206. lastActiveTool: ActiveTool | null;
  207. locked: boolean;
  208. } & ActiveTool;
  209. penMode: boolean;
  210. penDetected: boolean;
  211. exportBackground: boolean;
  212. exportEmbedScene: boolean;
  213. exportWithDarkMode: boolean;
  214. exportScale: number;
  215. currentItemStrokeColor: string;
  216. currentItemBackgroundColor: string;
  217. currentItemFillStyle: ExcalidrawElement["fillStyle"];
  218. currentItemStrokeWidth: number;
  219. currentItemStrokeStyle: ExcalidrawElement["strokeStyle"];
  220. currentItemRoughness: number;
  221. currentItemOpacity: number;
  222. currentItemFontFamily: FontFamilyValues;
  223. currentItemFontSize: number;
  224. currentItemTextAlign: TextAlign;
  225. currentItemStartArrowhead: Arrowhead | null;
  226. currentItemEndArrowhead: Arrowhead | null;
  227. currentItemRoundness: StrokeRoundness;
  228. viewBackgroundColor: string;
  229. scrollX: number;
  230. scrollY: number;
  231. cursorButton: "up" | "down";
  232. scrolledOutside: boolean;
  233. name: string;
  234. isResizing: boolean;
  235. isRotating: boolean;
  236. zoom: Zoom;
  237. openMenu: "canvas" | "shape" | null;
  238. openPopup: "canvasBackground" | "elementBackground" | "elementStroke" | null;
  239. openSidebar: { name: SidebarName; tab?: SidebarTabName } | null;
  240. openDialog:
  241. | null
  242. | { name: "imageExport" | "help" | "jsonExport" }
  243. | {
  244. name: "settings";
  245. source:
  246. | "tool" // when magicframe tool is selected
  247. | "generation" // when magicframe generate button is clicked
  248. | "settings"; // when AI settings dialog is explicitly invoked
  249. tab: "text-to-diagram" | "diagram-to-code";
  250. }
  251. | { name: "ttd"; tab: "text-to-diagram" | "mermaid" };
  252. /**
  253. * Reflects user preference for whether the default sidebar should be docked.
  254. *
  255. * NOTE this is only a user preference and does not reflect the actual docked
  256. * state of the sidebar, because the host apps can override this through
  257. * a DefaultSidebar prop, which is not reflected back to the appState.
  258. */
  259. defaultSidebarDockedPreference: boolean;
  260. lastPointerDownWith: PointerType;
  261. selectedElementIds: Readonly<{ [id: string]: true }>;
  262. previousSelectedElementIds: { [id: string]: true };
  263. selectedElementsAreBeingDragged: boolean;
  264. shouldCacheIgnoreZoom: boolean;
  265. toast: { message: string; closable?: boolean; duration?: number } | null;
  266. zenModeEnabled: boolean;
  267. theme: Theme;
  268. gridSize: number | null;
  269. viewModeEnabled: boolean;
  270. /** top-most selected groups (i.e. does not include nested groups) */
  271. selectedGroupIds: { [groupId: string]: boolean };
  272. /** group being edited when you drill down to its constituent element
  273. (e.g. when you double-click on a group's element) */
  274. editingGroupId: GroupId | null;
  275. width: number;
  276. height: number;
  277. offsetTop: number;
  278. offsetLeft: number;
  279. fileHandle: FileSystemHandle | null;
  280. collaborators: Map<SocketId, Collaborator>;
  281. showStats: boolean;
  282. currentChartType: ChartType;
  283. pasteDialog:
  284. | {
  285. shown: false;
  286. data: null;
  287. }
  288. | {
  289. shown: true;
  290. data: Spreadsheet;
  291. };
  292. /** imageElement waiting to be placed on canvas */
  293. pendingImageElementId: ExcalidrawImageElement["id"] | null;
  294. showHyperlinkPopup: false | "info" | "editor";
  295. selectedLinearElement: LinearElementEditor | null;
  296. snapLines: readonly SnapLine[];
  297. originSnapOffset: {
  298. x: number;
  299. y: number;
  300. } | null;
  301. objectsSnapModeEnabled: boolean;
  302. /** the user's clientId & username who is being followed on the canvas */
  303. userToFollow: UserToFollow | null;
  304. /** the clientIds of the users following the current user */
  305. followedBy: Set<SocketId>;
  306. }
  307. export type UIAppState = Omit<
  308. AppState,
  309. | "suggestedBindings"
  310. | "startBoundElement"
  311. | "cursorButton"
  312. | "scrollX"
  313. | "scrollY"
  314. >;
  315. export type NormalizedZoomValue = number & { _brand: "normalizedZoom" };
  316. export type Zoom = Readonly<{
  317. value: NormalizedZoomValue;
  318. }>;
  319. export type PointerCoords = Readonly<{
  320. x: number;
  321. y: number;
  322. }>;
  323. export type Gesture = {
  324. pointers: Map<number, PointerCoords>;
  325. lastCenter: { x: number; y: number } | null;
  326. initialDistance: number | null;
  327. initialScale: number | null;
  328. };
  329. export declare class GestureEvent extends UIEvent {
  330. readonly rotation: number;
  331. readonly scale: number;
  332. }
  333. // libraries
  334. // -----------------------------------------------------------------------------
  335. /** @deprecated legacy: do not use outside of migration paths */
  336. export type LibraryItem_v1 = readonly NonDeleted<ExcalidrawElement>[];
  337. /** @deprecated legacy: do not use outside of migration paths */
  338. type LibraryItems_v1 = readonly LibraryItem_v1[];
  339. /** v2 library item */
  340. export type LibraryItem = {
  341. id: string;
  342. status: "published" | "unpublished";
  343. elements: readonly NonDeleted<ExcalidrawElement>[];
  344. /** timestamp in epoch (ms) */
  345. created: number;
  346. name?: string;
  347. error?: string;
  348. };
  349. export type LibraryItems = readonly LibraryItem[];
  350. export type LibraryItems_anyVersion = LibraryItems | LibraryItems_v1;
  351. export type LibraryItemsSource =
  352. | ((
  353. currentLibraryItems: LibraryItems,
  354. ) =>
  355. | Blob
  356. | LibraryItems_anyVersion
  357. | Promise<LibraryItems_anyVersion | Blob>)
  358. | Blob
  359. | LibraryItems_anyVersion
  360. | Promise<LibraryItems_anyVersion | Blob>;
  361. // -----------------------------------------------------------------------------
  362. export type ExcalidrawInitialDataState = Merge<
  363. ImportedDataState,
  364. {
  365. libraryItems?:
  366. | Required<ImportedDataState>["libraryItems"]
  367. | Promise<Required<ImportedDataState>["libraryItems"]>;
  368. }
  369. >;
  370. export type OnUserFollowedPayload = {
  371. userToFollow: UserToFollow;
  372. action: "FOLLOW" | "UNFOLLOW";
  373. };
  374. export interface ExcalidrawProps {
  375. onChange?: (
  376. elements: readonly ExcalidrawElement[],
  377. appState: AppState,
  378. files: BinaryFiles,
  379. ) => void;
  380. initialData?:
  381. | ExcalidrawInitialDataState
  382. | null
  383. | Promise<ExcalidrawInitialDataState | null>;
  384. excalidrawAPI?: (api: ExcalidrawImperativeAPI) => void;
  385. isCollaborating?: boolean;
  386. onPointerUpdate?: (payload: {
  387. pointer: { x: number; y: number; tool: "pointer" | "laser" };
  388. button: "down" | "up";
  389. pointersMap: Gesture["pointers"];
  390. }) => void;
  391. onPaste?: (
  392. data: ClipboardData,
  393. event: ClipboardEvent | null,
  394. ) => Promise<boolean> | boolean;
  395. renderTopRightUI?: (
  396. isMobile: boolean,
  397. appState: UIAppState,
  398. ) => JSX.Element | null;
  399. langCode?: Language["code"];
  400. viewModeEnabled?: boolean;
  401. zenModeEnabled?: boolean;
  402. gridModeEnabled?: boolean;
  403. objectsSnapModeEnabled?: boolean;
  404. libraryReturnUrl?: string;
  405. theme?: Theme;
  406. name?: string;
  407. renderCustomStats?: (
  408. elements: readonly NonDeletedExcalidrawElement[],
  409. appState: UIAppState,
  410. ) => JSX.Element;
  411. UIOptions?: Partial<UIOptions>;
  412. detectScroll?: boolean;
  413. handleKeyboardGlobally?: boolean;
  414. onLibraryChange?: (libraryItems: LibraryItems) => void | Promise<any>;
  415. autoFocus?: boolean;
  416. generateIdForFile?: (file: File) => string | Promise<string>;
  417. onLinkOpen?: (
  418. element: NonDeletedExcalidrawElement,
  419. event: CustomEvent<{
  420. nativeEvent: MouseEvent | React.PointerEvent<HTMLCanvasElement>;
  421. }>,
  422. ) => void;
  423. onPointerDown?: (
  424. activeTool: AppState["activeTool"],
  425. pointerDownState: PointerDownState,
  426. ) => void;
  427. onPointerUp?: (
  428. activeTool: AppState["activeTool"],
  429. pointerDownState: PointerDownState,
  430. ) => void;
  431. onScrollChange?: (scrollX: number, scrollY: number, zoom: Zoom) => void;
  432. onUserFollow?: (payload: OnUserFollowedPayload) => void;
  433. children?: React.ReactNode;
  434. validateEmbeddable?:
  435. | boolean
  436. | string[]
  437. | RegExp
  438. | RegExp[]
  439. | ((link: string) => boolean | undefined);
  440. renderEmbeddable?: (
  441. element: NonDeleted<ExcalidrawEmbeddableElement>,
  442. appState: AppState,
  443. ) => JSX.Element | null;
  444. aiEnabled?: boolean;
  445. }
  446. export type SceneData = {
  447. elements?: ImportedDataState["elements"];
  448. appState?: ImportedDataState["appState"];
  449. collaborators?: Map<SocketId, Collaborator>;
  450. commitToHistory?: boolean;
  451. };
  452. export enum UserIdleState {
  453. ACTIVE = "active",
  454. AWAY = "away",
  455. IDLE = "idle",
  456. }
  457. export type ExportOpts = {
  458. saveFileToDisk?: boolean;
  459. onExportToBackend?: (
  460. exportedElements: readonly NonDeletedExcalidrawElement[],
  461. appState: UIAppState,
  462. files: BinaryFiles,
  463. ) => void;
  464. renderCustomUI?: (
  465. exportedElements: readonly NonDeletedExcalidrawElement[],
  466. appState: UIAppState,
  467. files: BinaryFiles,
  468. canvas: HTMLCanvasElement,
  469. ) => JSX.Element;
  470. };
  471. // NOTE at the moment, if action name corresponds to canvasAction prop, its
  472. // truthiness value will determine whether the action is rendered or not
  473. // (see manager renderAction). We also override canvasAction values in
  474. // Excalidraw package index.tsx.
  475. export type CanvasActions = Partial<{
  476. changeViewBackgroundColor: boolean;
  477. clearCanvas: boolean;
  478. export: false | ExportOpts;
  479. loadScene: boolean;
  480. saveToActiveFile: boolean;
  481. toggleTheme: boolean | null;
  482. saveAsImage: boolean;
  483. }>;
  484. export type UIOptions = Partial<{
  485. dockedSidebarBreakpoint: number;
  486. canvasActions: CanvasActions;
  487. tools: {
  488. image: boolean;
  489. };
  490. /** @deprecated does nothing. Will be removed in 0.15 */
  491. welcomeScreen?: boolean;
  492. }>;
  493. export type AppProps = Merge<
  494. ExcalidrawProps,
  495. {
  496. UIOptions: Merge<
  497. UIOptions,
  498. {
  499. canvasActions: Required<CanvasActions> & { export: ExportOpts };
  500. }
  501. >;
  502. detectScroll: boolean;
  503. handleKeyboardGlobally: boolean;
  504. isCollaborating: boolean;
  505. children?: React.ReactNode;
  506. aiEnabled: boolean;
  507. }
  508. >;
  509. /** A subset of App class properties that we need to use elsewhere
  510. * in the app, eg Manager. Factored out into a separate type to keep DRY. */
  511. export type AppClassProperties = {
  512. props: AppProps;
  513. interactiveCanvas: HTMLCanvasElement | null;
  514. /** static canvas */
  515. canvas: HTMLCanvasElement;
  516. focusContainer(): void;
  517. library: Library;
  518. imageCache: Map<
  519. FileId,
  520. {
  521. image: HTMLImageElement | Promise<HTMLImageElement>;
  522. mimeType: ValueOf<typeof IMAGE_MIME_TYPES>;
  523. }
  524. >;
  525. files: BinaryFiles;
  526. device: App["device"];
  527. scene: App["scene"];
  528. pasteFromClipboard: App["pasteFromClipboard"];
  529. id: App["id"];
  530. onInsertElements: App["onInsertElements"];
  531. onExportImage: App["onExportImage"];
  532. lastViewportPosition: App["lastViewportPosition"];
  533. scrollToContent: App["scrollToContent"];
  534. addFiles: App["addFiles"];
  535. addElementsFromPasteOrLibrary: App["addElementsFromPasteOrLibrary"];
  536. togglePenMode: App["togglePenMode"];
  537. setActiveTool: App["setActiveTool"];
  538. setOpenDialog: App["setOpenDialog"];
  539. insertEmbeddableElement: App["insertEmbeddableElement"];
  540. onMagicframeToolSelect: App["onMagicframeToolSelect"];
  541. };
  542. export type PointerDownState = Readonly<{
  543. // The first position at which pointerDown happened
  544. origin: Readonly<{ x: number; y: number }>;
  545. // Same as "origin" but snapped to the grid, if grid is on
  546. originInGrid: Readonly<{ x: number; y: number }>;
  547. // Scrollbar checks
  548. scrollbars: ReturnType<typeof isOverScrollBars>;
  549. // The previous pointer position
  550. lastCoords: { x: number; y: number };
  551. // map of original elements data
  552. originalElements: Map<string, NonDeleted<ExcalidrawElement>>;
  553. resize: {
  554. // Handle when resizing, might change during the pointer interaction
  555. handleType: MaybeTransformHandleType;
  556. // This is determined on the initial pointer down event
  557. isResizing: boolean;
  558. // This is determined on the initial pointer down event
  559. offset: { x: number; y: number };
  560. // This is determined on the initial pointer down event
  561. arrowDirection: "origin" | "end";
  562. // This is a center point of selected elements determined on the initial pointer down event (for rotation only)
  563. center: { x: number; y: number };
  564. };
  565. hit: {
  566. // The element the pointer is "hitting", is determined on the initial
  567. // pointer down event
  568. element: NonDeleted<ExcalidrawElement> | null;
  569. // The elements the pointer is "hitting", is determined on the initial
  570. // pointer down event
  571. allHitElements: NonDeleted<ExcalidrawElement>[];
  572. // This is determined on the initial pointer down event
  573. wasAddedToSelection: boolean;
  574. // Whether selected element(s) were duplicated, might change during the
  575. // pointer interaction
  576. hasBeenDuplicated: boolean;
  577. hasHitCommonBoundingBoxOfSelectedElements: boolean;
  578. };
  579. withCmdOrCtrl: boolean;
  580. drag: {
  581. // Might change during the pointer interaction
  582. hasOccurred: boolean;
  583. // Might change during the pointer interaction
  584. offset: { x: number; y: number } | null;
  585. };
  586. // We need to have these in the state so that we can unsubscribe them
  587. eventListeners: {
  588. // It's defined on the initial pointer down event
  589. onMove: null | ReturnType<typeof throttleRAF>;
  590. // It's defined on the initial pointer down event
  591. onUp: null | ((event: PointerEvent) => void);
  592. // It's defined on the initial pointer down event
  593. onKeyDown: null | ((event: KeyboardEvent) => void);
  594. // It's defined on the initial pointer down event
  595. onKeyUp: null | ((event: KeyboardEvent) => void);
  596. };
  597. boxSelection: {
  598. hasOccurred: boolean;
  599. };
  600. }>;
  601. export type UnsubscribeCallback = () => void;
  602. export type ExcalidrawImperativeAPI = {
  603. updateScene: InstanceType<typeof App>["updateScene"];
  604. updateLibrary: InstanceType<typeof Library>["updateLibrary"];
  605. resetScene: InstanceType<typeof App>["resetScene"];
  606. getSceneElementsIncludingDeleted: InstanceType<
  607. typeof App
  608. >["getSceneElementsIncludingDeleted"];
  609. history: {
  610. clear: InstanceType<typeof App>["resetHistory"];
  611. };
  612. scrollToContent: InstanceType<typeof App>["scrollToContent"];
  613. getSceneElements: InstanceType<typeof App>["getSceneElements"];
  614. getAppState: () => InstanceType<typeof App>["state"];
  615. getFiles: () => InstanceType<typeof App>["files"];
  616. registerAction: (action: Action) => void;
  617. refresh: InstanceType<typeof App>["refresh"];
  618. setToast: InstanceType<typeof App>["setToast"];
  619. addFiles: (data: BinaryFileData[]) => void;
  620. id: string;
  621. setActiveTool: InstanceType<typeof App>["setActiveTool"];
  622. setCursor: InstanceType<typeof App>["setCursor"];
  623. resetCursor: InstanceType<typeof App>["resetCursor"];
  624. toggleSidebar: InstanceType<typeof App>["toggleSidebar"];
  625. /**
  626. * Disables rendering of frames (including element clipping), but currently
  627. * the frames are still interactive in edit mode. As such, this API should be
  628. * used in conjunction with view mode (props.viewModeEnabled).
  629. */
  630. updateFrameRendering: InstanceType<typeof App>["updateFrameRendering"];
  631. onChange: (
  632. callback: (
  633. elements: readonly ExcalidrawElement[],
  634. appState: AppState,
  635. files: BinaryFiles,
  636. ) => void,
  637. ) => UnsubscribeCallback;
  638. onPointerDown: (
  639. callback: (
  640. activeTool: AppState["activeTool"],
  641. pointerDownState: PointerDownState,
  642. event: React.PointerEvent<HTMLElement>,
  643. ) => void,
  644. ) => UnsubscribeCallback;
  645. onPointerUp: (
  646. callback: (
  647. activeTool: AppState["activeTool"],
  648. pointerDownState: PointerDownState,
  649. event: PointerEvent,
  650. ) => void,
  651. ) => UnsubscribeCallback;
  652. onScrollChange: (
  653. callback: (scrollX: number, scrollY: number, zoom: Zoom) => void,
  654. ) => UnsubscribeCallback;
  655. onUserFollow: (
  656. callback: (payload: OnUserFollowedPayload) => void,
  657. ) => UnsubscribeCallback;
  658. };
  659. export type Device = Readonly<{
  660. viewport: {
  661. isMobile: boolean;
  662. isLandscape: boolean;
  663. };
  664. editor: {
  665. isMobile: boolean;
  666. canFitSidebar: boolean;
  667. };
  668. isTouchScreen: boolean;
  669. }>;
  670. type FrameNameBounds = {
  671. x: number;
  672. y: number;
  673. width: number;
  674. height: number;
  675. angle: number;
  676. };
  677. export type FrameNameBoundsCache = {
  678. get: (
  679. frameElement: ExcalidrawFrameLikeElement | ExcalidrawMagicFrameElement,
  680. ) => FrameNameBounds | null;
  681. _cache: Map<
  682. string,
  683. FrameNameBounds & {
  684. zoom: AppState["zoom"]["value"];
  685. versionNonce: ExcalidrawFrameLikeElement["versionNonce"];
  686. }
  687. >;
  688. };
  689. export type KeyboardModifiersObject = {
  690. ctrlKey: boolean;
  691. shiftKey: boolean;
  692. altKey: boolean;
  693. metaKey: boolean;
  694. };
  695. export type Primitive =
  696. | number
  697. | string
  698. | boolean
  699. | bigint
  700. | symbol
  701. | null
  702. | undefined;
  703. export type JSONValue = string | number | boolean | null | object;
  704. export type EmbedsValidationStatus = Map<
  705. ExcalidrawIframeLikeElement["id"],
  706. boolean
  707. >;
  708. export type ElementsPendingErasure = Set<ExcalidrawElement["id"]>;