diff --git a/.env.development b/.env.development
index 44955884f..95e21ff87 100644
--- a/.env.development
+++ b/.env.development
@@ -7,9 +7,6 @@ VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfu
# collaboration WebSocket server (https://github.com/excalidraw/excalidraw-room)
VITE_APP_WS_SERVER_URL=http://localhost:3002
-# set this only if using the collaboration workflow we use on excalidraw.com
-VITE_APP_PORTAL_URL=
-
VITE_APP_PLUS_LP=https://plus.excalidraw.com
VITE_APP_PLUS_APP=https://app.excalidraw.com
diff --git a/.env.production b/.env.production
index 26b46a52a..0c715854a 100644
--- a/.env.production
+++ b/.env.production
@@ -4,16 +4,13 @@ VITE_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/
VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com
VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries
-VITE_APP_PORTAL_URL=https://portal.excalidraw.com
-
VITE_APP_PLUS_LP=https://plus.excalidraw.com
VITE_APP_PLUS_APP=https://app.excalidraw.com
VITE_APP_AI_BACKEND=https://oss-ai.excalidraw.com
-# Fill to set socket server URL used for collaboration.
-# Meant for forks only: excalidraw.com uses custom VITE_APP_PORTAL_URL flow
-VITE_APP_WS_SERVER_URL=
+# socket server URL used for collaboration
+VITE_APP_WS_SERVER_URL=https://oss-collab.excalidraw.com
VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"excalidraw-room-persistence.firebaseapp.com","databaseURL":"https://excalidraw-room-persistence.firebaseio.com","projectId":"excalidraw-room-persistence","storageBucket":"excalidraw-room-persistence.appspot.com","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}'
diff --git a/.github/workflows/autorelease-excalidraw.yml b/.github/workflows/autorelease-excalidraw.yml
index 4eaeb11f1..5ff5690eb 100644
--- a/.github/workflows/autorelease-excalidraw.yml
+++ b/.github/workflows/autorelease-excalidraw.yml
@@ -23,5 +23,5 @@ jobs:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Auto release
run: |
- yarn add @actions/core
+ yarn add @actions/core -W
yarn autorelease
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index f922f5e75..82f826361 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -16,7 +16,7 @@ jobs:
- name: Install and lint
run: |
- yarn install:deps
+ yarn install
yarn test:other
yarn test:code
yarn test:typecheck
diff --git a/.github/workflows/size-limit.yml b/.github/workflows/size-limit.yml
index 02aade54e..5bd3c0d92 100644
--- a/.github/workflows/size-limit.yml
+++ b/.github/workflows/size-limit.yml
@@ -23,6 +23,6 @@ jobs:
- uses: andresz1/size-limit-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
- build_script: build:umd
+ build_script: build:esm
skip_step: install
directory: packages/excalidraw
diff --git a/.github/workflows/test-coverage-pr.yml b/.github/workflows/test-coverage-pr.yml
index 7d77d39f5..7ff40ad5d 100644
--- a/.github/workflows/test-coverage-pr.yml
+++ b/.github/workflows/test-coverage-pr.yml
@@ -16,7 +16,7 @@ jobs:
with:
node-version: "18.x"
- name: "Install Deps"
- run: yarn install:deps
+ run: yarn install
- name: "Test Coverage"
run: yarn test:coverage
- name: "Report Coverage"
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 124cae26e..2c458a810 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -13,5 +13,5 @@ jobs:
node-version: 18.x
- name: Install and test
run: |
- yarn install:deps
+ yarn install
yarn test:app
diff --git a/.gitignore b/.gitignore
index d670c78ab..81b63339f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,9 +22,8 @@ package-lock.json
yarn-debug.log*
yarn-error.log*
packages/excalidraw/types
-packages/excalidraw/example/public/bundle.js
-packages/excalidraw/example/public/excalidraw-assets-dev
-packages/excalidraw/example/public/excalidraw.development.js
coverage
dev-dist
html
+examples/**/bundle.*
+meta*.json
\ No newline at end of file
diff --git a/README.md b/README.md
index 2a8a3f908..e8cd3b06f 100644
--- a/README.md
+++ b/README.md
@@ -85,7 +85,7 @@ We'll be adding these features as drop-in plugins for the npm package in the fut
## Quick start
-Install the [Excalidraw npm package](https://www.npmjs.com/package/@excalidraw/excalidraw):
+**Note:** following instructions are for installing the Excalidraw [npm package](https://www.npmjs.com/package/@excalidraw/excalidraw) when integrating Excalidraw into your own app. To run the repository locally for development, please refer to our [Development Guide](https://docs.excalidraw.com/docs/introduction/development).
```
npm install react react-dom @excalidraw/excalidraw
@@ -97,7 +97,7 @@ or via yarn
yarn add react react-dom @excalidraw/excalidraw
```
-Don't forget to check out our [Documentation](https://docs.excalidraw.com)!
+Check out our [documentation](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/installation) for more details!
## Contributing
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx
index c27e96146..ffff19fb0 100644
--- a/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx
@@ -37,7 +37,7 @@ You can use this prop when you want to access some [Excalidraw APIs](https://git
| [setActiveTool](#setactivetool) | `function` | This API can be used to set the active tool |
| [setCursor](#setcursor) | `function` | This API can be used to set customise the mouse cursor on the canvas |
| [resetCursor](#resetcursor) | `function` | This API can be used to reset to default mouse cursor on the canvas |
-| [toggleMenu](#togglemenu) | `function` | Toggles specific menus on/off |
+| [toggleSidebar](#toggleSidebar) | `function` | Toggles specific sidebar on/off |
| [onChange](#onChange) | `function` | Subscribes to change events |
| [onPointerDown](#onPointerDown) | `function` | Subscribes to `pointerdown` events |
| [onPointerUp](#onPointerUp) | `function` | Subscribes to `pointerup` events |
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx
index 40773a1a2..766c723e4 100644
--- a/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx
@@ -23,7 +23,7 @@ All `props` are _optional_.
| [`libraryReturnUrl`](#libraryreturnurl) | `string` | _ | What URL should [libraries.excalidraw.com](https://libraries.excalidraw.com) be installed to |
| [`theme`](#theme) | `"light"` | `"dark"` | `"light"` | The theme of the Excalidraw component |
| [`name`](#name) | `string` | | Name of the drawing |
-| [`UIOptions`](/docs/@excalidraw/excalidraw/api/props/ui-options) | `object` | [DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/constants.ts#L151) | To customise UI options. Currently we support customising [`canvas actions`](#canvasactions) |
+| [`UIOptions`](/docs/@excalidraw/excalidraw/api/props/ui-options) | `object` | [DEFAULT UI OPTIONS](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/constants.ts#L151) | To customise UI options. Currently we support customising [`canvas actions`](/docs/@excalidraw/excalidraw/api/props/ui-options#canvasactions) |
| [`detectScroll`](#detectscroll) | `boolean` | `true` | Indicates whether to update the offsets when nearest ancestor is scrolled. |
| [`handleKeyboardGlobally`](#handlekeyboardglobally) | `boolean` | `false` | Indicates whether to bind the keyboard events to document. |
| [`autoFocus`](#autofocus) | `boolean` | `false` | indicates whether to focus the Excalidraw component on page load |
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/props/ui-options.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/props/ui-options.mdx
index 5c2c40ccb..9d77e390a 100644
--- a/dev-docs/docs/@excalidraw/excalidraw/api/props/ui-options.mdx
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/props/ui-options.mdx
@@ -73,9 +73,9 @@ function App() {
## tools
-This `prop ` controls the visibility of the tools in the editor.
+This `prop` controls the visibility of the tools in the editor.
Currently you can control the visibility of `image` tool via this prop.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
-| image | boolean | true | Decides whether `image` tool should be visible.
\ No newline at end of file
+| image | boolean | true | Decides whether `image` tool should be visible.
diff --git a/dev-docs/docs/@excalidraw/excalidraw/integration.mdx b/dev-docs/docs/@excalidraw/excalidraw/integration.mdx
index 87eb3777d..391b5800b 100644
--- a/dev-docs/docs/@excalidraw/excalidraw/integration.mdx
+++ b/dev-docs/docs/@excalidraw/excalidraw/integration.mdx
@@ -32,15 +32,9 @@ function App() {
### Next.js
-Since _Excalidraw_ doesn't support server side rendering, you should render the component once the host is `mounted`.
+Since Excalidraw doesn't support `server side rendering` so it should be rendered only on `client`. The way to achieve this in next.js is using `next.js dynamic import`.
-Here are two ways on how you can render **Excalidraw** on **Next.js**.
-
-
-
-1. Using **Next.js Dynamic** import [Recommended].
-
-Since Excalidraw doesn't support server side rendering so you can also use `dynamic import` to render by setting `ssr` to `false`.
+If you want to only import `Excalidraw` component you can do :point_down:
```jsx showLineNumbers
import dynamic from "next/dynamic";
@@ -55,25 +49,88 @@ export default function App() {
}
```
-Here is a working [demo](https://codesandbox.io/p/sandbox/excalidraw-with-next-dynamic-k8yjq2).
+However the above component only works for named component exports. If you want to import some util / constant or something else apart from Excalidraw, then this approach will not work. Instead you can write a wrapper over Excalidraw and import the wrapper dynamically.
+If you are using `pages router` then importing the wrapper dynamically would work, where as if you are using `app router` then you will have to also add `useClient` directive on top of the file in addition to dynamically importing the wrapper as shown :point_down:
-2. Importing Excalidraw once **client** is rendered.
+
+
-```jsx showLineNumbers
-import { useState, useEffect } from "react";
-export default function App() {
- const [Excalidraw, setExcalidraw] = useState(null);
- useEffect(() => {
- import("@excalidraw/excalidraw").then((comp) =>
- setExcalidraw(comp.Excalidraw),
+ ```jsx showLineNumbers
+ "use client";
+ import { Excalidraw, convertToExcalidrawElements } from "@excalidraw/excalidraw";
+
+ import "@excalidraw/excalidraw/index.css";
+
+ const ExcalidrawWrapper: React.FC = () => {
+ console.info(convertToExcalidrawElements([{
+ type: "rectangle",
+ id: "rect-1",
+ width: 186.47265625,
+ height: 141.9765625,
+ },]));
+ return (
+
+
+
);
- }, []);
- return <>{Excalidraw && }>;
-}
-```
+ };
+ export default ExcalidrawWrapper;
+ ```
+
+
+
+
+
+ ```jsx showLineNumbers
+ import dynamic from "next/dynamic";
+
+ // Since client components get prerenderd on server as well hence importing
+ // the excalidraw stuff dynamically with ssr false
+
+ const ExcalidrawWrapper = dynamic(
+ async () => (await import("../excalidrawWrapper")).default,
+ {
+ ssr: false,
+ },
+ );
+
+ export default function Page() {
+ return (
+
+ );
+ }
+ ```
+
+
+
+
+ ```jsx showLineNumbers
+ import dynamic from "next/dynamic";
+
+ // Since client components get prerenderd on server as well hence importing
+ // the excalidraw stuff dynamically with ssr false
+
+ const ExcalidrawWrapper = dynamic(
+ async () => (await import("../excalidrawWrapper")).default,
+ {
+ ssr: false,
+ },
+ );
+
+ export default function Page() {
+ return (
+
+ );
+ }
+ ```
+
+
+
+
+
+Here is a [source code](https://github.com/excalidraw/excalidraw/tree/master/examples/excalidraw/with-nextjs) for the example with app and pages router. You you can try it out [here](https://excalidraw-package-example-with-nextjs-gh6smrdnq-excalidraw.vercel.app/).
-Here is a working [demo](https://codesandbox.io/p/sandbox/excalidraw-with-next-5xb3d)
The `types` are available at `@excalidraw/excalidraw/types`, you can view [example for typescript](https://codesandbox.io/s/excalidraw-types-9h2dm)
diff --git a/packages/excalidraw/example/App.scss b/examples/excalidraw/components/App.scss
similarity index 83%
rename from packages/excalidraw/example/App.scss
rename to examples/excalidraw/components/App.scss
index 7f37540d8..e41a77ccc 100644
--- a/packages/excalidraw/example/App.scss
+++ b/examples/excalidraw/components/App.scss
@@ -15,14 +15,23 @@
border-radius: 50%;
}
}
+ .app-title {
+ margin-block-start: 0.83em;
+ margin-block-end: 0.83em;
+ }
}
-.button-wrapper button {
- z-index: 1;
- height: 40px;
- max-width: 200px;
- margin: 10px;
- padding: 5px;
+.button-wrapper {
+ input[type="checkbox"] {
+ margin: 5px;
+ }
+ button {
+ z-index: 1;
+ height: 40px;
+ max-width: 200px;
+ margin: 10px;
+ padding: 5px;
+ }
}
.excalidraw .App-menu_top .buttonList {
diff --git a/packages/excalidraw/example/App.tsx b/examples/excalidraw/components/App.tsx
similarity index 83%
rename from packages/excalidraw/example/App.tsx
rename to examples/excalidraw/components/App.tsx
index 15faede6a..eea0da6ca 100644
--- a/packages/excalidraw/example/App.tsx
+++ b/examples/excalidraw/components/App.tsx
@@ -1,23 +1,31 @@
-import { useEffect, useState, useRef, useCallback } from "react";
-
+import React, {
+ useEffect,
+ useState,
+ useRef,
+ useCallback,
+ Children,
+ cloneElement,
+} from "react";
import ExampleSidebar from "./sidebar/ExampleSidebar";
-import type * as TExcalidraw from "../index";
+import type * as TExcalidraw from "@excalidraw/excalidraw";
-import "./App.scss";
-import initialData from "./initialData";
import { nanoid } from "nanoid";
+
import {
resolvablePromise,
ResolvablePromise,
+ distance2d,
+ fileOpen,
withBatchedUpdates,
withBatchedUpdatesThrottled,
} from "../utils";
-import { EVENT, ROUNDNESS } from "../constants";
-import { distance2d } from "../math";
-import { fileOpen } from "../data/filesystem";
-import { loadSceneOrLibraryFromBlob } from "../../utils";
-import {
+
+import CustomFooter from "./CustomFooter";
+import MobileFooter from "./MobileFooter";
+import initialData from "../initialData";
+
+import type {
AppState,
BinaryFileData,
ExcalidrawImperativeAPI,
@@ -25,18 +33,14 @@ import {
Gesture,
LibraryItems,
PointerDownState as ExcalidrawPointerDownState,
-} from "../types";
-import { NonDeletedExcalidrawElement, Theme } from "../element/types";
-import { ImportedLibraryData } from "../data/types";
-import CustomFooter from "./CustomFooter";
-import MobileFooter from "./MobileFooter";
-import { KEYS } from "../keys";
+} from "@excalidraw/excalidraw/dist/excalidraw/types";
+import type {
+ NonDeletedExcalidrawElement,
+ Theme,
+} from "@excalidraw/excalidraw/dist/excalidraw/element/types";
+import type { ImportedLibraryData } from "@excalidraw/excalidraw/dist/excalidraw/data/types";
-declare global {
- interface Window {
- ExcalidrawLib: typeof TExcalidraw;
- }
-}
+import "./App.scss";
type Comment = {
x: number;
@@ -57,29 +61,6 @@ type PointerDownState = {
};
};
-// This is so that we use the bundled excalidraw.development.js file instead
-// of the actual source code
-const {
- exportToCanvas,
- exportToSvg,
- exportToBlob,
- exportToClipboard,
- Excalidraw,
- useHandleLibrary,
- MIME_TYPES,
- sceneCoordsToViewportCoords,
- viewportCoordsToSceneCoords,
- restoreElements,
- Sidebar,
- Footer,
- WelcomeScreen,
- MainMenu,
- LiveCollaborationTrigger,
- convertToExcalidrawElements,
- TTDDialog,
- TTDDialogTrigger,
-} = window.ExcalidrawLib;
-
const COMMENT_ICON_DIMENSION = 32;
const COMMENT_INPUT_HEIGHT = 50;
const COMMENT_INPUT_WIDTH = 150;
@@ -88,9 +69,38 @@ export interface AppProps {
appTitle: string;
useCustom: (api: ExcalidrawImperativeAPI | null, customArgs?: any[]) => void;
customArgs?: any[];
+ children: React.ReactNode;
+ excalidrawLib: typeof TExcalidraw;
}
-export default function App({ appTitle, useCustom, customArgs }: AppProps) {
+export default function App({
+ appTitle,
+ useCustom,
+ customArgs,
+ children,
+ excalidrawLib,
+}: AppProps) {
+ const {
+ exportToCanvas,
+ exportToSvg,
+ exportToBlob,
+ exportToClipboard,
+ useHandleLibrary,
+ MIME_TYPES,
+ sceneCoordsToViewportCoords,
+ viewportCoordsToSceneCoords,
+ restoreElements,
+ Sidebar,
+ Footer,
+ WelcomeScreen,
+ MainMenu,
+ LiveCollaborationTrigger,
+ convertToExcalidrawElements,
+ TTDDialog,
+ TTDDialogTrigger,
+ ROUNDNESS,
+ loadSceneOrLibraryFromBlob,
+ } = excalidrawLib;
const appRef = useRef(null);
const [viewModeEnabled, setViewModeEnabled] = useState(false);
const [zenModeEnabled, setZenModeEnabled] = useState(false);
@@ -152,8 +162,105 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
};
};
fetchData();
- }, [excalidrawAPI]);
+ }, [excalidrawAPI, convertToExcalidrawElements, MIME_TYPES]);
+ const renderExcalidraw = (children: React.ReactNode) => {
+ const Excalidraw: any = Children.toArray(children).find(
+ (child) =>
+ React.isValidElement(child) &&
+ typeof child.type !== "string" &&
+ //@ts-ignore
+ child.type.displayName === "Excalidraw",
+ );
+ if (!Excalidraw) {
+ return;
+ }
+ const newElement = cloneElement(
+ Excalidraw,
+ {
+ excalidrawAPI: (api: ExcalidrawImperativeAPI) => setExcalidrawAPI(api),
+ initialData: initialStatePromiseRef.current.promise,
+ onChange: (
+ elements: NonDeletedExcalidrawElement[],
+ state: AppState,
+ ) => {
+ console.info("Elements :", elements, "State : ", state);
+ },
+ onPointerUpdate: (payload: {
+ pointer: { x: number; y: number };
+ button: "down" | "up";
+ pointersMap: Gesture["pointers"];
+ }) => setPointerData(payload),
+ viewModeEnabled,
+ zenModeEnabled,
+ gridModeEnabled,
+ theme,
+ name: "Custom name of drawing",
+ UIOptions: {
+ canvasActions: {
+ loadScene: false,
+ },
+ tools: { image: !disableImageTool },
+ },
+ renderTopRightUI,
+ onLinkOpen,
+ onPointerDown,
+ onScrollChange: rerenderCommentIcons,
+ validateEmbeddable: true,
+ },
+ <>
+ {excalidrawAPI && (
+
+ )}
+
+
+
+
+ Tab one!
+ Tab two!
+
+ One
+ Two
+
+
+
+
+ Toggle Custom Sidebar
+
+ {renderMenu()}
+ {excalidrawAPI && (
+ 😀}>
+ Text to diagram
+
+ )}
+ {
+ console.info("submit");
+ // sleep for 2s
+ await new Promise((resolve) => setTimeout(resolve, 2000));
+ throw new Error("error, go away now");
+ // return "dummy";
+ }}
+ />
+ >,
+ );
+ return newElement;
+ };
const renderTopRightUI = (isMobile: boolean) => {
return (
<>
@@ -337,8 +444,8 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
pointerDownState: PointerDownState,
) => {
return withBatchedUpdates((event) => {
- window.removeEventListener(EVENT.POINTER_MOVE, pointerDownState.onMove);
- window.removeEventListener(EVENT.POINTER_UP, pointerDownState.onUp);
+ window.removeEventListener("pointermove", pointerDownState.onMove);
+ window.removeEventListener("pointerup", pointerDownState.onUp);
excalidrawAPI?.setActiveTool({ type: "selection" });
const distance = distance2d(
pointerDownState.x,
@@ -402,8 +509,8 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
onPointerMoveFromPointerDownHandler(pointerDownState);
const onPointerUp =
onPointerUpFromPointerDownHandler(pointerDownState);
- window.addEventListener(EVENT.POINTER_MOVE, onPointerMove);
- window.addEventListener(EVENT.POINTER_UP, onPointerUp);
+ window.addEventListener("pointermove", onPointerMove);
+ window.addEventListener("pointerup", onPointerUp);
pointerDownState.onMove = onPointerMove;
pointerDownState.onUp = onPointerUp;
@@ -495,7 +602,7 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
}}
onBlur={saveComment}
onKeyDown={(event) => {
- if (!event.shiftKey && event.key === KEYS.ENTER) {
+ if (!event.shiftKey && event.key === "Enter") {
event.preventDefault();
saveComment();
}
@@ -528,7 +635,12 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
- {excalidrawAPI && }
+ {excalidrawAPI && (
+
+ )}
);
};
@@ -677,83 +789,7 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {