From 225db4a7805befe009fe055fc2ef5daedd6c04f9 Mon Sep 17 00:00:00 2001
From: kj_sh604
Date: Sun, 15 Mar 2026 16:19:35 -0400
Subject: refactor: examples/
---
examples/with-nextjs/.gitignore | 39 +
examples/with-nextjs/README.md | 36 +
examples/with-nextjs/next.config.js | 12 +
examples/with-nextjs/package.json | 25 +
examples/with-nextjs/public/images/doremon.png | Bin 0 -> 201946 bytes
examples/with-nextjs/public/images/excalibot.png | Bin 0 -> 30330 bytes
examples/with-nextjs/public/images/pika.jpeg | Bin 0 -> 6250 bytes
examples/with-nextjs/public/images/rocket.jpeg | Bin 0 -> 40368 bytes
examples/with-nextjs/src/app/favicon.ico | Bin 0 -> 25931 bytes
examples/with-nextjs/src/app/layout.tsx | 11 +
examples/with-nextjs/src/app/page.tsx | 26 +
examples/with-nextjs/src/common.scss | 15 +
examples/with-nextjs/src/excalidrawWrapper.tsx | 22 +
.../with-nextjs/src/pages/excalidraw-in-pages.tsx | 22 +
examples/with-nextjs/tsconfig.json | 28 +
examples/with-nextjs/vercel.json | 3 +
examples/with-nextjs/yarn.lock | 252 ++++++
.../with-script-in-browser/.codesandbox/Dockerfile | 5 +
.../with-script-in-browser/.codesandbox/tasks.json | 35 +
examples/with-script-in-browser/.gitignore | 2 +
.../components/CustomFooter.tsx | 73 ++
.../components/ExampleApp.scss | 92 ++
.../components/ExampleApp.tsx | 961 ++++++++++++++++++++
.../components/MobileFooter.tsx | 28 +
.../components/sidebar/ExampleSidebar.scss | 66 ++
.../components/sidebar/ExampleSidebar.tsx | 31 +
examples/with-script-in-browser/index.html | 31 +
examples/with-script-in-browser/index.tsx | 28 +
examples/with-script-in-browser/initialData.tsx | 994 +++++++++++++++++++++
examples/with-script-in-browser/package.json | 21 +
.../public/images/doremon.png | Bin 0 -> 201946 bytes
.../public/images/excalibot.png | Bin 0 -> 30330 bytes
.../with-script-in-browser/public/images/pika.jpeg | Bin 0 -> 6250 bytes
.../public/images/rocket.jpeg | Bin 0 -> 40368 bytes
examples/with-script-in-browser/tsconfig.json | 9 +
examples/with-script-in-browser/utils.ts | 145 +++
examples/with-script-in-browser/vercel.json | 5 +
examples/with-script-in-browser/vite.config.mts | 19 +
38 files changed, 3036 insertions(+)
create mode 100644 examples/with-nextjs/.gitignore
create mode 100644 examples/with-nextjs/README.md
create mode 100644 examples/with-nextjs/next.config.js
create mode 100644 examples/with-nextjs/package.json
create mode 100644 examples/with-nextjs/public/images/doremon.png
create mode 100644 examples/with-nextjs/public/images/excalibot.png
create mode 100644 examples/with-nextjs/public/images/pika.jpeg
create mode 100644 examples/with-nextjs/public/images/rocket.jpeg
create mode 100644 examples/with-nextjs/src/app/favicon.ico
create mode 100644 examples/with-nextjs/src/app/layout.tsx
create mode 100644 examples/with-nextjs/src/app/page.tsx
create mode 100644 examples/with-nextjs/src/common.scss
create mode 100644 examples/with-nextjs/src/excalidrawWrapper.tsx
create mode 100644 examples/with-nextjs/src/pages/excalidraw-in-pages.tsx
create mode 100644 examples/with-nextjs/tsconfig.json
create mode 100644 examples/with-nextjs/vercel.json
create mode 100644 examples/with-nextjs/yarn.lock
create mode 100644 examples/with-script-in-browser/.codesandbox/Dockerfile
create mode 100644 examples/with-script-in-browser/.codesandbox/tasks.json
create mode 100644 examples/with-script-in-browser/.gitignore
create mode 100644 examples/with-script-in-browser/components/CustomFooter.tsx
create mode 100644 examples/with-script-in-browser/components/ExampleApp.scss
create mode 100644 examples/with-script-in-browser/components/ExampleApp.tsx
create mode 100644 examples/with-script-in-browser/components/MobileFooter.tsx
create mode 100644 examples/with-script-in-browser/components/sidebar/ExampleSidebar.scss
create mode 100644 examples/with-script-in-browser/components/sidebar/ExampleSidebar.tsx
create mode 100644 examples/with-script-in-browser/index.html
create mode 100644 examples/with-script-in-browser/index.tsx
create mode 100644 examples/with-script-in-browser/initialData.tsx
create mode 100644 examples/with-script-in-browser/package.json
create mode 100644 examples/with-script-in-browser/public/images/doremon.png
create mode 100644 examples/with-script-in-browser/public/images/excalibot.png
create mode 100644 examples/with-script-in-browser/public/images/pika.jpeg
create mode 100644 examples/with-script-in-browser/public/images/rocket.jpeg
create mode 100644 examples/with-script-in-browser/tsconfig.json
create mode 100644 examples/with-script-in-browser/utils.ts
create mode 100644 examples/with-script-in-browser/vercel.json
create mode 100644 examples/with-script-in-browser/vite.config.mts
(limited to 'examples')
diff --git a/examples/with-nextjs/.gitignore b/examples/with-nextjs/.gitignore
new file mode 100644
index 0000000..8f73eef
--- /dev/null
+++ b/examples/with-nextjs/.gitignore
@@ -0,0 +1,39 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+.yarn/install-state.gz
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
+
+# copied assets
+public/**/*.woff2
\ No newline at end of file
diff --git a/examples/with-nextjs/README.md b/examples/with-nextjs/README.md
new file mode 100644
index 0000000..9e8d9b9
--- /dev/null
+++ b/examples/with-nextjs/README.md
@@ -0,0 +1,36 @@
+This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun dev
+```
+
+Open [http://localhost:3000](http://localhost:3005) with your browser to see the result.
+
+You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
+
+This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
+
+## Learn More
+
+To learn more about Next.js, take a look at the following resources:
+
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
+
+## Deploy on Vercel
+
+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
diff --git a/examples/with-nextjs/next.config.js b/examples/with-nextjs/next.config.js
new file mode 100644
index 0000000..701438e
--- /dev/null
+++ b/examples/with-nextjs/next.config.js
@@ -0,0 +1,12 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ distDir: "build",
+ typescript: {
+ // The ts config doesn't work with `jsx: preserve" and if updated to `react-jsx` it gets ovewritten by next js throwing ts errors hence I am ignoring build errors until this is fixed.
+ ignoreBuildErrors: true,
+ },
+ // This is needed as in pages router the code for importing types throws error as its outside next js app
+ transpilePackages: ["../"],
+};
+
+module.exports = nextConfig;
diff --git a/examples/with-nextjs/package.json b/examples/with-nextjs/package.json
new file mode 100644
index 0000000..ee8e555
--- /dev/null
+++ b/examples/with-nextjs/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "with-nextjs",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "build:workspace": "yarn workspace @excalidraw/excalidraw run build:esm && yarn copy:assets",
+ "copy:assets": "cp -r ../../packages/excalidraw/dist/prod/fonts ./public",
+ "dev": "yarn build:workspace && next dev -p 3005",
+ "build": "yarn build:workspace && next build",
+ "start": "next start -p 3006",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "next": "14.1",
+ "react": "19.0.0",
+ "react-dom": "19.0.0"
+ },
+ "devDependencies": {
+ "@types/node": "^20",
+ "@types/react": "19.0.10",
+ "@types/react-dom": "19.0.4",
+ "path2d-polyfill": "2.0.1",
+ "typescript": "^5"
+ }
+}
diff --git a/examples/with-nextjs/public/images/doremon.png b/examples/with-nextjs/public/images/doremon.png
new file mode 100644
index 0000000..36208a4
Binary files /dev/null and b/examples/with-nextjs/public/images/doremon.png differ
diff --git a/examples/with-nextjs/public/images/excalibot.png b/examples/with-nextjs/public/images/excalibot.png
new file mode 100644
index 0000000..7928ec3
Binary files /dev/null and b/examples/with-nextjs/public/images/excalibot.png differ
diff --git a/examples/with-nextjs/public/images/pika.jpeg b/examples/with-nextjs/public/images/pika.jpeg
new file mode 100644
index 0000000..455ed52
Binary files /dev/null and b/examples/with-nextjs/public/images/pika.jpeg differ
diff --git a/examples/with-nextjs/public/images/rocket.jpeg b/examples/with-nextjs/public/images/rocket.jpeg
new file mode 100644
index 0000000..f17a74b
Binary files /dev/null and b/examples/with-nextjs/public/images/rocket.jpeg differ
diff --git a/examples/with-nextjs/src/app/favicon.ico b/examples/with-nextjs/src/app/favicon.ico
new file mode 100644
index 0000000..718d6fe
Binary files /dev/null and b/examples/with-nextjs/src/app/favicon.ico differ
diff --git a/examples/with-nextjs/src/app/layout.tsx b/examples/with-nextjs/src/app/layout.tsx
new file mode 100644
index 0000000..225b603
--- /dev/null
+++ b/examples/with-nextjs/src/app/layout.tsx
@@ -0,0 +1,11 @@
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+
{children}
+
+ );
+}
diff --git a/examples/with-nextjs/src/app/page.tsx b/examples/with-nextjs/src/app/page.tsx
new file mode 100644
index 0000000..191aca1
--- /dev/null
+++ b/examples/with-nextjs/src/app/page.tsx
@@ -0,0 +1,26 @@
+import dynamic from "next/dynamic";
+import Script from "next/script";
+import "../common.scss";
+
+// Since client components get prerenderd on server as well hence importing the excalidraw stuff dynamically
+// with ssr false
+const ExcalidrawWithClientOnly = dynamic(
+ async () => (await import("../excalidrawWrapper")).default,
+ {
+ ssr: false,
+ },
+);
+
+export default function Page() {
+ return (
+ <>
+ Switch to Pages router
+ App Router
+
+ {/* @ts-expect-error - https://github.com/vercel/next.js/issues/42292 */}
+
+ >
+ );
+}
diff --git a/examples/with-nextjs/src/common.scss b/examples/with-nextjs/src/common.scss
new file mode 100644
index 0000000..456bc76
--- /dev/null
+++ b/examples/with-nextjs/src/common.scss
@@ -0,0 +1,15 @@
+* {
+ box-sizing: border-box;
+ font-family: sans-serif;
+}
+
+a {
+ color: #1c7ed6;
+ font-size: 20px;
+ text-decoration: none;
+ font-weight: 500;
+}
+
+.page-title {
+ text-align: center;
+}
diff --git a/examples/with-nextjs/src/excalidrawWrapper.tsx b/examples/with-nextjs/src/excalidrawWrapper.tsx
new file mode 100644
index 0000000..b4c45fa
--- /dev/null
+++ b/examples/with-nextjs/src/excalidrawWrapper.tsx
@@ -0,0 +1,22 @@
+"use client";
+import * as excalidrawLib from "@excalidraw/excalidraw";
+import { Excalidraw } from "@excalidraw/excalidraw";
+import App from "../../with-script-in-browser/components/ExampleApp";
+
+import "@excalidraw/excalidraw/index.css";
+
+const ExcalidrawWrapper: React.FC = () => {
+ return (
+ <>
+ {}}
+ excalidrawLib={excalidrawLib}
+ >
+
+
+ >
+ );
+};
+
+export default ExcalidrawWrapper;
diff --git a/examples/with-nextjs/src/pages/excalidraw-in-pages.tsx b/examples/with-nextjs/src/pages/excalidraw-in-pages.tsx
new file mode 100644
index 0000000..527a346
--- /dev/null
+++ b/examples/with-nextjs/src/pages/excalidraw-in-pages.tsx
@@ -0,0 +1,22 @@
+import dynamic from "next/dynamic";
+import "../common.scss";
+
+// Since client components get prerenderd on server as well hence importing the excalidraw stuff dynamically
+// with ssr false
+const Excalidraw = dynamic(
+ async () => (await import("../excalidrawWrapper")).default,
+ {
+ ssr: false,
+ },
+);
+
+export default function Page() {
+ return (
+ <>
+ Switch to App router
+ Pages Router
+ {/* @ts-expect-error - https://github.com/vercel/next.js/issues/42292 */}
+
+ >
+ );
+}
diff --git a/examples/with-nextjs/tsconfig.json b/examples/with-nextjs/tsconfig.json
new file mode 100644
index 0000000..09ae73d
--- /dev/null
+++ b/examples/with-nextjs/tsconfig.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./src/*"]
+ },
+ "forceConsistentCasingInFileNames": true
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "build/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/examples/with-nextjs/vercel.json b/examples/with-nextjs/vercel.json
new file mode 100644
index 0000000..bd885f4
--- /dev/null
+++ b/examples/with-nextjs/vercel.json
@@ -0,0 +1,3 @@
+{
+ "outputDirectory": "build"
+}
diff --git a/examples/with-nextjs/yarn.lock b/examples/with-nextjs/yarn.lock
new file mode 100644
index 0000000..0072235
--- /dev/null
+++ b/examples/with-nextjs/yarn.lock
@@ -0,0 +1,252 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@excalidraw/excalidraw@workspace:^":
+ version "0.17.2"
+ resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.17.2.tgz#9a636a1e6bb3c88c5883347d3a7e75e9cce8ab96"
+ integrity sha512-7pqUWD8+mPjDhF4XxG3gw4rvE2JGaLW3Vss5UZfTbITPxAtFaGEc1K081bncitnaYhUwN9ENJE0i87QB3poDwQ==
+
+"@next/env@14.0.4":
+ version "14.0.4"
+ resolved "https://registry.yarnpkg.com/@next/env/-/env-14.0.4.tgz#d5cda0c4a862d70ae760e58c0cd96a8899a2e49a"
+ integrity sha512-irQnbMLbUNQpP1wcE5NstJtbuA/69kRfzBrpAD7Gsn8zm/CY6YQYc3HQBz8QPxwISG26tIm5afvvVbu508oBeQ==
+
+"@next/swc-darwin-arm64@14.0.4":
+ version "14.0.4"
+ resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.4.tgz#27b1854c2cd04eb1d5e75081a1a792ad91526618"
+ integrity sha512-mF05E/5uPthWzyYDyptcwHptucf/jj09i2SXBPwNzbgBNc+XnwzrL0U6BmPjQeOL+FiB+iG1gwBeq7mlDjSRPg==
+
+"@next/swc-darwin-x64@14.0.4":
+ version "14.0.4"
+ resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.4.tgz#9940c449e757d0ee50bb9e792d2600cc08a3eb3b"
+ integrity sha512-IZQ3C7Bx0k2rYtrZZxKKiusMTM9WWcK5ajyhOZkYYTCc8xytmwSzR1skU7qLgVT/EY9xtXDG0WhY6fyujnI3rw==
+
+"@next/swc-linux-arm64-gnu@14.0.4":
+ version "14.0.4"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.4.tgz#0eafd27c8587f68ace7b4fa80695711a8434de21"
+ integrity sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w==
+
+"@next/swc-linux-arm64-musl@14.0.4":
+ version "14.0.4"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.4.tgz#2b0072adb213f36dada5394ea67d6e82069ae7dd"
+ integrity sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ==
+
+"@next/swc-linux-x64-gnu@14.0.4":
+ version "14.0.4"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.4.tgz#68c67d20ebc8e3f6ced6ff23a4ba2a679dbcec32"
+ integrity sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A==
+
+"@next/swc-linux-x64-musl@14.0.4":
+ version "14.0.4"
+ resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.4.tgz#67cd81b42fb2caf313f7992fcf6d978af55a1247"
+ integrity sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw==
+
+"@next/swc-win32-arm64-msvc@14.0.4":
+ version "14.0.4"
+ resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.4.tgz#be06585906b195d755ceda28f33c633e1443f1a3"
+ integrity sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w==
+
+"@next/swc-win32-ia32-msvc@14.0.4":
+ version "14.0.4"
+ resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.4.tgz#e76cabefa9f2d891599c3d85928475bd8d3f6600"
+ integrity sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg==
+
+"@next/swc-win32-x64-msvc@14.0.4":
+ version "14.0.4"
+ resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.4.tgz#e74892f1a9ccf41d3bf5979ad6d3d77c07b9cba1"
+ integrity sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A==
+
+"@swc/helpers@0.5.2":
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.2.tgz#85ea0c76450b61ad7d10a37050289eded783c27d"
+ integrity sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==
+ dependencies:
+ tslib "^2.4.0"
+
+"@types/node@^20":
+ version "20.11.0"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.0.tgz#8e0b99e70c0c1ade1a86c4a282f7b7ef87c9552f"
+ integrity sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==
+ dependencies:
+ undici-types "~5.26.4"
+
+"@types/prop-types@*":
+ version "15.7.11"
+ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563"
+ integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==
+
+"@types/react-dom@^18":
+ version "18.2.18"
+ resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.18.tgz#16946e6cd43971256d874bc3d0a72074bb8571dd"
+ integrity sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==
+ dependencies:
+ "@types/react" "*"
+
+"@types/react@*", "@types/react@^18":
+ version "18.2.47"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.47.tgz#85074b27ab563df01fbc3f68dc64bf7050b0af40"
+ integrity sha512-xquNkkOirwyCgoClNk85BjP+aqnIS+ckAJ8i37gAbDs14jfW/J23f2GItAf33oiUPQnqNMALiFeoM9Y5mbjpVQ==
+ dependencies:
+ "@types/prop-types" "*"
+ "@types/scheduler" "*"
+ csstype "^3.0.2"
+
+"@types/scheduler@*":
+ version "0.16.8"
+ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.8.tgz#ce5ace04cfeabe7ef87c0091e50752e36707deff"
+ integrity sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==
+
+busboy@1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
+ integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
+ dependencies:
+ streamsearch "^1.1.0"
+
+caniuse-lite@^1.0.30001406:
+ version "1.0.30001576"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz#893be772cf8ee6056d6c1e2d07df365b9ec0a5c4"
+ integrity sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg==
+
+client-only@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
+ integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
+
+csstype@^3.0.2:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
+ integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
+
+glob-to-regexp@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
+ integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
+
+graceful-fs@^4.1.2, graceful-fs@^4.2.11:
+ version "4.2.11"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
+ integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
+
+"js-tokens@^3.0.0 || ^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+ integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
+loose-envify@^1.1.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
+ integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
+ dependencies:
+ js-tokens "^3.0.0 || ^4.0.0"
+
+nanoid@^3.3.6:
+ version "3.3.7"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
+ integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
+
+next@14.0.4:
+ version "14.0.4"
+ resolved "https://registry.yarnpkg.com/next/-/next-14.0.4.tgz#bf00b6f835b20d10a5057838fa2dfced1d0d84dc"
+ integrity sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA==
+ dependencies:
+ "@next/env" "14.0.4"
+ "@swc/helpers" "0.5.2"
+ busboy "1.6.0"
+ caniuse-lite "^1.0.30001406"
+ graceful-fs "^4.2.11"
+ postcss "8.4.31"
+ styled-jsx "5.1.1"
+ watchpack "2.4.0"
+ optionalDependencies:
+ "@next/swc-darwin-arm64" "14.0.4"
+ "@next/swc-darwin-x64" "14.0.4"
+ "@next/swc-linux-arm64-gnu" "14.0.4"
+ "@next/swc-linux-arm64-musl" "14.0.4"
+ "@next/swc-linux-x64-gnu" "14.0.4"
+ "@next/swc-linux-x64-musl" "14.0.4"
+ "@next/swc-win32-arm64-msvc" "14.0.4"
+ "@next/swc-win32-ia32-msvc" "14.0.4"
+ "@next/swc-win32-x64-msvc" "14.0.4"
+
+path2d-polyfill@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz#24c554a738f42700d6961992bf5f1049672f2391"
+ integrity sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==
+
+picocolors@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
+ integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
+
+postcss@8.4.31:
+ version "8.4.31"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
+ integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
+ dependencies:
+ nanoid "^3.3.6"
+ picocolors "^1.0.0"
+ source-map-js "^1.0.2"
+
+react-dom@^18:
+ version "18.2.0"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
+ integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
+ dependencies:
+ loose-envify "^1.1.0"
+ scheduler "^0.23.0"
+
+react@^18:
+ version "18.2.0"
+ resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
+ integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
+ dependencies:
+ loose-envify "^1.1.0"
+
+scheduler@^0.23.0:
+ version "0.23.0"
+ resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
+ integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
+ dependencies:
+ loose-envify "^1.1.0"
+
+source-map-js@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
+ integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
+
+streamsearch@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
+ integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
+
+styled-jsx@5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.1.tgz#839a1c3aaacc4e735fed0781b8619ea5d0009d1f"
+ integrity sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==
+ dependencies:
+ client-only "0.0.1"
+
+tslib@^2.4.0:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
+ integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
+
+typescript@^5:
+ version "5.3.3"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
+ integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
+
+undici-types@~5.26.4:
+ version "5.26.5"
+ resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
+ integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
+
+watchpack@2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
+ integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
+ dependencies:
+ glob-to-regexp "^0.4.1"
+ graceful-fs "^4.1.2"
diff --git a/examples/with-script-in-browser/.codesandbox/Dockerfile b/examples/with-script-in-browser/.codesandbox/Dockerfile
new file mode 100644
index 0000000..fd5b38d
--- /dev/null
+++ b/examples/with-script-in-browser/.codesandbox/Dockerfile
@@ -0,0 +1,5 @@
+FROM node:18-bullseye
+
+# Vite wants to open the browser using `open`, so we
+# need to install those utils.
+RUN apt update -y && apt install -y xdg-utils
diff --git a/examples/with-script-in-browser/.codesandbox/tasks.json b/examples/with-script-in-browser/.codesandbox/tasks.json
new file mode 100644
index 0000000..990c21a
--- /dev/null
+++ b/examples/with-script-in-browser/.codesandbox/tasks.json
@@ -0,0 +1,35 @@
+{
+ // These tasks will run in order when initializing your CodeSandbox project.
+ "setupTasks": [
+ {
+ "name": "Install Dependencies",
+ "command": "yarn install"
+ }
+ ],
+
+ // These tasks can be run from CodeSandbox. Running one will open a log in the app.
+ "tasks": {
+ "build": {
+ "name": "Build",
+ "command": "yarn build",
+ "runAtStart": false
+ },
+ "start": {
+ "name": "Start Example",
+ "command": "yarn start",
+ "runAtStart": true,
+ "preview": {
+ "port": 3001
+ }
+ },
+ "install-deps": {
+ "name": "Install Dependencies",
+ "command": "yarn install",
+ "restartOn": {
+ "files": ["yarn.lock"],
+ "branch": false,
+ "resume": false
+ }
+ }
+ }
+}
diff --git a/examples/with-script-in-browser/.gitignore b/examples/with-script-in-browser/.gitignore
new file mode 100644
index 0000000..f1b2f5e
--- /dev/null
+++ b/examples/with-script-in-browser/.gitignore
@@ -0,0 +1,2 @@
+# copied assets
+public/**/*.woff2
\ No newline at end of file
diff --git a/examples/with-script-in-browser/components/CustomFooter.tsx b/examples/with-script-in-browser/components/CustomFooter.tsx
new file mode 100644
index 0000000..72fd199
--- /dev/null
+++ b/examples/with-script-in-browser/components/CustomFooter.tsx
@@ -0,0 +1,73 @@
+import React from "react";
+import type * as TExcalidraw from "@excalidraw/excalidraw";
+import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
+
+const COMMENT_SVG = (
+
+);
+
+const CustomFooter = ({
+ excalidrawAPI,
+ excalidrawLib,
+}: {
+ excalidrawAPI: ExcalidrawImperativeAPI;
+ excalidrawLib: typeof TExcalidraw;
+}) => {
+ const { Button, MIME_TYPES } = excalidrawLib;
+
+ return (
+ <>
+
+
+ >
+ );
+};
+
+export default CustomFooter;
diff --git a/examples/with-script-in-browser/components/ExampleApp.scss b/examples/with-script-in-browser/components/ExampleApp.scss
new file mode 100644
index 0000000..e41a77c
--- /dev/null
+++ b/examples/with-script-in-browser/components/ExampleApp.scss
@@ -0,0 +1,92 @@
+.App {
+ font-family: sans-serif;
+ text-align: center;
+
+ .comment-avatar {
+ background: #faa2c1;
+ border-radius: 66px 67px 67px 0px;
+ width: 2rem;
+ height: 2rem;
+ padding: 4px;
+ margin: 4px;
+ img {
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ }
+ }
+ .app-title {
+ margin-block-start: 0.83em;
+ margin-block-end: 0.83em;
+ }
+}
+
+.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 {
+ display: flex;
+}
+
+.excalidraw-wrapper {
+ height: 800px;
+ margin: 50px;
+ position: relative;
+ overflow: hidden;
+}
+
+:root[dir="ltr"]
+ .excalidraw
+ .layer-ui__wrapper
+ .zen-mode-transition.App-menu_bottom--transition-left {
+ transform: none;
+}
+
+.excalidraw .panelColumn {
+ text-align: left;
+}
+
+.export-wrapper {
+ display: flex;
+ flex-direction: column;
+ margin: 50px;
+
+ &__checkbox {
+ display: flex;
+ }
+}
+
+.excalidraw {
+ --color-primary: #faa2c1;
+ --color-primary-darker: #f783ac;
+ --color-primary-darkest: #e64980;
+ --color-primary-light: #fcc2d7;
+
+ button.custom-element {
+ width: 2rem;
+ height: 2rem;
+ }
+
+ .custom-footer,
+ .custom-element {
+ padding: 0.1rem;
+ margin: 0 8px;
+ }
+ .layer-ui__wrapper__footer.App-menu_bottom {
+ align-items: stretch;
+ }
+ // till its merged in OSS
+ .App-toolbar-container .mobile-misc-tools-container {
+ position: absolute;
+ }
+}
diff --git a/examples/with-script-in-browser/components/ExampleApp.tsx b/examples/with-script-in-browser/components/ExampleApp.tsx
new file mode 100644
index 0000000..08c8032
--- /dev/null
+++ b/examples/with-script-in-browser/components/ExampleApp.tsx
@@ -0,0 +1,961 @@
+import React, {
+ useEffect,
+ useState,
+ useRef,
+ useCallback,
+ Children,
+ cloneElement,
+} from "react";
+import ExampleSidebar from "./sidebar/ExampleSidebar";
+
+import type * as TExcalidraw from "@excalidraw/excalidraw";
+
+import { nanoid } from "nanoid";
+
+import type { ResolvablePromise } from "../utils";
+import {
+ resolvablePromise,
+ distance2d,
+ fileOpen,
+ withBatchedUpdates,
+ withBatchedUpdatesThrottled,
+} from "../utils";
+
+import CustomFooter from "./CustomFooter";
+import MobileFooter from "./MobileFooter";
+import initialData from "../initialData";
+
+import type {
+ AppState,
+ BinaryFileData,
+ ExcalidrawImperativeAPI,
+ ExcalidrawInitialDataState,
+ Gesture,
+ LibraryItems,
+ PointerDownState as ExcalidrawPointerDownState,
+} from "@excalidraw/excalidraw/types";
+import type {
+ NonDeletedExcalidrawElement,
+ Theme,
+} from "@excalidraw/excalidraw/element/types";
+import type { ImportedLibraryData } from "@excalidraw/excalidraw/data/types";
+
+import "./ExampleApp.scss";
+
+type Comment = {
+ x: number;
+ y: number;
+ value: string;
+ id?: string;
+};
+
+type PointerDownState = {
+ x: number;
+ y: number;
+ hitElement: Comment;
+ onMove: any;
+ onUp: any;
+ hitElementOffsets: {
+ x: number;
+ y: number;
+ };
+};
+
+const COMMENT_ICON_DIMENSION = 32;
+const COMMENT_INPUT_HEIGHT = 50;
+const COMMENT_INPUT_WIDTH = 150;
+
+export interface AppProps {
+ appTitle: string;
+ useCustom: (api: ExcalidrawImperativeAPI | null, customArgs?: any[]) => void;
+ customArgs?: any[];
+ children: React.ReactNode;
+ excalidrawLib: typeof TExcalidraw;
+}
+
+export default function ExampleApp({
+ 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);
+ const [gridModeEnabled, setGridModeEnabled] = useState(false);
+ const [blobUrl, setBlobUrl] = useState("");
+ const [canvasUrl, setCanvasUrl] = useState("");
+ const [exportWithDarkMode, setExportWithDarkMode] = useState(false);
+ const [exportEmbedScene, setExportEmbedScene] = useState(false);
+ const [theme, setTheme] = useState("light");
+ const [disableImageTool, setDisableImageTool] = useState(false);
+ const [isCollaborating, setIsCollaborating] = useState(false);
+ const [commentIcons, setCommentIcons] = useState<{ [id: string]: Comment }>(
+ {},
+ );
+ const [comment, setComment] = useState(null);
+
+ const initialStatePromiseRef = useRef<{
+ promise: ResolvablePromise;
+ }>({ promise: null! });
+ if (!initialStatePromiseRef.current.promise) {
+ initialStatePromiseRef.current.promise =
+ resolvablePromise();
+ }
+
+ const [excalidrawAPI, setExcalidrawAPI] =
+ useState(null);
+
+ useCustom(excalidrawAPI, customArgs);
+
+ useHandleLibrary({ excalidrawAPI });
+
+ useEffect(() => {
+ if (!excalidrawAPI) {
+ return;
+ }
+ const fetchData = async () => {
+ const res = await fetch("/images/rocket.jpeg");
+ const imageData = await res.blob();
+ const reader = new FileReader();
+ reader.readAsDataURL(imageData);
+
+ reader.onload = function () {
+ const imagesArray: BinaryFileData[] = [
+ {
+ id: "rocket" as BinaryFileData["id"],
+ dataURL: reader.result as BinaryFileData["dataURL"],
+ mimeType: MIME_TYPES.jpg,
+ created: 1644915140367,
+ lastRetrieved: 1644915140367,
+ },
+ ];
+
+ //@ts-ignore
+ initialStatePromiseRef.current.promise.resolve({
+ ...initialData,
+ elements: convertToExcalidrawElements(initialData.elements),
+ });
+ excalidrawAPI.addFiles(imagesArray);
+ };
+ };
+ fetchData();
+ }, [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 (
+ <>
+ {!isMobile && (
+ {
+ window.alert("Collab dialog clicked");
+ }}
+ />
+ )}
+
+ >
+ );
+ };
+
+ const loadSceneOrLibrary = async () => {
+ const file = await fileOpen({ description: "Excalidraw or library file" });
+ const contents = await loadSceneOrLibraryFromBlob(file, null, null);
+ if (contents.type === MIME_TYPES.excalidraw) {
+ excalidrawAPI?.updateScene(contents.data as any);
+ } else if (contents.type === MIME_TYPES.excalidrawlib) {
+ excalidrawAPI?.updateLibrary({
+ libraryItems: (contents.data as ImportedLibraryData).libraryItems!,
+ openLibraryMenu: true,
+ });
+ }
+ };
+
+ const updateScene = () => {
+ const sceneData = {
+ elements: restoreElements(
+ convertToExcalidrawElements([
+ {
+ type: "rectangle",
+ id: "rect-1",
+ fillStyle: "hachure",
+ strokeWidth: 1,
+ strokeStyle: "solid",
+ roughness: 1,
+ angle: 0,
+ x: 100.50390625,
+ y: 93.67578125,
+ strokeColor: "#c92a2a",
+ width: 186.47265625,
+ height: 141.9765625,
+ seed: 1968410350,
+ roundness: {
+ type: ROUNDNESS.ADAPTIVE_RADIUS,
+ value: 32,
+ },
+ },
+ {
+ type: "arrow",
+ x: 300,
+ y: 150,
+ start: { id: "rect-1" },
+ end: { type: "ellipse" },
+ },
+ {
+ type: "text",
+ x: 300,
+ y: 100,
+ text: "HELLO WORLD!",
+ },
+ ]),
+ null,
+ ),
+ appState: {
+ viewBackgroundColor: "#edf2ff",
+ },
+ };
+ excalidrawAPI?.updateScene(sceneData);
+ };
+
+ const onLinkOpen = useCallback(
+ (
+ element: NonDeletedExcalidrawElement,
+ event: CustomEvent<{
+ nativeEvent: MouseEvent | React.PointerEvent;
+ }>,
+ ) => {
+ 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) {
+ // signal that we're handling the redirect ourselves
+ event.preventDefault();
+ // do a custom redirect, such as passing to react-router
+ // ...
+ }
+ },
+ [],
+ );
+
+ const onCopy = async (type: "png" | "svg" | "json") => {
+ if (!excalidrawAPI) {
+ return false;
+ }
+ await exportToClipboard({
+ elements: excalidrawAPI.getSceneElements(),
+ appState: excalidrawAPI.getAppState(),
+ files: excalidrawAPI.getFiles(),
+ type,
+ });
+ window.alert(`Copied to clipboard as ${type} successfully`);
+ };
+
+ const [pointerData, setPointerData] = useState<{
+ pointer: { x: number; y: number };
+ button: "down" | "up";
+ pointersMap: Gesture["pointers"];
+ } | null>(null);
+
+ const onPointerDown = (
+ activeTool: AppState["activeTool"],
+ pointerDownState: ExcalidrawPointerDownState,
+ ) => {
+ if (activeTool.type === "custom" && activeTool.customType === "comment") {
+ const { x, y } = pointerDownState.origin;
+ setComment({ x, y, value: "" });
+ }
+ };
+
+ const rerenderCommentIcons = () => {
+ if (!excalidrawAPI) {
+ return false;
+ }
+ const commentIconsElements = appRef.current.querySelectorAll(
+ ".comment-icon",
+ ) as HTMLElement[];
+ commentIconsElements.forEach((ele) => {
+ const id = ele.id;
+ const appstate = excalidrawAPI.getAppState();
+ const { x, y } = sceneCoordsToViewportCoords(
+ { sceneX: commentIcons[id].x, sceneY: commentIcons[id].y },
+ appstate,
+ );
+ ele.style.left = `${
+ x - COMMENT_ICON_DIMENSION / 2 - appstate!.offsetLeft
+ }px`;
+ ele.style.top = `${
+ y - COMMENT_ICON_DIMENSION / 2 - appstate!.offsetTop
+ }px`;
+ });
+ };
+
+ const onPointerMoveFromPointerDownHandler = (
+ pointerDownState: PointerDownState,
+ ) => {
+ return withBatchedUpdatesThrottled((event) => {
+ if (!excalidrawAPI) {
+ return false;
+ }
+ const { x, y } = viewportCoordsToSceneCoords(
+ {
+ clientX: event.clientX - pointerDownState.hitElementOffsets.x,
+ clientY: event.clientY - pointerDownState.hitElementOffsets.y,
+ },
+ excalidrawAPI.getAppState(),
+ );
+ setCommentIcons({
+ ...commentIcons,
+ [pointerDownState.hitElement.id!]: {
+ ...commentIcons[pointerDownState.hitElement.id!],
+ x,
+ y,
+ },
+ });
+ });
+ };
+ const onPointerUpFromPointerDownHandler = (
+ pointerDownState: PointerDownState,
+ ) => {
+ return withBatchedUpdates((event) => {
+ window.removeEventListener("pointermove", pointerDownState.onMove);
+ window.removeEventListener("pointerup", pointerDownState.onUp);
+ excalidrawAPI?.setActiveTool({ type: "selection" });
+ const distance = distance2d(
+ pointerDownState.x,
+ pointerDownState.y,
+ event.clientX,
+ event.clientY,
+ );
+ if (distance === 0) {
+ if (!comment) {
+ setComment({
+ x: pointerDownState.hitElement.x + 60,
+ y: pointerDownState.hitElement.y,
+ value: pointerDownState.hitElement.value,
+ id: pointerDownState.hitElement.id,
+ });
+ } else {
+ setComment(null);
+ }
+ }
+ });
+ };
+
+ const renderCommentIcons = () => {
+ return Object.values(commentIcons).map((commentIcon) => {
+ if (!excalidrawAPI) {
+ return false;
+ }
+ const appState = excalidrawAPI.getAppState();
+ const { x, y } = sceneCoordsToViewportCoords(
+ { sceneX: commentIcon.x, sceneY: commentIcon.y },
+ excalidrawAPI.getAppState(),
+ );
+ return (
+
+ );
+ });
+ };
+
+ const saveComment = () => {
+ if (!comment) {
+ return;
+ }
+ if (!comment.id && !comment.value) {
+ setComment(null);
+ return;
+ }
+ const id = comment.id || nanoid();
+ setCommentIcons({
+ ...commentIcons,
+ [id]: {
+ x: comment.id ? comment.x - 60 : comment.x,
+ y: comment.y,
+ id,
+ value: comment.value,
+ },
+ });
+ setComment(null);
+ };
+
+ const renderComment = () => {
+ if (!comment) {
+ return null;
+ }
+ const appState = excalidrawAPI?.getAppState()!;
+ const { x, y } = sceneCoordsToViewportCoords(
+ { sceneX: comment.x, sceneY: comment.y },
+ appState,
+ );
+ let top = y - COMMENT_ICON_DIMENSION / 2 - appState.offsetTop;
+ let left = x - COMMENT_ICON_DIMENSION / 2 - appState.offsetLeft;
+
+ if (
+ top + COMMENT_INPUT_HEIGHT <
+ appState.offsetTop + COMMENT_INPUT_HEIGHT
+ ) {
+ top = COMMENT_ICON_DIMENSION / 2;
+ }
+ if (top + COMMENT_INPUT_HEIGHT > appState.height) {
+ top = appState.height - COMMENT_INPUT_HEIGHT - COMMENT_ICON_DIMENSION / 2;
+ }
+ if (
+ left + COMMENT_INPUT_WIDTH <
+ appState.offsetLeft + COMMENT_INPUT_WIDTH
+ ) {
+ left = COMMENT_ICON_DIMENSION / 2;
+ }
+ if (left + COMMENT_INPUT_WIDTH > appState.width) {
+ left = appState.width - COMMENT_INPUT_WIDTH - COMMENT_ICON_DIMENSION / 2;
+ }
+
+ return (
+