types.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  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. };
  240. export enum UserIdleState {
  241. ACTIVE = "active",
  242. AWAY = "away",
  243. IDLE = "idle",
  244. }
  245. export type ExportOpts = {
  246. saveFileToDisk?: boolean;
  247. onExportToBackend?: (
  248. exportedElements: readonly NonDeletedExcalidrawElement[],
  249. appState: AppState,
  250. files: BinaryFiles,
  251. canvas: HTMLCanvasElement | null,
  252. ) => void;
  253. renderCustomUI?: (
  254. exportedElements: readonly NonDeletedExcalidrawElement[],
  255. appState: AppState,
  256. files: BinaryFiles,
  257. canvas: HTMLCanvasElement | null,
  258. ) => JSX.Element;
  259. };
  260. type CanvasActions = {
  261. changeViewBackgroundColor?: boolean;
  262. clearCanvas?: boolean;
  263. export?: false | ExportOpts;
  264. loadScene?: boolean;
  265. saveToActiveFile?: boolean;
  266. theme?: boolean;
  267. saveAsImage?: boolean;
  268. };
  269. export type UIOptions = {
  270. canvasActions?: CanvasActions;
  271. };
  272. export type AppProps = ExcalidrawProps & {
  273. UIOptions: {
  274. canvasActions: Required<CanvasActions> & { export: ExportOpts };
  275. };
  276. detectScroll: boolean;
  277. handleKeyboardGlobally: boolean;
  278. };
  279. /** A subset of App class properties that we need to use elsewhere
  280. * in the app, eg Manager. Factored out into a separate type to keep DRY. */
  281. export type AppClassProperties = {
  282. props: AppProps;
  283. canvas: HTMLCanvasElement | null;
  284. focusContainer(): void;
  285. library: Library;
  286. imageCache: Map<
  287. FileId,
  288. {
  289. image: HTMLImageElement | Promise<HTMLImageElement>;
  290. mimeType: typeof ALLOWED_IMAGE_MIME_TYPES[number];
  291. }
  292. >;
  293. files: BinaryFiles;
  294. };
  295. export type PointerDownState = Readonly<{
  296. // The first position at which pointerDown happened
  297. origin: Readonly<{ x: number; y: number }>;
  298. // Same as "origin" but snapped to the grid, if grid is on
  299. originInGrid: Readonly<{ x: number; y: number }>;
  300. // Scrollbar checks
  301. scrollbars: ReturnType<typeof isOverScrollBars>;
  302. // The previous pointer position
  303. lastCoords: { x: number; y: number };
  304. // map of original elements data
  305. originalElements: Map<string, NonDeleted<ExcalidrawElement>>;
  306. resize: {
  307. // Handle when resizing, might change during the pointer interaction
  308. handleType: MaybeTransformHandleType;
  309. // This is determined on the initial pointer down event
  310. isResizing: boolean;
  311. // This is determined on the initial pointer down event
  312. offset: { x: number; y: number };
  313. // This is determined on the initial pointer down event
  314. arrowDirection: "origin" | "end";
  315. // This is a center point of selected elements determined on the initial pointer down event (for rotation only)
  316. center: { x: number; y: number };
  317. };
  318. hit: {
  319. // The element the pointer is "hitting", is determined on the initial
  320. // pointer down event
  321. element: NonDeleted<ExcalidrawElement> | null;
  322. // The elements the pointer is "hitting", is determined on the initial
  323. // pointer down event
  324. allHitElements: NonDeleted<ExcalidrawElement>[];
  325. // This is determined on the initial pointer down event
  326. wasAddedToSelection: boolean;
  327. // Whether selected element(s) were duplicated, might change during the
  328. // pointer interaction
  329. hasBeenDuplicated: boolean;
  330. hasHitCommonBoundingBoxOfSelectedElements: boolean;
  331. hasHitElementInside: boolean;
  332. };
  333. withCmdOrCtrl: boolean;
  334. drag: {
  335. // Might change during the pointer interation
  336. hasOccurred: boolean;
  337. // Might change during the pointer interation
  338. offset: { x: number; y: number } | null;
  339. };
  340. // We need to have these in the state so that we can unsubscribe them
  341. eventListeners: {
  342. // It's defined on the initial pointer down event
  343. onMove: null | ((event: PointerEvent) => void);
  344. // It's defined on the initial pointer down event
  345. onUp: null | ((event: PointerEvent) => void);
  346. // It's defined on the initial pointer down event
  347. onKeyDown: null | ((event: KeyboardEvent) => void);
  348. // It's defined on the initial pointer down event
  349. onKeyUp: null | ((event: KeyboardEvent) => void);
  350. };
  351. boxSelection: {
  352. hasOccurred: boolean;
  353. };
  354. }>;
  355. export type ExcalidrawImperativeAPI = {
  356. updateScene: InstanceType<typeof App>["updateScene"];
  357. resetScene: InstanceType<typeof App>["resetScene"];
  358. getSceneElementsIncludingDeleted: InstanceType<
  359. typeof App
  360. >["getSceneElementsIncludingDeleted"];
  361. history: {
  362. clear: InstanceType<typeof App>["resetHistory"];
  363. };
  364. scrollToContent: InstanceType<typeof App>["scrollToContent"];
  365. getSceneElements: InstanceType<typeof App>["getSceneElements"];
  366. getAppState: () => InstanceType<typeof App>["state"];
  367. getFiles: () => InstanceType<typeof App>["files"];
  368. refresh: InstanceType<typeof App>["refresh"];
  369. importLibrary: InstanceType<typeof App>["importLibraryFromUrl"];
  370. setToastMessage: InstanceType<typeof App>["setToastMessage"];
  371. addFiles: (data: BinaryFileData[]) => void;
  372. readyPromise: ResolvablePromise<ExcalidrawImperativeAPI>;
  373. ready: true;
  374. id: string;
  375. };