0.1.11 • Published 1 year ago

@ezfc/excalidraw v0.1.11

Weekly downloads
-
License
MIT
Repository
github
Last release
1 year ago

Note

⚠️ ⚠️ ⚠️ You are viewing the docs for the next release, in case you want to check the docs for the stable release, you can view it here.

Excalidraw

Excalidraw exported as a component to directly embed in your projects.

Installation

You can use npm

npm install react react-dom @excalidraw/excalidraw

or via yarn

yarn add react react-dom @excalidraw/excalidraw

After installation you will see a folder excalidraw-assets and excalidraw-assets-dev in dist directory which contains the assets needed for this app in prod and dev mode respectively.

Move the folder excalidraw-assets and excalidraw-assets-dev to the path where your assets are served.

By default it will try to load the files from https://unpkg.com/@excalidraw/excalidraw/dist/

If you want to load assets from a different path you can set a variable window.EXCALIDRAW_ASSET_PATH depending on environment (for example if you have different URL's for dev and prod) to the url from where you want to load the assets.

Note

If you don't want to wait for the next stable release and try out the unreleased changes you can use @excalidraw/excalidraw@next.

Demo

Try here.

Usage

Using Web Bundler

If you are using a Web bundler (for instance, Webpack), you can import it as an ES6 module as shown below

import React, { useEffect, useState, useRef } from "react";
import { Excalidraw } from "@excalidraw/excalidraw";
import InitialData from "./initialData";

import "./styles.scss";

export default function App() {
  const excalidrawRef = useRef(null);

  const [viewModeEnabled, setViewModeEnabled] = useState(false);
  const [zenModeEnabled, setZenModeEnabled] = useState(false);
  const [gridModeEnabled, setGridModeEnabled] = useState(false);

  const updateScene = () => {
    const sceneData = {
      elements: [
        {
          type: "rectangle",
          version: 141,
          versionNonce: 361174001,
          isDeleted: false,
          id: "oDVXy8D6rom3H1-LLH2-f",
          fillStyle: "hachure",
          strokeWidth: 1,
          strokeStyle: "solid",
          roughness: 1,
          opacity: 100,
          angle: 0,
          x: 100.50390625,
          y: 93.67578125,
          strokeColor: "#c92a2a",
          backgroundColor: "transparent",
          width: 186.47265625,
          height: 141.9765625,
          seed: 1968410350,
          groupIds: [],
        },
      ],
      appState: {
        viewBackgroundColor: "#edf2ff",
      },
    };
    excalidrawRef.current.updateScene(sceneData);
  };

  return (
    <div className="App">
      <h1> Excalidraw Example</h1>
      <div className="button-wrapper">
        <button className="update-scene" onClick={updateScene}>
          Update Scene
        </button>
        <button
          className="reset-scene"
          onClick={() => {
            excalidrawRef.current.resetScene();
          }}
        >
          Reset Scene
        </button>
        <label>
          <input
            type="checkbox"
            checked={viewModeEnabled}
            onChange={() => setViewModeEnabled(!viewModeEnabled)}
          />
          View mode
        </label>
        <label>
          <input
            type="checkbox"
            checked={zenModeEnabled}
            onChange={() => setZenModeEnabled(!zenModeEnabled)}
          />
          Zen mode
        </label>
        <label>
          <input
            type="checkbox"
            checked={gridModeEnabled}
            onChange={() => setGridModeEnabled(!gridModeEnabled)}
          />
          Grid mode
        </label>
      </div>
      <div className="excalidraw-wrapper">
        <Excalidraw
          ref={excalidrawRef}
          initialData={InitialData}
          onChange={(elements, state) =>
            console.log("Elements :", elements, "State : ", state)
          }
          onPointerUpdate={(payload) => console.log(payload)}
          viewModeEnabled={viewModeEnabled}
          zenModeEnabled={zenModeEnabled}
          gridModeEnabled={gridModeEnabled}
        />
      </div>
    </div>
  );
}

To view the full example visit :point_down:

Edit excalidraw

Since Excalidraw doesn't support server side rendering yet, you should render the component once the host is mounted.

import { useState, useEffect } from "react";
export default function IndexPage() {
  const [Comp, setComp] = useState(null);
  useEffect(() => {
    import("@excalidraw/excalidraw").then((comp) => setComp(comp.default));
  }, []);
  return <>{Comp && <Comp />}</>;
}

The types are available at @excalidraw/excalidraw/types, you can view example for typescript

In Browser

To use it in a browser directly:

For development use :point_down:

<script
  type="text/javascript"
  src="https://unpkg.com/@excalidraw/excalidraw/dist/excalidraw.development.js"
></script>

For production use :point_down:

<script
  type="text/javascript"
  src="https://unpkg.com/@excalidraw/excalidraw/dist/excalidraw.production.min.js"
></script>

You will need to make sure react, react-dom is available as shown in the below example. For prod please use the production versions of react, react-dom.

<!DOCTYPE html>
<html>
  <head>
    <title>Excalidraw in browser</title>
    <meta charset="UTF-8" />
    <script src="https://unpkg.com/react@16.14.0/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.development.js"></script>

    <script
      type="text/javascript"
      src="https://unpkg.com/@excalidraw/excalidraw/dist/excalidraw.development.js"
    ></script>
  </head>

  <body>
    <div class="container">
      <h1>Excalidraw Embed Example</h1>
      <div id="app"></div>
    </div>
    <script type="text/javascript" src="src/index.js"></script>
  </body>
</html>
/*eslint-disable */
import "./styles.css";
import InitialData from "./initialData";

const App = () => {
  const excalidrawRef = React.useRef(null);

  const [viewModeEnabled, setViewModeEnabled] = React.useState(false);
  const [zenModeEnabled, setZenModeEnabled] = React.useState(false);
  const [gridModeEnabled, setGridModeEnabled] = React.useState(false);

  const updateScene = () => {
    const sceneData = {
      elements: [
        {
          type: "rectangle",
          version: 141,
          versionNonce: 361174001,
          isDeleted: false,
          id: "oDVXy8D6rom3H1-LLH2-f",
          fillStyle: "hachure",
          strokeWidth: 1,
          strokeStyle: "solid",
          roughness: 1,
          opacity: 100,
          angle: 0,
          x: 100.50390625,
          y: 93.67578125,
          strokeColor: "#c92a2a",
          backgroundColor: "transparent",
          width: 186.47265625,
          height: 141.9765625,
          seed: 1968410350,
          groupIds: [],
        },
      ],
      appState: {
        viewBackgroundColor: "#edf2ff",
      },
    };
    excalidrawRef.current.updateScene(sceneData);
  };

  return React.createElement(
    React.Fragment,
    null,
    React.createElement(
      "div",
      { className: "button-wrapper" },
      React.createElement(
        "button",
        {
          className: "update-scene",
          onClick: updateScene,
        },
        "Update Scene",
      ),
      React.createElement(
        "button",
        {
          className: "reset-scene",
          onClick: () => excalidrawRef.current.resetScene(),
        },
        "Reset Scene",
      ),
      React.createElement(
        "label",
        null,
        React.createElement("input", {
          type: "checkbox",
          checked: viewModeEnabled,
          onChange: () => setViewModeEnabled(!viewModeEnabled),
        }),
        "View mode",
      ),
      React.createElement(
        "label",
        null,
        React.createElement("input", {
          type: "checkbox",
          checked: zenModeEnabled,
          onChange: () => setZenModeEnabled(!zenModeEnabled),
        }),
        "Zen mode",
      ),
      React.createElement(
        "label",
        null,
        React.createElement("input", {
          type: "checkbox",
          checked: gridModeEnabled,
          onChange: () => setGridModeEnabled(!gridModeEnabled),
        }),
        "Grid mode",
      ),
    ),
    React.createElement(
      "div",
      {
        className: "excalidraw-wrapper",
        ref: excalidrawWrapperRef,
      },
      React.createElement(ExcalidrawLib.Excalidraw, {
        initialData: InitialData,
        onChange: (elements, state) =>
          console.log("Elements :", elements, "State : ", state),
        onPointerUpdate: (payload) => console.log(payload),
        viewModeEnabled: viewModeEnabled,
        zenModeEnabled: zenModeEnabled,
        gridModeEnabled: gridModeEnabled,
      }),
    ),
  );
};

const excalidrawWrapper = document.getElementById("app");

ReactDOM.render(React.createElement(App), excalidrawWrapper);

To view the full example visit :point_down:

Edit excalidraw-in-browser

Customizing styles

Excalidraw is using CSS variables to style certain components. To override them, you should set your own on the .excalidraw and .excalidraw.theme--dark (for dark mode variables) selectors.

Make sure the selector has higher specificity, e.g. by prefixing it with your app's selector:

.your-app .excalidraw {
  --color-primary: red;
}
.your-app .excalidraw.theme--dark {
  --color-primary: pink;
}

Most notably, you can customize the primary colors, by overriding these variables:

  • --color-primary
  • --color-primary-darker
  • --color-primary-darkest
  • --color-primary-light
  • --color-primary-contrast-offset — a slightly darker (in light mode), or lighter (in dark mode) --color-primary color to fix contrast issues (see Chubb illusion). It will fall back to --color-primary if not present.

For a complete list of variables, check theme.scss, though most of them will not make sense to override.

Does this package support collaboration?

No, Excalidraw package doesn't come with collaboration built in, since the implementation is specific to each host app. We expose APIs which you can use to communicate with Excalidraw which you can use to implement it. You can check our own implementation here.

Component API

Footer

Earlier we were using renderFooter prop to render custom footer which was removed in #5970. Now you can pass a Footer component instead to render the custom UI for footer.

You will need to import the Footer component from the package and wrap your component with the Footer component. The Footer should a valid React Node.

Usage

import { Footer } from "@excalidraw/excalidraw";

const CustomFooter = () => <button> custom button</button>;
const App = () => {
  return (
    <Excalidraw>
      <Footer>
        <CustomFooter />
      </Footer>
    </Excalidraw>
  );
};

Footer is only rendered in the desktop view.

In the mobile view you can render it inside the MainMenu (later we will expose other ways to customize the UI). You can use the useDevice hook to check the type of device, this will be available only inside the children of Excalidraw component.

import { useDevice, Footer } from "@excalidraw/excalidraw";

const MobileFooter = () => {
  const device = useDevice();
  if (device.isMobile) {
    return (
      <Footer>
        <button
          className="custom-footer"
          onClick={() => alert("This is custom footer in mobile menu")}
        >
          {" "}
          custom footer{" "}
        </button>
      </Footer>
    );
  }
  return null;
};

const App = () => {
  <Excalidraw>
    <MainMenu>
      <MainMenu.Item onSelect={() => window.alert("Item1")}>
        Item1
      </MainMenu.Item>
      <MainMenu.Item onSelect={() => window.alert("Item2")}>
        Item2
      </MainMenu.Item>
      <MobileFooter />
    </MainMenu>
  </Excalidraw>;
};

You can visit the example for working demo.

MainMenu

By default Excalidraw will render the MainMenu with default options. If you want to customise the MainMenu, you can pass the MainMenu component with the list options. You can visit codesandbox example for a working demo.

Usage

import { MainMenu } from "@excalidraw/excalidraw";
const App = () => {
  <Excalidraw>
    <MainMenu>
      <MainMenu.Item onSelect={() => window.alert("Item1")}>
        Item1
      </MainMenu.Item>
      <MainMenu.Item onSelect={() => window.alert("Item2")}>
        Item2
      </MainMenu.Item>
    </MainMenu>
  </Excalidraw>;
};

MainMenu

This is the MainMenu component which you need to import to render the menu with custom options.

MainMenu.Item

Use this component to render a menu item.

PropTypeRequiredDefaultDescription
onSelectFunctionYesundefinedThe handler is triggered when the item is selected.
childrenReact.ReactNodeYesundefinedThe content of the menu item
iconJSX.ElementNoundefinedThe icon used in the menu item
shortcutstringNoThe keyboard shortcut (label-only, does not affect behavior)

MainMenu.ItemLink

To render an external link in a menu item, you can use this component.

Usage

import { MainMenu } from "@excalidraw/excalidraw";
const App = () => (
  <Excalidraw>
    <MainMenu>
      <MainMenu.ItemLink href="https://google.com">Google</MainMenu.ItemLink>
      <MainMenu.ItemLink href="https://excalidraw.com">
        Excalidraw
      </MainMenu.ItemLink>
    </MainMenu>
  </Excalidraw>;
);
PropTypeRequiredDefaultDescription
hrefstringYesundefinedThe href attribute to be added to the anchor element.
childrenReact.ReactNodeYesundefinedThe content of the menu item
iconJSX.ElementNoundefinedThe icon used in the menu item
shortcutstringNoThe keyboard shortcut (label-only, does not affect behavior)

MainMenu.ItemCustom

To render a custom item, you can use MainMenu.ItemCustom.

Usage

import { MainMenu } from "@excalidraw/excalidraw";
const App = () => (
  <Excalidraw>
    <MainMenu>
      <MainMenu.ItemCustom>
        <button
          style={{ height: "2rem" }}
          onClick={() => window.alert("custom menu item")}
        >
          {" "}
          custom item
        </button>
      </MainMenu.ItemCustom>
    </MainMenu>
  </Excalidraw>;
);
PropTypeRequiredDefaultDescription
childrenReact.ReactNodeYesundefinedThe content of the menu item

MainMenu.DefaultItems

For the items which are shown in the menu in excalidraw.com, you can use MainMenu.DefaultItems

import { MainMenu } from "@excalidraw/excalidraw";
const App = () => (
  <Excalidraw>
    <MainMenu>
      <MainMenu.DefaultItems.Socials/>
      <MainMenu.DefaultItems.Export/>
      <MainMenu.Item onSelect={() => window.alert("Item1")}> Item1 </MainMenu.Item>
      <MainMenu.Item onSelect={() => window.alert("Item2")}> Item 2 </>
    </MainMenu>
  </Excalidraw>
)

Here is a complete list of the default items.

MainMenu.Group

To Group item in the main menu, you can use MainMenu.Group

import { MainMenu } from "@excalidraw/excalidraw";
const App = () => (
  <Excalidraw>
    <MainMenu>
      <MainMenu.Group title="Excalidraw items">
        <MainMenu.DefaultItems.Socials/>
        <MainMenu.DefaultItems.Export/>
      </MainMenu.Group>
      <MainMenu.Group title="custom items">
        <MainMenu.Item onSelect={() => window.alert("Item1")}> Item1 </MainMenu.Item>
        <MainMenu.Item onSelect={() => window.alert("Item2")}> Item 2 </>
      </MainMenu.Group>
    </MainMenu>
  </Excalidraw>
)
PropTypeRequiredDefaultDescription
childrenReact.ReactNodeYesundefinedThe content of the MainMenu.Group

WelcomeScreen

When the canvas is empty, Excalidraw shows a welcome "splash" screen with a logo, a few quick action items, and hints explaining what some of the UI buttons do. You can customize the welcome screen by rendering the WelcomeScreen component inside your Excalidraw instance.

You can also disable the welcome screen altogether by setting UIOptions.welcomeScreen to false.

Usage

import { WelcomScreen } from "@excalidraw/excalidraw";
const App = () => (
  <Excalidraw>
    <WelcomeScreen>
      <WelcomeScreen.Center>
        <WelcomeScreen.Center.Heading>
          Your data are autosaved to the cloud.
        </WelcomeScreen.Center.Heading>
        <WelcomeScreen.Center.Menu>
          <WelcomeScreen.Center.MenuItem
            onClick={() => console.log("clicked!")}
          >
            Click me!
          </WelcomeScreen.Center.MenuItem>
          <WelcomeScreen.Center.MenuItemLink href="https://github.com/excalidraw/excalidraw">
            Excalidraw GitHub
          </WelcomeScreen.Center.MenuItemLink>
          <WelcomeScreen.Center.MenuItemHelp />
        </WelcomeScreen.Center.Menu>
      </WelcomeScreen.Center>
    </WelcomeScreen>
  </Excalidraw>
);

To disable the WelcomeScreen:

import { WelcomScreen } from "@excalidraw/excalidraw";
const App = () => <Excalidraw UIOptions={{ welcomeScreen: false }} />;

WelcomeScreen

If you render the <WelcomeScreen> component, you are responsible for rendering the content.

There are 2 main parts: 1) welcome screen center component, and 2) welcome screen hints.

