types.ts 12 KB

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