Compare commits

..

6 Commits

Author SHA1 Message Date
zsviczian
452cceae40 Update App.tsx
do not delete last element if draggingElement is type selection
2023-09-27 21:33:50 +02:00
David Luzar
4765f5536e fix: frame name not editable on dbl-click (#7037) 2023-09-25 16:54:23 +02:00
David Luzar
556175558a fix: polyfill Element.replaceChildren (#7034) 2023-09-24 19:07:35 +02:00
Aakansha Doshi
4db73a7f95 docs: release @excalidraw/excalidraw@0.16.1 🎉 (#7020) 2023-09-21 10:28:21 +05:30
David Luzar
f8b3692262 fix: more eye-droper fixes (#7019) 2023-09-21 09:54:03 +05:30
Aakansha Doshi
741d5f1a18 refactor: move excalidraw-app outside src (#6987)
* refactor: move excalidraw-app outside src

* move some tests to excal app and fix some

* fix tests

* fix

* port remaining tests

* fix

* update snap

* move tests inside test folder

* fix

* fix
2023-09-21 09:28:48 +05:30
72 changed files with 783 additions and 479 deletions

View File

@@ -1,14 +1,14 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { debounce, getVersion, nFormatter } from "../utils"; import { debounce, getVersion, nFormatter } from "../src/utils";
import { import {
getElementsStorageSize, getElementsStorageSize,
getTotalStorageSize, getTotalStorageSize,
} from "./data/localStorage"; } from "./data/localStorage";
import { DEFAULT_VERSION } from "../constants"; import { DEFAULT_VERSION } from "../src/constants";
import { t } from "../i18n"; import { t } from "../src/i18n";
import { copyTextToSystemClipboard } from "../clipboard"; import { copyTextToSystemClipboard } from "../src/clipboard";
import { NonDeletedExcalidrawElement } from "../element/types"; import { NonDeletedExcalidrawElement } from "../src/element/types";
import { UIAppState } from "../types"; import { UIAppState } from "../src/types";
type StorageSizes = { scene: number; total: number }; type StorageSizes = { scene: number; total: number };

View File

@@ -1,23 +1,23 @@
import throttle from "lodash.throttle"; import throttle from "lodash.throttle";
import { PureComponent } from "react"; import { PureComponent } from "react";
import { ExcalidrawImperativeAPI } from "../../types"; import { ExcalidrawImperativeAPI } from "../../src/types";
import { ErrorDialog } from "../../components/ErrorDialog"; import { ErrorDialog } from "../../src/components/ErrorDialog";
import { APP_NAME, ENV, EVENT } from "../../constants"; import { APP_NAME, ENV, EVENT } from "../../src/constants";
import { ImportedDataState } from "../../data/types"; import { ImportedDataState } from "../../src/data/types";
import { import {
ExcalidrawElement, ExcalidrawElement,
InitializedExcalidrawImageElement, InitializedExcalidrawImageElement,
} from "../../element/types"; } from "../../src/element/types";
import { import {
getSceneVersion, getSceneVersion,
restoreElements, restoreElements,
} from "../../packages/excalidraw/index"; } from "../../src/packages/excalidraw/index";
import { Collaborator, Gesture } from "../../types"; import { Collaborator, Gesture } from "../../src/types";
import { import {
preventUnload, preventUnload,
resolvablePromise, resolvablePromise,
withBatchedUpdates, withBatchedUpdates,
} from "../../utils"; } from "../../src/utils";
import { import {
CURSOR_SYNC_TIMEOUT, CURSOR_SYNC_TIMEOUT,
FILE_UPLOAD_MAX_BYTES, FILE_UPLOAD_MAX_BYTES,
@@ -48,25 +48,25 @@ import {
} from "../data/localStorage"; } from "../data/localStorage";
import Portal from "./Portal"; import Portal from "./Portal";
import RoomDialog from "./RoomDialog"; import RoomDialog from "./RoomDialog";
import { t } from "../../i18n"; import { t } from "../../src/i18n";
import { UserIdleState } from "../../types"; import { UserIdleState } from "../../src/types";
import { IDLE_THRESHOLD, ACTIVE_THRESHOLD } from "../../constants"; import { IDLE_THRESHOLD, ACTIVE_THRESHOLD } from "../../src/constants";
import { import {
encodeFilesForUpload, encodeFilesForUpload,
FileManager, FileManager,
updateStaleImageStatuses, updateStaleImageStatuses,
} from "../data/FileManager"; } from "../data/FileManager";
import { AbortError } from "../../errors"; import { AbortError } from "../../src/errors";
import { import {
isImageElement, isImageElement,
isInitializedImageElement, isInitializedImageElement,
} from "../../element/typeChecks"; } from "../../src/element/typeChecks";
import { newElementWith } from "../../element/mutateElement"; import { newElementWith } from "../../src/element/mutateElement";
import { import {
ReconciledElements, ReconciledElements,
reconcileElements as _reconcileElements, reconcileElements as _reconcileElements,
} from "./reconciliation"; } from "./reconciliation";
import { decryptData } from "../../data/encryption"; import { decryptData } from "../../src/data/encryption";
import { resetBrowserStateVersions } from "../data/tabSync"; import { resetBrowserStateVersions } from "../data/tabSync";
import { LocalData } from "../data/LocalData"; import { LocalData } from "../data/LocalData";
import { atom, useAtom } from "jotai"; import { atom, useAtom } from "jotai";

View File

@@ -6,19 +6,19 @@ import {
import { TCollabClass } from "./Collab"; import { TCollabClass } from "./Collab";
import { ExcalidrawElement } from "../../element/types"; import { ExcalidrawElement } from "../../src/element/types";
import { import {
WS_EVENTS, WS_EVENTS,
FILE_UPLOAD_TIMEOUT, FILE_UPLOAD_TIMEOUT,
WS_SCENE_EVENT_TYPES, WS_SCENE_EVENT_TYPES,
} from "../app_constants"; } from "../app_constants";
import { UserIdleState } from "../../types"; import { UserIdleState } from "../../src/types";
import { trackEvent } from "../../analytics"; import { trackEvent } from "../../src/analytics";
import throttle from "lodash.throttle"; import throttle from "lodash.throttle";
import { newElementWith } from "../../element/mutateElement"; import { newElementWith } from "../../src/element/mutateElement";
import { BroadcastedExcalidrawElement } from "./reconciliation"; import { BroadcastedExcalidrawElement } from "./reconciliation";
import { encryptData } from "../../data/encryption"; import { encryptData } from "../../src/data/encryption";
import { PRECEDING_ELEMENT_KEY } from "../../constants"; import { PRECEDING_ELEMENT_KEY } from "../../src/constants";
class Portal { class Portal {
collab: TCollabClass; collab: TCollabClass;

View File

@@ -1,4 +1,4 @@
@import "../../css/variables.module"; @import "../../src/css/variables.module";
.excalidraw { .excalidraw {
.RoomDialog { .RoomDialog {

View File

@@ -1,13 +1,13 @@
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import * as Popover from "@radix-ui/react-popover"; import * as Popover from "@radix-ui/react-popover";
import { copyTextToSystemClipboard } from "../../clipboard"; import { copyTextToSystemClipboard } from "../../src/clipboard";
import { trackEvent } from "../../analytics"; import { trackEvent } from "../../src/analytics";
import { getFrame } from "../../utils"; import { getFrame } from "../../src/utils";
import { useI18n } from "../../i18n"; import { useI18n } from "../../src/i18n";
import { KEYS } from "../../keys"; import { KEYS } from "../../src/keys";
import { Dialog } from "../../components/Dialog"; import { Dialog } from "../../src/components/Dialog";
import { import {
copyIcon, copyIcon,
playerPlayIcon, playerPlayIcon,
@@ -16,11 +16,11 @@ import {
shareIOS, shareIOS,
shareWindows, shareWindows,
tablerCheckIcon, tablerCheckIcon,
} from "../../components/icons"; } from "../../src/components/icons";
import { TextField } from "../../components/TextField"; import { TextField } from "../../src/components/TextField";
import { FilledButton } from "../../components/FilledButton"; import { FilledButton } from "../../src/components/FilledButton";
import { ReactComponent as CollabImage } from "../../assets/lock.svg"; import { ReactComponent as CollabImage } from "../../src/assets/lock.svg";
import "./RoomDialog.scss"; import "./RoomDialog.scss";
const getShareIcon = () => { const getShareIcon = () => {

View File

@@ -1,7 +1,7 @@
import { PRECEDING_ELEMENT_KEY } from "../../constants"; import { PRECEDING_ELEMENT_KEY } from "../../src/constants";
import { ExcalidrawElement } from "../../element/types"; import { ExcalidrawElement } from "../../src/element/types";
import { AppState } from "../../types"; import { AppState } from "../../src/types";
import { arrayToMapWithIndex } from "../../utils"; import { arrayToMapWithIndex } from "../../src/utils";
export type ReconciledElements = readonly ExcalidrawElement[] & { export type ReconciledElements = readonly ExcalidrawElement[] & {
_brand: "reconciledElements"; _brand: "reconciledElements";

View File

@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { Footer } from "../../packages/excalidraw/index"; import { Footer } from "../../src/packages/excalidraw/index";
import { EncryptedIcon } from "./EncryptedIcon"; import { EncryptedIcon } from "./EncryptedIcon";
import { ExcalidrawPlusAppLink } from "./ExcalidrawPlusAppLink"; import { ExcalidrawPlusAppLink } from "./ExcalidrawPlusAppLink";
import { isExcalidrawPlusSignedUser } from "../app_constants"; import { isExcalidrawPlusSignedUser } from "../app_constants";

View File

@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { PlusPromoIcon } from "../../components/icons"; import { PlusPromoIcon } from "../../src/components/icons";
import { MainMenu } from "../../packages/excalidraw/index"; import { MainMenu } from "../../src/packages/excalidraw/index";
import { LanguageList } from "./LanguageList"; import { LanguageList } from "./LanguageList";
export const AppMainMenu: React.FC<{ export const AppMainMenu: React.FC<{

View File

@@ -1,9 +1,9 @@
import React from "react"; import React from "react";
import { PlusPromoIcon } from "../../components/icons"; import { PlusPromoIcon } from "../../src/components/icons";
import { useI18n } from "../../i18n"; import { useI18n } from "../../src/i18n";
import { WelcomeScreen } from "../../packages/excalidraw/index"; import { WelcomeScreen } from "../../src/packages/excalidraw/index";
import { isExcalidrawPlusSignedUser } from "../app_constants"; import { isExcalidrawPlusSignedUser } from "../app_constants";
import { POINTER_EVENTS } from "../../constants"; import { POINTER_EVENTS } from "../../src/constants";
export const AppWelcomeScreen: React.FC<{ export const AppWelcomeScreen: React.FC<{
setCollabDialogShown: (toggle: boolean) => any; setCollabDialogShown: (toggle: boolean) => any;

View File

@@ -1,6 +1,6 @@
import { shield } from "../../components/icons"; import { shield } from "../../src/components/icons";
import { Tooltip } from "../../components/Tooltip"; import { Tooltip } from "../../src/components/Tooltip";
import { useI18n } from "../../i18n"; import { useI18n } from "../../src/i18n";
export const EncryptedIcon = () => { export const EncryptedIcon = () => {
const { t } = useI18n(); const { t } = useI18n();

View File

@@ -1,20 +1,20 @@
import React from "react"; import React from "react";
import { Card } from "../../components/Card"; import { Card } from "../../src/components/Card";
import { ToolButton } from "../../components/ToolButton"; import { ToolButton } from "../../src/components/ToolButton";
import { serializeAsJSON } from "../../data/json"; import { serializeAsJSON } from "../../src/data/json";
import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase"; import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase";
import { FileId, NonDeletedExcalidrawElement } from "../../element/types"; import { FileId, NonDeletedExcalidrawElement } from "../../src/element/types";
import { AppState, BinaryFileData, BinaryFiles } from "../../types"; import { AppState, BinaryFileData, BinaryFiles } from "../../src/types";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { useI18n } from "../../i18n"; import { useI18n } from "../../src/i18n";
import { encryptData, generateEncryptionKey } from "../../data/encryption"; import { encryptData, generateEncryptionKey } from "../../src/data/encryption";
import { isInitializedImageElement } from "../../element/typeChecks"; import { isInitializedImageElement } from "../../src/element/typeChecks";
import { FILE_UPLOAD_MAX_BYTES } from "../app_constants"; import { FILE_UPLOAD_MAX_BYTES } from "../app_constants";
import { encodeFilesForUpload } from "../data/FileManager"; import { encodeFilesForUpload } from "../data/FileManager";
import { MIME_TYPES } from "../../constants"; import { MIME_TYPES } from "../../src/constants";
import { trackEvent } from "../../analytics"; import { trackEvent } from "../../src/analytics";
import { getFrame } from "../../utils"; import { getFrame } from "../../src/utils";
import { ExcalidrawLogo } from "../../components/ExcalidrawLogo"; import { ExcalidrawLogo } from "../../src/components/ExcalidrawLogo";
export const exportToExcalidrawPlus = async ( export const exportToExcalidrawPlus = async (
elements: readonly NonDeletedExcalidrawElement[], elements: readonly NonDeletedExcalidrawElement[],

View File

@@ -1,7 +1,7 @@
import oc from "open-color"; import oc from "open-color";
import React from "react"; import React from "react";
import { THEME } from "../../constants"; import { THEME } from "../../src/constants";
import { Theme } from "../../element/types"; import { Theme } from "../../src/element/types";
// https://github.com/tholman/github-corners // https://github.com/tholman/github-corners
export const GitHubCorner = React.memo( export const GitHubCorner = React.memo(

View File

@@ -1,8 +1,8 @@
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import React from "react"; import React from "react";
import { appLangCodeAtom } from ".."; import { appLangCodeAtom } from "..";
import { useI18n } from "../../i18n"; import { useI18n } from "../../src/i18n";
import { languages } from "../../i18n"; import { languages } from "../../src/i18n";
export const LanguageList = ({ style }: { style?: React.CSSProperties }) => { export const LanguageList = ({ style }: { style?: React.CSSProperties }) => {
const { t, langCode } = useI18n(); const { t, langCode } = useI18n();

View File

@@ -1,19 +1,19 @@
import { compressData } from "../../data/encode"; import { compressData } from "../../src/data/encode";
import { newElementWith } from "../../element/mutateElement"; import { newElementWith } from "../../src/element/mutateElement";
import { isInitializedImageElement } from "../../element/typeChecks"; import { isInitializedImageElement } from "../../src/element/typeChecks";
import { import {
ExcalidrawElement, ExcalidrawElement,
ExcalidrawImageElement, ExcalidrawImageElement,
FileId, FileId,
InitializedExcalidrawImageElement, InitializedExcalidrawImageElement,
} from "../../element/types"; } from "../../src/element/types";
import { t } from "../../i18n"; import { t } from "../../src/i18n";
import { import {
BinaryFileData, BinaryFileData,
BinaryFileMetadata, BinaryFileMetadata,
ExcalidrawImperativeAPI, ExcalidrawImperativeAPI,
BinaryFiles, BinaryFiles,
} from "../../types"; } from "../../src/types";
export class FileManager { export class FileManager {
/** files being fetched */ /** files being fetched */

View File

@@ -11,11 +11,11 @@
*/ */
import { createStore, entries, del, getMany, set, setMany } from "idb-keyval"; import { createStore, entries, del, getMany, set, setMany } from "idb-keyval";
import { clearAppStateForLocalStorage } from "../../appState"; import { clearAppStateForLocalStorage } from "../../src/appState";
import { clearElementsForLocalStorage } from "../../element"; import { clearElementsForLocalStorage } from "../../src/element";
import { ExcalidrawElement, FileId } from "../../element/types"; import { ExcalidrawElement, FileId } from "../../src/element/types";
import { AppState, BinaryFileData, BinaryFiles } from "../../types"; import { AppState, BinaryFileData, BinaryFiles } from "../../src/types";
import { debounce } from "../../utils"; import { debounce } from "../../src/utils";
import { SAVE_TO_LOCAL_STORAGE_TIMEOUT, STORAGE_KEYS } from "../app_constants"; import { SAVE_TO_LOCAL_STORAGE_TIMEOUT, STORAGE_KEYS } from "../app_constants";
import { FileManager } from "./FileManager"; import { FileManager } from "./FileManager";
import { Locker } from "./Locker"; import { Locker } from "./Locker";

View File

@@ -1,20 +1,20 @@
import { ExcalidrawElement, FileId } from "../../element/types"; import { ExcalidrawElement, FileId } from "../../src/element/types";
import { getSceneVersion } from "../../element"; import { getSceneVersion } from "../../src/element";
import Portal from "../collab/Portal"; import Portal from "../collab/Portal";
import { restoreElements } from "../../data/restore"; import { restoreElements } from "../../src/data/restore";
import { import {
AppState, AppState,
BinaryFileData, BinaryFileData,
BinaryFileMetadata, BinaryFileMetadata,
DataURL, DataURL,
} from "../../types"; } from "../../src/types";
import { FILE_CACHE_MAX_AGE_SEC } from "../app_constants"; import { FILE_CACHE_MAX_AGE_SEC } from "../app_constants";
import { decompressData } from "../../data/encode"; import { decompressData } from "../../src/data/encode";
import { encryptData, decryptData } from "../../data/encryption"; import { encryptData, decryptData } from "../../src/data/encryption";
import { MIME_TYPES } from "../../constants"; import { MIME_TYPES } from "../../src/constants";
import { reconcileElements } from "../collab/reconciliation"; import { reconcileElements } from "../collab/reconciliation";
import { getSyncableElements, SyncableExcalidrawElement } from "."; import { getSyncableElements, SyncableExcalidrawElement } from ".";
import { ResolutionType } from "../../utility-types"; import { ResolutionType } from "../../src/utility-types";
// private // private
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------

View File

@@ -1,23 +1,23 @@
import { compressData, decompressData } from "../../data/encode"; import { compressData, decompressData } from "../../src/data/encode";
import { import {
decryptData, decryptData,
generateEncryptionKey, generateEncryptionKey,
IV_LENGTH_BYTES, IV_LENGTH_BYTES,
} from "../../data/encryption"; } from "../../src/data/encryption";
import { serializeAsJSON } from "../../data/json"; import { serializeAsJSON } from "../../src/data/json";
import { restore } from "../../data/restore"; import { restore } from "../../src/data/restore";
import { ImportedDataState } from "../../data/types"; import { ImportedDataState } from "../../src/data/types";
import { isInvisiblySmallElement } from "../../element/sizeHelpers"; import { isInvisiblySmallElement } from "../../src/element/sizeHelpers";
import { isInitializedImageElement } from "../../element/typeChecks"; import { isInitializedImageElement } from "../../src/element/typeChecks";
import { ExcalidrawElement, FileId } from "../../element/types"; import { ExcalidrawElement, FileId } from "../../src/element/types";
import { t } from "../../i18n"; import { t } from "../../src/i18n";
import { import {
AppState, AppState,
BinaryFileData, BinaryFileData,
BinaryFiles, BinaryFiles,
UserIdleState, UserIdleState,
} from "../../types"; } from "../../src/types";
import { bytesToHexString } from "../../utils"; import { bytesToHexString } from "../../src/utils";
import { import {
DELETED_ELEMENT_TIMEOUT, DELETED_ELEMENT_TIMEOUT,
FILE_UPLOAD_MAX_BYTES, FILE_UPLOAD_MAX_BYTES,

View File

@@ -1,12 +1,12 @@
import { ExcalidrawElement } from "../../element/types"; import { ExcalidrawElement } from "../../src/element/types";
import { AppState } from "../../types"; import { AppState } from "../../src/types";
import { import {
clearAppStateForLocalStorage, clearAppStateForLocalStorage,
getDefaultAppState, getDefaultAppState,
} from "../../appState"; } from "../../src/appState";
import { clearElementsForLocalStorage } from "../../element"; import { clearElementsForLocalStorage } from "../../src/element";
import { STORAGE_KEYS } from "../app_constants"; import { STORAGE_KEYS } from "../app_constants";
import { ImportedDataState } from "../../data/types"; import { ImportedDataState } from "../../src/data/types";
export const saveUsernameToLocalStorage = (username: string) => { export const saveUsernameToLocalStorage = (username: string) => {
try { try {

View File

@@ -1,31 +1,31 @@
import polyfill from "../polyfill"; import polyfill from "../src/polyfill";
import LanguageDetector from "i18next-browser-languagedetector"; import LanguageDetector from "i18next-browser-languagedetector";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { trackEvent } from "../analytics"; import { trackEvent } from "../src/analytics";
import { getDefaultAppState } from "../appState"; import { getDefaultAppState } from "../src/appState";
import { ErrorDialog } from "../components/ErrorDialog"; import { ErrorDialog } from "../src/components/ErrorDialog";
import { TopErrorBoundary } from "../components/TopErrorBoundary"; import { TopErrorBoundary } from "../src/components/TopErrorBoundary";
import { import {
APP_NAME, APP_NAME,
EVENT, EVENT,
THEME, THEME,
TITLE_TIMEOUT, TITLE_TIMEOUT,
VERSION_TIMEOUT, VERSION_TIMEOUT,
} from "../constants"; } from "../src/constants";
import { loadFromBlob } from "../data/blob"; import { loadFromBlob } from "../src/data/blob";
import { import {
ExcalidrawElement, ExcalidrawElement,
FileId, FileId,
NonDeletedExcalidrawElement, NonDeletedExcalidrawElement,
Theme, Theme,
} from "../element/types"; } from "../src/element/types";
import { useCallbackRefState } from "../hooks/useCallbackRefState"; import { useCallbackRefState } from "../src/hooks/useCallbackRefState";
import { t } from "../i18n"; import { t } from "../src/i18n";
import { import {
Excalidraw, Excalidraw,
defaultLang, defaultLang,
LiveCollaborationTrigger, LiveCollaborationTrigger,
} from "../packages/excalidraw/index"; } from "../src/packages/excalidraw/index";
import { import {
AppState, AppState,
LibraryItems, LibraryItems,
@@ -33,7 +33,7 @@ import {
BinaryFiles, BinaryFiles,
ExcalidrawInitialDataState, ExcalidrawInitialDataState,
UIAppState, UIAppState,
} from "../types"; } from "../src/types";
import { import {
debounce, debounce,
getVersion, getVersion,
@@ -43,7 +43,7 @@ import {
ResolvablePromise, ResolvablePromise,
resolvablePromise, resolvablePromise,
isRunningInIframe, isRunningInIframe,
} from "../utils"; } from "../src/utils";
import { import {
FIREBASE_STORAGE_PREFIXES, FIREBASE_STORAGE_PREFIXES,
STORAGE_KEYS, STORAGE_KEYS,
@@ -68,33 +68,40 @@ import {
importUsernameFromLocalStorage, importUsernameFromLocalStorage,
} from "./data/localStorage"; } from "./data/localStorage";
import CustomStats from "./CustomStats"; import CustomStats from "./CustomStats";
import { restore, restoreAppState, RestoredDataState } from "../data/restore"; import {
restore,
restoreAppState,
RestoredDataState,
} from "../src/data/restore";
import { import {
ExportToExcalidrawPlus, ExportToExcalidrawPlus,
exportToExcalidrawPlus, exportToExcalidrawPlus,
} from "./components/ExportToExcalidrawPlus"; } from "./components/ExportToExcalidrawPlus";
import { updateStaleImageStatuses } from "./data/FileManager"; import { updateStaleImageStatuses } from "./data/FileManager";
import { newElementWith } from "../element/mutateElement"; import { newElementWith } from "../src/element/mutateElement";
import { isInitializedImageElement } from "../element/typeChecks"; import { isInitializedImageElement } from "../src/element/typeChecks";
import { loadFilesFromFirebase } from "./data/firebase"; import { loadFilesFromFirebase } from "./data/firebase";
import { LocalData } from "./data/LocalData"; import { LocalData } from "./data/LocalData";
import { isBrowserStorageStateNewer } from "./data/tabSync"; import { isBrowserStorageStateNewer } from "./data/tabSync";
import clsx from "clsx"; import clsx from "clsx";
import { reconcileElements } from "./collab/reconciliation"; import { reconcileElements } from "./collab/reconciliation";
import { parseLibraryTokensFromUrl, useHandleLibrary } from "../data/library"; import {
parseLibraryTokensFromUrl,
useHandleLibrary,
} from "../src/data/library";
import { AppMainMenu } from "./components/AppMainMenu"; import { AppMainMenu } from "./components/AppMainMenu";
import { AppWelcomeScreen } from "./components/AppWelcomeScreen"; import { AppWelcomeScreen } from "./components/AppWelcomeScreen";
import { AppFooter } from "./components/AppFooter"; import { AppFooter } from "./components/AppFooter";
import { atom, Provider, useAtom, useAtomValue } from "jotai"; import { atom, Provider, useAtom, useAtomValue } from "jotai";
import { useAtomWithInitialValue } from "../jotai"; import { useAtomWithInitialValue } from "../src/jotai";
import { appJotaiStore } from "./app-jotai"; import { appJotaiStore } from "./app-jotai";
import "./index.scss"; import "./index.scss";
import { ResolutionType } from "../utility-types"; import { ResolutionType } from "../src/utility-types";
import { ShareableLinkDialog } from "../components/ShareableLinkDialog"; import { ShareableLinkDialog } from "../src/components/ShareableLinkDialog";
import { openConfirmModal } from "../components/OverwriteConfirm/OverwriteConfirmState"; import { openConfirmModal } from "../src/components/OverwriteConfirm/OverwriteConfirmState";
import { OverwriteConfirmDialog } from "../components/OverwriteConfirm/OverwriteConfirm"; import { OverwriteConfirmDialog } from "../src/components/OverwriteConfirm/OverwriteConfirm";
import Trans from "../components/Trans"; import Trans from "../src/components/Trans";
polyfill(); polyfill();

View File

@@ -0,0 +1,29 @@
import { defaultLang } from "../../src/i18n";
import { UI } from "../../src/tests/helpers/ui";
import { screen, fireEvent, waitFor, render } from "../../src/tests/test-utils";
import ExcalidrawApp from "../../excalidraw-app";
describe("Test LanguageList", () => {
it("rerenders UI on language change", async () => {
await render(<ExcalidrawApp />);
// select rectangle tool to show properties menu
UI.clickTool("rectangle");
// english lang should display `thin` label
expect(screen.queryByTitle(/thin/i)).not.toBeNull();
fireEvent.click(document.querySelector(".dropdown-menu-button")!);
fireEvent.change(document.querySelector(".dropdown-select__language")!, {
target: { value: "de-DE" },
});
// switching to german, `thin` label should no longer exist
await waitFor(() => expect(screen.queryByTitle(/thin/i)).toBeNull());
// reset language
fireEvent.change(document.querySelector(".dropdown-select__language")!, {
target: { value: defaultLang.code },
});
// switching back to English
await waitFor(() => expect(screen.queryByTitle(/thin/i)).not.toBeNull());
});
});

View File

@@ -1,11 +1,11 @@
import ExcalidrawApp from "../excalidraw-app"; import ExcalidrawApp from "../../excalidraw-app";
import { import {
mockBoundingClientRect, mockBoundingClientRect,
render, render,
restoreOriginalGetBoundingClientRect, restoreOriginalGetBoundingClientRect,
} from "./test-utils"; } from "../../src/tests/test-utils";
import { UI } from "./helpers/ui"; import { UI } from "../../src/tests/helpers/ui";
describe("Test MobileMenu", () => { describe("Test MobileMenu", () => {
const { h } = window; const { h } = window;

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +1,8 @@
import { vi } from "vitest"; import { vi } from "vitest";
import { render, updateSceneData, waitFor } from "./test-utils"; import { render, updateSceneData, waitFor } from "../../src/tests/test-utils";
import ExcalidrawApp from "../excalidraw-app"; import ExcalidrawApp from "../../excalidraw-app";
import { API } from "./helpers/api"; import { API } from "../../src/tests/helpers/api";
import { createUndoAction } from "../actions/actionHistory"; import { createUndoAction } from "../../src/actions/actionHistory";
const { h } = window; const { h } = window;
Object.defineProperty(window, "crypto", { Object.defineProperty(window, "crypto", {
@@ -16,7 +16,7 @@ Object.defineProperty(window, "crypto", {
}, },
}); });
vi.mock("../excalidraw-app/data/index.ts", async (importActual) => { vi.mock("../../excalidraw-app/data/index.ts", async (importActual) => {
const module = (await importActual()) as any; const module = (await importActual()) as any;
return { return {
__esmodule: true, __esmodule: true,
@@ -27,7 +27,7 @@ vi.mock("../excalidraw-app/data/index.ts", async (importActual) => {
}; };
}); });
vi.mock("../excalidraw-app/data/firebase.ts", () => { vi.mock("../../excalidraw-app/data/firebase.ts", () => {
const loadFromFirebase = async () => null; const loadFromFirebase = async () => null;
const saveToFirebase = () => {}; const saveToFirebase = () => {};
const isSavedToFirebase = () => true; const isSavedToFirebase = () => true;

View File

@@ -1,13 +1,13 @@
import { expect } from "chai"; import { expect } from "chai";
import { PRECEDING_ELEMENT_KEY } from "../constants"; import { PRECEDING_ELEMENT_KEY } from "../../src/constants";
import { ExcalidrawElement } from "../element/types"; import { ExcalidrawElement } from "../../src/element/types";
import { import {
BroadcastedExcalidrawElement, BroadcastedExcalidrawElement,
ReconciledElements, ReconciledElements,
reconcileElements, reconcileElements,
} from "../excalidraw-app/collab/reconciliation"; } from "../../excalidraw-app/collab/reconciliation";
import { randomInteger } from "../random"; import { randomInteger } from "../../src/random";
import { AppState } from "../types"; import { AppState } from "../../src/types";
type Id = string; type Id = string;
type ElementLike = { type ElementLike = {

View File

@@ -1086,7 +1086,7 @@ class App extends React.Component<AppProps, AppState> {
cursor: CURSOR_TYPE.MOVE, cursor: CURSOR_TYPE.MOVE,
pointerEvents: this.state.viewModeEnabled pointerEvents: this.state.viewModeEnabled
? POINTER_EVENTS.disabled ? POINTER_EVENTS.disabled
: POINTER_EVENTS.inheritFromUI, : POINTER_EVENTS.enabled,
}} }}
onPointerDown={(event) => this.handleCanvasPointerDown(event)} onPointerDown={(event) => this.handleCanvasPointerDown(event)}
onWheel={(event) => this.handleWheel(event)} onWheel={(event) => this.handleWheel(event)}
@@ -1330,7 +1330,8 @@ class App extends React.Component<AppProps, AppState> {
private openEyeDropper = ({ type }: { type: "stroke" | "background" }) => { private openEyeDropper = ({ type }: { type: "stroke" | "background" }) => {
jotaiStore.set(activeEyeDropperAtom, { jotaiStore.set(activeEyeDropperAtom, {
swapPreviewOnAlt: true, swapPreviewOnAlt: true,
previewType: type === "stroke" ? "strokeColor" : "backgroundColor", colorPickerType:
type === "stroke" ? "elementStroke" : "elementBackground",
onSelect: (color, event) => { onSelect: (color, event) => {
const shouldUpdateStrokeColor = const shouldUpdateStrokeColor =
(type === "background" && event.altKey) || (type === "background" && event.altKey) ||
@@ -1341,12 +1342,14 @@ class App extends React.Component<AppProps, AppState> {
this.state.activeTool.type !== "selection" this.state.activeTool.type !== "selection"
) { ) {
if (shouldUpdateStrokeColor) { if (shouldUpdateStrokeColor) {
this.setState({ this.syncActionResult({
currentItemStrokeColor: color, appState: { ...this.state, currentItemStrokeColor: color },
commitToHistory: true,
}); });
} else { } else {
this.setState({ this.syncActionResult({
currentItemBackgroundColor: color, appState: { ...this.state, currentItemBackgroundColor: color },
commitToHistory: true,
}); });
} }
} else { } else {
@@ -6397,9 +6400,11 @@ class App extends React.Component<AppProps, AppState> {
isInvisiblySmallElement(draggingElement) isInvisiblySmallElement(draggingElement)
) { ) {
// remove invisible element which was added in onPointerDown // remove invisible element which was added in onPointerDown
if (draggingElement.type !== "selection") {
this.scene.replaceAllElements( this.scene.replaceAllElements(
this.scene.getElementsIncludingDeleted().slice(0, -1), this.scene.getElementsIncludingDeleted().slice(0, -1),
); );
}
this.setState({ this.setState({
draggingElement: null, draggingElement: null,
}); });

View File

@@ -1,7 +1,10 @@
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { getColor } from "./ColorPicker"; import { getColor } from "./ColorPicker";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { activeColorPickerSectionAtom } from "./colorPickerUtils"; import {
ColorPickerType,
activeColorPickerSectionAtom,
} from "./colorPickerUtils";
import { eyeDropperIcon } from "../icons"; import { eyeDropperIcon } from "../icons";
import { jotaiScope } from "../../jotai"; import { jotaiScope } from "../../jotai";
import { KEYS } from "../../keys"; import { KEYS } from "../../keys";
@@ -15,14 +18,14 @@ interface ColorInputProps {
color: string; color: string;
onChange: (color: string) => void; onChange: (color: string) => void;
label: string; label: string;
eyeDropperType: "strokeColor" | "backgroundColor"; colorPickerType: ColorPickerType;
} }
export const ColorInput = ({ export const ColorInput = ({
color, color,
onChange, onChange,
label, label,
eyeDropperType, colorPickerType,
}: ColorInputProps) => { }: ColorInputProps) => {
const device = useDevice(); const device = useDevice();
const [innerValue, setInnerValue] = useState(color); const [innerValue, setInnerValue] = useState(color);
@@ -116,7 +119,7 @@ export const ColorInput = ({
: { : {
keepOpenOnAlt: false, keepOpenOnAlt: false,
onSelect: (color) => onChange(color), onSelect: (color) => onChange(color),
previewType: eyeDropperType, colorPickerType,
}, },
) )
} }

View File

@@ -82,14 +82,7 @@ const ColorPickerPopupContent = ({
const { container } = useExcalidrawContainer(); const { container } = useExcalidrawContainer();
const { isMobile, isLandscape } = useDevice(); const { isMobile, isLandscape } = useDevice();
const eyeDropperType = const colorInputJSX = (
type === "canvasBackground"
? undefined
: type === "elementBackground"
? "backgroundColor"
: "strokeColor";
const colorInputJSX = eyeDropperType && (
<div> <div>
<PickerHeading>{t("colorPicker.hexCode")}</PickerHeading> <PickerHeading>{t("colorPicker.hexCode")}</PickerHeading>
<ColorInput <ColorInput
@@ -98,7 +91,7 @@ const ColorPickerPopupContent = ({
onChange={(color) => { onChange={(color) => {
onChange(color); onChange(color);
}} }}
eyeDropperType={eyeDropperType} colorPickerType={type}
/> />
</div> </div>
); );
@@ -160,7 +153,7 @@ const ColorPickerPopupContent = ({
"0px 7px 14px rgba(0, 0, 0, 0.05), 0px 0px 3.12708px rgba(0, 0, 0, 0.0798), 0px 0px 0.931014px rgba(0, 0, 0, 0.1702)", "0px 7px 14px rgba(0, 0, 0, 0.05), 0px 0px 3.12708px rgba(0, 0, 0, 0.0798), 0px 0px 0.931014px rgba(0, 0, 0, 0.1702)",
}} }}
> >
{palette && eyeDropperType ? ( {palette ? (
<Picker <Picker
palette={palette} palette={palette}
color={color} color={color}
@@ -173,7 +166,7 @@ const ColorPickerPopupContent = ({
state = state || { state = state || {
keepOpenOnAlt: true, keepOpenOnAlt: true,
onSelect: onChange, onSelect: onChange,
previewType: eyeDropperType, colorPickerType: type,
}; };
state.keepOpenOnAlt = true; state.keepOpenOnAlt = true;
return state; return state;
@@ -184,7 +177,7 @@ const ColorPickerPopupContent = ({
: { : {
keepOpenOnAlt: false, keepOpenOnAlt: false,
onSelect: onChange, onSelect: onChange,
previewType: eyeDropperType, colorPickerType: type,
}; };
}); });
}} }}

View File

@@ -1,35 +1,47 @@
import { atom } from "jotai"; import { atom } from "jotai";
import { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import { rgbToHex } from "../colors"; import { rgbToHex } from "../colors";
import { EVENT } from "../constants"; import { EVENT } from "../constants";
import { useUIAppState } from "../context/ui-appState"; import { useUIAppState } from "../context/ui-appState";
import { mutateElement } from "../element/mutateElement";
import { useCreatePortalContainer } from "../hooks/useCreatePortalContainer"; import { useCreatePortalContainer } from "../hooks/useCreatePortalContainer";
import { useOutsideClick } from "../hooks/useOutsideClick"; import { useOutsideClick } from "../hooks/useOutsideClick";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { getSelectedElements } from "../scene"; import { getSelectedElements } from "../scene";
import Scene from "../scene/Scene";
import { ShapeCache } from "../scene/ShapeCache";
import { useApp, useExcalidrawContainer, useExcalidrawElements } from "./App"; import { useApp, useExcalidrawContainer, useExcalidrawElements } from "./App";
import { useStable } from "../hooks/useStable";
import "./EyeDropper.scss"; import "./EyeDropper.scss";
import { ColorPickerType } from "./ColorPicker/colorPickerUtils";
import { ExcalidrawElement } from "../element/types";
type EyeDropperProperties = { export type EyeDropperProperties = {
keepOpenOnAlt: boolean; keepOpenOnAlt: boolean;
swapPreviewOnAlt?: boolean; swapPreviewOnAlt?: boolean;
/** called when user picks color (on pointerup) */
onSelect: (color: string, event: PointerEvent) => void; onSelect: (color: string, event: PointerEvent) => void;
previewType: "strokeColor" | "backgroundColor"; /**
* property of selected elements to update live when alt-dragging.
* Supply `null` if not applicable (e.g. updating the canvas bg instead of
* elements)
**/
colorPickerType: ColorPickerType;
}; };
export const activeEyeDropperAtom = atom<null | EyeDropperProperties>(null); export const activeEyeDropperAtom = atom<null | EyeDropperProperties>(null);
export const EyeDropper: React.FC<{ export const EyeDropper: React.FC<{
onCancel: () => void; onCancel: () => void;
onSelect: Required<EyeDropperProperties>["onSelect"]; onSelect: EyeDropperProperties["onSelect"];
swapPreviewOnAlt?: EyeDropperProperties["swapPreviewOnAlt"]; /** called when color changes, on pointerdown for preview */
previewType: EyeDropperProperties["previewType"]; onChange: (
}> = ({ onCancel, onSelect, swapPreviewOnAlt, previewType }) => { type: ColorPickerType,
color: string,
selectedElements: ExcalidrawElement[],
event: { altKey: boolean },
) => void;
colorPickerType: EyeDropperProperties["colorPickerType"];
}> = ({ onCancel, onChange, onSelect, colorPickerType }) => {
const eyeDropperContainer = useCreatePortalContainer({ const eyeDropperContainer = useCreatePortalContainer({
className: "excalidraw-eye-dropper-backdrop", className: "excalidraw-eye-dropper-backdrop",
parentSelector: ".excalidraw-eye-dropper-container", parentSelector: ".excalidraw-eye-dropper-container",
@@ -40,9 +52,13 @@ export const EyeDropper: React.FC<{
const selectedElements = getSelectedElements(elements, appState); const selectedElements = getSelectedElements(elements, appState);
const metaStuffRef = useRef({ selectedElements, app }); const stableProps = useStable({
metaStuffRef.current.selectedElements = selectedElements; app,
metaStuffRef.current.app = app; onCancel,
onChange,
onSelect,
selectedElements,
});
const { container: excalidrawContainer } = useExcalidrawContainer(); const { container: excalidrawContainer } = useExcalidrawContainer();
@@ -90,28 +106,28 @@ export const EyeDropper: React.FC<{
const currentColor = getCurrentColor({ clientX, clientY }); const currentColor = getCurrentColor({ clientX, clientY });
if (isHoldingPointerDown) { if (isHoldingPointerDown) {
for (const element of metaStuffRef.current.selectedElements) { stableProps.onChange(
mutateElement( colorPickerType,
element, currentColor,
{ stableProps.selectedElements,
[altKey && swapPreviewOnAlt { altKey },
? previewType === "strokeColor"
? "backgroundColor"
: "strokeColor"
: previewType]: currentColor,
},
false,
); );
ShapeCache.delete(element);
}
Scene.getScene(
metaStuffRef.current.selectedElements[0],
)?.informMutation();
} }
colorPreviewDiv.style.background = currentColor; colorPreviewDiv.style.background = currentColor;
}; };
const onCancel = () => {
stableProps.onCancel();
};
const onSelect: Required<EyeDropperProperties>["onSelect"] = (
color,
event,
) => {
stableProps.onSelect(color, event);
};
const pointerDownListener = (event: PointerEvent) => { const pointerDownListener = (event: PointerEvent) => {
isHoldingPointerDown = true; isHoldingPointerDown = true;
// NOTE we can't event.preventDefault() as that would stop // NOTE we can't event.preventDefault() as that would stop
@@ -148,8 +164,8 @@ export const EyeDropper: React.FC<{
// init color preview else it would show only after the first mouse move // init color preview else it would show only after the first mouse move
mouseMoveListener({ mouseMoveListener({
clientX: metaStuffRef.current.app.lastViewportPosition.x, clientX: stableProps.app.lastViewportPosition.x,
clientY: metaStuffRef.current.app.lastViewportPosition.y, clientY: stableProps.app.lastViewportPosition.y,
altKey: false, altKey: false,
}); });
@@ -179,12 +195,10 @@ export const EyeDropper: React.FC<{
window.removeEventListener(EVENT.BLUR, onCancel); window.removeEventListener(EVENT.BLUR, onCancel);
}; };
}, [ }, [
stableProps,
app.canvas, app.canvas,
eyeDropperContainer, eyeDropperContainer,
onCancel, colorPickerType,
onSelect,
swapPreviewOnAlt,
previewType,
excalidrawContainer, excalidrawContainer,
appState.offsetLeft, appState.offsetLeft,
appState.offsetTop, appState.offsetTop,

View File

@@ -52,6 +52,9 @@ import { EyeDropper, activeEyeDropperAtom } from "./EyeDropper";
import "./LayerUI.scss"; import "./LayerUI.scss";
import "./Toolbar.scss"; import "./Toolbar.scss";
import { mutateElement } from "../element/mutateElement";
import { ShapeCache } from "../scene/ShapeCache";
import Scene from "../scene/Scene";
interface LayerUIProps { interface LayerUIProps {
actionManager: ActionManager; actionManager: ActionManager;
@@ -368,11 +371,44 @@ const LayerUI = ({
)} )}
{eyeDropperState && !device.isMobile && ( {eyeDropperState && !device.isMobile && (
<EyeDropper <EyeDropper
swapPreviewOnAlt={eyeDropperState.swapPreviewOnAlt} colorPickerType={eyeDropperState.colorPickerType}
previewType={eyeDropperState.previewType}
onCancel={() => { onCancel={() => {
setEyeDropperState(null); setEyeDropperState(null);
}} }}
onChange={(colorPickerType, color, selectedElements, { altKey }) => {
if (
colorPickerType !== "elementBackground" &&
colorPickerType !== "elementStroke"
) {
return;
}
if (selectedElements.length) {
for (const element of selectedElements) {
mutateElement(
element,
{
[altKey && eyeDropperState.swapPreviewOnAlt
? colorPickerType === "elementBackground"
? "strokeColor"
: "backgroundColor"
: colorPickerType === "elementBackground"
? "backgroundColor"
: "strokeColor"]: color,
},
false,
);
ShapeCache.delete(element);
}
Scene.getScene(selectedElements[0])?.informMutation();
} else if (colorPickerType === "elementBackground") {
setAppState({
currentItemBackgroundColor: color,
});
} else {
setAppState({ currentItemStrokeColor: color });
}
}}
onSelect={(color, event) => { onSelect={(color, event) => {
setEyeDropperState((state) => { setEyeDropperState((state) => {
return state?.keepOpenOnAlt && event.altKey ? state : null; return state?.keepOpenOnAlt && event.altKey ? state : null;

View File

@@ -203,7 +203,6 @@ describe("duplicating multiple elements", () => {
); );
clonedArrows.forEach((arrow) => { clonedArrows.forEach((arrow) => {
// console.log(arrow);
expect( expect(
clonedRectangle.boundElements!.find((e) => e.id === arrow.id), clonedRectangle.boundElements!.find((e) => e.id === arrow.id),
).toEqual( ).toEqual(

View File

@@ -1,5 +1,5 @@
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { GlobalTestState, render, screen } from "../tests/test-utils"; import { GlobalTestState, render, screen } from "../tests/test-utils";
import { Keyboard, Pointer, UI } from "../tests/helpers/ui"; import { Keyboard, Pointer, UI } from "../tests/helpers/ui";
import { CODES, KEYS } from "../keys"; import { CODES, KEYS } from "../keys";
@@ -41,7 +41,7 @@ describe("textWysiwyg", () => {
describe("start text editing", () => { describe("start text editing", () => {
const { h } = window; const { h } = window;
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
h.elements = []; h.elements = [];
}); });
@@ -243,7 +243,7 @@ describe("textWysiwyg", () => {
}); });
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
//@ts-ignore //@ts-ignore
h.app.refreshDeviceState(h.app.excalidrawContainerRef.current!); h.app.refreshDeviceState(h.app.excalidrawContainerRef.current!);
@@ -477,7 +477,7 @@ describe("textWysiwyg", () => {
const { h } = window; const { h } = window;
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
h.elements = []; h.elements = [];
rectangle = UI.createElement("rectangle", { rectangle = UI.createElement("rectangle", {
@@ -1511,7 +1511,7 @@ describe("textWysiwyg", () => {
}); });
it("should bump the version of labelled arrow when label updated", async () => { it("should bump the version of labelled arrow when label updated", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
const arrow = UI.createElement("arrow", { const arrow = UI.createElement("arrow", {
width: 300, width: 300,
height: 0, height: 0,

7
src/hooks/useStable.ts Normal file
View File

@@ -0,0 +1,7 @@
import { useRef } from "react";
export const useStable = <T extends Record<string, any>>(value: T) => {
const ref = useRef<T>(value);
Object.assign(ref.current, value);
return ref.current;
};

View File

@@ -1,9 +1,9 @@
import { StrictMode } from "react"; import { StrictMode } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import ExcalidrawApp from "./excalidraw-app"; import ExcalidrawApp from "../excalidraw-app";
import { registerSW } from "virtual:pwa-register"; import { registerSW } from "virtual:pwa-register";
import "./excalidraw-app/sentry"; import "../excalidraw-app/sentry";
window.__EXCALIDRAW_SHA__ = import.meta.env.VITE_APP_GIT_SHA; window.__EXCALIDRAW_SHA__ = import.meta.env.VITE_APP_GIT_SHA;
const rootElement = document.getElementById("root")!; const rootElement = document.getElementById("root")!;
const root = createRoot(rootElement); const root = createRoot(rootElement);

View File

@@ -11,6 +11,22 @@ The change should be grouped under one of the below section and must contain PR
Please add the latest change on the top under the correct section. Please add the latest change on the top under the correct section.
--> -->
## 0.16.1 (2023-09-21)
## Excalidraw Library
**_This section lists the updates made to the excalidraw library and will not affect the integration._**
### Fixes
- More eye-droper fixes [#7019](https://github.com/excalidraw/excalidraw/pull/7019)
### Refactor
- Move excalidraw-app outside src [#6987](https://github.com/excalidraw/excalidraw/pull/6987)
---
## 0.16.0 (2023-09-19) ## 0.16.0 (2023-09-19)
- Support creating containers, linear elements, text containers, labelled arrows and arrow bindings programatically [#6546](https://github.com/excalidraw/excalidraw/pull/6546) - Support creating containers, linear elements, text containers, labelled arrows and arrow bindings programatically [#6546](https://github.com/excalidraw/excalidraw/pull/6546)

View File

@@ -1,6 +1,6 @@
{ {
"name": "@excalidraw/excalidraw", "name": "@excalidraw/excalidraw",
"version": "0.16.0", "version": "0.16.1",
"main": "main.js", "main": "main.js",
"types": "types/packages/excalidraw/index.d.ts", "types": "types/packages/excalidraw/index.d.ts",
"files": [ "files": [

View File

@@ -22,5 +22,12 @@ const polyfill = () => {
configurable: true, configurable: true,
}); });
} }
if (!Element.prototype.replaceChildren) {
Element.prototype.replaceChildren = function (...nodes) {
this.innerHTML = "";
this.append(...nodes);
};
}
}; };
export default polyfill; export default polyfill;

View File

@@ -3,7 +3,7 @@ import * as Renderer from "../renderer/renderScene";
import { reseed } from "../random"; import { reseed } from "../random";
import { render, queryByTestId } from "../tests/test-utils"; import { render, queryByTestId } from "../tests/test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { vi } from "vitest"; import { vi } from "vitest";
const renderStaticScene = vi.spyOn(Renderer, "renderStaticScene"); const renderStaticScene = vi.spyOn(Renderer, "renderStaticScene");
@@ -35,7 +35,7 @@ describe("Test <App/>", () => {
}; };
}; };
await render(<ExcalidrawApp />); await render(<Excalidraw />);
expect( expect(
queryByTestId( queryByTestId(
document.querySelector(".excalidraw-modal-container")!, document.querySelector(".excalidraw-modal-container")!,

View File

@@ -0,0 +1,50 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Test <App/> > should show error modal when using brave and measureText API is not working 1`] = `
<div
data-testid="brave-measure-text-error"
>
<p>
Looks like you are using Brave browser with the
<span
style="font-weight: 600;"
>
Aggressively Block Fingerprinting
</span>
setting enabled.
</p>
<p>
This could result in breaking the
<span
style="font-weight: 600;"
>
Text Elements
</span>
in your drawings.
</p>
<p>
We strongly recommend disabling this setting. You can follow
<a
href="http://docs.excalidraw.com/docs/@excalidraw/excalidraw/faq#turning-off-aggresive-block-fingerprinting-in-brave-browser"
>
these steps
</a>
on how to do so.
</p>
<p>
If disabling this setting doesn't fix the display of text elements, please open an
<a
href="https://github.com/excalidraw/excalidraw/issues/new"
>
issue
</a>
on our GitHub, or write us on
<a
href="https://discord.gg/UexuTaE"
>
Discord
.
</a>
</p>
</div>
`;

View File

@@ -13089,126 +13089,6 @@ exports[`regression tests > pinch-to-zoom works > [end of test] number of elemen
exports[`regression tests > pinch-to-zoom works > [end of test] number of renders 1`] = `7`; exports[`regression tests > pinch-to-zoom works > [end of test] number of renders 1`] = `7`;
exports[`regression tests > rerenders UI on language change > [end of test] appState 1`] = `
{
"activeEmbeddable": null,
"activeTool": {
"customType": null,
"lastActiveTool": null,
"locked": false,
"type": "rectangle",
},
"collaborators": Map {},
"contextMenu": null,
"currentChartType": "bar",
"currentItemBackgroundColor": "transparent",
"currentItemEndArrowhead": "arrow",
"currentItemFillStyle": "hachure",
"currentItemFontFamily": 1,
"currentItemFontSize": 20,
"currentItemOpacity": 100,
"currentItemRoughness": 1,
"currentItemRoundness": "round",
"currentItemStartArrowhead": null,
"currentItemStrokeColor": "#1e1e1e",
"currentItemStrokeStyle": "solid",
"currentItemStrokeWidth": 1,
"currentItemTextAlign": "left",
"cursorButton": "up",
"defaultSidebarDockedPreference": false,
"draggingElement": null,
"editingElement": null,
"editingFrame": null,
"editingGroupId": null,
"editingLinearElement": null,
"elementsToHighlight": null,
"errorMessage": null,
"exportBackground": true,
"exportEmbedScene": false,
"exportScale": 1,
"exportWithDarkMode": false,
"fileHandle": null,
"frameRendering": {
"clip": true,
"enabled": true,
"name": true,
"outline": true,
},
"frameToHighlight": null,
"gridSize": null,
"height": 768,
"isBindingEnabled": true,
"isLoading": false,
"isResizing": false,
"isRotating": false,
"lastPointerDownWith": "mouse",
"multiElement": null,
"name": "Untitled-201933152653",
"offsetLeft": 0,
"offsetTop": 0,
"openDialog": null,
"openMenu": "canvas",
"openPopup": null,
"openSidebar": null,
"pasteDialog": {
"data": null,
"shown": false,
},
"penDetected": false,
"penMode": false,
"pendingImageElementId": null,
"previousSelectedElementIds": {},
"resizingElement": null,
"scrollX": 0,
"scrollY": 0,
"scrolledOutside": false,
"selectedElementIds": {},
"selectedElementsAreBeingDragged": false,
"selectedGroupIds": {},
"selectedLinearElement": null,
"selectionElement": null,
"shouldCacheIgnoreZoom": false,
"showHyperlinkPopup": false,
"showStats": false,
"showWelcomeScreen": true,
"startBoundElement": null,
"suggestedBindings": [],
"theme": "light",
"toast": null,
"viewBackgroundColor": "#ffffff",
"viewModeEnabled": false,
"width": 1024,
"zenModeEnabled": false,
"zoom": {
"value": 1,
},
}
`;
exports[`regression tests > rerenders UI on language change > [end of test] history 1`] = `
{
"recording": false,
"redoStack": [],
"stateHistory": [
{
"appState": {
"editingGroupId": null,
"editingLinearElement": null,
"name": "Untitled-201933152653",
"selectedElementIds": {},
"selectedGroupIds": {},
"viewBackgroundColor": "#ffffff",
},
"elements": [],
},
],
}
`;
exports[`regression tests > rerenders UI on language change > [end of test] number of elements 1`] = `0`;
exports[`regression tests > rerenders UI on language change > [end of test] number of renders 1`] = `5`;
exports[`regression tests > shift click on selected element should deselect it on pointer up > [end of test] appState 1`] = ` exports[`regression tests > shift click on selected element should deselect it on pointer up > [end of test] appState 1`] = `
{ {
"activeEmbeddable": null, "activeEmbeddable": null,

View File

@@ -1,4 +1,4 @@
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { CODES } from "../keys"; import { CODES } from "../keys";
import { API } from "../tests/helpers/api"; import { API } from "../tests/helpers/api";
import { Keyboard, Pointer, UI } from "../tests/helpers/ui"; import { Keyboard, Pointer, UI } from "../tests/helpers/ui";
@@ -9,7 +9,7 @@ import {
screen, screen,
togglePopover, togglePopover,
} from "../tests/test-utils"; } from "../tests/test-utils";
import { copiedStyles } from "./actionStyles"; import { copiedStyles } from "../actions/actionStyles";
const { h } = window; const { h } = window;
@@ -17,7 +17,7 @@ const mouse = new Pointer("mouse");
describe("actionStyles", () => { describe("actionStyles", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
}); });
afterEach(async () => { afterEach(async () => {

View File

@@ -1,6 +1,6 @@
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { render } from "./test-utils"; import { render } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../../src/packages/excalidraw/index";
import { defaultLang, setLanguage } from "../i18n"; import { defaultLang, setLanguage } from "../i18n";
import { UI, Pointer, Keyboard } from "./helpers/ui"; import { UI, Pointer, Keyboard } from "./helpers/ui";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
@@ -60,7 +60,7 @@ describe("aligning", () => {
mouse.reset(); mouse.reset();
await setLanguage(defaultLang); await setLanguage(defaultLang);
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
}); });
it("aligns two objects correctly to the top", () => { it("aligns two objects correctly to the top", () => {

View File

@@ -1,6 +1,6 @@
import { queryByTestId, render, waitFor } from "./test-utils"; import { queryByTestId, render, waitFor } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
import { getDefaultAppState } from "../appState"; import { getDefaultAppState } from "../appState";
import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants"; import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants";
@@ -14,14 +14,17 @@ describe("appState", () => {
const defaultAppState = getDefaultAppState(); const defaultAppState = getDefaultAppState();
const exportBackground = !defaultAppState.exportBackground; const exportBackground = !defaultAppState.exportBackground;
await render(<ExcalidrawApp />, { await render(
localStorageData: { <Excalidraw
initialData={{
appState: { appState: {
exportBackground, exportBackground,
viewBackgroundColor: "#F00", viewBackgroundColor: "#F00",
}, },
}, }}
}); />,
{},
);
await waitFor(() => { await waitFor(() => {
expect(h.state.exportBackground).toBe(exportBackground); expect(h.state.exportBackground).toBe(exportBackground);
@@ -53,13 +56,15 @@ describe("appState", () => {
}); });
it("changing fontSize with text tool selected (no element created yet)", async () => { it("changing fontSize with text tool selected (no element created yet)", async () => {
const { container } = await render(<ExcalidrawApp />, { const { container } = await render(
localStorageData: { <Excalidraw
initialData={{
appState: { appState: {
currentItemFontSize: 30, currentItemFontSize: 30,
}, },
}, }}
}); />,
);
UI.clickTool("text"); UI.clickTool("text");

View File

@@ -1,5 +1,5 @@
import { fireEvent, render } from "./test-utils"; import { fireEvent, render } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../../src/packages/excalidraw/index";
import { UI, Pointer, Keyboard } from "./helpers/ui"; import { UI, Pointer, Keyboard } from "./helpers/ui";
import { getTransformHandles } from "../element/transformHandles"; import { getTransformHandles } from "../element/transformHandles";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
@@ -12,7 +12,7 @@ const mouse = new Pointer("mouse");
describe("element binding", () => { describe("element binding", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
}); });
it("should create valid binding if duplicate start/end points", async () => { it("should create valid binding if duplicate start/end points", async () => {

View File

@@ -7,7 +7,7 @@ import {
createPasteEvent, createPasteEvent,
} from "./test-utils"; } from "./test-utils";
import { Pointer, Keyboard } from "./helpers/ui"; import { Pointer, Keyboard } from "./helpers/ui";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { import {
getDefaultLineHeight, getDefaultLineHeight,
@@ -79,8 +79,13 @@ beforeEach(async () => {
mouse.reset(); mouse.reset();
await render(<ExcalidrawApp />); await render(
h.app.setAppState({ zoom: { value: 1 as NormalizedZoomValue } }); <Excalidraw
autoFocus={true}
handleKeyboardGlobally={true}
initialData={{ appState: { zoom: { value: 1 as NormalizedZoomValue } } }}
/>,
);
setClipboardText(""); setClipboardText("");
Object.assign(document, { Object.assign(document, {
elementFromPoint: () => GlobalTestState.canvas, elementFromPoint: () => GlobalTestState.canvas,
@@ -91,7 +96,6 @@ describe("general paste behavior", () => {
it("should randomize seed on paste", async () => { it("should randomize seed on paste", async () => {
const rectangle = API.createElement({ type: "rectangle" }); const rectangle = API.createElement({ type: "rectangle" });
const clipboardJSON = (await copyToClipboard([rectangle], null))!; const clipboardJSON = (await copyToClipboard([rectangle], null))!;
pasteWithCtrlCmdV(clipboardJSON); pasteWithCtrlCmdV(clipboardJSON);
await waitFor(() => { await waitFor(() => {

View File

@@ -11,7 +11,7 @@ import {
waitFor, waitFor,
togglePopover, togglePopover,
} from "./test-utils"; } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import * as Renderer from "../renderer/renderScene"; import * as Renderer from "../renderer/renderScene";
import { reseed } from "../random"; import { reseed } from "../random";
import { UI, Pointer, Keyboard } from "./helpers/ui"; import { UI, Pointer, Keyboard } from "./helpers/ui";
@@ -20,7 +20,6 @@ import { ShortcutName } from "../actions/shortcuts";
import { copiedStyles } from "../actions/actionStyles"; import { copiedStyles } from "../actions/actionStyles";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
import { setDateTimeForTests } from "../utils"; import { setDateTimeForTests } from "../utils";
import { LibraryItem } from "../types";
import { vi } from "vitest"; import { vi } from "vitest";
const checkpoint = (name: string) => { const checkpoint = (name: string) => {
@@ -56,7 +55,7 @@ describe("contextMenu element", () => {
reseed(7); reseed(7);
setDateTimeForTests("201933152653"); setDateTimeForTests("201933152653");
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
}); });
beforeAll(() => { beforeAll(() => {
@@ -394,11 +393,9 @@ describe("contextMenu element", () => {
const contextMenu = UI.queryContextMenu(); const contextMenu = UI.queryContextMenu();
fireEvent.click(queryByText(contextMenu!, "Add to library")!); fireEvent.click(queryByText(contextMenu!, "Add to library")!);
await waitFor(() => { await waitFor(async () => {
const library = localStorage.getItem("excalidraw-library"); const libraryItems = await h.app.library.getLatestLibrary();
expect(library).not.toBeNull(); expect(libraryItems[0].elements[0]).toEqual(h.elements[0]);
const addedElement = JSON.parse(library!)[0] as LibraryItem;
expect(addedElement.elements[0]).toEqual(h.elements[0]);
}); });
}); });

View File

@@ -1,5 +1,5 @@
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import * as Renderer from "../renderer/renderScene"; import * as Renderer from "../renderer/renderScene";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { import {
@@ -30,7 +30,7 @@ const { h } = window;
describe("Test dragCreate", () => { describe("Test dragCreate", () => {
describe("add element to the scene when pointer dragging long enough", () => { describe("add element to the scene when pointer dragging long enough", () => {
it("rectangle", async () => { it("rectangle", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("rectangle"); const tool = getByToolName("rectangle");
fireEvent.click(tool); fireEvent.click(tool);
@@ -62,7 +62,7 @@ describe("Test dragCreate", () => {
}); });
it("ellipse", async () => { it("ellipse", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("ellipse"); const tool = getByToolName("ellipse");
fireEvent.click(tool); fireEvent.click(tool);
@@ -95,7 +95,7 @@ describe("Test dragCreate", () => {
}); });
it("diamond", async () => { it("diamond", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("diamond"); const tool = getByToolName("diamond");
fireEvent.click(tool); fireEvent.click(tool);
@@ -127,7 +127,7 @@ describe("Test dragCreate", () => {
}); });
it("arrow", async () => { it("arrow", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("arrow"); const tool = getByToolName("arrow");
fireEvent.click(tool); fireEvent.click(tool);
@@ -163,7 +163,7 @@ describe("Test dragCreate", () => {
}); });
it("line", async () => { it("line", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("line"); const tool = getByToolName("line");
fireEvent.click(tool); fireEvent.click(tool);
@@ -207,7 +207,7 @@ describe("Test dragCreate", () => {
}); });
it("rectangle", async () => { it("rectangle", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("rectangle"); const tool = getByToolName("rectangle");
fireEvent.click(tool); fireEvent.click(tool);
@@ -227,7 +227,7 @@ describe("Test dragCreate", () => {
}); });
it("ellipse", async () => { it("ellipse", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("ellipse"); const tool = getByToolName("ellipse");
fireEvent.click(tool); fireEvent.click(tool);
@@ -247,7 +247,7 @@ describe("Test dragCreate", () => {
}); });
it("diamond", async () => { it("diamond", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("diamond"); const tool = getByToolName("diamond");
fireEvent.click(tool); fireEvent.click(tool);
@@ -267,7 +267,9 @@ describe("Test dragCreate", () => {
}); });
it("arrow", async () => { it("arrow", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(
<Excalidraw handleKeyboardGlobally={true} />,
);
// select tool // select tool
const tool = getByToolName("arrow"); const tool = getByToolName("arrow");
fireEvent.click(tool); fireEvent.click(tool);
@@ -292,7 +294,9 @@ describe("Test dragCreate", () => {
}); });
it("line", async () => { it("line", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(
<Excalidraw handleKeyboardGlobally={true} />,
);
// select tool // select tool
const tool = getByToolName("line"); const tool = getByToolName("line");
fireEvent.click(tool); fireEvent.click(tool);

View File

@@ -1,5 +1,5 @@
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { render } from "../tests/test-utils"; import { render } from "../tests/test-utils";
import { Keyboard, Pointer, UI } from "../tests/helpers/ui"; import { Keyboard, Pointer, UI } from "../tests/helpers/ui";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
@@ -15,7 +15,7 @@ const h = window.h;
describe("element locking", () => { describe("element locking", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
h.elements = []; h.elements = [];
}); });

View File

@@ -1,5 +1,5 @@
import { render, waitFor } from "./test-utils"; import { render, waitFor } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
import { import {
encodePngMetadata, encodePngMetadata,
@@ -42,7 +42,7 @@ Object.defineProperty(window, "TextDecoder", {
describe("export", () => { describe("export", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
}); });
it("export embedded png and reimport", async () => { it("export embedded png and reimport", async () => {

View File

@@ -1,14 +1,14 @@
import { render } from "./test-utils"; import { render } from "./test-utils";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { vi } from "vitest"; import { vi } from "vitest";
const { h } = window; const { h } = window;
describe("fitToContent", () => { describe("fitToContent", () => {
it("should zoom to fit the selected element", async () => { it("should zoom to fit the selected element", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
h.state.width = 10; h.state.width = 10;
h.state.height = 10; h.state.height = 10;
@@ -30,7 +30,7 @@ describe("fitToContent", () => {
}); });
it("should zoom to fit multiple elements", async () => { it("should zoom to fit multiple elements", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
const topLeft = API.createElement({ const topLeft = API.createElement({
width: 20, width: 20,
@@ -61,7 +61,7 @@ describe("fitToContent", () => {
}); });
it("should scroll the viewport to the selected element", async () => { it("should scroll the viewport to the selected element", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
h.state.width = 10; h.state.width = 10;
h.state.height = 10; h.state.height = 10;
@@ -106,7 +106,7 @@ describe("fitToContent animated", () => {
}); });
it("should ease scroll the viewport to the selected element", async () => { it("should ease scroll the viewport to the selected element", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
h.state.width = 10; h.state.width = 10;
h.state.height = 10; h.state.height = 10;
@@ -142,7 +142,7 @@ describe("fitToContent animated", () => {
}); });
it("should animate the scroll but not the zoom", async () => { it("should animate the scroll but not the zoom", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
h.state.width = 50; h.state.width = 50;
h.state.height = 50; h.state.height = 50;

View File

@@ -19,7 +19,7 @@ import {
FileId, FileId,
} from "../element/types"; } from "../element/types";
import { newLinearElement } from "../element"; import { newLinearElement } from "../element";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { mutateElement } from "../element/mutateElement"; import { mutateElement } from "../element/mutateElement";
import { NormalizedZoomValue } from "../types"; import { NormalizedZoomValue } from "../types";
import { ROUNDNESS } from "../constants"; import { ROUNDNESS } from "../constants";
@@ -52,7 +52,7 @@ beforeEach(async () => {
Object.assign(document, { Object.assign(document, {
elementFromPoint: () => GlobalTestState.canvas, elementFromPoint: () => GlobalTestState.canvas,
}); });
await render(<ExcalidrawApp />); await render(<Excalidraw autoFocus={true} handleKeyboardGlobally={true} />);
h.setState({ h.setState({
zoom: { zoom: {
value: 1 as NormalizedZoomValue, value: 1 as NormalizedZoomValue,

View File

@@ -1,5 +1,5 @@
import { assertSelectedElements, render } from "./test-utils"; import { assertSelectedElements, render } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { Keyboard, Pointer, UI } from "./helpers/ui"; import { Keyboard, Pointer, UI } from "./helpers/ui";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
import { getDefaultAppState } from "../appState"; import { getDefaultAppState } from "../appState";
@@ -13,14 +13,16 @@ const mouse = new Pointer("mouse");
describe("history", () => { describe("history", () => {
it("initializing scene should end up with single history entry", async () => { it("initializing scene should end up with single history entry", async () => {
await render(<ExcalidrawApp />, { await render(
localStorageData: { <Excalidraw
initialData={{
elements: [API.createElement({ type: "rectangle", id: "A" })], elements: [API.createElement({ type: "rectangle", id: "A" })],
appState: { appState: {
zenModeEnabled: true, zenModeEnabled: true,
}, },
}, }}
}); />,
);
await waitFor(() => expect(h.state.zenModeEnabled).toBe(true)); await waitFor(() => expect(h.state.zenModeEnabled).toBe(true));
await waitFor(() => await waitFor(() =>
@@ -60,14 +62,16 @@ describe("history", () => {
}); });
it("scene import via drag&drop should create new history entry", async () => { it("scene import via drag&drop should create new history entry", async () => {
await render(<ExcalidrawApp />, { await render(
localStorageData: { <Excalidraw
initialData={{
elements: [API.createElement({ type: "rectangle", id: "A" })], elements: [API.createElement({ type: "rectangle", id: "A" })],
appState: { appState: {
viewBackgroundColor: "#FFF", viewBackgroundColor: "#FFF",
}, },
}, }}
}); />,
);
await waitFor(() => expect(h.state.viewBackgroundColor).toBe("#FFF")); await waitFor(() => expect(h.state.viewBackgroundColor).toBe("#FFF"));
await waitFor(() => await waitFor(() =>
@@ -113,7 +117,7 @@ describe("history", () => {
}); });
it("undo/redo works properly with groups", async () => { it("undo/redo works properly with groups", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
const rect1 = API.createElement({ type: "rectangle", groupIds: ["A"] }); const rect1 = API.createElement({ type: "rectangle", groupIds: ["A"] });
const rect2 = API.createElement({ type: "rectangle", groupIds: ["A"] }); const rect2 = API.createElement({ type: "rectangle", groupIds: ["A"] });

View File

@@ -2,7 +2,7 @@ import { vi } from "vitest";
import { fireEvent, render, waitFor } from "./test-utils"; import { fireEvent, render, waitFor } from "./test-utils";
import { queryByTestId } from "@testing-library/react"; import { queryByTestId } from "@testing-library/react";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
import { MIME_TYPES } from "../constants"; import { MIME_TYPES } from "../constants";
import { LibraryItem, LibraryItems } from "../types"; import { LibraryItem, LibraryItems } from "../types";
@@ -42,7 +42,7 @@ vi.mock("../data/filesystem.ts", async (importOriginal) => {
describe("library", () => { describe("library", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
h.app.library.resetLibrary(); h.app.library.resetLibrary();
}); });
@@ -189,7 +189,7 @@ describe("library", () => {
describe("library menu", () => { describe("library menu", () => {
it("should load library from file picker", async () => { it("should load library from file picker", async () => {
const { container } = await render(<ExcalidrawApp />); const { container } = await render(<Excalidraw />);
const latestLibrary = await h.app.library.getLatestLibrary(); const latestLibrary = await h.app.library.getLatestLibrary();
expect(latestLibrary.length).toBe(0); expect(latestLibrary.length).toBe(0);

View File

@@ -5,7 +5,7 @@ import {
ExcalidrawTextElementWithContainer, ExcalidrawTextElementWithContainer,
FontString, FontString,
} from "../element/types"; } from "../element/types";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { centerPoint } from "../math"; import { centerPoint } from "../math";
import { reseed } from "../random"; import { reseed } from "../random";
import * as Renderer from "../renderer/renderScene"; import * as Renderer from "../renderer/renderScene";
@@ -43,7 +43,7 @@ describe("Test Linear Elements", () => {
renderInteractiveScene.mockClear(); renderInteractiveScene.mockClear();
renderStaticScene.mockClear(); renderStaticScene.mockClear();
reseed(7); reseed(7);
const comp = await render(<ExcalidrawApp />); const comp = await render(<Excalidraw handleKeyboardGlobally={true} />);
h.state.width = 1000; h.state.width = 1000;
h.state.height = 1000; h.state.height = 1000;
container = comp.container; container = comp.container;

View File

@@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { render, fireEvent } from "./test-utils"; import { render, fireEvent } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import * as Renderer from "../renderer/renderScene"; import * as Renderer from "../renderer/renderScene";
import { reseed } from "../random"; import { reseed } from "../random";
import { bindOrUnbindLinearElement } from "../element/binding"; import { bindOrUnbindLinearElement } from "../element/binding";
@@ -31,7 +31,7 @@ const { h } = window;
describe("move element", () => { describe("move element", () => {
it("rectangle", async () => { it("rectangle", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
const canvas = container.querySelector("canvas.interactive")!; const canvas = container.querySelector("canvas.interactive")!;
{ {
@@ -67,7 +67,7 @@ describe("move element", () => {
}); });
it("rectangles with binding arrow", async () => { it("rectangles with binding arrow", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
// create elements // create elements
const rectA = UI.createElement("rectangle", { size: 100 }); const rectA = UI.createElement("rectangle", { size: 100 });
@@ -119,7 +119,7 @@ describe("move element", () => {
describe("duplicate element on move when ALT is clicked", () => { describe("duplicate element on move when ALT is clicked", () => {
it("rectangle", async () => { it("rectangle", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
const canvas = container.querySelector("canvas.interactive")!; const canvas = container.querySelector("canvas.interactive")!;
{ {

View File

@@ -5,7 +5,7 @@ import {
mockBoundingClientRect, mockBoundingClientRect,
restoreOriginalGetBoundingClientRect, restoreOriginalGetBoundingClientRect,
} from "./test-utils"; } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import * as Renderer from "../renderer/renderScene"; import * as Renderer from "../renderer/renderScene";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { ExcalidrawLinearElement } from "../element/types"; import { ExcalidrawLinearElement } from "../element/types";
@@ -29,7 +29,7 @@ const { h } = window;
describe("remove shape in non linear elements", () => { describe("remove shape in non linear elements", () => {
beforeAll(() => { beforeAll(() => {
mockBoundingClientRect(); mockBoundingClientRect({ width: 1000, height: 1000 });
}); });
afterAll(() => { afterAll(() => {
@@ -37,12 +37,13 @@ describe("remove shape in non linear elements", () => {
}); });
it("rectangle", async () => { it("rectangle", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("rectangle"); const tool = getByToolName("rectangle");
fireEvent.click(tool); fireEvent.click(tool);
const canvas = container.querySelector("canvas.interactive")!; const canvas = container.querySelector("canvas.interactive")!;
fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 }); fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 });
fireEvent.pointerUp(canvas, { clientX: 30, clientY: 30 }); fireEvent.pointerUp(canvas, { clientX: 30, clientY: 30 });
@@ -52,7 +53,7 @@ describe("remove shape in non linear elements", () => {
}); });
it("ellipse", async () => { it("ellipse", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("ellipse"); const tool = getByToolName("ellipse");
fireEvent.click(tool); fireEvent.click(tool);
@@ -67,7 +68,7 @@ describe("remove shape in non linear elements", () => {
}); });
it("diamond", async () => { it("diamond", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("diamond"); const tool = getByToolName("diamond");
fireEvent.click(tool); fireEvent.click(tool);
@@ -84,7 +85,7 @@ describe("remove shape in non linear elements", () => {
describe("multi point mode in linear elements", () => { describe("multi point mode in linear elements", () => {
it("arrow", async () => { it("arrow", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("arrow"); const tool = getByToolName("arrow");
fireEvent.click(tool); fireEvent.click(tool);
@@ -109,8 +110,8 @@ describe("multi point mode in linear elements", () => {
key: KEYS.ENTER, key: KEYS.ENTER,
}); });
expect(renderInteractiveScene).toHaveBeenCalledTimes(10); expect(renderInteractiveScene).toHaveBeenCalledTimes(9);
expect(renderStaticScene).toHaveBeenCalledTimes(11); expect(renderStaticScene).toHaveBeenCalledTimes(10);
expect(h.elements.length).toEqual(1); expect(h.elements.length).toEqual(1);
const element = h.elements[0] as ExcalidrawLinearElement; const element = h.elements[0] as ExcalidrawLinearElement;
@@ -128,7 +129,7 @@ describe("multi point mode in linear elements", () => {
}); });
it("line", async () => { it("line", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("line"); const tool = getByToolName("line");
fireEvent.click(tool); fireEvent.click(tool);
@@ -153,8 +154,8 @@ describe("multi point mode in linear elements", () => {
key: KEYS.ENTER, key: KEYS.ENTER,
}); });
expect(renderInteractiveScene).toHaveBeenCalledTimes(10); expect(renderInteractiveScene).toHaveBeenCalledTimes(9);
expect(renderStaticScene).toHaveBeenCalledTimes(11); expect(renderStaticScene).toHaveBeenCalledTimes(10);
expect(h.elements.length).toEqual(1); expect(h.elements.length).toEqual(1);
const element = h.elements[0] as ExcalidrawLinearElement; const element = h.elements[0] as ExcalidrawLinearElement;

View File

@@ -1,7 +1,7 @@
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { ExcalidrawElement } from "../element/types"; import { ExcalidrawElement } from "../element/types";
import { CODES, KEYS } from "../keys"; import { CODES, KEYS } from "../keys";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { reseed } from "../random"; import { reseed } from "../random";
import * as Renderer from "../renderer/renderScene"; import * as Renderer from "../renderer/renderScene";
import { setDateTimeForTests } from "../utils"; import { setDateTimeForTests } from "../utils";
@@ -13,9 +13,7 @@ import {
render, render,
screen, screen,
togglePopover, togglePopover,
waitFor,
} from "./test-utils"; } from "./test-utils";
import { defaultLang } from "../i18n";
import { FONT_FAMILY } from "../constants"; import { FONT_FAMILY } from "../constants";
import { vi } from "vitest"; import { vi } from "vitest";
@@ -56,7 +54,7 @@ beforeEach(async () => {
finger1.reset(); finger1.reset();
finger2.reset(); finger2.reset();
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
h.setState({ height: 768, width: 1024 }); h.setState({ height: 768, width: 1024 });
}); });
@@ -443,26 +441,6 @@ describe("regression tests", () => {
expect(h.state.zoom.value).toBe(1); expect(h.state.zoom.value).toBe(1);
}); });
it("rerenders UI on language change", async () => {
// select rectangle tool to show properties menu
UI.clickTool("rectangle");
// english lang should display `thin` label
expect(screen.queryByTitle(/thin/i)).not.toBeNull();
fireEvent.click(document.querySelector(".dropdown-menu-button")!);
fireEvent.change(document.querySelector(".dropdown-select__language")!, {
target: { value: "de-DE" },
});
// switching to german, `thin` label should no longer exist
await waitFor(() => expect(screen.queryByTitle(/thin/i)).toBeNull());
// reset language
fireEvent.change(document.querySelector(".dropdown-select__language")!, {
target: { value: defaultLang.code },
});
// switching back to English
await waitFor(() => expect(screen.queryByTitle(/thin/i)).not.toBeNull());
});
it("make a group and duplicate it", () => { it("make a group and duplicate it", () => {
UI.clickTool("rectangle"); UI.clickTool("rectangle");
mouse.down(10, 10); mouse.down(10, 10);

View File

@@ -6,7 +6,7 @@ import { reseed } from "../random";
import { UI, Keyboard } from "./helpers/ui"; import { UI, Keyboard } from "./helpers/ui";
import { resize } from "./utils"; import { resize } from "./utils";
import { ExcalidrawTextElement } from "../element/types"; import { ExcalidrawTextElement } from "../element/types";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { vi } from "vitest"; import { vi } from "vitest";
@@ -126,7 +126,7 @@ describe("resize rectangle ellipses and diamond elements", () => {
describe("Test text element", () => { describe("Test text element", () => {
it("should update font size via keyboard", async () => { it("should update font size via keyboard", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw handleKeyboardGlobally={true} />);
const textElement = API.createElement({ const textElement = API.createElement({
type: "text", type: "text",

View File

@@ -8,7 +8,6 @@ import { Excalidraw } from "../packages/excalidraw/index";
import { API } from "./helpers/api"; import { API } from "./helpers/api";
import { Keyboard } from "./helpers/ui"; import { Keyboard } from "./helpers/ui";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import ExcalidrawApp from "../excalidraw-app";
const { h } = window; const { h } = window;
@@ -56,7 +55,7 @@ describe("appState", () => {
it("moving by page up/down/left/right", async () => { it("moving by page up/down/left/right", async () => {
mockBoundingClientRect(); mockBoundingClientRect();
await render(<ExcalidrawApp />, {}); await render(<Excalidraw handleKeyboardGlobally={true} />, {});
const scrollTest = () => { const scrollTest = () => {
const initialScrollY = h.state.scrollY; const initialScrollY = h.state.scrollY;

View File

@@ -6,7 +6,7 @@ import {
restoreOriginalGetBoundingClientRect, restoreOriginalGetBoundingClientRect,
assertSelectedElements, assertSelectedElements,
} from "./test-utils"; } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import * as Renderer from "../renderer/renderScene"; import * as Renderer from "../renderer/renderScene";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { reseed } from "../random"; import { reseed } from "../random";
@@ -34,7 +34,7 @@ const mouse = new Pointer("mouse");
describe("box-selection", () => { describe("box-selection", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
}); });
it("should allow adding to selection via box-select when holding shift", async () => { it("should allow adding to selection via box-select when holding shift", async () => {
@@ -102,7 +102,7 @@ describe("box-selection", () => {
describe("inner box-selection", () => { describe("inner box-selection", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
}); });
it("selecting elements visually nested inside another", async () => { it("selecting elements visually nested inside another", async () => {
const rect1 = API.createElement({ const rect1 = API.createElement({
@@ -218,7 +218,7 @@ describe("inner box-selection", () => {
describe("selection element", () => { describe("selection element", () => {
it("create selection element on pointer down", async () => { it("create selection element on pointer down", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("selection"); const tool = getByToolName("selection");
fireEvent.click(tool); fireEvent.click(tool);
@@ -239,7 +239,7 @@ describe("selection element", () => {
}); });
it("resize selection element on pointer move", async () => { it("resize selection element on pointer move", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("selection"); const tool = getByToolName("selection");
fireEvent.click(tool); fireEvent.click(tool);
@@ -261,7 +261,7 @@ describe("selection element", () => {
}); });
it("remove selection element on pointer up", async () => { it("remove selection element on pointer up", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(<Excalidraw />);
// select tool // select tool
const tool = getByToolName("selection"); const tool = getByToolName("selection");
fireEvent.click(tool); fireEvent.click(tool);
@@ -287,7 +287,9 @@ describe("select single element on the scene", () => {
}); });
it("rectangle", async () => { it("rectangle", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(
<Excalidraw handleKeyboardGlobally={true} />,
);
const canvas = container.querySelector("canvas.interactive")!; const canvas = container.querySelector("canvas.interactive")!;
{ {
// create element // create element
@@ -317,7 +319,9 @@ describe("select single element on the scene", () => {
}); });
it("diamond", async () => { it("diamond", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(
<Excalidraw handleKeyboardGlobally={true} />,
);
const canvas = container.querySelector("canvas.interactive")!; const canvas = container.querySelector("canvas.interactive")!;
{ {
// create element // create element
@@ -347,7 +351,9 @@ describe("select single element on the scene", () => {
}); });
it("ellipse", async () => { it("ellipse", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(
<Excalidraw handleKeyboardGlobally={true} />,
);
const canvas = container.querySelector("canvas.interactive")!; const canvas = container.querySelector("canvas.interactive")!;
{ {
// create element // create element
@@ -377,7 +383,9 @@ describe("select single element on the scene", () => {
}); });
it("arrow", async () => { it("arrow", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(
<Excalidraw handleKeyboardGlobally={true} />,
);
const canvas = container.querySelector("canvas.interactive")!; const canvas = container.querySelector("canvas.interactive")!;
{ {
// create element // create element
@@ -419,7 +427,9 @@ describe("select single element on the scene", () => {
}); });
it("arrow escape", async () => { it("arrow escape", async () => {
const { getByToolName, container } = await render(<ExcalidrawApp />); const { getByToolName, container } = await render(
<Excalidraw handleKeyboardGlobally={true} />,
);
const canvas = container.querySelector("canvas.interactive")!; const canvas = container.querySelector("canvas.interactive")!;
{ {
// create element // create element
@@ -464,7 +474,7 @@ describe("select single element on the scene", () => {
describe("tool locking & selection", () => { describe("tool locking & selection", () => {
it("should not select newly created element while tool is locked", async () => { it("should not select newly created element while tool is locked", async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
UI.clickTool("lock"); UI.clickTool("lock");
expect(h.state.activeTool.locked).toBe(true); expect(h.state.activeTool.locked).toBe(true);
@@ -480,7 +490,7 @@ describe("tool locking & selection", () => {
describe("selectedElementIds stability", () => { describe("selectedElementIds stability", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
}); });
it("box-selection should be stable when not changing selection", () => { it("box-selection should be stable when not changing selection", () => {

View File

@@ -11,7 +11,7 @@ import {
import * as toolQueries from "./queries/toolQueries"; import * as toolQueries from "./queries/toolQueries";
import { ImportedDataState } from "../data/types"; import { ImportedDataState } from "../data/types";
import { STORAGE_KEYS } from "../excalidraw-app/app_constants"; import { STORAGE_KEYS } from "../../excalidraw-app/app_constants";
import { SceneData } from "../types"; import { SceneData } from "../types";
import { getSelectedElements } from "../scene/selection"; import { getSelectedElements } from "../scene/selection";

View File

@@ -1,5 +1,5 @@
import { render, GlobalTestState } from "./test-utils"; import { render, GlobalTestState } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
import { Keyboard, Pointer, UI } from "./helpers/ui"; import { Keyboard, Pointer, UI } from "./helpers/ui";
import { CURSOR_TYPE } from "../constants"; import { CURSOR_TYPE } from "../constants";
@@ -12,7 +12,7 @@ const pointerTypes = [mouse, touch, pen];
describe("view mode", () => { describe("view mode", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
}); });
it("after switching to view mode cursor type should be pointer", async () => { it("after switching to view mode cursor type should be pointer", async () => {

View File

@@ -1,6 +1,6 @@
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import { render } from "./test-utils"; import { render } from "./test-utils";
import ExcalidrawApp from "../excalidraw-app"; import { Excalidraw } from "../packages/excalidraw/index";
import { reseed } from "../random"; import { reseed } from "../random";
import { import {
actionSendBackward, actionSendBackward,
@@ -121,7 +121,6 @@ const assertZindex = ({
operations: [Actions, string[]][]; operations: [Actions, string[]][];
}) => { }) => {
const selectedElementIds = populateElements(elements, appState); const selectedElementIds = populateElements(elements, appState);
operations.forEach(([action, expected]) => { operations.forEach(([action, expected]) => {
h.app.actionManager.executeAction(action); h.app.actionManager.executeAction(action);
expect(h.elements.map((element) => element.id)).toEqual(expected); expect(h.elements.map((element) => element.id)).toEqual(expected);
@@ -131,7 +130,7 @@ const assertZindex = ({
describe("z-index manipulation", () => { describe("z-index manipulation", () => {
beforeEach(async () => { beforeEach(async () => {
await render(<ExcalidrawApp />); await render(<Excalidraw />);
}); });
it("send back", () => { it("send back", () => {

View File

@@ -96,9 +96,9 @@ const getTargetIndexAccountingForBinding = (
if (direction === "left") { if (direction === "left") {
return elements.indexOf(nextElement); return elements.indexOf(nextElement);
} }
const boundTextElement = const boundTextElement =
Scene.getScene(nextElement)!.getElement(boundElementId); Scene.getScene(nextElement)!.getElement(boundElementId);
if (boundTextElement) { if (boundTextElement) {
return elements.indexOf(boundTextElement); return elements.indexOf(boundTextElement);
} }

View File

@@ -16,6 +16,6 @@
"noEmit": true, "noEmit": true,
"jsx": "react-jsx" "jsx": "react-jsx"
}, },
"include": ["src"], "include": ["src", "excalidraw-app"],
"exclude": ["src/packages/excalidraw/types"] "exclude": ["src/packages/excalidraw/types"]
} }