WelcomeScreen overview

WelcomeScreen.Center

This is the center piece of the welcome screen, containing the logo, heading, and menu. All three sub-components are optional, and you can render whatever you wish into the center component.

WelcomeScreen.Center.Logo

By default renders the Excalidraw logo and name. Supply children to customize.

WelcomeScreen.Center.Heading

Supply children to change the default message.

WelcomeScreen.Center.Menu

Wrapper component for the menu items. You can build your menu using the <WelcomeScreen.Center.MenuItem> and <WelcomeScreen.Center.MenuItemLink> components, render your own, or render one of the default menu items.

The default menu items are:

  • <WelcomeScreen.Center.MenuItemHelp/> - opens the help dialog.
  • <WelcomeScreen.Center.MenuItemLoadScene/> - open the load file dialog.
  • <WelcomeScreen.Center.MenuItemLiveCollaborationTrigger/> - intended to open the live collaboration dialog. Works similarly to <LiveCollaborationTrigger> and you must supply onSelect() handler to integrate with your collaboration implementation.

Usage

import { WelcomScreen } from "@excalidraw/excalidraw";
const App = () => (
  <Excalidraw>
    <WelcomeScreen>
      <WelcomeScreen.Center>
        <WelcomeScreen.Center.Menu>
          <WelcomeScreen.Center.MenuItem
            onClick={() => console.log("clicked!")}
          >
            Click me!
          </WelcomeScreen.Center.MenuItem>
          <WelcomeScreen.Center.MenuItemLink href="https://github.com/excalidraw/excalidraw">
            Excalidraw GitHub
          </WelcomeScreen.Center.MenuItemLink>
          <WelcomeScreen.Center.MenuItemHelp />
        </WelcomeScreen.Center.Menu>
      </WelcomeScreen.Center>
    </WelcomeScreen>
  </Excalidraw>
);

