瀏覽代碼

refactor: separate elements logic into a standalone package (#9285)

Marcel Mraz 4 月之前
父節點
當前提交
432a46ef9e
共有 100 個文件被更改,包括 1020 次插入776 次删除
  1. 13 14
      excalidraw-app/App.tsx
  2. 7 3
      excalidraw-app/CustomStats.tsx
  3. 1 1
      excalidraw-app/ExcalidrawPlusIframeExport.tsx
  4. 14 15
      excalidraw-app/collab/Collab.tsx
  5. 3 3
      excalidraw-app/collab/Portal.tsx
  6. 1 1
      excalidraw-app/components/AI.tsx
  7. 2 2
      excalidraw-app/components/AppMainMenu.tsx
  8. 1 1
      excalidraw-app/components/AppWelcomeScreen.tsx
  9. 8 7
      excalidraw-app/components/DebugCanvas.tsx
  10. 7 7
      excalidraw-app/components/ExportToExcalidrawPlus.tsx
  11. 2 2
      excalidraw-app/components/GitHubCorner.tsx
  12. 3 3
      excalidraw-app/data/FileManager.ts
  13. 5 8
      excalidraw-app/data/LocalData.ts
  14. 3 3
      excalidraw-app/data/firebase.ts
  15. 7 7
      excalidraw-app/data/index.ts
  16. 2 2
      excalidraw-app/data/localStorage.ts
  17. 1 2
      excalidraw-app/share/ShareDialog.tsx
  18. 1 1
      excalidraw-app/tests/collab.test.tsx
  19. 2 3
      excalidraw-app/useHandleAppTheme.ts
  20. 24 8
      excalidraw-app/vite.config.mts
  21. 1 3
      package.json
  22. 3 0
      packages/common/.eslintrc.json
  23. 19 0
      packages/common/README.md
  24. 3 0
      packages/common/global.d.ts
  25. 65 0
      packages/common/package.json
  26. 1 1
      packages/common/src/binary-heap.ts
  27. 0 0
      packages/common/src/colors.ts
  28. 6 3
      packages/common/src/constants.ts
  29. 36 17
      packages/common/src/font-metadata.ts
  30. 11 0
      packages/common/src/index.ts
  31. 0 0
      packages/common/src/keys.ts
  32. 17 0
      packages/common/src/points.ts
  33. 50 0
      packages/common/src/promise-pool.ts
  34. 3 2
      packages/common/src/queue.ts
  35. 0 0
      packages/common/src/random.ts
  36. 1 1
      packages/common/src/url.ts
  37. 0 0
      packages/common/src/utility-types.ts
  38. 18 63
      packages/common/src/utils.ts
  39. 1 1
      packages/common/tests/keys.test.ts
  40. 1 1
      packages/common/tests/queue.test.ts
  41. 1 1
      packages/common/tests/url.test.tsx
  42. 6 0
      packages/common/tsconfig.json
  43. 3 0
      packages/element/.eslintrc.json
  44. 19 0
      packages/element/README.md
  45. 3 0
      packages/element/global.d.ts
  46. 63 0
      packages/element/package.json
  47. 19 12
      packages/element/src/Shape.ts
  48. 14 8
      packages/element/src/ShapeCache.ts
  49. 7 6
      packages/element/src/align.ts
  50. 17 14
      packages/element/src/binding.ts
  51. 11 8
      packages/element/src/bounds.ts
  52. 8 5
      packages/element/src/collision.ts
  53. 1 1
      packages/element/src/comparisons.ts
  54. 0 0
      packages/element/src/containerCache.ts
  55. 0 0
      packages/element/src/cropElement.ts
  56. 1 0
      packages/element/src/distance.ts
  57. 4 3
      packages/element/src/distribute.ts
  58. 17 11
      packages/element/src/dragElements.ts
  59. 15 14
      packages/element/src/duplicate.ts
  60. 12 5
      packages/element/src/elbowArrow.ts
  61. 5 4
      packages/element/src/elementLink.ts
  62. 9 35
      packages/element/src/embeddable.ts
  63. 8 6
      packages/element/src/flowchart.ts
  64. 10 6
      packages/element/src/fractionalIndex.ts
  65. 24 22
      packages/element/src/frame.ts
  66. 28 27
      packages/element/src/groups.ts
  67. 0 0
      packages/element/src/heading.ts
  68. 7 2
      packages/element/src/image.ts
  69. 0 50
      packages/element/src/index.ts
  70. 42 27
      packages/element/src/linearElementEditor.ts
  71. 14 6
      packages/element/src/mutateElement.ts
  72. 13 10
      packages/element/src/newElement.ts
  73. 42 32
      packages/element/src/renderElement.ts
  74. 14 7
      packages/element/src/resizeElements.ts
  75. 3 2
      packages/element/src/resizeTest.ts
  76. 12 7
      packages/element/src/selection.ts
  77. 19 117
      packages/element/src/shapes.ts
  78. 3 2
      packages/element/src/showSelectedShapeActions.ts
  79. 6 3
      packages/element/src/sizeHelpers.ts
  80. 1 1
      packages/element/src/sortElements.ts
  81. 11 7
      packages/element/src/textElement.ts
  82. 4 2
      packages/element/src/textMeasurements.ts
  83. 1 1
      packages/element/src/textWrapping.ts
  84. 11 6
      packages/element/src/transformHandles.ts
  85. 5 4
      packages/element/src/typeChecks.ts
  86. 3 2
      packages/element/src/types.ts
  87. 2 2
      packages/element/src/utils.ts
  88. 36 21
      packages/element/src/zindex.ts
  89. 12 9
      packages/element/tests/align.test.tsx
  90. 10 9
      packages/element/tests/binding.test.tsx
  91. 4 5
      packages/element/tests/bounds.test.ts
  92. 18 12
      packages/element/tests/duplicate.test.tsx
  93. 15 13
      packages/element/tests/elbowArrow.test.tsx
  94. 10 6
      packages/element/tests/flowchart.test.tsx
  95. 11 6
      packages/element/tests/fractionalIndex.test.ts
  96. 13 7
      packages/element/tests/frame.test.tsx
  97. 22 17
      packages/element/tests/resize.test.tsx
  98. 1 1
      packages/element/tests/selection.test.ts
  99. 3 3
      packages/element/tests/sizeHelpers.test.ts
  100. 4 4
      packages/element/tests/sortElements.test.ts

+ 13 - 14
excalidraw-app/App.tsx

@@ -22,13 +22,6 @@ import {
   THEME,
   TITLE_TIMEOUT,
   VERSION_TIMEOUT,
-} from "@excalidraw/excalidraw/constants";
-import polyfill from "@excalidraw/excalidraw/polyfill";
-import { useCallback, useEffect, useRef, useState } from "react";
-import { loadFromBlob } from "@excalidraw/excalidraw/data/blob";
-import { useCallbackRefState } from "@excalidraw/excalidraw/hooks/useCallbackRefState";
-import { t } from "@excalidraw/excalidraw/i18n";
-import {
   debounce,
   getVersion,
   getFrame,
@@ -37,7 +30,13 @@ import {
   resolvablePromise,
   isRunningInIframe,
   isDevEnv,
-} from "@excalidraw/excalidraw/utils";
+} from "@excalidraw/common";
+import polyfill from "@excalidraw/excalidraw/polyfill";
+import { useCallback, useEffect, useRef, useState } from "react";
+import { loadFromBlob } from "@excalidraw/excalidraw/data/blob";
+import { useCallbackRefState } from "@excalidraw/excalidraw/hooks/useCallbackRefState";
+import { t } from "@excalidraw/excalidraw/i18n";
+
 import {
   GithubIcon,
   XBrandIcon,
@@ -48,10 +47,10 @@ import {
   share,
   youtubeIcon,
 } from "@excalidraw/excalidraw/components/icons";
-import { isElementLink } from "@excalidraw/excalidraw/element/elementLink";
+import { isElementLink } from "@excalidraw/element/elementLink";
 import { restore, restoreAppState } from "@excalidraw/excalidraw/data/restore";
-import { newElementWith } from "@excalidraw/excalidraw/element/mutateElement";
-import { isInitializedImageElement } from "@excalidraw/excalidraw/element/typeChecks";
+import { newElementWith } from "@excalidraw/element/mutateElement";
+import { isInitializedImageElement } from "@excalidraw/element/typeChecks";
 import clsx from "clsx";
 import {
   parseLibraryTokensFromUrl,
@@ -64,7 +63,7 @@ import type {
   FileId,
   NonDeletedExcalidrawElement,
   OrderedExcalidrawElement,
-} from "@excalidraw/excalidraw/element/types";
+} from "@excalidraw/element/types";
 import type {
   AppState,
   ExcalidrawImperativeAPI,
@@ -72,8 +71,8 @@ import type {
   ExcalidrawInitialDataState,
   UIAppState,
 } from "@excalidraw/excalidraw/types";
-import type { ResolutionType } from "@excalidraw/excalidraw/utility-types";
-import type { ResolvablePromise } from "@excalidraw/excalidraw/utils";
+import type { ResolutionType } from "@excalidraw/common/utility-types";
+import type { ResolvablePromise } from "@excalidraw/common/utils";
 
 import CustomStats from "./CustomStats";
 import {

+ 7 - 3
excalidraw-app/CustomStats.tsx

@@ -1,11 +1,15 @@
 import { Stats } from "@excalidraw/excalidraw";
 import { copyTextToSystemClipboard } from "@excalidraw/excalidraw/clipboard";
-import { DEFAULT_VERSION } from "@excalidraw/excalidraw/constants";
+import {
+  DEFAULT_VERSION,
+  debounce,
+  getVersion,
+  nFormatter,
+} from "@excalidraw/common";
 import { t } from "@excalidraw/excalidraw/i18n";
-import { debounce, getVersion, nFormatter } from "@excalidraw/excalidraw/utils";
 import { useEffect, useState } from "react";
 
-import type { NonDeletedExcalidrawElement } from "@excalidraw/excalidraw/element/types";
+import type { NonDeletedExcalidrawElement } from "@excalidraw/element/types";
 import type { UIAppState } from "@excalidraw/excalidraw/types";
 
 import {

+ 1 - 1
excalidraw-app/ExcalidrawPlusIframeExport.tsx

@@ -5,7 +5,7 @@ import { useLayoutEffect, useRef } from "react";
 import type {
   FileId,
   OrderedExcalidrawElement,
-} from "@excalidraw/excalidraw/element/types";
+} from "@excalidraw/element/types";
 import type { AppState, BinaryFileData } from "@excalidraw/excalidraw/types";
 
 import { STORAGE_KEYS } from "./app_constants";

+ 14 - 15
excalidraw-app/collab/Collab.tsx

@@ -6,30 +6,29 @@ import {
   reconcileElements,
 } from "@excalidraw/excalidraw";
 import { ErrorDialog } from "@excalidraw/excalidraw/components/ErrorDialog";
-import { APP_NAME, EVENT } from "@excalidraw/excalidraw/constants";
+import { APP_NAME, EVENT } from "@excalidraw/common";
 import {
   IDLE_THRESHOLD,
   ACTIVE_THRESHOLD,
   UserIdleState,
-} from "@excalidraw/excalidraw/constants";
+  assertNever,
+  isDevEnv,
+  isTestEnv,
+  preventUnload,
+  resolvablePromise,
+  throttleRAF,
+} from "@excalidraw/common";
 import { decryptData } from "@excalidraw/excalidraw/data/encryption";
-import { getVisibleSceneBounds } from "@excalidraw/excalidraw/element/bounds";
-import { newElementWith } from "@excalidraw/excalidraw/element/mutateElement";
+import { getVisibleSceneBounds } from "@excalidraw/element/bounds";
+import { newElementWith } from "@excalidraw/element/mutateElement";
 import {
   isImageElement,
   isInitializedImageElement,
-} from "@excalidraw/excalidraw/element/typeChecks";
+} from "@excalidraw/element/typeChecks";
 import { AbortError } from "@excalidraw/excalidraw/errors";
 import { t } from "@excalidraw/excalidraw/i18n";
 import { withBatchedUpdates } from "@excalidraw/excalidraw/reactUtils";
-import {
-  assertNever,
-  isDevEnv,
-  isTestEnv,
-  preventUnload,
-  resolvablePromise,
-  throttleRAF,
-} from "@excalidraw/excalidraw/utils";
+
 import throttle from "lodash.throttle";
 import { PureComponent } from "react";
 
@@ -43,7 +42,7 @@ import type {
   FileId,
   InitializedExcalidrawImageElement,
   OrderedExcalidrawElement,
-} from "@excalidraw/excalidraw/element/types";
+} from "@excalidraw/element/types";
 import type {
   BinaryFileData,
   ExcalidrawImperativeAPI,
@@ -51,7 +50,7 @@ import type {
   Collaborator,
   Gesture,
 } from "@excalidraw/excalidraw/types";
-import type { Mutable, ValueOf } from "@excalidraw/excalidraw/utility-types";
+import type { Mutable, ValueOf } from "@excalidraw/common/utility-types";
 
 import { appJotaiStore, atom } from "../app-jotai";
 import {

+ 3 - 3
excalidraw-app/collab/Portal.tsx

@@ -1,11 +1,11 @@
 import { CaptureUpdateAction } from "@excalidraw/excalidraw";
 import { trackEvent } from "@excalidraw/excalidraw/analytics";
 import { encryptData } from "@excalidraw/excalidraw/data/encryption";
-import { newElementWith } from "@excalidraw/excalidraw/element/mutateElement";
+import { newElementWith } from "@excalidraw/element/mutateElement";
 import throttle from "lodash.throttle";
 
-import type { UserIdleState } from "@excalidraw/excalidraw/constants";
-import type { OrderedExcalidrawElement } from "@excalidraw/excalidraw/element/types";
+import type { UserIdleState } from "@excalidraw/common";
+import type { OrderedExcalidrawElement } from "@excalidraw/element/types";
 import type {
   OnUserFollowedPayload,
   SocketId,

+ 1 - 1
excalidraw-app/components/AI.tsx

@@ -6,7 +6,7 @@ import {
   TTDDialog,
 } from "@excalidraw/excalidraw";
 import { getDataURL } from "@excalidraw/excalidraw/data/blob";
-import { safelyParseJSON } from "@excalidraw/excalidraw/utils";
+import { safelyParseJSON } from "@excalidraw/common";
 
 import type { ExcalidrawImperativeAPI } from "@excalidraw/excalidraw/types";
 

+ 2 - 2
excalidraw-app/components/AppMainMenu.tsx

@@ -6,9 +6,9 @@ import {
 import { MainMenu } from "@excalidraw/excalidraw/index";
 import React from "react";
 
-import { isDevEnv } from "@excalidraw/excalidraw/utils";
+import { isDevEnv } from "@excalidraw/common";
 
-import type { Theme } from "@excalidraw/excalidraw/element/types";
+import type { Theme } from "@excalidraw/element/types";
 
 import { LanguageList } from "../app-language/LanguageList";
 import { isExcalidrawPlusSignedUser } from "../app_constants";

+ 1 - 1
excalidraw-app/components/AppWelcomeScreen.tsx

@@ -1,5 +1,5 @@
 import { loginIcon } from "@excalidraw/excalidraw/components/icons";
-import { POINTER_EVENTS } from "@excalidraw/excalidraw/constants";
+import { POINTER_EVENTS } from "@excalidraw/common";
 import { useI18n } from "@excalidraw/excalidraw/i18n";
 import { WelcomeScreen } from "@excalidraw/excalidraw/index";
 import React from "react";

+ 8 - 7
excalidraw-app/components/DebugCanvas.tsx

@@ -8,20 +8,21 @@ import {
   getNormalizedCanvasDimensions,
 } from "@excalidraw/excalidraw/renderer/helpers";
 import { type AppState } from "@excalidraw/excalidraw/types";
-import { throttleRAF } from "@excalidraw/excalidraw/utils";
+import { throttleRAF } from "@excalidraw/common";
 import { useCallback, useImperativeHandle, useRef } from "react";
 
-import type { DebugElement } from "@excalidraw/excalidraw/visualdebug";
-
 import {
   isLineSegment,
   type GlobalPoint,
   type LineSegment,
-} from "../../packages/math";
-import { isCurve } from "../../packages/math/curve";
-import { STORAGE_KEYS } from "../app_constants";
+} from "@excalidraw/math";
+import { isCurve } from "@excalidraw/math/curve";
 
-import type { Curve } from "../../packages/math";
+import type { DebugElement } from "@excalidraw/excalidraw/visualdebug";
+
+import type { Curve } from "@excalidraw/math";
+
+import { STORAGE_KEYS } from "../app_constants";
 
 const renderLine = (
   context: CanvasRenderingContext2D,

+ 7 - 7
excalidraw-app/components/ExportToExcalidrawPlus.tsx

@@ -1,24 +1,24 @@
+import React from "react";
+import { uploadBytes, ref } from "firebase/storage";
+import { nanoid } from "nanoid";
+
 import { trackEvent } from "@excalidraw/excalidraw/analytics";
 import { Card } from "@excalidraw/excalidraw/components/Card";
 import { ExcalidrawLogo } from "@excalidraw/excalidraw/components/ExcalidrawLogo";
 import { ToolButton } from "@excalidraw/excalidraw/components/ToolButton";
-import { MIME_TYPES } from "@excalidraw/excalidraw/constants";
+import { MIME_TYPES, getFrame } from "@excalidraw/common";
 import {
   encryptData,
   generateEncryptionKey,
 } from "@excalidraw/excalidraw/data/encryption";
 import { serializeAsJSON } from "@excalidraw/excalidraw/data/json";
-import { isInitializedImageElement } from "@excalidraw/excalidraw/element/typeChecks";
+import { isInitializedImageElement } from "@excalidraw/element/typeChecks";
 import { useI18n } from "@excalidraw/excalidraw/i18n";
-import { getFrame } from "@excalidraw/excalidraw/utils";
-import { uploadBytes, ref } from "firebase/storage";
-import { nanoid } from "nanoid";
-import React from "react";
 
 import type {
   FileId,
   NonDeletedExcalidrawElement,
-} from "@excalidraw/excalidraw/element/types";
+} from "@excalidraw/element/types";
 import type {
   AppState,
   BinaryFileData,

+ 2 - 2
excalidraw-app/components/GitHubCorner.tsx

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

+ 3 - 3
excalidraw-app/data/FileManager.ts

@@ -1,7 +1,7 @@
 import { CaptureUpdateAction } from "@excalidraw/excalidraw";
 import { compressData } from "@excalidraw/excalidraw/data/encode";
-import { newElementWith } from "@excalidraw/excalidraw/element/mutateElement";
-import { isInitializedImageElement } from "@excalidraw/excalidraw/element/typeChecks";
+import { newElementWith } from "@excalidraw/element/mutateElement";
+import { isInitializedImageElement } from "@excalidraw/element/typeChecks";
 import { t } from "@excalidraw/excalidraw/i18n";
 
 import type {
@@ -9,7 +9,7 @@ import type {
   ExcalidrawImageElement,
   FileId,
   InitializedExcalidrawImageElement,
-} from "@excalidraw/excalidraw/element/types";
+} from "@excalidraw/element/types";
 import type {
   BinaryFileData,
   BinaryFileMetadata,

+ 5 - 8
excalidraw-app/data/LocalData.ts

@@ -14,9 +14,9 @@ import { clearAppStateForLocalStorage } from "@excalidraw/excalidraw/appState";
 import {
   CANVAS_SEARCH_TAB,
   DEFAULT_SIDEBAR,
-} from "@excalidraw/excalidraw/constants";
-import { clearElementsForLocalStorage } from "@excalidraw/excalidraw/element";
-import { debounce } from "@excalidraw/excalidraw/utils";
+  debounce,
+} from "@excalidraw/common";
+import { clearElementsForLocalStorage } from "@excalidraw/element";
 import {
   createStore,
   entries,
@@ -29,16 +29,13 @@ import {
 
 import type { LibraryPersistedData } from "@excalidraw/excalidraw/data/library";
 import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
-import type {
-  ExcalidrawElement,
-  FileId,
-} from "@excalidraw/excalidraw/element/types";
+import type { ExcalidrawElement, FileId } from "@excalidraw/element/types";
 import type {
   AppState,
   BinaryFileData,
   BinaryFiles,
 } from "@excalidraw/excalidraw/types";
-import type { MaybePromise } from "@excalidraw/excalidraw/utility-types";
+import type { MaybePromise } from "@excalidraw/common/utility-types";
 
 import { SAVE_TO_LOCAL_STORAGE_TIMEOUT, STORAGE_KEYS } from "../app_constants";
 

+ 3 - 3
excalidraw-app/data/firebase.ts

@@ -1,12 +1,12 @@
 import { reconcileElements } from "@excalidraw/excalidraw";
-import { MIME_TYPES } from "@excalidraw/excalidraw/constants";
+import { MIME_TYPES } from "@excalidraw/common";
 import { decompressData } from "@excalidraw/excalidraw/data/encode";
 import {
   encryptData,
   decryptData,
 } from "@excalidraw/excalidraw/data/encryption";
 import { restoreElements } from "@excalidraw/excalidraw/data/restore";
-import { getSceneVersion } from "@excalidraw/excalidraw/element";
+import { getSceneVersion } from "@excalidraw/element";
 import { initializeApp } from "firebase/app";
 import {
   getFirestore,
@@ -22,7 +22,7 @@ import type {
   ExcalidrawElement,
   FileId,
   OrderedExcalidrawElement,
-} from "@excalidraw/excalidraw/element/types";
+} from "@excalidraw/element/types";
 import type {
   AppState,
   BinaryFileData,

+ 7 - 7
excalidraw-app/data/index.ts

@@ -9,26 +9,26 @@ import {
 } from "@excalidraw/excalidraw/data/encryption";
 import { serializeAsJSON } from "@excalidraw/excalidraw/data/json";
 import { restore } from "@excalidraw/excalidraw/data/restore";
-import { isInvisiblySmallElement } from "@excalidraw/excalidraw/element/sizeHelpers";
-import { isInitializedImageElement } from "@excalidraw/excalidraw/element/typeChecks";
+import { isInvisiblySmallElement } from "@excalidraw/element/sizeHelpers";
+import { isInitializedImageElement } from "@excalidraw/element/typeChecks";
 import { t } from "@excalidraw/excalidraw/i18n";
-import { bytesToHexString } from "@excalidraw/excalidraw/utils";
+import { bytesToHexString } from "@excalidraw/common";
 
-import type { UserIdleState } from "@excalidraw/excalidraw/constants";
+import type { UserIdleState } from "@excalidraw/common";
 import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
-import type { SceneBounds } from "@excalidraw/excalidraw/element/bounds";
+import type { SceneBounds } from "@excalidraw/element/bounds";
 import type {
   ExcalidrawElement,
   FileId,
   OrderedExcalidrawElement,
-} from "@excalidraw/excalidraw/element/types";
+} from "@excalidraw/element/types";
 import type {
   AppState,
   BinaryFileData,
   BinaryFiles,
   SocketId,
 } from "@excalidraw/excalidraw/types";
-import type { MakeBrand } from "@excalidraw/excalidraw/utility-types";
+import type { MakeBrand } from "@excalidraw/common/utility-types";
 
 import {
   DELETED_ELEMENT_TIMEOUT,

+ 2 - 2
excalidraw-app/data/localStorage.ts

@@ -2,9 +2,9 @@ import {
   clearAppStateForLocalStorage,
   getDefaultAppState,
 } from "@excalidraw/excalidraw/appState";
-import { clearElementsForLocalStorage } from "@excalidraw/excalidraw/element";
+import { clearElementsForLocalStorage } from "@excalidraw/element";
 
-import type { ExcalidrawElement } from "@excalidraw/excalidraw/element/types";
+import type { ExcalidrawElement } from "@excalidraw/element/types";
 import type { AppState } from "@excalidraw/excalidraw/types";
 
 import { STORAGE_KEYS } from "../app_constants";

+ 1 - 2
excalidraw-app/share/ShareDialog.tsx

@@ -15,8 +15,7 @@ import {
 import { useUIAppState } from "@excalidraw/excalidraw/context/ui-appState";
 import { useCopyStatus } from "@excalidraw/excalidraw/hooks/useCopiedIndicator";
 import { useI18n } from "@excalidraw/excalidraw/i18n";
-import { KEYS } from "@excalidraw/excalidraw/keys";
-import { getFrame } from "@excalidraw/excalidraw/utils";
+import { KEYS, getFrame } from "@excalidraw/common";
 import { useEffect, useRef, useState } from "react";
 
 import { atom, useAtom, useAtomValue } from "../app-jotai";

+ 1 - 1
excalidraw-app/tests/collab.test.tsx

@@ -3,7 +3,7 @@ import {
   createRedoAction,
   createUndoAction,
 } from "@excalidraw/excalidraw/actions/actionHistory";
-import { syncInvalidIndices } from "@excalidraw/excalidraw/fractionalIndex";
+import { syncInvalidIndices } from "@excalidraw/element/fractionalIndex";
 import { API } from "@excalidraw/excalidraw/tests/helpers/api";
 import { act, render, waitFor } from "@excalidraw/excalidraw/tests/test-utils";
 import { vi } from "vitest";

+ 2 - 3
excalidraw-app/useHandleAppTheme.ts

@@ -1,9 +1,8 @@
 import { THEME } from "@excalidraw/excalidraw";
-import { EVENT } from "@excalidraw/excalidraw/constants";
-import { CODES, KEYS } from "@excalidraw/excalidraw/keys";
+import { EVENT, CODES, KEYS } from "@excalidraw/common";
 import { useEffect, useLayoutEffect, useState } from "react";
 
-import type { Theme } from "@excalidraw/excalidraw/element/types";
+import type { Theme } from "@excalidraw/element/types";
 
 import { STORAGE_KEYS } from "./app_constants";
 

+ 24 - 8
excalidraw-app/vite.config.mts

@@ -23,6 +23,22 @@ export default defineConfig(({ mode }) => {
     envDir: "../",
     resolve: {
       alias: [
+        {
+          find: /^@excalidraw\/common$/,
+          replacement: path.resolve(__dirname, "../packages/common/src/index.ts"),
+        },
+        {
+          find: /^@excalidraw\/common\/(.*?)/,
+          replacement: path.resolve(__dirname, "../packages/common/src/$1"),
+        },
+        {
+          find: /^@excalidraw\/element$/,
+          replacement: path.resolve(__dirname, "../packages/element/src/index.ts"),
+        },
+        {
+          find: /^@excalidraw\/element\/(.*?)/,
+          replacement: path.resolve(__dirname, "../packages/element/src/$1"),
+        },
         {
           find: /^@excalidraw\/excalidraw$/,
           replacement: path.resolve(__dirname, "../packages/excalidraw/index.tsx"),
@@ -32,20 +48,20 @@ export default defineConfig(({ mode }) => {
           replacement: path.resolve(__dirname, "../packages/excalidraw/$1"),
         },
         {
-          find: /^@excalidraw\/utils$/,
-          replacement: path.resolve(__dirname, "../packages/utils/index.ts"),
+          find: /^@excalidraw\/math$/,
+          replacement: path.resolve(__dirname, "../packages/math/src/index.ts"),
         },
         {
-          find: /^@excalidraw\/utils\/(.*?)/,
-          replacement: path.resolve(__dirname, "../packages/utils/$1"),
+          find: /^@excalidraw\/math\/(.*?)/,
+          replacement: path.resolve(__dirname, "../packages/math/src/$1"),
         },
         {
-          find: /^@excalidraw\/math$/,
-          replacement: path.resolve(__dirname, "../packages/math/index.ts"),
+          find: /^@excalidraw\/utils$/,
+          replacement: path.resolve(__dirname, "../packages/utils/src/index.ts"),
         },
         {
-          find: /^@excalidraw\/math\/(.*?)/,
-          replacement: path.resolve(__dirname, "../packages/math/$1"),
+          find: /^@excalidraw\/utils\/(.*?)/,
+          replacement: path.resolve(__dirname, "../packages/utils/src/$1"),
         },
       ],
     },

+ 1 - 3
package.json

@@ -4,9 +4,7 @@
   "packageManager": "[email protected]",
   "workspaces": [
     "excalidraw-app",
-    "packages/excalidraw",
-    "packages/utils",
-    "packages/math",
+    "packages/*",
     "examples/*"
   ],
   "devDependencies": {

+ 3 - 0
packages/common/.eslintrc.json

@@ -0,0 +1,3 @@
+{
+  "extends": ["../eslintrc.base.json"]
+}

+ 19 - 0
packages/common/README.md

@@ -0,0 +1,19 @@
+# @excalidraw/common
+
+## Install
+
+```bash
+npm install @excalidraw/common
+```
+
+If you prefer Yarn over npm, use this command to install the Excalidraw utils package:
+
+```bash
+yarn add @excalidraw/common
+```
+
+With PNPM, similarly install the package with this command:
+
+```bash
+pnpm add @excalidraw/common
+```

+ 3 - 0
packages/common/global.d.ts

@@ -0,0 +1,3 @@
+/// <reference types="vite/client" />
+import "@excalidraw/excalidraw/global";
+import "@excalidraw/excalidraw/css";

+ 65 - 0
packages/common/package.json

@@ -0,0 +1,65 @@
+{
+  "name": "@excalidraw/common",
+  "version": "0.1.0",
+  "type": "module",
+  "types": "./dist/types/common/index.d.ts",
+  "main": "./dist/prod/index.js",
+  "module": "./dist/prod/index.js",
+  "exports": {
+    ".": {
+      "types": "./dist/types/common/index.d.ts",
+      "development": "./dist/dev/index.js",
+      "production": "./dist/prod/index.js",
+      "default": "./dist/prod/index.js"
+    },
+    "./*": {
+      "types": "./../common/dist/types/common/*"
+    }
+  },
+  "files": [
+    "dist/*"
+  ],
+  "description": "Excalidraw common functions, constants, etc.",
+  "publishConfig": {
+    "access": "public"
+  },
+  "license": "MIT",
+  "keywords": [
+    "excalidraw",
+    "excalidraw-utils"
+  ],
+  "browserslist": {
+    "production": [
+      ">0.2%",
+      "not dead",
+      "not ie <= 11",
+      "not op_mini all",
+      "not safari < 12",
+      "not kaios <= 2.5",
+      "not edge < 79",
+      "not chrome < 70",
+      "not and_uc < 13",
+      "not samsung < 10"
+    ],
+    "development": [
+      "last 1 chrome version",
+      "last 1 firefox version",
+      "last 1 safari version"
+    ]
+  },
+  "dependencies": {
+    "es6-promise-pool": "2.5.0",
+    "nanoid": "3.3.3",
+    "open-color": "1.9.1",
+    "roughjs": "4.6.4"
+  },
+  "devDependencies": {
+    "typescript": "4.9.4"
+  },
+  "bugs": "https://github.com/excalidraw/excalidraw/issues",
+  "repository": "https://github.com/excalidraw/excalidraw",
+  "scripts": {
+    "gen:types": "rm -rf types && tsc",
+    "build:esm": "rm -rf dist && node ../../scripts/buildBase.js && yarn gen:types"
+  }
+}

+ 1 - 1
packages/excalidraw/binaryheap.ts → packages/common/src/binary-heap.ts

@@ -1,4 +1,4 @@
-export default class BinaryHeap<T> {
+export class BinaryHeap<T> {
   private content: T[] = [];
 
   constructor(private scoreFunction: (node: T) => number) {}

+ 0 - 0
packages/excalidraw/colors.ts → packages/common/src/colors.ts


+ 6 - 3
packages/excalidraw/constants.ts → packages/common/src/constants.ts

@@ -1,7 +1,10 @@
-import { COLOR_PALETTE } from "./colors";
+import type {
+  ExcalidrawElement,
+  FontFamilyValues,
+} from "@excalidraw/element/types";
+import type { AppProps, AppState } from "@excalidraw/excalidraw/types";
 
-import type { ExcalidrawElement, FontFamilyValues } from "./element/types";
-import type { AppProps, AppState } from "./types";
+import { COLOR_PALETTE } from "./colors";
 
 export const isDarwin = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
 export const isWindows = /^Win/.test(navigator.platform);

+ 36 - 17
packages/excalidraw/fonts/FontMetadata.ts → packages/common/src/font-metadata.ts

@@ -1,12 +1,9 @@
-import {
-  FreedrawIcon,
-  FontFamilyNormalIcon,
-  FontFamilyHeadingIcon,
-  FontFamilyCodeIcon,
-} from "../components/icons";
-import { FONT_FAMILY, FONT_FAMILY_FALLBACKS } from "../constants";
+import type {
+  ExcalidrawTextElement,
+  FontFamilyValues,
+} from "@excalidraw/element/types";
 
-import type { JSX } from "react";
+import { FONT_FAMILY, FONT_FAMILY_FALLBACKS } from "./constants";
 
 /**
  * Encapsulates font metrics with additional font metadata.
@@ -23,8 +20,6 @@ export interface FontMetadata {
     /** harcoded unitless line-height, https://github.com/excalidraw/excalidraw/pull/6360#issuecomment-1477635971 */
     lineHeight: number;
   };
-  /** element to be displayed as an icon  */
-  icon?: JSX.Element;
   /** flag to indicate a deprecated font */
   deprecated?: true;
   /** flag to indicate a server-side only font */
@@ -43,7 +38,6 @@ export const FONT_METADATA: Record<number, FontMetadata> = {
       descender: -374,
       lineHeight: 1.25,
     },
-    icon: FreedrawIcon,
   },
   [FONT_FAMILY.Nunito]: {
     metrics: {
@@ -52,7 +46,6 @@ export const FONT_METADATA: Record<number, FontMetadata> = {
       descender: -353,
       lineHeight: 1.35,
     },
-    icon: FontFamilyNormalIcon,
   },
   [FONT_FAMILY["Lilita One"]]: {
     metrics: {
@@ -61,7 +54,6 @@ export const FONT_METADATA: Record<number, FontMetadata> = {
       descender: -220,
       lineHeight: 1.15,
     },
-    icon: FontFamilyHeadingIcon,
   },
   [FONT_FAMILY["Comic Shanns"]]: {
     metrics: {
@@ -70,7 +62,6 @@ export const FONT_METADATA: Record<number, FontMetadata> = {
       descender: -250,
       lineHeight: 1.25,
     },
-    icon: FontFamilyCodeIcon,
   },
   [FONT_FAMILY.Virgil]: {
     metrics: {
@@ -79,7 +70,6 @@ export const FONT_METADATA: Record<number, FontMetadata> = {
       descender: -374,
       lineHeight: 1.25,
     },
-    icon: FreedrawIcon,
     deprecated: true,
   },
   [FONT_FAMILY.Helvetica]: {
@@ -89,7 +79,6 @@ export const FONT_METADATA: Record<number, FontMetadata> = {
       descender: -471,
       lineHeight: 1.15,
     },
-    icon: FontFamilyNormalIcon,
     deprecated: true,
     local: true,
   },
@@ -100,7 +89,6 @@ export const FONT_METADATA: Record<number, FontMetadata> = {
       descender: -480,
       lineHeight: 1.2,
     },
-    icon: FontFamilyCodeIcon,
     deprecated: true,
   },
   [FONT_FAMILY["Liberation Sans"]]: {
@@ -149,3 +137,34 @@ export const GOOGLE_FONTS_RANGES = {
 
 /** local protocol to skip the local font from registering or inlining */
 export const LOCAL_FONT_PROTOCOL = "local:";
+
+/**
+ * Calculates vertical offset for a text with alphabetic baseline.
+ */
+export const getVerticalOffset = (
+  fontFamily: ExcalidrawTextElement["fontFamily"],
+  fontSize: ExcalidrawTextElement["fontSize"],
+  lineHeightPx: number,
+) => {
+  const { unitsPerEm, ascender, descender } =
+    FONT_METADATA[fontFamily]?.metrics ||
+    FONT_METADATA[FONT_FAMILY.Excalifont].metrics;
+
+  const fontSizeEm = fontSize / unitsPerEm;
+  const lineGap =
+    (lineHeightPx - fontSizeEm * ascender + fontSizeEm * descender) / 2;
+
+  const verticalOffset = fontSizeEm * ascender + lineGap;
+  return verticalOffset;
+};
+
+/**
+ * Gets line height for a selected family.
+ */
+export const getLineHeight = (fontFamily: FontFamilyValues) => {
+  const { lineHeight } =
+    FONT_METADATA[fontFamily]?.metrics ||
+    FONT_METADATA[FONT_FAMILY.Excalifont].metrics;
+
+  return lineHeight as ExcalidrawTextElement["lineHeight"];
+};

+ 11 - 0
packages/common/src/index.ts

@@ -0,0 +1,11 @@
+export * from "./binary-heap";
+export * from "./colors";
+export * from "./constants";
+export * from "./font-metadata";
+export * from "./queue";
+export * from "./keys";
+export * from "./points";
+export * from "./promise-pool";
+export * from "./random";
+export * from "./url";
+export * from "./utils";

+ 0 - 0
packages/excalidraw/keys.ts → packages/common/src/keys.ts


+ 17 - 0
packages/excalidraw/points.ts → packages/common/src/points.ts

@@ -4,6 +4,8 @@ import {
   type LocalPoint,
 } from "@excalidraw/math";
 
+import type { NullableGridSize } from "@excalidraw/excalidraw/types";
+
 export const getSizeFromPoints = (
   points: readonly (GlobalPoint | LocalPoint)[],
 ) => {
@@ -61,3 +63,18 @@ export const rescalePoints = <Point extends GlobalPoint | LocalPoint>(
 
   return nextPoints;
 };
+
+// TODO: Rounding this point causes some shake when free drawing
+export const getGridPoint = (
+  x: number,
+  y: number,
+  gridSize: NullableGridSize,
+): [number, number] => {
+  if (gridSize) {
+    return [
+      Math.round(x / gridSize) * gridSize,
+      Math.round(y / gridSize) * gridSize,
+    ];
+  }
+  return [x, y];
+};

+ 50 - 0
packages/common/src/promise-pool.ts

@@ -0,0 +1,50 @@
+import Pool from "es6-promise-pool";
+
+// extending the missing types
+// relying on the [Index, T] to keep a correct order
+type TPromisePool<T, Index = number> = Pool<[Index, T][]> & {
+  addEventListener: (
+    type: "fulfilled",
+    listener: (event: { data: { result: [Index, T] } }) => void,
+  ) => (event: { data: { result: [Index, T] } }) => void;
+  removeEventListener: (
+    type: "fulfilled",
+    listener: (event: { data: { result: [Index, T] } }) => void,
+  ) => void;
+};
+
+export class PromisePool<T> {
+  private readonly pool: TPromisePool<T>;
+  private readonly entries: Record<number, T> = {};
+
+  constructor(
+    source: IterableIterator<Promise<void | readonly [number, T]>>,
+    concurrency: number,
+  ) {
+    this.pool = new Pool(
+      source as unknown as () => void | PromiseLike<[number, T][]>,
+      concurrency,
+    ) as TPromisePool<T>;
+  }
+
+  public all() {
+    const listener = (event: { data: { result: void | [number, T] } }) => {
+      if (event.data.result) {
+        // by default pool does not return the results, so we are gathering them manually
+        // with the correct call order (represented by the index in the tuple)
+        const [index, value] = event.data.result;
+        this.entries[index] = value;
+      }
+    };
+
+    this.pool.addEventListener("fulfilled", listener);
+
+    return this.pool.start().then(() => {
+      setTimeout(() => {
+        this.pool.removeEventListener("fulfilled", listener);
+      });
+
+      return Object.values(this.entries);
+    });
+  }
+}

+ 3 - 2
packages/excalidraw/queue.ts → packages/common/src/queue.ts

@@ -1,7 +1,8 @@
-import { promiseTry, resolvablePromise } from "./utils";
+import { promiseTry, resolvablePromise } from ".";
+
+import type { ResolvablePromise } from ".";
 
 import type { MaybePromise } from "./utility-types";
-import type { ResolvablePromise } from "./utils";
 
 type Job<T, TArgs extends unknown[]> = (...args: TArgs) => MaybePromise<T>;
 

+ 0 - 0
packages/excalidraw/random.ts → packages/common/src/random.ts


+ 1 - 1
packages/excalidraw/data/url.ts → packages/common/src/url.ts

@@ -1,6 +1,6 @@
 import { sanitizeUrl } from "@braintree/sanitize-url";
 
-import { escapeDoubleQuotes } from "../utils";
+import { escapeDoubleQuotes } from "./utils";
 
 export const normalizeLink = (link: string) => {
   link = link.trim();

+ 0 - 0
packages/excalidraw/utility-types.ts → packages/common/src/utility-types.ts


+ 18 - 63
packages/excalidraw/utils.ts → packages/common/src/utils.ts

@@ -1,30 +1,33 @@
 import { average } from "@excalidraw/math";
-import Pool from "es6-promise-pool";
 
-import { COLOR_PALETTE } from "./colors";
-import {
-  DEFAULT_VERSION,
-  FONT_FAMILY,
-  getFontFamilyFallbacks,
-  isDarwin,
-  WINDOWS_EMOJI_FALLBACK_FONT,
-} from "./constants";
-
-import type { EVENT } from "./constants";
 import type {
   ExcalidrawBindableElement,
   FontFamilyValues,
   FontString,
-} from "./element/types";
+} from "@excalidraw/element/types";
+
 import type {
   ActiveTool,
   AppState,
   ToolType,
   UnsubscribeCallback,
   Zoom,
-} from "./types";
+} from "@excalidraw/excalidraw/types";
+
+import { COLOR_PALETTE } from "./colors";
+import {
+  DEFAULT_VERSION,
+  ENV,
+  FONT_FAMILY,
+  getFontFamilyFallbacks,
+  isDarwin,
+  WINDOWS_EMOJI_FALLBACK_FONT,
+} from "./constants";
+
 import type { MaybePromise, ResolutionType } from "./utility-types";
 
+import type { EVENT } from "./constants";
+
 let mockDateTime: string | null = null;
 
 export const setDateTimeForTests = (dateTime: string) => {
@@ -730,9 +733,9 @@ export const arrayToList = <T>(array: readonly T[]): Node<T>[] =>
     return acc;
   }, [] as Node<T>[]);
 
-export const isTestEnv = () => import.meta.env.MODE === "test";
+export const isTestEnv = () => import.meta.env.MODE === ENV.TEST;
 
-export const isDevEnv = () => import.meta.env.MODE === "development";
+export const isDevEnv = () => import.meta.env.MODE === ENV.DEVELOPMENT;
 
 export const isServerEnv = () =>
   typeof process !== "undefined" && !!process?.env?.NODE_ENV;
@@ -1186,54 +1189,6 @@ export const safelyParseJSON = (json: string): Record<string, any> | null => {
     return null;
   }
 };
-// extending the missing types
-// relying on the [Index, T] to keep a correct order
-type TPromisePool<T, Index = number> = Pool<[Index, T][]> & {
-  addEventListener: (
-    type: "fulfilled",
-    listener: (event: { data: { result: [Index, T] } }) => void,
-  ) => (event: { data: { result: [Index, T] } }) => void;
-  removeEventListener: (
-    type: "fulfilled",
-    listener: (event: { data: { result: [Index, T] } }) => void,
-  ) => void;
-};
-
-export class PromisePool<T> {
-  private readonly pool: TPromisePool<T>;
-  private readonly entries: Record<number, T> = {};
-
-  constructor(
-    source: IterableIterator<Promise<void | readonly [number, T]>>,
-    concurrency: number,
-  ) {
-    this.pool = new Pool(
-      source as unknown as () => void | PromiseLike<[number, T][]>,
-      concurrency,
-    ) as TPromisePool<T>;
-  }
-
-  public all() {
-    const listener = (event: { data: { result: void | [number, T] } }) => {
-      if (event.data.result) {
-        // by default pool does not return the results, so we are gathering them manually
-        // with the correct call order (represented by the index in the tuple)
-        const [index, value] = event.data.result;
-        this.entries[index] = value;
-      }
-    };
-
-    this.pool.addEventListener("fulfilled", listener);
-
-    return this.pool.start().then(() => {
-      setTimeout(() => {
-        this.pool.removeEventListener("fulfilled", listener);
-      });
-
-      return Object.values(this.entries);
-    });
-  }
-}
 
 /**
  * use when you need to render unsafe string as HTML attribute, but MAKE SURE

+ 1 - 1
packages/excalidraw/keys.test.ts → packages/common/tests/keys.test.ts

@@ -1,4 +1,4 @@
-import { KEYS, matchKey } from "./keys";
+import { KEYS, matchKey } from "../src/keys";
 
 describe("key matcher", async () => {
   it("should not match unexpected key", async () => {

+ 1 - 1
packages/excalidraw/queue.test.ts → packages/common/tests/queue.test.ts

@@ -1,4 +1,4 @@
-import { Queue } from "./queue";
+import { Queue } from "../src/queue";
 
 describe("Queue", () => {
   const calls: any[] = [];

+ 1 - 1
packages/excalidraw/data/url.test.tsx → packages/common/tests/url.test.tsx

@@ -1,4 +1,4 @@
-import { normalizeLink } from "./url";
+import { normalizeLink } from "../src/url";
 
 describe("normalizeLink", () => {
   // NOTE not an extensive XSS test suite, just to check if we're not

+ 6 - 0
packages/common/tsconfig.json

@@ -0,0 +1,6 @@
+{
+  "extends": "../tsconfig.base.json",
+  "compilerOptions": {
+    "outDir": "./dist/types"
+  }
+}

+ 3 - 0
packages/element/.eslintrc.json

@@ -0,0 +1,3 @@
+{
+  "extends": ["../eslintrc.base.json"]
+}

+ 19 - 0
packages/element/README.md

@@ -0,0 +1,19 @@
+# @excalidraw/element
+
+## Install
+
+```bash
+npm install @excalidraw/element
+```
+
+If you prefer Yarn over npm, use this command to install the Excalidraw utils package:
+
+```bash
+yarn add @excalidraw/element
+```
+
+With PNPM, similarly install the package with this command:
+
+```bash
+pnpm add @excalidraw/element
+```

+ 3 - 0
packages/element/global.d.ts

@@ -0,0 +1,3 @@
+/// <reference types="vite/client" />
+import "@excalidraw/excalidraw/global";
+import "@excalidraw/excalidraw/css";

+ 63 - 0
packages/element/package.json

@@ -0,0 +1,63 @@
+{
+  "name": "@excalidraw/element",
+  "version": "0.1.0",
+  "type": "module",
+  "types": "./dist/types/element/index.d.ts",
+  "main": "./dist/prod/index.js",
+  "module": "./dist/prod/index.js",
+  "exports": {
+    ".": {
+      "types": "./dist/types/element/index.d.ts",
+      "development": "./dist/dev/index.js",
+      "production": "./dist/prod/index.js",
+      "default": "./dist/prod/index.js"
+    },
+    "./*": {
+      "types": "./../element/dist/types/element/*"
+    }
+  },
+  "files": [
+    "dist/*"
+  ],
+  "description": "Excalidraw elements-related logic",
+  "publishConfig": {
+    "access": "public"
+  },
+  "license": "MIT",
+  "keywords": [
+    "excalidraw",
+    "excalidraw-utils"
+  ],
+  "browserslist": {
+    "production": [
+      ">0.2%",
+      "not dead",
+      "not ie <= 11",
+      "not op_mini all",
+      "not safari < 12",
+      "not kaios <= 2.5",
+      "not edge < 79",
+      "not chrome < 70",
+      "not and_uc < 13",
+      "not samsung < 10"
+    ],
+    "development": [
+      "last 1 chrome version",
+      "last 1 firefox version",
+      "last 1 safari version"
+    ]
+  },
+  "dependencies": {
+    "fractional-indexing": "3.2.0",
+    "perfect-freehand": "1.2.0",
+    "points-on-curve": "1.0.1",
+    "roughjs": "4.6.4"
+  },
+  "devDependencies": {},
+  "bugs": "https://github.com/excalidraw/excalidraw/issues",
+  "repository": "https://github.com/excalidraw/excalidraw",
+  "scripts": {
+    "gen:types": "rm -rf types && tsc",
+    "build:esm": "rm -rf dist && node ../../scripts/buildBase.js && yarn gen:types"
+  }
+}

+ 19 - 12
packages/excalidraw/scene/Shape.ts → packages/element/src/Shape.ts

@@ -1,21 +1,26 @@
-import { pointFrom, pointDistance, type LocalPoint } from "@excalidraw/math";
 import { simplify } from "points-on-curve";
 
-import { ROUGHNESS } from "../constants";
-import { getDiamondPoints, getArrowheadPoints } from "../element";
-import { headingForPointIsHorizontal } from "../element/heading";
+import { pointFrom, pointDistance, type LocalPoint } from "@excalidraw/math";
+import { ROUGHNESS, isTransparent, assertNever } from "@excalidraw/common";
+
+import type { Mutable } from "@excalidraw/common/utility-types";
+
+import type { EmbedsValidationStatus } from "@excalidraw/excalidraw/types";
+import type { ElementShapes } from "@excalidraw/excalidraw/scene/types";
+
 import {
   isElbowArrow,
   isEmbeddableElement,
   isIframeElement,
   isIframeLikeElement,
   isLinearElement,
-} from "../element/typeChecks";
-import { generateFreeDrawShape } from "../renderer/renderElement";
-import { getCornerRadius, isPathALoop } from "../shapes";
-import { isTransparent, assertNever } from "../utils";
+} from "./typeChecks";
+import { getCornerRadius, isPathALoop } from "./shapes";
+import { headingForPointIsHorizontal } from "./heading";
 
 import { canChangeRoundness } from "./comparisons";
+import { generateFreeDrawShape } from "./renderElement";
+import { getArrowheadPoints, getDiamondPoints } from "./bounds";
 
 import type {
   ExcalidrawElement,
@@ -23,9 +28,8 @@ import type {
   ExcalidrawSelectionElement,
   ExcalidrawLinearElement,
   Arrowhead,
-} from "../element/types";
-import type { EmbedsValidationStatus } from "../types";
-import type { ElementShapes } from "./types";
+} from "./types";
+
 import type { Drawable, Options } from "roughjs/bin/core";
 import type { RoughGenerator } from "roughjs/bin/generator";
 import type { Point as RoughPoint } from "roughjs/bin/geometry";
@@ -511,7 +515,10 @@ export const _generateElementShape = (
 
       if (isPathALoop(element.points)) {
         // generate rough polygon to fill freedraw shape
-        const simplifiedPoints = simplify(element.points, 0.75);
+        const simplifiedPoints = simplify(
+          element.points as Mutable<LocalPoint[]>,
+          0.75,
+        );
         shape = generator.curve(simplifiedPoints as [number, number][], {
           ...generateRoughOptions(element),
           stroke: "none",

+ 14 - 8
packages/excalidraw/scene/ShapeCache.ts → packages/element/src/ShapeCache.ts

@@ -1,16 +1,22 @@
 import { RoughGenerator } from "roughjs/bin/generator";
 
-import { COLOR_PALETTE } from "../colors";
-import { elementWithCanvasCache } from "../renderer/renderElement";
+import { COLOR_PALETTE } from "@excalidraw/common";
+
+import type {
+  AppState,
+  EmbedsValidationStatus,
+} from "@excalidraw/excalidraw/types";
+import type {
+  ElementShape,
+  ElementShapes,
+} from "@excalidraw/excalidraw/scene/types";
 
 import { _generateElementShape } from "./Shape";
 
-import type {
-  ExcalidrawElement,
-  ExcalidrawSelectionElement,
-} from "../element/types";
-import type { AppState, EmbedsValidationStatus } from "../types";
-import type { ElementShape, ElementShapes } from "./types";
+import { elementWithCanvasCache } from "./renderElement";
+
+import type { ExcalidrawElement, ExcalidrawSelectionElement } from "./types";
+
 import type { Drawable } from "roughjs/bin/core";
 
 export class ShapeCache {

+ 7 - 6
packages/excalidraw/align.ts → packages/element/src/align.ts

@@ -1,11 +1,12 @@
-import { updateBoundElements } from "./element/binding";
-import { getCommonBoundingBox } from "./element/bounds";
-import { mutateElement } from "./element/mutateElement";
+import type Scene from "@excalidraw/excalidraw/scene/Scene";
+
+import { updateBoundElements } from "./binding";
+import { getCommonBoundingBox } from "./bounds";
+import { mutateElement } from "./mutateElement";
 import { getMaximumGroups } from "./groups";
 
-import type { BoundingBox } from "./element/bounds";
-import type { ElementsMap, ExcalidrawElement } from "./element/types";
-import type Scene from "./scene/Scene";
+import type { BoundingBox } from "./bounds";
+import type { ElementsMap, ExcalidrawElement } from "./types";
 
 export interface Alignment {
   position: "start" | "center" | "end";

+ 17 - 14
packages/excalidraw/element/binding.ts → packages/element/src/binding.ts

@@ -1,3 +1,13 @@
+import {
+  KEYS,
+  arrayToMap,
+  isBindingFallthroughEnabled,
+  tupleToCoors,
+  invariant,
+  isDevEnv,
+  isTestEnv,
+} from "@excalidraw/common";
+
 import {
   lineSegment,
   pointFrom,
@@ -15,20 +25,16 @@ import {
   lineSegmentIntersectionPoints,
   PRECISION,
 } from "@excalidraw/math";
+
 import { isPointOnShape } from "@excalidraw/utils/collision";
 
 import type { LocalPoint, Radians } from "@excalidraw/math";
 
-import { KEYS } from "../keys";
-import { aabbForElement, getElementShape, pointInsideBounds } from "../shapes";
-import {
-  arrayToMap,
-  invariant,
-  isBindingFallthroughEnabled,
-  isDevEnv,
-  isTestEnv,
-  tupleToCoors,
-} from "../utils";
+import type Scene from "@excalidraw/excalidraw/scene/Scene";
+
+import type { AppState } from "@excalidraw/excalidraw/types";
+
+import type { Mutable } from "@excalidraw/common/utility-types";
 
 import {
   getCenterForBounds,
@@ -58,10 +64,9 @@ import {
   isTextElement,
 } from "./typeChecks";
 
+import { aabbForElement, getElementShape, pointInsideBounds } from "./shapes";
 import { updateElbowArrowPoints } from "./elbowArrow";
 
-import type { Mutable } from "../utility-types";
-
 import type { Bounds } from "./bounds";
 import type { ElementUpdate } from "./mutateElement";
 import type {
@@ -81,8 +86,6 @@ import type {
   SceneElementsMap,
   FixedPointBinding,
 } from "./types";
-import type Scene from "../scene/Scene";
-import type { AppState } from "../types";
 
 export type SuggestedBinding =
   | NonDeleted<ExcalidrawBindableElement>

+ 11 - 8
packages/excalidraw/element/bounds.ts → packages/element/src/bounds.ts

@@ -1,3 +1,7 @@
+import rough from "roughjs/bin/rough";
+
+import { rescalePoints, arrayToMap, invariant } from "@excalidraw/common";
+
 import {
   degreesToRadians,
   lineSegment,
@@ -6,8 +10,8 @@ import {
   pointFromArray,
   pointRotateRads,
 } from "@excalidraw/math";
-import { getCurvePathOps } from "@excalidraw/utils/geometry/shape";
-import rough from "roughjs/bin/rough";
+
+import { getCurvePathOps } from "@excalidraw/utils/shape";
 
 import type {
   Degrees,
@@ -17,11 +21,12 @@ import type {
   Radians,
 } from "@excalidraw/math";
 
-import { rescalePoints } from "../points";
-import { generateRoughOptions } from "../scene/Shape";
-import { ShapeCache } from "../scene/ShapeCache";
-import { arrayToMap, invariant } from "../utils";
+import type { AppState } from "@excalidraw/excalidraw/types";
+
+import type { Mutable } from "@excalidraw/common/utility-types";
 
+import { ShapeCache } from "./ShapeCache";
+import { generateRoughOptions } from "./Shape";
 import { LinearElementEditor } from "./linearElementEditor";
 import { getBoundTextElement, getContainerElement } from "./textElement";
 import {
@@ -32,8 +37,6 @@ import {
   isTextElement,
 } from "./typeChecks";
 
-import type { AppState } from "../types";
-import type { Mutable } from "../utility-types";
 import type {
   ExcalidrawElement,
   ExcalidrawLinearElement,

+ 8 - 5
packages/excalidraw/element/collision.ts → packages/element/src/collision.ts

@@ -1,3 +1,4 @@
+import { isTransparent } from "@excalidraw/common";
 import {
   curveIntersectLineSegment,
   isPointWithinBounds,
@@ -8,12 +9,14 @@ import {
   pointRotateRads,
   pointsEqual,
 } from "@excalidraw/math";
+
 import {
   ellipse,
   ellipseLineIntersectionPoints,
 } from "@excalidraw/math/ellipse";
+
 import { isPointInShape, isPointOnShape } from "@excalidraw/utils/collision";
-import { getPolygonShape } from "@excalidraw/utils/geometry/shape";
+import { getPolygonShape } from "@excalidraw/utils/shape";
 
 import type {
   GlobalPoint,
@@ -22,11 +25,12 @@ import type {
   Polygon,
   Radians,
 } from "@excalidraw/math";
-import type { GeometricShape } from "@excalidraw/utils/geometry/shape";
 
-import { getBoundTextShape, isPathALoop } from "../shapes";
-import { isTransparent } from "../utils";
+import type { GeometricShape } from "@excalidraw/utils/shape";
+
+import type { FrameNameBounds } from "@excalidraw/excalidraw/types";
 
+import { getBoundTextShape, isPathALoop } from "./shapes";
 import { getElementBounds } from "./bounds";
 import {
   hasBoundTextElement,
@@ -47,7 +51,6 @@ import type {
   ExcalidrawRectangleElement,
   ExcalidrawRectanguloidElement,
 } from "./types";
-import type { FrameNameBounds } from "../types";
 
 export const shouldTestInside = (element: ExcalidrawElement) => {
   if (element.type === "arrow") {

+ 1 - 1
packages/excalidraw/scene/comparisons.ts → packages/element/src/comparisons.ts

@@ -1,4 +1,4 @@
-import type { ElementOrToolType } from "../types";
+import type { ElementOrToolType } from "@excalidraw/excalidraw/types";
 
 export const hasBackground = (type: ElementOrToolType) =>
   type === "rectangle" ||

+ 0 - 0
packages/excalidraw/element/containerCache.ts → packages/element/src/containerCache.ts


+ 0 - 0
packages/excalidraw/element/cropElement.ts → packages/element/src/cropElement.ts


+ 1 - 0
packages/excalidraw/element/distance.ts → packages/element/src/distance.ts

@@ -4,6 +4,7 @@ import {
   pointFrom,
   pointRotateRads,
 } from "@excalidraw/math";
+
 import { ellipse, ellipseDistanceFromPoint } from "@excalidraw/math/ellipse";
 
 import type { GlobalPoint, Radians } from "@excalidraw/math";

+ 4 - 3
packages/excalidraw/distribute.ts → packages/element/src/distribute.ts

@@ -1,8 +1,9 @@
-import { getCommonBoundingBox } from "./element/bounds";
-import { newElementWith } from "./element/mutateElement";
+import { getCommonBoundingBox } from "./bounds";
+import { newElementWith } from "./mutateElement";
+
 import { getMaximumGroups } from "./groups";
 
-import type { ElementsMap, ExcalidrawElement } from "./element/types";
+import type { ElementsMap, ExcalidrawElement } from "./types";
 
 export interface Distribution {
   space: "between";

+ 17 - 11
packages/excalidraw/element/dragElements.ts → packages/element/src/dragElements.ts

@@ -1,6 +1,19 @@
-import { TEXT_AUTOWRAP_THRESHOLD } from "../constants";
-import { getGridPoint } from "../snapping";
-import { getFontString } from "../utils";
+import {
+  TEXT_AUTOWRAP_THRESHOLD,
+  getGridPoint,
+  getFontString,
+} from "@excalidraw/common";
+
+import type {
+  AppState,
+  NormalizedZoomValue,
+  NullableGridSize,
+  PointerDownState,
+} from "@excalidraw/excalidraw/types";
+
+import type Scene from "@excalidraw/excalidraw/scene/Scene";
+
+import type { NonDeletedExcalidrawElement } from "@excalidraw/element/types";
 
 import { updateBoundElements } from "./binding";
 import { getCommonBounds } from "./bounds";
@@ -17,14 +30,7 @@ import {
 } from "./typeChecks";
 
 import type { Bounds } from "./bounds";
-import type { ExcalidrawElement, NonDeletedExcalidrawElement } from "./types";
-import type Scene from "../scene/Scene";
-import type {
-  AppState,
-  NormalizedZoomValue,
-  NullableGridSize,
-  PointerDownState,
-} from "../types";
+import type { ExcalidrawElement } from "./types";
 
 export const dragSelectedElements = (
   pointerDownState: PointerDownState,

+ 15 - 14
packages/excalidraw/element/duplicate.ts → packages/element/src/duplicate.ts

@@ -1,24 +1,28 @@
-import { ORIG_ID } from "../constants";
-import {
-  getElementsInGroup,
-  getNewGroupIdsForDuplication,
-  getSelectedGroupForElement,
-} from "../groups";
-
-import { randomId, randomInteger } from "../random";
-
 import {
+  ORIG_ID,
+  randomId,
+  randomInteger,
   arrayToMap,
   castArray,
   findLastIndex,
   getUpdatedTimestamp,
   isTestEnv,
-} from "../utils";
+} from "@excalidraw/common";
+
+import type { Mutable } from "@excalidraw/common/utility-types";
+
+import type { AppState } from "@excalidraw/excalidraw/types";
+
+import {
+  getElementsInGroup,
+  getNewGroupIdsForDuplication,
+  getSelectedGroupForElement,
+} from "./groups";
 
 import {
   bindElementsToFramesAfterDuplication,
   getFrameChildren,
-} from "../frame";
+} from "./frame";
 
 import { normalizeElementOrder } from "./sortElements";
 
@@ -34,9 +38,6 @@ import { getBoundTextElement, getContainerElement } from "./textElement";
 
 import { fixBindingsAfterDuplication } from "./binding";
 
-import type { AppState } from "../types";
-import type { Mutable } from "../utility-types";
-
 import type {
   ElementsMap,
   ExcalidrawElement,

+ 12 - 5
packages/excalidraw/element/elbowArrow.ts → packages/element/src/elbowArrow.ts

@@ -13,10 +13,16 @@ import {
   type LocalPoint,
 } from "@excalidraw/math";
 
-import BinaryHeap from "../binaryheap";
-import { getSizeFromPoints } from "../points";
-import { aabbForElement, pointInsideBounds } from "../shapes";
-import { invariant, isAnyTrue, isDevEnv, tupleToCoors } from "../utils";
+import {
+  BinaryHeap,
+  invariant,
+  isAnyTrue,
+  tupleToCoors,
+  getSizeFromPoints,
+  isDevEnv,
+} from "@excalidraw/common";
+
+import type { AppState } from "@excalidraw/excalidraw/types";
 
 import {
   bindPointToSnapToElementOutline,
@@ -47,6 +53,8 @@ import {
   type SceneElementsMap,
 } from "./types";
 
+import { aabbForElement, pointInsideBounds } from "./shapes";
+
 import type { Bounds } from "./bounds";
 import type { Heading } from "./heading";
 import type {
@@ -57,7 +65,6 @@ import type {
   FixedSegment,
   NonDeletedExcalidrawElement,
 } from "./types";
-import type { AppState } from "../types";
 
 type GridAddress = [number, number] & { _brand: "gridaddress" };
 

+ 5 - 4
packages/excalidraw/element/elementLink.ts → packages/element/src/elementLink.ts

@@ -2,11 +2,12 @@
  * Create and link between shapes.
  */
 
-import { ELEMENT_LINK_KEY } from "../constants";
-import { normalizeLink } from "../data/url";
-import { elementsAreInSameGroup } from "../groups";
+import { ELEMENT_LINK_KEY, normalizeLink } from "@excalidraw/common";
+
+import type { AppProps, AppState } from "@excalidraw/excalidraw/types";
+
+import { elementsAreInSameGroup } from "./groups";
 
-import type { AppProps, AppState } from "../types";
 import type { ExcalidrawElement } from "./types";
 
 export const defaultGetElementLinkFromSelection: Exclude<

+ 9 - 35
packages/excalidraw/element/embeddable.ts → packages/element/src/embeddable.ts

@@ -1,15 +1,17 @@
-import { register } from "../actions/register";
-import { FONT_FAMILY, VERTICAL_ALIGN } from "../constants";
-import { setCursorForShape } from "../cursor";
-import { CaptureUpdateAction } from "../store";
-import { escapeDoubleQuotes, getFontString, updateActiveTool } from "../utils";
+import {
+  FONT_FAMILY,
+  VERTICAL_ALIGN,
+  escapeDoubleQuotes,
+  getFontString,
+} from "@excalidraw/common";
+
+import type { ExcalidrawProps } from "@excalidraw/excalidraw/types";
+import type { MarkRequired } from "@excalidraw/common/utility-types";
 
 import { newTextElement } from "./newElement";
 import { wrapText } from "./textWrapping";
 import { isIframeElement } from "./typeChecks";
 
-import type { ExcalidrawProps } from "../types";
-import type { MarkRequired } from "../utility-types";
 import type {
   ExcalidrawElement,
   ExcalidrawIframeLikeElement,
@@ -319,34 +321,6 @@ export const createPlaceholderEmbeddableLabel = (
   });
 };
 
-export const actionSetEmbeddableAsActiveTool = register({
-  name: "setEmbeddableAsActiveTool",
-  trackEvent: { category: "toolbar" },
-  target: "Tool",
-  label: "toolBar.embeddable",
-  perform: (elements, appState, _, app) => {
-    const nextActiveTool = updateActiveTool(appState, {
-      type: "embeddable",
-    });
-
-    setCursorForShape(app.canvas, {
-      ...appState,
-      activeTool: nextActiveTool,
-    });
-
-    return {
-      elements,
-      appState: {
-        ...appState,
-        activeTool: updateActiveTool(appState, {
-          type: "embeddable",
-        }),
-      },
-      captureUpdate: CaptureUpdateAction.EVENTUALLY,
-    };
-  },
-});
-
 const matchHostname = (
   url: string,
   /** using a Set assumes it already contains normalized bare domains */

+ 8 - 6
packages/excalidraw/element/flowchart.ts → packages/element/src/flowchart.ts

@@ -1,9 +1,11 @@
+import { KEYS, invariant, toBrandedType } from "@excalidraw/common";
+
 import { type GlobalPoint, pointFrom, type LocalPoint } from "@excalidraw/math";
 
-import { elementOverlapsWithFrame, elementsAreInFrameBounds } from "../frame";
-import { KEYS } from "../keys";
-import { aabbForElement } from "../shapes";
-import { invariant, toBrandedType } from "../utils";
+import type {
+  AppState,
+  PendingExcalidrawElements,
+} from "@excalidraw/excalidraw/types";
 
 import { bindLinearElement } from "./binding";
 import { updateElbowArrowPoints } from "./elbowArrow";
@@ -19,6 +21,8 @@ import {
 import { LinearElementEditor } from "./linearElementEditor";
 import { mutateElement } from "./mutateElement";
 import { newArrowElement, newElement } from "./newElement";
+import { aabbForElement } from "./shapes";
+import { elementsAreInFrameBounds, elementOverlapsWithFrame } from "./frame";
 import {
   isBindableElement,
   isElbowArrow,
@@ -35,8 +39,6 @@ import {
   type OrderedExcalidrawElement,
 } from "./types";
 
-import type { AppState, PendingExcalidrawElements } from "../types";
-
 type LinkDirection = "up" | "right" | "down" | "left";
 
 const VERTICAL_OFFSET = 100;

+ 10 - 6
packages/excalidraw/fractionalIndex.ts → packages/element/src/fractionalIndex.ts

@@ -1,16 +1,20 @@
 import { generateNKeysBetween } from "fractional-indexing";
 
-import { mutateElement } from "./element/mutateElement";
-import { getBoundTextElement } from "./element/textElement";
-import { hasBoundTextElement } from "./element/typeChecks";
-import { InvalidFractionalIndexError } from "./errors";
-import { arrayToMap } from "./utils";
+import { arrayToMap } from "@excalidraw/common";
+
+import { mutateElement } from "./mutateElement";
+import { getBoundTextElement } from "./textElement";
+import { hasBoundTextElement } from "./typeChecks";
 
 import type {
   ExcalidrawElement,
   FractionalIndex,
   OrderedExcalidrawElement,
-} from "./element/types";
+} from "./types";
+
+export class InvalidFractionalIndexError extends Error {
+  public code = "ELEMENT_HAS_INVALID_INDEX" as const;
+}
 
 /**
  * Envisioned relation between array order and fractional indices:

+ 24 - 22
packages/excalidraw/frame.ts → packages/element/src/frame.ts

@@ -1,24 +1,33 @@
+import { arrayToMap } from "@excalidraw/common";
 import { isPointWithinBounds, pointFrom } from "@excalidraw/math";
-import {
-  doLineSegmentsIntersect,
-  elementsOverlappingBBox,
-} from "@excalidraw/utils";
+import { doLineSegmentsIntersect } from "@excalidraw/utils/bbox";
+import { elementsOverlappingBBox } from "@excalidraw/utils/withinBounds";
+
+import type { ExcalidrawElementsIncludingDeleted } from "@excalidraw/excalidraw/scene/Scene";
+
+import type {
+  AppClassProperties,
+  AppState,
+  StaticCanvasAppState,
+} from "@excalidraw/excalidraw/types";
+
+import type { ReadonlySetLike } from "@excalidraw/common/utility-types";
+
+import { getElementsWithinSelection, getSelectedElements } from "./selection";
+import { getElementsInGroup, selectGroupsFromGivenElements } from "./groups";
 
 import {
+  getElementLineSegments,
   getCommonBounds,
   getElementAbsoluteCoords,
-  isTextElement,
-} from "./element";
-import { getElementLineSegments } from "./element/bounds";
-import { mutateElement } from "./element/mutateElement";
+} from "./bounds";
+import { mutateElement } from "./mutateElement";
+import { getBoundTextElement, getContainerElement } from "./textElement";
 import {
-  getBoundTextElement,
-  getContainerElement,
-} from "./element/textElement";
-import { isFrameElement, isFrameLikeElement } from "./element/typeChecks";
-import { getElementsInGroup, selectGroupsFromGivenElements } from "./groups";
-import { getElementsWithinSelection, getSelectedElements } from "./scene";
-import { arrayToMap } from "./utils";
+  isFrameElement,
+  isFrameLikeElement,
+  isTextElement,
+} from "./typeChecks";
 
 import type {
   ElementsMap,
@@ -27,14 +36,7 @@ import type {
   ExcalidrawFrameLikeElement,
   NonDeleted,
   NonDeletedExcalidrawElement,
-} from "./element/types";
-import type { ExcalidrawElementsIncludingDeleted } from "./scene/Scene";
-import type {
-  AppClassProperties,
-  AppState,
-  StaticCanvasAppState,
 } from "./types";
-import type { ReadonlySetLike } from "./utility-types";
 
 // --------------------------- Frame State ------------------------------------
 export const bindElementsToFramesAfterDuplication = (

+ 28 - 27
packages/excalidraw/groups.ts → packages/element/src/groups.ts

@@ -1,6 +1,13 @@
-import { getBoundTextElement } from "./element/textElement";
-import { getSelectedElements } from "./scene";
-import { makeNextSelectedElementIds } from "./scene/selection";
+import type {
+  AppClassProperties,
+  AppState,
+  InteractiveCanvasAppState,
+} from "@excalidraw/excalidraw/types";
+import type { Mutable } from "@excalidraw/common/utility-types";
+
+import { getBoundTextElement } from "./textElement";
+
+import { makeNextSelectedElementIds, getSelectedElements } from "./selection";
 
 import type {
   GroupId,
@@ -9,13 +16,7 @@ import type {
   NonDeletedExcalidrawElement,
   ElementsMapOrArray,
   ElementsMap,
-} from "./element/types";
-import type {
-  AppClassProperties,
-  AppState,
-  InteractiveCanvasAppState,
 } from "./types";
-import type { Mutable } from "./utility-types";
 
 export const selectGroup = (
   groupId: GroupId,
@@ -297,24 +298,6 @@ export const getSelectedGroupIdForElement = (
   selectedGroupIds: { [groupId: string]: boolean },
 ) => element.groupIds.find((groupId) => selectedGroupIds[groupId]);
 
-export const getNewGroupIdsForDuplication = (
-  groupIds: ExcalidrawElement["groupIds"],
-  editingGroupId: AppState["editingGroupId"],
-  mapper: (groupId: GroupId) => GroupId,
-) => {
-  const copy = [...groupIds];
-  const positionOfEditingGroupId = editingGroupId
-    ? groupIds.indexOf(editingGroupId)
-    : -1;
-  const endIndex =
-    positionOfEditingGroupId > -1 ? positionOfEditingGroupId : groupIds.length;
-  for (let index = 0; index < endIndex; index++) {
-    copy[index] = mapper(copy[index]);
-  }
-
-  return copy;
-};
-
 export const addToGroup = (
   prevGroupIds: ExcalidrawElement["groupIds"],
   newGroupId: GroupId,
@@ -401,3 +384,21 @@ export const elementsAreInSameGroup = (
 export const isInGroup = (element: NonDeletedExcalidrawElement) => {
   return element.groupIds.length > 0;
 };
+
+export const getNewGroupIdsForDuplication = (
+  groupIds: ExcalidrawElement["groupIds"],
+  editingGroupId: AppState["editingGroupId"],
+  mapper: (groupId: GroupId) => GroupId,
+) => {
+  const copy = [...groupIds];
+  const positionOfEditingGroupId = editingGroupId
+    ? groupIds.indexOf(editingGroupId)
+    : -1;
+  const endIndex =
+    positionOfEditingGroupId > -1 ? positionOfEditingGroupId : groupIds.length;
+  for (let index = 0; index < endIndex; index++) {
+    copy[index] = mapper(copy[index]);
+  }
+
+  return copy;
+};

+ 0 - 0
packages/excalidraw/element/heading.ts → packages/element/src/heading.ts


+ 7 - 2
packages/excalidraw/element/image.ts → packages/element/src/image.ts

@@ -2,11 +2,16 @@
 // ExcalidrawImageElement & related helpers
 // -----------------------------------------------------------------------------
 
-import { MIME_TYPES, SVG_NS } from "../constants";
+import { MIME_TYPES, SVG_NS } from "@excalidraw/common";
+
+import type {
+  AppClassProperties,
+  DataURL,
+  BinaryFiles,
+} from "@excalidraw/excalidraw/types";
 
 import { isInitializedImageElement } from "./typeChecks";
 
-import type { AppClassProperties, DataURL, BinaryFiles } from "../types";
 import type {
   ExcalidrawElement,
   FileId,

+ 0 - 50
packages/excalidraw/element/index.ts → packages/element/src/index.ts

@@ -7,56 +7,6 @@ import type {
   NonDeleted,
 } from "./types";
 
-export {
-  newElement,
-  newTextElement,
-  refreshTextDimensions,
-  newLinearElement,
-  newArrowElement,
-  newImageElement,
-} from "./newElement";
-export { duplicateElement } from "./duplicate";
-export {
-  getElementAbsoluteCoords,
-  getElementBounds,
-  getCommonBounds,
-  getDiamondPoints,
-  getArrowheadPoints,
-  getClosestElementBounds,
-} from "./bounds";
-
-export {
-  OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
-  getTransformHandlesFromCoords,
-  getTransformHandles,
-} from "./transformHandles";
-export {
-  resizeTest,
-  getCursorForResizingElement,
-  getElementWithTransformHandleType,
-  getTransformHandleTypeFromCoords,
-} from "./resizeTest";
-export {
-  transformElements,
-  getResizeOffsetXY,
-  getResizeArrowDirection,
-} from "./resizeElements";
-export {
-  dragSelectedElements,
-  getDragOffsetXY,
-  dragNewElement,
-} from "./dragElements";
-export { isTextElement, isExcalidrawElement } from "./typeChecks";
-export { redrawTextBoundingBox, getTextFromElements } from "./textElement";
-export {
-  getPerfectElementSize,
-  getLockedLinearCursorAlignSize,
-  isInvisiblySmallElement,
-  resizePerfectLineForNWHandler,
-  getNormalizedDimensions,
-} from "./sizeHelpers";
-export { showSelectedShapeActions } from "./showSelectedShapeActions";
-
 /**
  * @deprecated unsafe, use hashElementsVersion instead
  */

+ 42 - 27
packages/excalidraw/element/linearElementEditor.ts → packages/element/src/linearElementEditor.ts

@@ -8,34 +8,50 @@ import {
   pointDistance,
   vectorFromPoint,
 } from "@excalidraw/math";
-import { getCurvePathOps } from "@excalidraw/utils/geometry/shape";
 
-import type { Radians } from "@excalidraw/math";
+import { getCurvePathOps } from "@excalidraw/utils/shape";
 
-import { DRAGGING_THRESHOLD } from "../constants";
-import { KEYS, shouldRotateWithDiscreteAngle } from "../keys";
-import { ShapeCache } from "../scene/ShapeCache";
 import {
-  getBezierCurveLength,
-  getBezierXY,
-  getControlPointsForBezierCurve,
-  isPathALoop,
-  mapIntervalToBezierT,
-} from "../shapes";
-import { getGridPoint } from "../snapping";
-import { invariant, tupleToCoors } from "../utils";
+  DRAGGING_THRESHOLD,
+  KEYS,
+  shouldRotateWithDiscreteAngle,
+  getGridPoint,
+  invariant,
+  tupleToCoors,
+} from "@excalidraw/common";
+
+// TODO: remove direct dependency on the scene, should be passed in or injected instead
+// eslint-disable-next-line @typescript-eslint/no-restricted-imports
+import Scene from "@excalidraw/excalidraw/scene/Scene";
+
+import type { Store } from "@excalidraw/excalidraw/store";
+
+import type { Radians } from "@excalidraw/math";
+
+import type {
+  AppState,
+  PointerCoords,
+  InteractiveCanvasAppState,
+  AppClassProperties,
+  NullableGridSize,
+  Zoom,
+} from "@excalidraw/excalidraw/types";
 
-import Scene from "../scene/Scene";
+import type { Mutable } from "@excalidraw/common/utility-types";
 
 import {
   bindOrUnbindLinearElement,
   getHoveredElementForBinding,
   isBindingEnabled,
 } from "./binding";
+import {
+  getElementAbsoluteCoords,
+  getElementPointsCoords,
+  getMinMaxXYFromCurvePathOps,
+} from "./bounds";
 
 import { updateElbowArrowPoints } from "./elbowArrow";
 
-import { getElementPointsCoords, getMinMaxXYFromCurvePathOps } from "./bounds";
 import { headingIsHorizontal, vectorToHeading } from "./heading";
 import { bumpVersion, mutateElement } from "./mutateElement";
 import { getBoundTextElement, handleBindTextResize } from "./textElement";
@@ -45,7 +61,17 @@ import {
   isFixedPointBinding,
 } from "./typeChecks";
 
-import { getElementAbsoluteCoords, getLockedLinearCursorAlignSize } from ".";
+import { ShapeCache } from "./ShapeCache";
+
+import {
+  isPathALoop,
+  getBezierCurveLength,
+  getControlPointsForBezierCurve,
+  mapIntervalToBezierT,
+  getBezierXY,
+} from "./shapes";
+
+import { getLockedLinearCursorAlignSize } from "./sizeHelpers";
 
 import type { Bounds } from "./bounds";
 import type {
@@ -62,17 +88,6 @@ import type {
   FixedSegment,
   ExcalidrawElbowArrowElement,
 } from "./types";
-import type { Store } from "../store";
-import type {
-  AppState,
-  PointerCoords,
-  InteractiveCanvasAppState,
-  AppClassProperties,
-  NullableGridSize,
-  Zoom,
-} from "../types";
-
-import type { Mutable } from "../utility-types";
 
 const editorMidPointsCache: {
   version: number | null;

+ 14 - 6
packages/excalidraw/element/mutateElement.ts → packages/element/src/mutateElement.ts

@@ -1,16 +1,24 @@
+import {
+  getSizeFromPoints,
+  randomInteger,
+  getUpdatedTimestamp,
+  toBrandedType,
+} from "@excalidraw/common";
+
+// TODO: remove direct dependency on the scene, should be passed in or injected instead
+// eslint-disable-next-line @typescript-eslint/no-restricted-imports
+import Scene from "@excalidraw/excalidraw/scene/Scene";
+
 import type { Radians } from "@excalidraw/math";
 
-import { getSizeFromPoints } from "../points";
-import { randomInteger } from "../random";
-import Scene from "../scene/Scene";
-import { ShapeCache } from "../scene/ShapeCache";
-import { getUpdatedTimestamp, toBrandedType } from "../utils";
+import type { Mutable } from "@excalidraw/common/utility-types";
+
+import { ShapeCache } from "./ShapeCache";
 
 import { updateElbowArrowPoints } from "./elbowArrow";
 import { isElbowArrow } from "./typeChecks";
 
 import type { ExcalidrawElement, NonDeletedSceneElementsMap } from "./types";
-import type { Mutable } from "../utility-types";
 
 export type ElementUpdate<TElement extends ExcalidrawElement> = Omit<
   Partial<TElement>,

+ 13 - 10
packages/excalidraw/element/newElement.ts → packages/element/src/newElement.ts

@@ -1,5 +1,3 @@
-import type { Radians } from "@excalidraw/math";
-
 import {
   DEFAULT_ELEMENT_PROPS,
   DEFAULT_FONT_FAMILY,
@@ -7,20 +5,26 @@ import {
   DEFAULT_TEXT_ALIGN,
   DEFAULT_VERTICAL_ALIGN,
   VERTICAL_ALIGN,
-} from "../constants";
-import { getLineHeight } from "../fonts";
-import { randomInteger, randomId } from "../random";
+  randomInteger,
+  randomId,
+  getFontString,
+  getUpdatedTimestamp,
+  getLineHeight,
+} from "@excalidraw/common";
+
+import type { Radians } from "@excalidraw/math";
 
-import { getFontString, getUpdatedTimestamp } from "../utils";
+import type { MarkOptional, Merge } from "@excalidraw/common/utility-types";
 
-import { getResizedElementAbsoluteCoords } from "./bounds";
+import {
+  getElementAbsoluteCoords,
+  getResizedElementAbsoluteCoords,
+} from "./bounds";
 import { newElementWith } from "./mutateElement";
 import { getBoundTextMaxWidth } from "./textElement";
 import { normalizeText, measureText } from "./textMeasurements";
 import { wrapText } from "./textWrapping";
 
-import { getElementAbsoluteCoords } from ".";
-
 import type {
   ExcalidrawElement,
   ExcalidrawImageElement,
@@ -43,7 +47,6 @@ import type {
   FixedSegment,
   ExcalidrawElbowArrowElement,
 } from "./types";
-import type { MarkOptional, Merge } from "../utility-types";
 
 export type ElementConstructorOpts = MarkOptional<
   Omit<ExcalidrawGenericElement, "id" | "type" | "isDeleted" | "updated">,

+ 42 - 32
packages/excalidraw/renderer/renderElement.ts → packages/element/src/renderElement.ts

@@ -1,8 +1,8 @@
-import { isRightAngleRads } from "@excalidraw/math";
-import { getStroke } from "perfect-freehand";
 import rough from "roughjs/bin/rough";
+import { getStroke } from "perfect-freehand";
+
+import { isRightAngleRads } from "@excalidraw/math";
 
-import { getDefaultAppState } from "../appState";
 import {
   BOUND_TEXT_PADDING,
   DEFAULT_REDUCED_GLOBAL_ALPHA,
@@ -10,18 +10,39 @@ import {
   FRAME_STYLE,
   MIME_TYPES,
   THEME,
-} from "../constants";
-import { getElementAbsoluteCoords } from "../element/bounds";
-import { getUncroppedImageElement } from "../element/cropElement";
-import { LinearElementEditor } from "../element/linearElementEditor";
+  distance,
+  getFontString,
+  isRTL,
+  getVerticalOffset,
+} from "@excalidraw/common";
+
+import type {
+  AppState,
+  StaticCanvasAppState,
+  Zoom,
+  InteractiveCanvasAppState,
+  ElementsPendingErasure,
+  PendingExcalidrawElements,
+  NormalizedZoomValue,
+} from "@excalidraw/excalidraw/types";
+
+import type {
+  StaticCanvasRenderConfig,
+  RenderableElementsMap,
+  InteractiveCanvasRenderConfig,
+} from "@excalidraw/excalidraw/scene/types";
+
+import { getElementAbsoluteCoords } from "./bounds";
+import { getUncroppedImageElement } from "./cropElement";
+import { LinearElementEditor } from "./linearElementEditor";
 import {
   getBoundTextElement,
   getContainerCoords,
   getContainerElement,
   getBoundTextMaxHeight,
   getBoundTextMaxWidth,
-} from "../element/textElement";
-import { getLineHeightInPx } from "../element/textMeasurements";
+} from "./textElement";
+import { getLineHeightInPx } from "./textMeasurements";
 import {
   isTextElement,
   isLinearElement,
@@ -31,12 +52,11 @@ import {
   hasBoundTextElement,
   isMagicFrameElement,
   isImageElement,
-} from "../element/typeChecks";
-import { getVerticalOffset } from "../fonts";
-import { getContainingFrame } from "../frame";
-import { ShapeCache } from "../scene/ShapeCache";
-import { getCornerRadius } from "../shapes";
-import { distance, getFontString, isRTL } from "../utils";
+} from "./typeChecks";
+import { getContainingFrame } from "./frame";
+import { getCornerRadius } from "./shapes";
+
+import { ShapeCache } from "./ShapeCache";
 
 import type {
   ExcalidrawElement,
@@ -48,20 +68,8 @@ import type {
   ExcalidrawFrameLikeElement,
   NonDeletedSceneElementsMap,
   ElementsMap,
-} from "../element/types";
-import type {
-  StaticCanvasRenderConfig,
-  RenderableElementsMap,
-  InteractiveCanvasRenderConfig,
-} from "../scene/types";
-import type {
-  AppState,
-  StaticCanvasAppState,
-  Zoom,
-  InteractiveCanvasAppState,
-  ElementsPendingErasure,
-  PendingExcalidrawElements,
-} from "../types";
+} from "./types";
+
 import type { StrokeOptions } from "perfect-freehand";
 import type { RoughCanvas } from "roughjs/bin/canvas";
 
@@ -72,8 +80,6 @@ import type { RoughCanvas } from "roughjs/bin/canvas";
 export const IMAGE_INVERT_FILTER =
   "invert(100%) hue-rotate(180deg) saturate(1.25)";
 
-const defaultAppState = getDefaultAppState();
-
 const isPendingImageElement = (
   element: ExcalidrawElement,
   renderConfig: StaticCanvasRenderConfig,
@@ -533,7 +539,11 @@ const generateElementWithCanvas = (
   renderConfig: StaticCanvasRenderConfig,
   appState: StaticCanvasAppState,
 ) => {
-  const zoom: Zoom = renderConfig ? appState.zoom : defaultAppState.zoom;
+  const zoom: Zoom = renderConfig
+    ? appState.zoom
+    : {
+        value: 1 as NormalizedZoomValue,
+      };
   const prevElementWithCanvas = elementWithCanvasCache.get(element);
   const shouldRegenerateBecauseZoom =
     prevElementWithCanvas &&

+ 14 - 7
packages/excalidraw/element/resizeElements.ts → packages/element/src/resizeElements.ts

@@ -8,12 +8,20 @@ import {
   type LocalPoint,
 } from "@excalidraw/math";
 
+import {
+  MIN_FONT_SIZE,
+  SHIFT_LOCKING_ANGLE,
+  rescalePoints,
+  getFontString,
+} from "@excalidraw/common";
+
 import type { GlobalPoint } from "@excalidraw/math";
 
-import { MIN_FONT_SIZE, SHIFT_LOCKING_ANGLE } from "../constants";
-import { isInGroup } from "../groups";
-import { rescalePoints } from "../points";
-import { getFontString } from "../utils";
+import type Scene from "@excalidraw/excalidraw/scene/Scene";
+
+import type { PointerDownState } from "@excalidraw/excalidraw/types";
+
+import type { Mutable } from "@excalidraw/common/utility-types";
 
 import { getArrowLocalFixedPoints, updateBoundElements } from "./binding";
 import {
@@ -50,6 +58,8 @@ import {
   isTextElement,
 } from "./typeChecks";
 
+import { isInGroup } from "./groups";
+
 import type { BoundingBox } from "./bounds";
 import type {
   MaybeTransformHandleType,
@@ -67,9 +77,6 @@ import type {
   SceneElementsMap,
   ExcalidrawElbowArrowElement,
 } from "./types";
-import type Scene from "../scene/Scene";
-import type { PointerDownState } from "../types";
-import type { Mutable } from "../utility-types";
 
 // Returns true when transform (resizing/rotation) happened
 export const transformElements = (

+ 3 - 2
packages/excalidraw/element/resizeTest.ts → packages/element/src/resizeTest.ts

@@ -5,9 +5,11 @@ import {
   type Radians,
 } from "@excalidraw/math";
 
+import { SIDE_RESIZING_THRESHOLD } from "@excalidraw/common";
+
 import type { GlobalPoint, LineSegment, LocalPoint } from "@excalidraw/math";
 
-import { SIDE_RESIZING_THRESHOLD } from "../constants";
+import type { AppState, Device, Zoom } from "@excalidraw/excalidraw/types";
 
 import { getElementAbsoluteCoords } from "./bounds";
 import {
@@ -18,7 +20,6 @@ import {
 } from "./transformHandles";
 import { isImageElement, isLinearElement } from "./typeChecks";
 
-import type { AppState, Device, Zoom } from "../types";
 import type { Bounds } from "./bounds";
 import type {
   TransformHandleType,

+ 12 - 7
packages/excalidraw/scene/selection.ts → packages/element/src/selection.ts

@@ -1,20 +1,25 @@
-import { getElementAbsoluteCoords, getElementBounds } from "../element";
-import { isElementInViewport } from "../element/sizeHelpers";
-import { isBoundToContainer, isFrameLikeElement } from "../element/typeChecks";
+import { isShallowEqual } from "@excalidraw/common";
+
+import type {
+  AppState,
+  InteractiveCanvasAppState,
+} from "@excalidraw/excalidraw/types";
+
+import { getElementAbsoluteCoords, getElementBounds } from "./bounds";
+import { isElementInViewport } from "./sizeHelpers";
+import { isBoundToContainer, isFrameLikeElement } from "./typeChecks";
 import {
   elementOverlapsWithFrame,
   getContainingFrame,
   getFrameChildren,
-} from "../frame";
-import { isShallowEqual } from "../utils";
+} from "./frame";
 
 import type {
   ElementsMap,
   ElementsMapOrArray,
   ExcalidrawElement,
   NonDeletedExcalidrawElement,
-} from "../element/types";
-import type { AppState, InteractiveCanvasAppState } from "../types";
+} from "./types";
 
 /**
  * Frames and their containing elements are not to be selected at the same time.

+ 19 - 117
packages/excalidraw/shapes.tsx → packages/element/src/shapes.ts

@@ -1,3 +1,10 @@
+import {
+  DEFAULT_ADAPTIVE_RADIUS,
+  DEFAULT_PROPORTIONAL_RADIUS,
+  LINE_CONFIRM_THRESHOLD,
+  ROUNDNESS,
+  invariant,
+} from "@excalidraw/common";
 import {
   isPoint,
   pointFrom,
@@ -16,131 +23,26 @@ import {
   getFreedrawShape,
   getPolygonShape,
   type GeometricShape,
-} from "@excalidraw/utils/geometry/shape";
+} from "@excalidraw/utils/shape";
+
+import type { NormalizedZoomValue, Zoom } from "@excalidraw/excalidraw/types";
+
+import { shouldTestInside } from "./collision";
+import { LinearElementEditor } from "./linearElementEditor";
+import { getBoundTextElement } from "./textElement";
+import { ShapeCache } from "./ShapeCache";
+
+import { getElementAbsoluteCoords, type Bounds } from "./bounds";
 
-import {
-  ArrowIcon,
-  DiamondIcon,
-  EllipseIcon,
-  EraserIcon,
-  FreedrawIcon,
-  ImageIcon,
-  LineIcon,
-  RectangleIcon,
-  SelectionIcon,
-  TextIcon,
-} from "./components/icons";
-import {
-  DEFAULT_ADAPTIVE_RADIUS,
-  DEFAULT_PROPORTIONAL_RADIUS,
-  LINE_CONFIRM_THRESHOLD,
-  ROUNDNESS,
-} from "./constants";
-import { getElementAbsoluteCoords } from "./element";
-import { shouldTestInside } from "./element/collision";
-import { LinearElementEditor } from "./element/linearElementEditor";
-import { getBoundTextElement } from "./element/textElement";
-import { KEYS } from "./keys";
-import { ShapeCache } from "./scene/ShapeCache";
-import { invariant } from "./utils";
-
-import type { Bounds } from "./element/bounds";
 import type {
   ElementsMap,
   ExcalidrawElement,
   ExcalidrawLinearElement,
   NonDeleted,
-} from "./element/types";
-import type { NormalizedZoomValue, Zoom } from "./types";
-
-export const SHAPES = [
-  {
-    icon: SelectionIcon,
-    value: "selection",
-    key: KEYS.V,
-    numericKey: KEYS["1"],
-    fillable: true,
-  },
-  {
-    icon: RectangleIcon,
-    value: "rectangle",
-    key: KEYS.R,
-    numericKey: KEYS["2"],
-    fillable: true,
-  },
-  {
-    icon: DiamondIcon,
-    value: "diamond",
-    key: KEYS.D,
-    numericKey: KEYS["3"],
-    fillable: true,
-  },
-  {
-    icon: EllipseIcon,
-    value: "ellipse",
-    key: KEYS.O,
-    numericKey: KEYS["4"],
-    fillable: true,
-  },
-  {
-    icon: ArrowIcon,
-    value: "arrow",
-    key: KEYS.A,
-    numericKey: KEYS["5"],
-    fillable: true,
-  },
-  {
-    icon: LineIcon,
-    value: "line",
-    key: KEYS.L,
-    numericKey: KEYS["6"],
-    fillable: true,
-  },
-  {
-    icon: FreedrawIcon,
-    value: "freedraw",
-    key: [KEYS.P, KEYS.X],
-    numericKey: KEYS["7"],
-    fillable: false,
-  },
-  {
-    icon: TextIcon,
-    value: "text",
-    key: KEYS.T,
-    numericKey: KEYS["8"],
-    fillable: false,
-  },
-  {
-    icon: ImageIcon,
-    value: "image",
-    key: null,
-    numericKey: KEYS["9"],
-    fillable: false,
-  },
-  {
-    icon: EraserIcon,
-    value: "eraser",
-    key: KEYS.E,
-    numericKey: KEYS["0"],
-    fillable: false,
-  },
-] as const;
-
-export const findShapeByKey = (key: string) => {
-  const shape = SHAPES.find((shape, index) => {
-    return (
-      (shape.numericKey != null && key === shape.numericKey.toString()) ||
-      (shape.key &&
-        (typeof shape.key === "string"
-          ? shape.key === key
-          : (shape.key as readonly string[]).includes(key)))
-    );
-  });
-  return shape?.value || null;
-};
+} from "./types";
 
 /**
- * get the pure geometric shape of an excalidraw element
+ * get the pure geometric shape of an excalidraw elementw
  * which is then used for hit detection
  */
 export const getElementShape = <Point extends GlobalPoint | LocalPoint>(

+ 3 - 2
packages/excalidraw/element/showSelectedShapeActions.ts → packages/element/src/showSelectedShapeActions.ts

@@ -1,6 +1,7 @@
-import { getSelectedElements } from "../scene";
+import type { UIAppState } from "@excalidraw/excalidraw/types";
+
+import { getSelectedElements } from "./selection";
 
-import type { UIAppState } from "../types";
 import type { NonDeletedExcalidrawElement } from "./types";
 
 export const showSelectedShapeActions = (

+ 6 - 3
packages/excalidraw/element/sizeHelpers.ts → packages/element/src/sizeHelpers.ts

@@ -1,12 +1,15 @@
-import { SHIFT_LOCKING_ANGLE } from "../constants";
-import { viewportCoordsToSceneCoords } from "../utils";
+import {
+  SHIFT_LOCKING_ANGLE,
+  viewportCoordsToSceneCoords,
+} from "@excalidraw/common";
+
+import type { AppState, Offsets, Zoom } from "@excalidraw/excalidraw/types";
 
 import { getCommonBounds, getElementBounds } from "./bounds";
 import { mutateElement } from "./mutateElement";
 import { isFreeDrawElement, isLinearElement } from "./typeChecks";
 
 import type { ElementsMap, ExcalidrawElement } from "./types";
-import type { AppState, Offsets, Zoom } from "../types";
 
 // TODO:  remove invisible elements consistently actions, so that invisible elements are not recorded by the store, exported, broadcasted or persisted
 //        - perhaps could be as part of a standalone 'cleanup' action, in addition to 'finalize'

+ 1 - 1
packages/excalidraw/element/sortElements.ts → packages/element/src/sortElements.ts

@@ -1,4 +1,4 @@
-import { arrayToMapWithIndex } from "../utils";
+import { arrayToMapWithIndex } from "@excalidraw/common";
 
 import type { ExcalidrawElement } from "./types";
 

+ 11 - 7
packages/excalidraw/element/textElement.ts → packages/element/src/textElement.ts

@@ -5,8 +5,12 @@ import {
   DEFAULT_FONT_SIZE,
   TEXT_ALIGN,
   VERTICAL_ALIGN,
-} from "../constants";
-import { getFontString } from "../utils";
+  getFontString,
+} from "@excalidraw/common";
+
+import type { AppState } from "@excalidraw/excalidraw/types";
+
+import type { ExtractSetType } from "@excalidraw/common/utility-types";
 
 import {
   resetOriginalContainerCache,
@@ -16,9 +20,11 @@ import { LinearElementEditor } from "./linearElementEditor";
 import { mutateElement } from "./mutateElement";
 import { measureText } from "./textMeasurements";
 import { wrapText } from "./textWrapping";
-import { isBoundToContainer, isArrowElement } from "./typeChecks";
-
-import { isTextElement } from ".";
+import {
+  isBoundToContainer,
+  isArrowElement,
+  isTextElement,
+} from "./typeChecks";
 
 import type { MaybeTransformHandleType } from "./transformHandles";
 import type {
@@ -30,8 +36,6 @@ import type {
   ExcalidrawTextElementWithContainer,
   NonDeletedExcalidrawElement,
 } from "./types";
-import type { AppState } from "../types";
-import type { ExtractSetType } from "../utility-types";
 
 export const redrawTextBoundingBox = (
   textElement: ExcalidrawTextElement,

+ 4 - 2
packages/excalidraw/element/textMeasurements.ts → packages/element/src/textMeasurements.ts

@@ -2,8 +2,10 @@ import {
   BOUND_TEXT_PADDING,
   DEFAULT_FONT_SIZE,
   DEFAULT_FONT_FAMILY,
-} from "../constants";
-import { getFontString, isTestEnv, normalizeEOL } from "../utils";
+  getFontString,
+  isTestEnv,
+  normalizeEOL,
+} from "@excalidraw/common";
 
 import type { FontString, ExcalidrawTextElement } from "./types";
 

+ 1 - 1
packages/excalidraw/element/textWrapping.ts → packages/element/src/textWrapping.ts

@@ -1,4 +1,4 @@
-import { isDevEnv, isTestEnv } from "../utils";
+import { isDevEnv, isTestEnv } from "@excalidraw/common";
 
 import { charWidth, getLineWidth } from "./textMeasurements";
 

+ 11 - 6
packages/excalidraw/element/transformHandles.ts → packages/element/src/transformHandles.ts

@@ -1,12 +1,18 @@
-import { pointFrom, pointRotateRads } from "@excalidraw/math";
-
-import type { Radians } from "@excalidraw/math";
-
 import {
   DEFAULT_TRANSFORM_HANDLE_SPACING,
   isAndroid,
   isIOS,
-} from "../constants";
+} from "@excalidraw/common";
+
+import { pointFrom, pointRotateRads } from "@excalidraw/math";
+
+import type { Radians } from "@excalidraw/math";
+
+import type {
+  Device,
+  InteractiveCanvasAppState,
+  Zoom,
+} from "@excalidraw/excalidraw/types";
 
 import { getElementAbsoluteCoords } from "./bounds";
 import {
@@ -16,7 +22,6 @@ import {
   isLinearElement,
 } from "./typeChecks";
 
-import type { Device, InteractiveCanvasAppState, Zoom } from "../types";
 import type { Bounds } from "./bounds";
 import type {
   ElementsMap,

+ 5 - 4
packages/excalidraw/element/typeChecks.ts → packages/element/src/typeChecks.ts

@@ -1,8 +1,9 @@
-import { ROUNDNESS } from "../constants";
-import { assertNever } from "../utils";
+import { ROUNDNESS, assertNever } from "@excalidraw/common";
+
+import type { ElementOrToolType } from "@excalidraw/excalidraw/types";
+
+import type { MarkNonNullable } from "@excalidraw/common/utility-types";
 
-import type { ElementOrToolType } from "../types";
-import type { MarkNonNullable } from "../utility-types";
 import type { Bounds } from "./bounds";
 import type {
   ExcalidrawElement,

+ 3 - 2
packages/excalidraw/element/types.ts → packages/element/src/types.ts

@@ -6,13 +6,14 @@ import type {
   TEXT_ALIGN,
   THEME,
   VERTICAL_ALIGN,
-} from "../constants";
+} from "@excalidraw/common";
+
 import type {
   MakeBrand,
   MarkNonNullable,
   Merge,
   ValueOf,
-} from "../utility-types";
+} from "@excalidraw/common/utility-types";
 
 export type ChartType = "bar" | "line";
 export type FillStyle = "hachure" | "cross-hatch" | "solid" | "zigzag";

+ 2 - 2
packages/excalidraw/element/utils.ts → packages/element/src/utils.ts

@@ -12,9 +12,9 @@ import {
 
 import type { Curve, LineSegment } from "@excalidraw/math";
 
-import { getCornerRadius } from "../shapes";
+import { getCornerRadius } from "./shapes";
 
-import { getDiamondPoints } from ".";
+import { getDiamondPoints } from "./bounds";
 
 import type {
   ExcalidrawDiamondElement,

+ 36 - 21
packages/excalidraw/zindex.ts → packages/element/src/zindex.ts

@@ -1,15 +1,18 @@
-import { isFrameLikeElement } from "./element/typeChecks";
-import { syncMovedIndices } from "./fractionalIndex";
+import { arrayToMap, findIndex, findLastIndex } from "@excalidraw/common";
+
+import type { AppState } from "@excalidraw/excalidraw/types";
+
+import type Scene from "@excalidraw/excalidraw/scene/Scene";
+
+import { isFrameLikeElement } from "./typeChecks";
+
 import { getElementsInGroup } from "./groups";
-import { getSelectedElements } from "./scene";
-import Scene from "./scene/Scene";
-import { arrayToMap, findIndex, findLastIndex } from "./utils";
 
-import type {
-  ExcalidrawElement,
-  ExcalidrawFrameLikeElement,
-} from "./element/types";
-import type { AppState } from "./types";
+import { syncMovedIndices } from "./fractionalIndex";
+
+import { getSelectedElements } from "./selection";
+
+import type { ExcalidrawElement, ExcalidrawFrameLikeElement } from "./types";
 
 const isOfTargetFrame = (element: ExcalidrawElement, frameId: string) => {
   return element.frameId === frameId || element.id === frameId;
@@ -79,11 +82,11 @@ const getTargetIndexAccountingForBinding = (
   nextElement: ExcalidrawElement,
   elements: readonly ExcalidrawElement[],
   direction: "left" | "right",
+  scene: Scene,
 ) => {
   if ("containerId" in nextElement && nextElement.containerId) {
-    const containerElement = Scene.getScene(nextElement)!.getElement(
-      nextElement.containerId,
-    );
+    // TODO: why not to get the container from the nextElements?
+    const containerElement = scene.getElement(nextElement.containerId);
     if (containerElement) {
       return direction === "left"
         ? Math.min(
@@ -100,8 +103,7 @@ const getTargetIndexAccountingForBinding = (
       (binding) => binding.type !== "arrow",
     )?.id;
     if (boundElementId) {
-      const boundTextElement =
-        Scene.getScene(nextElement)!.getElement(boundElementId);
+      const boundTextElement = scene.getElement(boundElementId);
       if (boundTextElement) {
         return direction === "left"
           ? Math.min(
@@ -151,6 +153,7 @@ const getTargetIndex = (
    * If whole frame (including all children) is being moved, supply `null`.
    */
   containingFrame: ExcalidrawFrameLikeElement["id"] | null,
+  scene: Scene,
 ) => {
   const sourceElement = elements[boundaryIndex];
 
@@ -190,8 +193,12 @@ const getTargetIndex = (
       sourceElement?.groupIds.join("") === nextElement?.groupIds.join("")
     ) {
       return (
-        getTargetIndexAccountingForBinding(nextElement, elements, direction) ??
-        candidateIndex
+        getTargetIndexAccountingForBinding(
+          nextElement,
+          elements,
+          direction,
+          scene,
+        ) ?? candidateIndex
       );
     } else if (!nextElement?.groupIds.includes(appState.editingGroupId)) {
       // candidate element is outside current editing group → prevent
@@ -214,8 +221,12 @@ const getTargetIndex = (
 
   if (!nextElement.groupIds.length) {
     return (
-      getTargetIndexAccountingForBinding(nextElement, elements, direction) ??
-      candidateIndex
+      getTargetIndexAccountingForBinding(
+        nextElement,
+        elements,
+        direction,
+        scene,
+      ) ?? candidateIndex
     );
   }
 
@@ -255,6 +266,7 @@ const shiftElementsByOne = (
   elements: readonly ExcalidrawElement[],
   appState: AppState,
   direction: "left" | "right",
+  scene: Scene,
 ) => {
   const indicesToMove = getIndicesToMove(elements, appState);
   const targetElementsMap = getTargetElementsMap(elements, indicesToMove);
@@ -289,6 +301,7 @@ const shiftElementsByOne = (
       boundaryIndex,
       direction,
       containingFrame,
+      scene,
     );
 
     if (targetIndex === -1 || boundaryIndex === targetIndex) {
@@ -502,15 +515,17 @@ function shiftElementsAccountingForFrames(
 export const moveOneLeft = (
   allElements: readonly ExcalidrawElement[],
   appState: AppState,
+  scene: Scene,
 ) => {
-  return shiftElementsByOne(allElements, appState, "left");
+  return shiftElementsByOne(allElements, appState, "left", scene);
 };
 
 export const moveOneRight = (
   allElements: readonly ExcalidrawElement[],
   appState: AppState,
+  scene: Scene,
 ) => {
-  return shiftElementsByOne(allElements, appState, "right");
+  return shiftElementsByOne(allElements, appState, "right", scene);
 };
 
 export const moveAllLeft = (

+ 12 - 9
packages/excalidraw/tests/align.test.tsx → packages/element/tests/align.test.tsx

@@ -1,4 +1,4 @@
-import React from "react";
+import { KEYS } from "@excalidraw/common";
 
 import {
   actionAlignVerticallyCentered,
@@ -8,14 +8,17 @@ import {
   actionAlignBottom,
   actionAlignLeft,
   actionAlignRight,
-} from "../actions";
-import { defaultLang, setLanguage } from "../i18n";
-import { Excalidraw } from "../index";
-import { KEYS } from "../keys";
-
-import { API } from "./helpers/api";
-import { UI, Pointer, Keyboard } from "./helpers/ui";
-import { act, unmountComponent, render } from "./test-utils";
+} from "@excalidraw/excalidraw/actions";
+import { defaultLang, setLanguage } from "@excalidraw/excalidraw/i18n";
+import { Excalidraw } from "@excalidraw/excalidraw";
+
+import { API } from "@excalidraw/excalidraw/tests/helpers/api";
+import { UI, Pointer, Keyboard } from "@excalidraw/excalidraw/tests/helpers/ui";
+import {
+  act,
+  unmountComponent,
+  render,
+} from "@excalidraw/excalidraw/tests/test-utils";
 
 const mouse = new Pointer("mouse");
 

+ 10 - 9
packages/excalidraw/tests/binding.test.tsx → packages/element/tests/binding.test.tsx

@@ -1,15 +1,16 @@
+import { KEYS, arrayToMap } from "@excalidraw/common";
+
 import { pointFrom } from "@excalidraw/math";
-import React from "react";
 
-import { actionWrapTextInContainer } from "../actions/actionBoundText";
-import { getTransformHandles } from "../element/transformHandles";
-import { Excalidraw, isLinearElement } from "../index";
-import { KEYS } from "../keys";
-import { arrayToMap } from "../utils";
+import { actionWrapTextInContainer } from "@excalidraw/excalidraw/actions/actionBoundText";
+
+import { Excalidraw, isLinearElement } from "@excalidraw/excalidraw";
+
+import { API } from "@excalidraw/excalidraw/tests/helpers/api";
+import { UI, Pointer, Keyboard } from "@excalidraw/excalidraw/tests/helpers/ui";
+import { fireEvent, render } from "@excalidraw/excalidraw/tests/test-utils";
 
-import { API } from "./helpers/api";
-import { UI, Pointer, Keyboard } from "./helpers/ui";
-import { fireEvent, render } from "./test-utils";
+import { getTransformHandles } from "../src/transformHandles";
 
 const { h } = window;
 

+ 4 - 5
packages/excalidraw/element/bounds.test.ts → packages/element/tests/bounds.test.ts

@@ -1,13 +1,12 @@
 import { pointFrom } from "@excalidraw/math";
 
-import type { LocalPoint } from "@excalidraw/math";
+import { arrayToMap, ROUNDNESS } from "@excalidraw/common";
 
-import { ROUNDNESS } from "../constants";
-import { arrayToMap } from "../utils";
+import type { LocalPoint } from "@excalidraw/math";
 
-import { getElementAbsoluteCoords, getElementBounds } from "./bounds";
+import { getElementAbsoluteCoords, getElementBounds } from "../src/bounds";
 
-import type { ExcalidrawElement, ExcalidrawLinearElement } from "./types";
+import type { ExcalidrawElement, ExcalidrawLinearElement } from "../src/types";
 
 const _ce = ({
   x,

+ 18 - 12
packages/excalidraw/element/duplicate.test.tsx → packages/element/tests/duplicate.test.tsx

@@ -1,28 +1,34 @@
 import React from "react";
 import { pointFrom } from "@excalidraw/math";
 
-import type { LocalPoint } from "@excalidraw/math";
+import {
+  FONT_FAMILY,
+  ORIG_ID,
+  ROUNDNESS,
+  isPrimitive,
+} from "@excalidraw/common";
+
+import { Excalidraw } from "@excalidraw/excalidraw";
+
+import { actionDuplicateSelection } from "@excalidraw/excalidraw/actions";
 
-import { FONT_FAMILY, ORIG_ID, ROUNDNESS } from "../constants";
-import { API } from "../tests/helpers/api";
-import { isPrimitive } from "../utils";
+import { API } from "@excalidraw/excalidraw/tests/helpers/api";
+
+import { Keyboard, Pointer } from "@excalidraw/excalidraw/tests/helpers/ui";
 
 import {
   act,
   assertElements,
   getCloneByOrigId,
   render,
-} from "../tests/test-utils";
-import { Excalidraw } from "..";
-import { actionDuplicateSelection } from "../actions";
-
-import { Keyboard, Pointer } from "../tests/helpers/ui";
+} from "@excalidraw/excalidraw/tests/test-utils";
 
-import { mutateElement } from "./mutateElement";
+import type { LocalPoint } from "@excalidraw/math";
 
-import { duplicateElement, duplicateElements } from "./duplicate";
+import { mutateElement } from "../src/mutateElement";
+import { duplicateElement, duplicateElements } from "../src/duplicate";
 
-import type { ExcalidrawLinearElement } from "./types";
+import type { ExcalidrawLinearElement } from "../src/types";
 
 const { h } = window;
 const mouse = new Pointer("mouse");

+ 15 - 13
packages/excalidraw/element/elbowArrow.test.tsx → packages/element/tests/elbowArrow.test.tsx

@@ -1,31 +1,33 @@
+import { ARROW_TYPE } from "@excalidraw/common";
 import { pointFrom } from "@excalidraw/math";
-import React from "react";
+import { Excalidraw, mutateElement } from "@excalidraw/excalidraw";
 
-import type { LocalPoint } from "@excalidraw/math";
+import Scene from "@excalidraw/excalidraw/scene/Scene";
+import { actionSelectAll } from "@excalidraw/excalidraw/actions";
+import { actionDuplicateSelection } from "@excalidraw/excalidraw/actions/actionDuplicateSelection";
+
+import { API } from "@excalidraw/excalidraw/tests/helpers/api";
+import { Pointer, UI } from "@excalidraw/excalidraw/tests/helpers/ui";
 
-import "../../utils/test-utils";
-import { actionSelectAll } from "../actions";
-import { actionDuplicateSelection } from "../actions/actionDuplicateSelection";
-import { ARROW_TYPE } from "../constants";
-import { Excalidraw, mutateElement } from "../index";
-import Scene from "../scene/Scene";
-import { API } from "../tests/helpers/api";
-import { Pointer, UI } from "../tests/helpers/ui";
 import {
   act,
   fireEvent,
   GlobalTestState,
   queryByTestId,
   render,
-} from "../tests/test-utils";
+} from "@excalidraw/excalidraw/tests/test-utils";
+
+import "@excalidraw/utils/test-utils";
+
+import type { LocalPoint } from "@excalidraw/math";
 
-import { bindLinearElement } from "./binding";
+import { bindLinearElement } from "../src/binding";
 
 import type {
   ExcalidrawArrowElement,
   ExcalidrawBindableElement,
   ExcalidrawElbowArrowElement,
-} from "./types";
+} from "../src/types";
 
 const { h } = window;
 

+ 10 - 6
packages/excalidraw/element/flowchart.test.tsx → packages/element/tests/flowchart.test.tsx

@@ -1,9 +1,13 @@
-import { Excalidraw } from "../index";
-import { KEYS } from "../keys";
-import { reseed } from "../random";
-import { API } from "../tests/helpers/api";
-import { UI, Keyboard, Pointer } from "../tests/helpers/ui";
-import { render, unmountComponent } from "../tests/test-utils";
+import { KEYS, reseed } from "@excalidraw/common";
+
+import { Excalidraw } from "@excalidraw/excalidraw";
+
+import { API } from "@excalidraw/excalidraw/tests/helpers/api";
+import { UI, Keyboard, Pointer } from "@excalidraw/excalidraw/tests/helpers/ui";
+import {
+  render,
+  unmountComponent,
+} from "@excalidraw/excalidraw/tests/test-utils";
 
 unmountComponent();
 

+ 11 - 6
packages/excalidraw/tests/fractionalIndex.test.ts → packages/element/tests/fractionalIndex.test.ts

@@ -1,19 +1,24 @@
 /* eslint-disable no-lone-blocks */
 import { generateKeyBetween } from "fractional-indexing";
 
-import { InvalidFractionalIndexError } from "../errors";
+import { arrayToMap } from "@excalidraw/common";
+
 import {
   syncInvalidIndices,
   syncMovedIndices,
   validateFractionalIndices,
-} from "../fractionalIndex";
-import { arrayToMap } from "../utils";
+} from "@excalidraw/element/fractionalIndex";
+
+import { deepCopyElement } from "@excalidraw/element/duplicate";
 
-import { deepCopyElement } from "../element/duplicate";
+import { API } from "@excalidraw/excalidraw/tests/helpers/api";
 
-import { API } from "./helpers/api";
+import type {
+  ExcalidrawElement,
+  FractionalIndex,
+} from "@excalidraw/element/types";
 
-import type { ExcalidrawElement, FractionalIndex } from "../element/types";
+import { InvalidFractionalIndexError } from "../src/fractionalIndex";
 
 describe("sync invalid indices with array order", () => {
   describe("should NOT sync empty array", () => {

+ 13 - 7
packages/excalidraw/frame.test.tsx → packages/element/tests/frame.test.tsx

@@ -1,10 +1,16 @@
-import { API } from "./tests/helpers/api";
-import { Keyboard, Pointer } from "./tests/helpers/ui";
-import { getCloneByOrigId, render } from "./tests/test-utils";
-
-import { convertToExcalidrawElements, Excalidraw } from "./index";
-
-import type { ExcalidrawElement } from "./element/types";
+import {
+  convertToExcalidrawElements,
+  Excalidraw,
+} from "@excalidraw/excalidraw";
+
+import { API } from "@excalidraw/excalidraw/tests/helpers/api";
+import { Keyboard, Pointer } from "@excalidraw/excalidraw/tests/helpers/ui";
+import {
+  getCloneByOrigId,
+  render,
+} from "@excalidraw/excalidraw/tests/test-utils";
+
+import type { ExcalidrawElement } from "../src/types";
 
 const { h } = window;
 const mouse = new Pointer("mouse");

+ 22 - 17
packages/excalidraw/tests/resize.test.tsx → packages/element/tests/resize.test.tsx

@@ -1,28 +1,33 @@
 import { pointFrom } from "@excalidraw/math";
-import React from "react";
+
+import { Excalidraw } from "@excalidraw/excalidraw";
+import {
+  KEYS,
+  getSizeFromPoints,
+  reseed,
+  arrayToMap,
+} from "@excalidraw/common";
+
+import { API } from "@excalidraw/excalidraw/tests/helpers/api";
+import { UI, Keyboard, Pointer } from "@excalidraw/excalidraw/tests/helpers/ui";
+import {
+  render,
+  unmountComponent,
+} from "@excalidraw/excalidraw/tests/test-utils";
 
 import type { LocalPoint } from "@excalidraw/math";
 
-import { getElementPointsCoords } from "../element/bounds";
-import { LinearElementEditor } from "../element/linearElementEditor";
-import { resizeSingleElement } from "../element/resizeElements";
-import { isLinearElement } from "../element/typeChecks";
-import { Excalidraw } from "../index";
-import { KEYS } from "../keys";
-import { getSizeFromPoints } from "../points";
-import { reseed } from "../random";
-import { arrayToMap } from "../utils";
-
-import { API } from "./helpers/api";
-import { UI, Keyboard, Pointer } from "./helpers/ui";
-import { render, unmountComponent } from "./test-utils";
-
-import type { Bounds } from "../element/bounds";
+import { isLinearElement } from "../src/typeChecks";
+import { resizeSingleElement } from "../src/resizeElements";
+import { LinearElementEditor } from "../src/linearElementEditor";
+import { getElementPointsCoords } from "../src/bounds";
+
+import type { Bounds } from "../src/bounds";
 import type {
   ExcalidrawElbowArrowElement,
   ExcalidrawFreeDrawElement,
   ExcalidrawLinearElement,
-} from "../element/types";
+} from "../src/types";
 
 unmountComponent();
 

+ 1 - 1
packages/excalidraw/scene/selection.test.ts → packages/element/tests/selection.test.ts

@@ -1,4 +1,4 @@
-import { makeNextSelectedElementIds } from "./selection";
+import { makeNextSelectedElementIds } from "../src/selection";
 
 describe("makeNextSelectedElementIds", () => {
   const _makeNextSelectedElementIds = (

+ 3 - 3
packages/excalidraw/element/sizeHelpers.test.ts → packages/element/tests/sizeHelpers.test.ts

@@ -1,15 +1,15 @@
 import { vi } from "vitest";
 
-import * as constants from "../constants";
+import * as constants from "@excalidraw/common";
 
-import { getPerfectElementSize } from "./sizeHelpers";
+import { getPerfectElementSize } from "../src/sizeHelpers";
 
 const EPSILON_DIGITS = 3;
 // Needed so that we can mock the value of constants which is done in
 // below tests. In Jest this wasn't needed as global override was possible
 // but vite doesn't allow that hence we need to mock
 vi.mock(
-  "../constants.ts",
+  "@excalidraw/common",
   //@ts-ignore
   async (importOriginal) => {
     const module: any = await importOriginal();

+ 4 - 4
packages/excalidraw/element/sortElements.test.ts → packages/element/tests/sortElements.test.ts

@@ -1,9 +1,9 @@
-import { API } from "../tests/helpers/api";
+import { API } from "@excalidraw/excalidraw/tests/helpers/api";
 
-import { mutateElement } from "./mutateElement";
-import { normalizeElementOrder } from "./sortElements";
+import { mutateElement } from "../src/mutateElement";
+import { normalizeElementOrder } from "../src/sortElements";
 
-import type { ExcalidrawElement } from "./types";
+import type { ExcalidrawElement } from "../src/types";
 
 const assertOrder = (
   elements: readonly ExcalidrawElement[],

部分文件因文件數量過多而無法顯示