types.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. import {
  2. PointerType,
  3. ExcalidrawLinearElement,
  4. NonDeletedExcalidrawElement,
  5. NonDeleted,
  6. TextAlign,
  7. ExcalidrawElement,
  8. GroupId,
  9. ExcalidrawBindableElement,
  10. Arrowhead,
  11. ChartType,
  12. FontFamilyValues,
  13. FileId,
  14. ExcalidrawImageElement,
  15. Theme,
  16. } from "./element/types";
  17. import { SHAPES } from "./shapes";
  18. import { Point as RoughPoint } from "roughjs/bin/geometry";
  19. import { LinearElementEditor } from "./element/linearElementEditor";
  20. import { SuggestedBinding } from "./element/binding";
  21. import { ImportedDataState } from "./data/types";
  22. import type App from "./components/App";
  23. import type { ResolvablePromise, throttleRAF } from "./utils";
  24. import { Spreadsheet } from "./charts";
  25. import { Language } from "./i18n";
  26. import { ClipboardData } from "./clipboard";
  27. import { isOverScrollBars } from "./scene";
  28. import { MaybeTransformHandleType } from "./element/transformHandles";
  29. import Library from "./data/library";
  30. import type { FileSystemHandle } from "./data/filesystem";
  31. import type { ALLOWED_IMAGE_MIME_TYPES, MIME_TYPES } from "./constants";
  32. export type Point = Readonly<RoughPoint>;
  33. export type Collaborator = {
  34. pointer?: {
  35. x: number;
  36. y: number;
  37. };
  38. button?: "up" | "down";
  39. selectedElementIds?: AppState["selectedElementIds"];
  40. username?: string | null;
  41. userState?: UserIdleState;
  42. color?: {
  43. background: string;
  44. stroke: string;
  45. };
  46. };
  47. export type DataURL = string & { _brand: "DataURL" };
  48. export type BinaryFileData = {
  49. mimeType:
  50. | typeof ALLOWED_IMAGE_MIME_TYPES[number]
  51. // future user or unknown file type
  52. | typeof MIME_TYPES.binary;
  53. id: FileId;
  54. dataURL: DataURL;
  55. created: number;
  56. };
  57. export type BinaryFileMetadata = Omit<BinaryFileData, "dataURL">;
  58. export type BinaryFiles = Record<ExcalidrawElement["id"], BinaryFileData>;
  59. export type AppState = {
  60. isLoading: boolean;
  61. errorMessage: string | null;
  62. draggingElement: NonDeletedExcalidrawElement | null;
  63. resizingElement: NonDeletedExcalidrawElement | null;
  64. multiElement: NonDeleted<ExcalidrawLinearElement> | null;
  65. selectionElement: NonDeletedExcalidrawElement | null;
  66. isBindingEnabled: boolean;
  67. startBoundElement: NonDeleted<ExcalidrawBindableElement> | null;
  68. suggestedBindings: SuggestedBinding[];
  69. // element being edited, but not necessarily added to elements array yet
  70. // (e.g. text element when typing into the input)
  71. editingElement: NonDeletedExcalidrawElement | null;
  72. editingLinearElement: LinearElementEditor | null;
  73. elementType: typeof SHAPES[number]["value"];
  74. elementLocked: boolean;
  75. penMode: boolean;
  76. penDetected: boolean;
  77. exportBackground: boolean;
  78. exportEmbedScene: boolean;
  79. exportWithDarkMode: boolean;
  80. exportScale: number;
  81. currentItemStrokeColor: string;
  82. currentItemBackgroundColor: string;
  83. currentItemFillStyle: ExcalidrawElement["fillStyle"];
  84. currentItemStrokeWidth: number;
  85. currentItemStrokeStyle: ExcalidrawElement["strokeStyle"];
  86. currentItemRoughness: number;
  87. currentItemOpacity: number;
  88. currentItemFontFamily: FontFamilyValues;
  89. currentItemFontSize: number;
  90. currentItemTextAlign: TextAlign;
  91. currentItemStrokeSharpness: ExcalidrawElement["strokeSharpness"];
  92. currentItemStartArrowhead: Arrowhead | null;
  93. currentItemEndArrowhead: Arrowhead | null;
  94. currentItemLinearStrokeSharpness: ExcalidrawElement["strokeSharpness"];
  95. viewBackgroundColor: string;
  96. scrollX: number;
  97. scrollY: number;
  98. cursorButton: "up" | "down";
  99. scrolledOutside: boolean;
  100. name: string;
  101. isResizing: boolean;
  102. isRotating: boolean;
  103. zoom: Zoom;
  104. openMenu: "canvas" | "shape" | null;
  105. openPopup:
  106. | "canvasColorPicker"
  107. | "backgroundColorPicker"
  108. | "strokeColorPicker"
  109. | null;
  110. lastPointerDownWith: PointerType;
  111. selectedElementIds: { [id: string]: boolean };
  112. previousSelectedElementIds: { [id: string]: boolean };
  113. shouldCacheIgnoreZoom: boolean;
  114. showHelpDialog: boolean;
  115. toastMessage: string | null;
  116. zenModeEnabled: boolean;
  117. theme: Theme;
  118. gridSize: number | null;
  119. viewModeEnabled: boolean;
  120. /** top-most selected groups (i.e. does not include nested groups) */
  121. selectedGroupIds: { [groupId: string]: boolean };
  122. /** group being edited when you drill down to its constituent element
  123. (e.g. when you double-click on a group's element) */
  124. editingGroupId: GroupId | null;
  125. width: number;
  126. height: number;
  127. offsetTop: number;
  128. offsetLeft: number;
  129. isLibraryOpen: boolean;
  130. fileHandle: FileSystemHandle | null;
  131. collaborators: Map<string, Collaborator>;
  132. showStats: boolean;
  133. currentChartType: ChartType;
  134. pasteDialog:
  135. | {
  136. shown: false;
  137. data: null;
  138. }
  139. | {
  140. shown: true;
  141. data: Spreadsheet;
  142. };
  143. /** imageElement waiting to be placed on canvas */
  144. pendingImageElement: NonDeleted<ExcalidrawImageElement> | null;
  145. showHyperlinkPopup: false | "info" | "editor";
  146. };
  147. export type NormalizedZoomValue = number & { _brand: "normalizedZoom" };
  148. export type Zoom = Readonly<{
  149. value: NormalizedZoomValue;
  150. }>;
  151. export type PointerCoords = Readonly<{
  152. x: number;
  153. y: number;
  154. }>;
  155. export type Gesture = {
  156. pointers: Map<number, PointerCoords>;
  157. lastCenter: { x: number; y: number } | null;
  158. initialDistance: number | null;
  159. initialScale: number | null;
  160. };
  161. export declare class GestureEvent extends UIEvent {
  162. readonly rotation: number;
  163. readonly scale: number;
  164. }
  165. // libraries
  166. // -----------------------------------------------------------------------------
  167. /** @deprecated legacy: do not use outside of migration paths */
  168. export type LibraryItem_v1 = readonly NonDeleted<ExcalidrawElement>[];
  169. /** @deprecated legacy: do not use outside of migration paths */
  170. export type LibraryItems_v1 = readonly LibraryItem_v1[];
  171. /** v2 library item */
  172. export type LibraryItem = {
  173. id: string;
  174. status: "published" | "unpublished";
  175. elements: readonly NonDeleted<ExcalidrawElement>[];
  176. /** timestamp in epoch (ms) */
  177. created: number;
  178. name?: string;
  179. error?: string;
  180. };
  181. export type LibraryItems = readonly LibraryItem[];
  182. // -----------------------------------------------------------------------------
  183. // NOTE ready/readyPromise props are optional for host apps' sake (our own
  184. // implem guarantees existence)
  185. export type ExcalidrawAPIRefValue =
  186. | ExcalidrawImperativeAPI
  187. | {
  188. readyPromise?: ResolvablePromise<ExcalidrawImperativeAPI>;
  189. ready?: false;
  190. };
  191. export interface ExcalidrawProps {
  192. onChange?: (
  193. elements: readonly ExcalidrawElement[],
  194. appState: AppState,
  195. files: BinaryFiles,
  196. ) => void;
  197. initialData?: ImportedDataState | null | Promise<ImportedDataState | null>;
  198. excalidrawRef?: ForwardRef<ExcalidrawAPIRefValue>;
  199. onCollabButtonClick?: () => void;
  200. isCollaborating?: boolean;
  201. onPointerUpdate?: (payload: {
  202. pointer: { x: number; y: number };
  203. button: "down" | "up";
  204. pointersMap: Gesture["pointers"];
  205. }) => void;
  206. onPaste?: (
  207. data: ClipboardData,
  208. event: ClipboardEvent | null,
  209. ) => Promise<boolean> | boolean;
  210. renderTopRightUI?: (
  211. isMobile: boolean,
  212. appState: AppState,
  213. ) => JSX.Element | null;
  214. renderFooter?: (isMobile: boolean, appState: AppState) => JSX.Element;
  215. langCode?: Language["code"];
  216. viewModeEnabled?: boolean;
  217. zenModeEnabled?: boolean;
  218. gridModeEnabled?: boolean;
  219. libraryReturnUrl?: string;
  220. theme?: Theme;
  221. name?: string;
  222. renderCustomStats?: (
  223. elements: readonly NonDeletedExcalidrawElement[],
  224. appState: AppState,
  225. ) => JSX.Element;
  226. UIOptions?: UIOptions;
  227. detectScroll?: boolean;
  228. handleKeyboardGlobally?: boolean;
  229. onLibraryChange?: (libraryItems: LibraryItems) => void | Promise<any>;
  230. autoFocus?: boolean;
  231. generateIdForFile?: (file: File) => string | Promise<string>;
  232. onLinkOpen?: (
  233. element: NonDeletedExcalidrawElement,
  234. event: CustomEvent<{
  235. nativeEvent: MouseEvent | React.PointerEvent<HTMLCanvasElement>;
  236. }>,
  237. ) => void;
  238. }
  239. export type SceneData = {
  240. elements?: ImportedDataState["elements"];
  241. appState?: ImportedDataState["appState"];
  242. collaborators?: Map<string, Collaborator>;
  243. commitToHistory?: boolean;
  244. libraryItems?: LibraryItems | LibraryItems_v1;
  245. };
  246. export enum UserIdleState {
  247. ACTIVE = "active",
  248. AWAY = "away",
  249. IDLE = "idle",
  250. }
  251. export type ExportOpts = {
  252. saveFileToDisk?: boolean;
  253. onExportToBackend?: (
  254. exportedElements: readonly NonDeletedExcalidrawElement[],
  255. appState: AppState,
  256. files: BinaryFiles,
  257. canvas: HTMLCanvasElement | null,
  258. ) => void;
  259. renderCustomUI?: (
  260. exportedElements: readonly NonDeletedExcalidrawElement[],
  261. appState: AppState,
  262. files: BinaryFiles,
  263. canvas: HTMLCanvasElement | null,
  264. ) => JSX.Element;
  265. };
  266. type CanvasActions = {
  267. changeViewBackgroundColor?: boolean;
  268. clearCanvas?: boolean;
  269. export?: false | ExportOpts;
  270. loadScene?: boolean;
  271. saveToActiveFile?: boolean;
  272. theme?: boolean;
  273. saveAsImage?: boolean;
  274. };
  275. export type UIOptions = {
  276. canvasActions?: CanvasActions;
  277. };
  278. export type AppProps = ExcalidrawProps & {
  279. UIOptions: {
  280. canvasActions: Required<CanvasActions> & { export: ExportOpts };
  281. };
  282. detectScroll: boolean;
  283. handleKeyboardGlobally: boolean;
  284. isCollaborating: boolean;
  285. };
  286. /** A subset of App class properties that we need to use elsewhere
  287. * in the app, eg Manager. Factored out into a separate type to keep DRY. */
  288. export type AppClassProperties = {
  289. props: AppProps;
  290. canvas: HTMLCanvasElement | null;
  291. focusContainer(): void;
  292. library: Library;
  293. imageCache: Map<
  294. FileId,
  295. {
  296. image: HTMLImageElement | Promise<HTMLImageElement>;
  297. mimeType: typeof ALLOWED_IMAGE_MIME_TYPES[number];
  298. }
  299. >;
  300. files: BinaryFiles;
  301. };
  302. export type PointerDownState = Readonly<{
  303. // The first position at which pointerDown happened
  304. origin: Readonly<{ x: number; y: number }>;
  305. // Same as "origin" but snapped to the grid, if grid is on
  306. originInGrid: Readonly<{ x: number; y: number }>;
  307. // Scrollbar checks
  308. scrollbars: ReturnType<typeof isOverScrollBars>;
  309. // The previous pointer position
  310. lastCoords: { x: number; y: number };
  311. // map of original elements data
  312. originalElements: Map<string, NonDeleted<ExcalidrawElement>>;
  313. resize: {
  314. // Handle when resizing, might change during the pointer interaction
  315. handleType: MaybeTransformHandleType;
  316. // This is determined on the initial pointer down event
  317. isResizing: boolean;
  318. // This is determined on the initial pointer down event
  319. offset: { x: number; y: number };
  320. // This is determined on the initial pointer down event
  321. arrowDirection: "origin" | "end";
  322. // This is a center point of selected elements determined on the initial pointer down event (for rotation only)
  323. center: { x: number; y: number };
  324. };
  325. hit: {
  326. // The element the pointer is "hitting", is determined on the initial
  327. // pointer down event
  328. element: NonDeleted<ExcalidrawElement> | null;
  329. // The elements the pointer is "hitting", is determined on the initial
  330. // pointer down event
  331. allHitElements: NonDeleted<ExcalidrawElement>[];
  332. // This is determined on the initial pointer down event
  333. wasAddedToSelection: boolean;
  334. // Whether selected element(s) were duplicated, might change during the
  335. // pointer interaction
  336. hasBeenDuplicated: boolean;
  337. hasHitCommonBoundingBoxOfSelectedElements: boolean;
  338. hasHitElementInside: boolean;
  339. };
  340. withCmdOrCtrl: boolean;
  341. drag: {
  342. // Might change during the pointer interaction
  343. hasOccurred: boolean;
  344. // Might change during the pointer interaction
  345. offset: { x: number; y: number } | null;
  346. };
  347. // We need to have these in the state so that we can unsubscribe them
  348. eventListeners: {
  349. // It's defined on the initial pointer down event
  350. onMove: null | ReturnType<typeof throttleRAF>;
  351. // It's defined on the initial pointer down event
  352. onUp: null | ((event: PointerEvent) => void);
  353. // It's defined on the initial pointer down event
  354. onKeyDown: null | ((event: KeyboardEvent) => void);
  355. // It's defined on the initial pointer down event
  356. onKeyUp: null | ((event: KeyboardEvent) => void);
  357. };
  358. boxSelection: {
  359. hasOccurred: boolean;
  360. };
  361. }>;
  362. export type ExcalidrawImperativeAPI = {
  363. updateScene: InstanceType<typeof App>["updateScene"];
  364. resetScene: InstanceType<typeof App>["resetScene"];
  365. getSceneElementsIncludingDeleted: InstanceType<
  366. typeof App
  367. >["getSceneElementsIncludingDeleted"];
  368. history: {
  369. clear: InstanceType<typeof App>["resetHistory"];
  370. };
  371. scrollToContent: InstanceType<typeof App>["scrollToContent"];
  372. getSceneElements: InstanceType<typeof App>["getSceneElements"];
  373. getAppState: () => InstanceType<typeof App>["state"];
  374. getFiles: () => InstanceType<typeof App>["files"];
  375. refresh: InstanceType<typeof App>["refresh"];
  376. importLibrary: InstanceType<typeof App>["importLibraryFromUrl"];
  377. setToastMessage: InstanceType<typeof App>["setToastMessage"];
  378. addFiles: (data: BinaryFileData[]) => void;
  379. readyPromise: ResolvablePromise<ExcalidrawImperativeAPI>;
  380. ready: true;
  381. id: string;
  382. };