WelcomeScreen.Center.MenuItem

Use this component to render a menu item.

PropTypeRequiredDefaultDescription
onSelectFunctionYesThe handler is triggered when the item is selected.
childrenReact.ReactNodeYesThe content of the menu item
iconJSX.ElementNoThe icon used in the menu item
shortcutstringNoThe keyboard shortcut (label-only, does not affect behavior)

WelcomeScreen.Center.MenuItemLink

To render an external link in a menu item, you can use this component.

PropTypeRequiredDefaultDescription
hrefstringYesThe href attribute to be added to the anchor element.
childrenReact.ReactNodeYesThe content of the menu item
iconJSX.ElementNoThe icon used in the menu item
shortcutstringNoThe keyboard shortcut (label-only, does not affect behavior)

WelcomeScreen.Hints

These subcomponents render the UI hints. Text of each hint can be customized by supplying children.

WelcomeScreen.Hints.Menu

Hint for the main menu. Supply children to customize the hint text.

WelcomeScreen.Hints.Toolbar

Hint for the toolbar. Supply children to customize the hint text.

WelcomeScreen.Hints.Help

Hint for the help dialog. Supply children to customize the hint text.

LiveCollaborationTrigger

If you implement live collaboration support and want to expose the same UI button as on excalidraw.com, you can render the <LiveCollaborationTrigger> component using the renderTopRightUI prop. You'll need to supply onSelect() to handle opening of your collaboration dialog, but the button will display current appState.collaborators count for you.

PropTypeRequiredDefaultDescription
onSelect() => anyYesHandler called when the user click on the button
isCollaboratingbooleanYesfalseWhether live collaboration session is in effect. Modifies button style.

Usage

import { LiveCollaborationTrigger } from "@excalidraw/excalidraw";
const App = () => (
  <Excalidraw
    renderTopRightUI={(isMobile) => {
      if (isMobile) {
        return null;
      }
      return (
        <LiveCollaborationTrigger
          isCollaborating={isCollaborating}
          onSelect={() => setCollabDialogShown(true)}
        />
      );
    }}
  />
);

Props

NameTypeDefaultDescription
onChangeFunctionThis callback is triggered whenever the component updates due to any change. This callback will receive the excalidraw elements and the current app state.
initialData{elements?: ExcalidrawElement[], appState?: AppState } nullThe initial data with which app loads.
[ref](#ref)[createRef](https://reactjs.org/docs/refs-and-the-dom.html#creating-refs) | [useRef](https://reactjs.org/docs/hooks-reference.html#useref) | [callbackRef](https://reactjs.org/docs/refs-and-the-dom.html#callback-refs) | { current: { readyPromise: resolvablePromise } }Ref to be passed to Excalidraw
isCollaboratingbooleanThis implies if the app is in collaboration mode
onPointerUpdateFunctionCallback triggered when mouse pointer is updated.
langCodestringenLanguage code string
renderTopRightUIFunctionFunction that renders custom UI in top right corner
renderCustomStatsFunctionFunction that can be used to render custom stats on the stats dialog.
renderSIdebarFunctionRender function that renders custom sidebar.
viewModeEnabledbooleanThis implies if the app is in view mode.
zenModeEnabledbooleanThis implies if the zen mode is enabled
gridModeEnabledbooleanThis implies if the grid mode is enabled
libraryReturnUrlstringWhat URL should libraries.excalidraw.com be installed to
themeTHEME.LIGHT | THEME.DARKTHEME.LIGHTThe theme of the Excalidraw component
namestringName of the drawing
UIOptions{ canvasActions: CanvasActions }[DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/src/constants.ts#L129)To customise UI options. Currently we support customising [canvas actions](#canvasActions)
[onPaste](#onPaste)(data: ClipboardData, event: ClipboardEvent | null) => booleanCallback to be triggered if passed when the something is pasted in to the scene
detectScrollbooleantrueIndicates whether to update the offsets when nearest ancestor is scrolled.
handleKeyboardGloballybooleanfalseIndicates whether to bind the keyboard events to document.
onLibraryChange(items: LibraryItems) => void | Promise<any> The callback if supplied is triggered when the library is updated and receives the library items.
autoFocusbooleanfalseImplies whether to focus the Excalidraw component on page load
generateIdForFile(file: File) => string | Promise<string>Allows you to override id generation for files added on canvas
onLinkOpen(element: NonDeletedExcalidrawElement, event: CustomEvent) This prop if passed will be triggered when link of an element is clicked.
onPointerDown(activeTool: AppState["activeTool"], pointerDownState: PointerDownState) => voidThis prop if passed gets triggered on pointer down evenets
onScrollChange(scrollX: number, scrollY: number)This prop if passed gets triggered when scrolling the canvas.

Dimensions of Excalidraw

Excalidraw takes 100% of width and height of the containing block so make sure the container in which you render Excalidraw has non zero dimensions.

onChange

Every time component updates, this callback if passed will get triggered and has the below signature.

(excalidrawElements, appState, files) => void;

1.excalidrawElements: Array of excalidrawElements in the scene.

2.appState: AppState of the scene.

  1. files: The BinaryFiles(BinaryFiles which are added to the scene.

Here you can try saving the data to your backend or local storage for example.

initialData

This helps to load Excalidraw with initialData. It must be an object or a promise which resolves to an object containing the below optional fields.

NameTypeDescription
elementsExcalidrawElement[]The elements with which Excalidraw should be mounted.
appStateAppStateThe App state with which Excalidraw should be mounted.
scrollToContentbooleanThis attribute implies whether to scroll to the nearest element to center once Excalidraw is mounted. By default, it will not scroll the nearest element to the center. Make sure you pass initialData.appState.scrollX and initialData.appState.scrollY when scrollToContent is false so that scroll positions are retained
libraryItemsLibraryItems | Promise[LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200)This library items with which Excalidraw should be mounted.
filesBinaryFilesThe files added to the scene.
{
  "elements": [
    {
      "type": "rectangle",
      "version": 141,
      "versionNonce": 361174001,
      "isDeleted": false,
      "id": "oDVXy8D6rom3H1-LLH2-f",
      "fillStyle": "hachure",
      "strokeWidth": 1,
      "strokeStyle": "solid",
      "roughness": 1,
      "opacity": 100,
      "angle": 0,
      "x": 100.50390625,
      "y": 93.67578125,
      "strokeColor": "#000000",
      "backgroundColor": "transparent",
      "width": 186.47265625,
      "height": 141.9765625,
      "seed": 1968410350,
      "groupIds": []
    }
  ],
  "appState": { "zenModeEnabled": true, "viewBackgroundColor": "#AFEEEE" }
}

You might want to use this when you want to load excalidraw with some initial elements and app state.

Storing custom data on Excalidraw elements

Beyond attributes that Excalidraw elements already support, you can store custom data on each element in a customData object. The type of the attribute is Record<string, any> and is optional.

You can use this to add any extra information you need to keep track of.

You can add customData to elements when passing them as initialData, or using updateScene/updateLibrary afterwards.

ref

You can pass a ref when you want to access some excalidraw APIs. We expose the below APIs:

APISignatureUsage
readybooleanThis is set to true once Excalidraw is rendered
readyPromiseresolvablePromiseThis promise will be resolved with the api once excalidraw has rendered. This will be helpful when you want do some action on the host app once this promise resolves. For this to work you will have to pass ref as shown here
updateScene(scene: sceneData) => void updates the scene with the sceneData
updateLibrary(opts) => Promise<LibraryItems> updates the scene with the sceneData
addFiles(files: BinaryFileData) => void add files data to the appState
resetScene({ resetLoadingState: boolean }) => voidResets the scene. If resetLoadingState is passed as true then it will also force set the loading state to false.
getSceneElementsIncludingDeleted () => ExcalidrawElement[]Returns all the elements including the deleted in the scene
getSceneElements () => ExcalidrawElement[]Returns all the elements excluding the deleted in the scene
getAppState () => AppStateReturns current appState
history{ clear: () => void }This is the history API. history.clear() will clear the history
scrollToContent (target?: ExcalidrawElement | ExcalidrawElement[]) => void Scroll the nearest element out of the elements supplied to the center. Defaults to the elements on the scene.
refresh() => voidUpdates the offsets for the Excalidraw component so that the coordinates are computed correctly (for example the cursor position). You don't have to call this when the position is changed on page scroll or when the excalidraw container resizes (we handle that ourselves). For any other cases if the position of excalidraw is updated (example due to scroll on parent container and not page scroll) you should call this API.
importLibrary(url: string, token?: string) => voidImports library from given URL
setToast({ message: string, closable?:boolean, duration?:number } | null) => voidThis API can be used to show the toast with custom message.
idstringUnique ID for the excalidraw component.
getFiles() => files This API can be used to get the files present in the scene. It may contain files that aren't referenced by any element, so if you're persisting the files to a storage, you should compare them against stored elements.
setActiveTool(tool: { type: typeof SHAPES number| "eraser" } | { type: "custom"; customType: string }) => voidThis API can be used to set the active tool
setCursor(cursor: string) => void This API can be used to set customise the mouse cursor on the canvas
resetCursor() => void This API can be used to reset to default mouse cursor on the canvas
toggleMenu(type: string, force?: boolean) => booleanToggles specific menus on/off

readyPromise

Since plain object is passed as a ref, the readyPromise is resolved as soon as the component is mounted. Most of the time you will not need this unless you have a specific use case where you can't pass the ref in the react way and want to do some action on the host when this promise resolves. You can check the example for the usage.

updateScene

You can use this function to update the scene with the sceneData. It accepts the below attributes.

NameTypeDescription
elementsImportedDataState["elements"]The elements to be updated in the scene
appStateImportedDataState["appState"]The appState to be updated in the scene.
collaboratorsMap<string, Collaborator>The list of collaborators to be updated in the scene.
commitToHistorybooleanImplies if the history (undo/redo) should be recorded. Defaults to false.
libraryItemsLibraryItems | Promise[LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200) | ((currentItems: LibraryItems>) => LibraryItems | Promise[LibraryItems](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L200))The libraryItems to be update in the scene.

updateLibrary

You can use this function to update the library. It accepts the below attributes.

NameTypeDefaultDescription
libraryItemsLibraryItemsThe libraryItems to be replaced/merged with current library
mergebooleanfalseWhether to merge with existing library items.
promptbooleanfalseWhether to prompt user for confirmation.
openLibraryMenubooleanfalseWhether to open the library menu before importing.
defaultStatus"unpublished" | "published""unpublished"Default library item's status if not present.

addFiles

Adds supplied files data to the appState.files cache on top of existing files present in the cache.

isCollaborating

This prop indicates if the app is in collaboration mode.

onPointerUpdate

This callback is triggered when mouse pointer is updated.

({ x, y }, button, pointersMap}) => void;

1.{x, y}: Pointer coordinates

2.button: The position of the button. This will be one of ["down", "up"]

3.pointersMap: pointers map of the scene

(exportedElements, appState, canvas) => void
  1. exportedElements: An array of non deleted elements which needs to be exported.
  2. appState: AppState of the scene.
  3. canvas: The HTMLCanvasElement of the scene.

langCode

Determines the language of the UI. It should be one of the available language codes. Defaults to en (English). We also export default language and supported languages which you can import as shown below.

import { defaultLang, languages } from "@excalidraw/excalidraw";
nametype
defaultLangstring
languagesLanguage[]

renderTopRightUI

A function returning JSX to render custom UI in the top right corner of the app.

renderCustomStats

A function that can be used to render custom stats (returns JSX) in the nerd stats dialog. For example you can use this prop to render the size of the elements in the storage.

renderSidebar

Optional function that can render custom sidebar. This sidebar is the same that the library menu sidebar is using, and can be used for any purposes your app needs. The render function should return a <Sidebar> instance — a component that is exported from the Excalidraw package. It accepts children which can be any content you like to render inside.

The <Sidebar> component takes these props (all are optional except children):

nametypedescription
classNamestring
childrenReact.ReactNodeContent you want to render inside the sidebar.
onClose() => voidInvoked when the component is closed (by user, or the editor). No need to act on this event, as the editor manages the sidebar open state on its own.
onDock(isDocked: boolean) => voidInvoked when the user toggles the dock button.
dockedbooleanIndicates whether the sidebar is docked. By default, the sidebar is undocked. If passed, the docking becomes controlled, and you are responsible for updating the docked state by listening on onDock callback. See here for more info docking.
dockablebooleanIndicates whether the sidebar can be docked by user (=the dock button is shown). If false, you can still dock programmatically by passing docked=true

The sidebar will always include a header with close/dock buttons (when applicable).

You can also add custom content to the header, by rendering <Sidebar.Header> as a child of the <Sidebar> component. Note that the custom header will still include the default buttons.

The <Sidebar.Header> component takes these props children (all are optional):

nametypedescription
classNamestring
childrenReact.ReactNodeContent you want to render inside the sidebar header, sibling of the header buttons.

For example code, see the example App.tsx file.

viewModeEnabled

This prop indicates whether the app is in view mode. When supplied, the value takes precedence over intialData.appState.viewModeEnabled, the view mode will be fully controlled by the host app, and users won't be able to toggle it from within the app.

zenModeEnabled

This prop indicates whether the app is in zen mode. When supplied, the value takes precedence over intialData.appState.zenModeEnabled, the zen mode will be fully controlled by the host app, and users won't be able to toggle it from within the app.

gridModeEnabled

This prop indicates whether the shows the grid. When supplied, the value takes precedence over intialData.appState.gridModeEnabled, the grid will be fully controlled by the host app, and users won't be able to toggle it from within the app.

libraryReturnUrl

If supplied, this URL will be used when user tries to install a library from libraries.excalidraw.com. Defaults to window.location.origin + window.location.pathname. To install the libraries in the same tab from which it was opened, you need to set window.name (to any alphanumeric string) — if it's not set it will open in a new tab.

theme

This prop controls Excalidraw's theme. When supplied, the value takes precedence over intialData.appState.theme, the theme will be fully controlled by the host app, and users won't be able to toggle it from within the app unless UIOptions.canvasActions.toggleTheme is set to true, in which case the theme prop will control Excalidraw's default theme with ability to allow theme switching (you must take care of updating the theme prop when you detect a change to appState.theme from the onChange callback).

You can use THEME to specify the theme.

name

This prop sets the name of the drawing which will be used when exporting the drawing. When supplied, the value takes precedence over intialData.appState.name, the name will be fully controlled by host app and the users won't be able to edit from within Excalidraw.

UIOptions

This prop can be used to customise UI of Excalidraw. Currently we support customising canvasActions and dockedSidebarBreakpoint. It accepts the below parameters

canvasActions
AttributeTypeDefaultDescription
changeViewBackgroundColorbooleantrueImplies whether to show Background color picker
clearCanvasbooleantrueImplies whether to show Clear canvas button
exportfalse | exportOpts{ saveFileToDisk: true }This prop allows to customize the UI inside the export dialog. By default it shows the "saveFileToDisk". If this prop is false the export button will not be rendered. For more details visit exportOpts.
loadScenebooleantrueImplies whether to show Load button
saveToActiveFilebooleantrueImplies whether to show Save button to save to current file
toggleThemeboolean | nullnullImplies whether to show Theme toggle. When defined as boolean, takes precedence over props.theme to show Theme toggle
saveAsImagebooleantrueImplies whether to show Save as image button
dockedSidebarBreakpoint

This prop indicates at what point should we break to a docked, permanent sidebar. If not passed it defaults to MQ_RIGHT_SIDEBAR_MAX_WIDTH_PORTRAIT. If the width of the excalidraw container exceeds dockedSidebarBreakpoint, the sidebar will be dockable. If user choses to dock the sidebar, it will push the right part of the UI towards the left, making space for the sidebar as shown below.

image

exportOpts

The below attributes can be set in UIOptions.canvasActions.export to customize the export dialog. If UIOptions.canvasActions.export is false the export button will not be rendered.

AttributeTypeDefaultDescription
saveFileToDiskbooleantrueImplies if save file to disk button should be shown
onExportToBackend (exportedElements: readonly NonDeletedExcalidrawElement[],appState: AppState,canvas: HTMLCanvasElement | null) => void This callback is triggered when the shareable-link button is clicked in the export dialog. The link button will only be shown if this callback is passed.
renderCustomUI (exportedElements: readonly NonDeletedExcalidrawElement[],appState: AppState,canvas: HTMLCanvasElement | null) => void This callback should be supplied if you want to render custom UI in the export dialog.

onPaste

This callback is triggered if passed when something is pasted into the scene. You can use this callback in case you want to do something additional when the paste event occurs.

This callback must return a boolean value or a promise which resolves to a boolean value.

In case you want to prevent the excalidraw paste action you must return false, it will stop the native excalidraw clipboard management flow (nothing will be pasted into the scene).

importLibrary

Imports library from given URL. You should call this on hashchange, passing the addLibrary value if you detect it as shown below. Optionally pass a CSRF token to skip prompting during installation (retrievable via token key from the url coming from https://libraries.excalidraw.com).

useEffect(() => {
  const onHashChange = () => {
    const hash = new URLSearchParams(window.location.hash.slice(1));
    const libraryUrl = hash.get("addLibrary");
    if (libraryUrl) {
      excalidrawRef.current.importLibrary(libraryUrl, hash.get("token"));
    }
  };
  window.addEventListener("hashchange", onHashChange, false);
  return () => {
    window.removeEventListener("hashchange", onHashChange);
  };
}, []);

Try out the Demo to see it in action.

setToast

This API can be used to show the toast with custom message.

AttributetypeDescription
messagestringThe message to be shown on the toast.
closablebooleanIndicates whether to show the closable button on toast to dismiss the toast.
durationnumberDetermines the duration after which the toast should auto dismiss. To prevent autodimiss you can pass Infinity.

To dismiss an existing toast you can simple pass null

setToast(null);

setActiveTool

This API has the below signature. It sets the tool passed in param as the active tool.

setCursor

This API can be used to customise the mouse cursor on the canvas and has the below signature. It sets the mouse cursor to the cursor passed in param.

toggleMenu

This API can be used to toggle a specific menu (currently only the sidebars), and returns whether the menu was toggled on or off. If the force flag passed, it will force the menu to be toggled either on/off based on the boolean passed.

This API is especially useful when you render a custom sidebar using renderSidebar prop, and you want to toggle it from your app based on a user action.

resetCursor

This API can be used to reset to default mouse cursor.

detectScroll

Indicates whether Excalidraw should listen for scroll event on the nearest scrollable container in the DOM tree and recompute the coordinates (e.g. to correctly handle the cursor) when the component's position changes. You can disable this when you either know this doesn't affect your app or you want to take care of it yourself (calling the refresh() method).

handleKeyboardGlobally

Indicates whether to bind keyboard events to document. Disabled by default, meaning the keyboard events are bound to the Excalidraw component. This allows for multiple Excalidraw components to live on the same page, and ensures that Excalidraw keyboard handling doesn't collide with your app's (or the browser) when the component isn't focused.

Enable this if you want Excalidraw to handle keyboard even if the component isn't focused (e.g. a user is interacting with the navbar, sidebar, or similar).

onLibraryChange

This callback if supplied will get triggered when the library is updated and has the below signature.

It is invoked with empty items when user clears the library. You can use this callback when you want to do something additional when library is updated for example persisting it to local storage.

id

The unique id of the excalidraw component. This can be used to identify the excalidraw component, for example importing the library items to the excalidraw component from where it was initiated when you have multiple excalidraw components rendered on the same page as shown in multiple excalidraw demo.

autoFocus

This prop implies whether to focus the Excalidraw component on page load. Defaults to false.

generateIdForFile

Allows you to override id generation for files added on canvas (images). By default, an SHA-1 digest of the file is used.

(file: File) => string | Promise<string>

onLinkOpen

This prop if passed will be triggered when clicked on link. To handle the redirect yourself (such as when using your own router for internal links), you must call event.preventDefault().

(element: ExcalidrawElement, event: CustomEvent<{ nativeEvent: MouseEvent }>) => void

Example:

const history = useHistory();

// open internal links using the app's router, but opens external links in
// a new tab/window
const onLinkOpen: ExcalidrawProps["onLinkOpen"] = useCallback(
  (element, event) => {
    const link = element.link;
    const { nativeEvent } = event.detail;
    const isNewTab = nativeEvent.ctrlKey || nativeEvent.metaKey;
    const isNewWindow = nativeEvent.shiftKey;
    const isInternalLink =
      link.startsWith("/") || link.includes(window.location.origin);
    if (isInternalLink && !isNewTab && !isNewWindow) {
      history.push(link.replace(window.location.origin, ""));
      // signal that we're handling the redirect ourselves
      event.preventDefault();
    }
  },
  [history],
);

onPointerDown

This prop if passed will be triggered on pointer down events and has the below signature.

onScrollChange

This prop if passed will be triggered when canvas is scrolled and has the below signature.

(scrollX: number, scrollY: number) => void

Restore utilities

restoreAppState

Signature

How to use

import { restoreAppState } from "@excalidraw/excalidraw";

This function will make sure all the keys have appropriate values in appState and if any key is missing, it will be set to default value.

When localAppState is supplied, it's used in place of values that are missing (undefined) in appState instead of defaults. Use this as a way to not override user's defaults if you persist them. Required: supply null/undefined if not applicable.

restoreElements

Signature

How to use

import { restoreElements } from "@excalidraw/excalidraw";

This function will make sure all properties of element is correctly set and if any attribute is missing, it will be set to default value.

When localElements are supplied, they are used to ensure that existing restored elements reuse version (and increment it), and regenerate versionNonce. Use this when you import elements which may already be present in the scene to ensure that you do not disregard the newly imported elements if you're using element version to detect the updates.

Parameter refreshDimensions indicates whether we should also recalculate text element dimensions. Defaults to false. Since this is a potentially costly operation, you may want to disable it if you restore elements in tight loops, such as during collaboration.

restore

Signature

See restoreAppState() about localAppState, and restoreElements() about localElements.

How to use

import { restore } from "@excalidraw/excalidraw";

This function makes sure elements and state is set to appropriate values and set to default value if not present. It is a combination of restoreElements and restoreAppState.

restoreLibraryItems

Signature

How to use

import { restoreLibraryItems } from "@excalidraw/excalidraw";

restoreLibraryItems(libraryItems, "unpublished");

This function normalizes library items elements, adding missing values when needed.

Export utilities

exportToCanvas

Signature

NameTypeDefaultDescription
elementsExcalidraw Element []The elements to be exported to canvas
appStateAppStatedefaultAppStateThe app state of the scene
getDimensions(width: number, height: number) => { width: number, height: number, scale?: number }undefinedA function which returns the width, height, and optionally scale (defaults 1), with which canvas is to be exported.
maxWidthOrHeightnumberundefinedThe maximum width or height of the exported image. If provided, getDimensions is ignored.
filesBinaryFiles(The BinaryFilesundefinedThe files added to the scene.
exportPaddingnumber10The padding to be added on canvas

How to use

import { exportToCanvas } from "@excalidraw/excalidraw";

This function returns the canvas with the exported elements, appState and dimensions.

exportToBlob

Signature

NameTypeDefaultDescription
optsThis param is passed to exportToCanvas. You can refer to exportToCanvas
mimeTypestring"image/png"Indicates the image format
qualitynumber0.92A value between 0 and 1 indicating the image quality. Applies only to image/jpeg/image/webp MIME types.
exportPaddingnumber10The padding to be added on canvas

How to use

import { exportToBlob } from "@excalidraw/excalidraw";

Returns a promise which resolves with a blob. It internally uses canvas.ToBlob.

exportToSvg

Signature

NameTypeDefaultDescription
elementsExcalidraw Element []The elements to exported as svg
appStateAppStatedefaultAppStateThe app state of the scene
exportPaddingnumber10The padding to be added on canvas
filesBinaryFiles(The BinaryFilesundefinedThe files added to the scene.

This function returns a promise which resolves to svg of the exported drawing.

exportToClipboard

Signature

NameTypeDefaultDescription
optsThis param is same as the params passed to exportToCanvas. You can refer to exportToCanvas.
mimeTypestring"image/png"Indicates the image format, this will be used when exporting as png.
qualitynumber0.92A value between 0 and 1 indicating the image quality. Applies only to image/jpeg/image/webp MIME types. This will be used when exporting as png.
type'png''svg''json'This determines the format to which the scene data should be exported.

How to use

import { exportToClipboard } from "@excalidraw/excalidraw";

Copies the scene data in the specified format (determined by type) to clipboard.

Additional attributes of appState for export\* APIs
NameTypeDefaultDescription
exportBackgroundbooleantrueIndicates whether background should be exported
viewBackgroundColorstring#fffThe default background color
exportWithDarkModebooleanfalseIndicates whether to export with dark mode
exportEmbedScenebooleanfalseIndicates whether scene data should be embedded in svg/png. This will increase the image size.

Extra API's

serializeAsJSON

Signature

Takes the scene elements and state and returns a JSON string. Deleted elementsas well as most properties from AppState are removed from the resulting JSON. (see serializeAsJSON() source for details).

If you want to overwrite the source field in the JSON string, you can set window.EXCALIDRAW_EXPORT_SOURCE to the desired value.

`serializeLibr

0.1.11

1 year ago

0.1.10

1 year ago

0.1.9

1 year ago

0.1.8

1 year ago

0.1.7

1 year ago

0.1.6

1 year ago

0.1.5

1 year ago

0.1.4

1 year ago

0.1.3

1 year ago

0.1.2

1 year ago

0.1.0

1 year ago