Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| be5d0bd925 | |||
| 292aa1cddb | |||
| e95222ed32 | |||
| d87620b239 | |||
| 7cc31ac64a | |||
| 071b17a217 | |||
| 859207b8bc | |||
| becaabfa0f | |||
| f06484c6ab | |||
| bf4c65f483 |
@@ -12,10 +12,10 @@ jobs:
|
|||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
- name: Setup Node.js 18.x
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 20.x
|
||||||
- name: Set up publish access
|
- name: Set up publish access
|
||||||
run: |
|
run: |
|
||||||
npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN}
|
npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN}
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Setup Node.js 18.x
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 20.x
|
||||||
|
|
||||||
- name: Install and lint
|
- name: Install and lint
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
|
token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
|
||||||
|
|
||||||
- name: Setup Node.js 18.x
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 20.x
|
||||||
|
|
||||||
- name: Create report file
|
- name: Create report file
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Setup Node.js 18.x
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 20.x
|
||||||
- name: Install and build
|
- name: Install and build
|
||||||
run: |
|
run: |
|
||||||
yarn --frozen-lockfile
|
yarn --frozen-lockfile
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Setup Node.js 18.x
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 20.x
|
||||||
- name: Install in packages/excalidraw
|
- name: Install in packages/excalidraw
|
||||||
run: yarn
|
run: yarn
|
||||||
working-directory: packages/excalidraw
|
working-directory: packages/excalidraw
|
||||||
|
|||||||
@@ -746,7 +746,10 @@ class Collab extends PureComponent<CollabProps, CollabState> {
|
|||||||
): ReconciledExcalidrawElement[] => {
|
): ReconciledExcalidrawElement[] => {
|
||||||
const localElements = this.getSceneElementsIncludingDeleted();
|
const localElements = this.getSceneElementsIncludingDeleted();
|
||||||
const appState = this.excalidrawAPI.getAppState();
|
const appState = this.excalidrawAPI.getAppState();
|
||||||
const restoredRemoteElements = restoreElements(remoteElements, null);
|
const restoredRemoteElements = restoreElements(
|
||||||
|
remoteElements,
|
||||||
|
this.excalidrawAPI.getSceneElementsMapIncludingDeleted(),
|
||||||
|
);
|
||||||
const reconciledElements = reconcileElements(
|
const reconciledElements = reconcileElements(
|
||||||
localElements,
|
localElements,
|
||||||
restoredRemoteElements as RemoteExcalidrawElement[],
|
restoredRemoteElements as RemoteExcalidrawElement[],
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* x and y position of top left corner, x and y position of bottom right corner
|
||||||
|
*/
|
||||||
|
export type Bounds = readonly [
|
||||||
|
minX: number,
|
||||||
|
minY: number,
|
||||||
|
maxX: number,
|
||||||
|
maxY: number,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const isBounds = (box: unknown): box is Bounds =>
|
||||||
|
Array.isArray(box) &&
|
||||||
|
box.length === 4 &&
|
||||||
|
typeof box[0] === "number" &&
|
||||||
|
typeof box[1] === "number" &&
|
||||||
|
typeof box[2] === "number" &&
|
||||||
|
typeof box[3] === "number";
|
||||||
@@ -108,6 +108,13 @@ export const CLASSES = {
|
|||||||
FRAME_NAME: "frame-name",
|
FRAME_NAME: "frame-name",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const FONT_SIZES = {
|
||||||
|
sm: 16,
|
||||||
|
md: 20,
|
||||||
|
lg: 28,
|
||||||
|
xl: 36,
|
||||||
|
} as const;
|
||||||
|
|
||||||
export const CJK_HAND_DRAWN_FALLBACK_FONT = "Xiaolai";
|
export const CJK_HAND_DRAWN_FALLBACK_FONT = "Xiaolai";
|
||||||
export const WINDOWS_EMOJI_FALLBACK_FONT = "Segoe UI Emoji";
|
export const WINDOWS_EMOJI_FALLBACK_FONT = "Segoe UI Emoji";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from "./binary-heap";
|
export * from "./binary-heap";
|
||||||
|
export * from "./bounds";
|
||||||
export * from "./colors";
|
export * from "./colors";
|
||||||
export * from "./constants";
|
export * from "./constants";
|
||||||
export * from "./font-metadata";
|
export * from "./font-metadata";
|
||||||
|
|||||||
@@ -6,12 +6,10 @@ import {
|
|||||||
type LocalPoint,
|
type LocalPoint,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import { isBounds } from "@excalidraw/element";
|
|
||||||
|
|
||||||
import type { Curve } from "@excalidraw/math";
|
import type { Curve } from "@excalidraw/math";
|
||||||
import type { LineSegment } from "@excalidraw/utils";
|
import type { LineSegment } from "@excalidraw/utils";
|
||||||
|
|
||||||
import type { Bounds } from "@excalidraw/element";
|
import { type Bounds, isBounds } from "./bounds";
|
||||||
|
|
||||||
// The global data holder to collect the debug operations
|
// The global data holder to collect the debug operations
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
+5
-4
@@ -1,11 +1,12 @@
|
|||||||
import { pointFrom } from "@excalidraw/math";
|
import { pointFrom } from "@excalidraw/math";
|
||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
|
|
||||||
import type { ExcalidrawArrowElement } from "@excalidraw/element/types";
|
import {
|
||||||
|
convertToExcalidrawElements,
|
||||||
|
type ExcalidrawElementSkeleton,
|
||||||
|
} from "../transform";
|
||||||
|
|
||||||
import { convertToExcalidrawElements } from "./transform";
|
import type { ExcalidrawArrowElement } from "../types";
|
||||||
|
|
||||||
import type { ExcalidrawElementSkeleton } from "./transform";
|
|
||||||
|
|
||||||
const opts = { regenerateIds: false };
|
const opts = { regenerateIds: false };
|
||||||
|
|
||||||
@@ -22,10 +22,9 @@ import {
|
|||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import type { LineSegment, LocalPoint, Radians } from "@excalidraw/math";
|
import type { LineSegment, LocalPoint, Radians } from "@excalidraw/math";
|
||||||
|
|
||||||
import type { AppState } from "@excalidraw/excalidraw/types";
|
import type { AppState } from "@excalidraw/excalidraw/types";
|
||||||
|
|
||||||
import type { MapEntry, Mutable } from "@excalidraw/common/utility-types";
|
import type { MapEntry, Mutable } from "@excalidraw/common/utility-types";
|
||||||
|
import type { Bounds } from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
doBoundsIntersect,
|
doBoundsIntersect,
|
||||||
@@ -54,17 +53,21 @@ import {
|
|||||||
isBindableElement,
|
isBindableElement,
|
||||||
isBoundToContainer,
|
isBoundToContainer,
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
|
isRectangularElement,
|
||||||
isRectanguloidElement,
|
isRectanguloidElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
|
|
||||||
import { aabbForElement, elementCenterPoint } from "./bounds";
|
import { aabbForElement, elementCenterPoint } from "./bounds";
|
||||||
import { updateElbowArrowPoints } from "./elbowArrow";
|
import { updateElbowArrowPoints } from "./elbowArrow";
|
||||||
import { projectFixedPointOntoDiagonal } from "./utils";
|
import {
|
||||||
|
deconstructDiamondElement,
|
||||||
|
deconstructRectanguloidElement,
|
||||||
|
projectFixedPointOntoDiagonal,
|
||||||
|
} from "./utils";
|
||||||
|
|
||||||
import type { Scene } from "./Scene";
|
import type { Scene } from "./Scene";
|
||||||
|
|
||||||
import type { Bounds } from "./bounds";
|
|
||||||
import type { ElementUpdate } from "./mutateElement";
|
import type { ElementUpdate } from "./mutateElement";
|
||||||
import type {
|
import type {
|
||||||
BindMode,
|
BindMode,
|
||||||
@@ -73,6 +76,7 @@ import type {
|
|||||||
ExcalidrawBindableElement,
|
ExcalidrawBindableElement,
|
||||||
ExcalidrawElbowArrowElement,
|
ExcalidrawElbowArrowElement,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
|
ExcalidrawRectanguloidElement,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
FixedPoint,
|
FixedPoint,
|
||||||
FixedPointBinding,
|
FixedPointBinding,
|
||||||
@@ -146,17 +150,22 @@ export const isBindingEnabled = (appState: AppState): boolean => {
|
|||||||
export const bindOrUnbindBindingElement = (
|
export const bindOrUnbindBindingElement = (
|
||||||
arrow: NonDeleted<ExcalidrawArrowElement>,
|
arrow: NonDeleted<ExcalidrawArrowElement>,
|
||||||
draggingPoints: PointsPositionUpdates,
|
draggingPoints: PointsPositionUpdates,
|
||||||
|
scenePointerX: number,
|
||||||
|
scenePointerY: number,
|
||||||
scene: Scene,
|
scene: Scene,
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
opts?: {
|
opts?: {
|
||||||
newArrow?: boolean;
|
newArrow?: boolean;
|
||||||
altKey?: boolean;
|
altKey?: boolean;
|
||||||
|
angleLocked?: boolean;
|
||||||
initialBinding?: boolean;
|
initialBinding?: boolean;
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
const { start, end } = getBindingStrategyForDraggingBindingElementEndpoints(
|
const { start, end } = getBindingStrategyForDraggingBindingElementEndpoints(
|
||||||
arrow,
|
arrow,
|
||||||
draggingPoints,
|
draggingPoints,
|
||||||
|
scenePointerX,
|
||||||
|
scenePointerY,
|
||||||
scene.getNonDeletedElementsMap(),
|
scene.getNonDeletedElementsMap(),
|
||||||
scene.getNonDeletedElements(),
|
scene.getNonDeletedElements(),
|
||||||
appState,
|
appState,
|
||||||
@@ -556,12 +565,14 @@ const bindingStrategyForSimpleArrowEndpointDragging_complex = (
|
|||||||
export const getBindingStrategyForDraggingBindingElementEndpoints = (
|
export const getBindingStrategyForDraggingBindingElementEndpoints = (
|
||||||
arrow: NonDeleted<ExcalidrawArrowElement>,
|
arrow: NonDeleted<ExcalidrawArrowElement>,
|
||||||
draggingPoints: PointsPositionUpdates,
|
draggingPoints: PointsPositionUpdates,
|
||||||
|
screenPointerX: number,
|
||||||
|
screenPointerY: number,
|
||||||
elementsMap: NonDeletedSceneElementsMap,
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
elements: readonly Ordered<NonDeletedExcalidrawElement>[],
|
elements: readonly Ordered<NonDeletedExcalidrawElement>[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
opts?: {
|
opts?: {
|
||||||
newArrow?: boolean;
|
newArrow?: boolean;
|
||||||
shiftKey?: boolean;
|
angleLocked?: boolean;
|
||||||
altKey?: boolean;
|
altKey?: boolean;
|
||||||
finalize?: boolean;
|
finalize?: boolean;
|
||||||
initialBinding?: boolean;
|
initialBinding?: boolean;
|
||||||
@@ -582,6 +593,8 @@ export const getBindingStrategyForDraggingBindingElementEndpoints = (
|
|||||||
return getBindingStrategyForDraggingBindingElementEndpoints_simple(
|
return getBindingStrategyForDraggingBindingElementEndpoints_simple(
|
||||||
arrow,
|
arrow,
|
||||||
draggingPoints,
|
draggingPoints,
|
||||||
|
screenPointerX,
|
||||||
|
screenPointerY,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
@@ -592,12 +605,14 @@ export const getBindingStrategyForDraggingBindingElementEndpoints = (
|
|||||||
const getBindingStrategyForDraggingBindingElementEndpoints_simple = (
|
const getBindingStrategyForDraggingBindingElementEndpoints_simple = (
|
||||||
arrow: NonDeleted<ExcalidrawArrowElement>,
|
arrow: NonDeleted<ExcalidrawArrowElement>,
|
||||||
draggingPoints: PointsPositionUpdates,
|
draggingPoints: PointsPositionUpdates,
|
||||||
|
scenePointerX: number,
|
||||||
|
scenePointerY: number,
|
||||||
elementsMap: NonDeletedSceneElementsMap,
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
elements: readonly Ordered<NonDeletedExcalidrawElement>[],
|
elements: readonly Ordered<NonDeletedExcalidrawElement>[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
opts?: {
|
opts?: {
|
||||||
newArrow?: boolean;
|
newArrow?: boolean;
|
||||||
shiftKey?: boolean;
|
angleLocked?: boolean;
|
||||||
altKey?: boolean;
|
altKey?: boolean;
|
||||||
finalize?: boolean;
|
finalize?: boolean;
|
||||||
initialBinding?: boolean;
|
initialBinding?: boolean;
|
||||||
@@ -669,7 +684,15 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = (
|
|||||||
elementsMap,
|
elementsMap,
|
||||||
(e) => maxBindingDistance_simple(appState.zoom),
|
(e) => maxBindingDistance_simple(appState.zoom),
|
||||||
);
|
);
|
||||||
const pointInElement = hit && isPointInElement(globalPoint, hit, elementsMap);
|
const pointInElement =
|
||||||
|
hit &&
|
||||||
|
(opts?.angleLocked
|
||||||
|
? isPointInElement(
|
||||||
|
pointFrom<GlobalPoint>(scenePointerX, scenePointerY),
|
||||||
|
hit,
|
||||||
|
elementsMap,
|
||||||
|
)
|
||||||
|
: isPointInElement(globalPoint, hit, elementsMap));
|
||||||
const otherBindableElement = otherBinding
|
const otherBindableElement = otherBinding
|
||||||
? (elementsMap.get(
|
? (elementsMap.get(
|
||||||
otherBinding.elementId,
|
otherBinding.elementId,
|
||||||
@@ -770,6 +793,12 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = (
|
|||||||
}
|
}
|
||||||
: { mode: null };
|
: { mode: null };
|
||||||
|
|
||||||
|
const otherEndpoint = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
|
arrow,
|
||||||
|
startDragged ? -1 : 0,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
const other: BindingStrategy =
|
const other: BindingStrategy =
|
||||||
otherBindableElement &&
|
otherBindableElement &&
|
||||||
!otherFocusPointIsInElement &&
|
!otherFocusPointIsInElement &&
|
||||||
@@ -779,6 +808,19 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = (
|
|||||||
element: otherBindableElement,
|
element: otherBindableElement,
|
||||||
focusPoint: appState.selectedLinearElement.initialState.altFocusPoint,
|
focusPoint: appState.selectedLinearElement.initialState.altFocusPoint,
|
||||||
}
|
}
|
||||||
|
: opts?.angleLocked && otherBindableElement
|
||||||
|
? {
|
||||||
|
mode: "orbit",
|
||||||
|
element: otherBindableElement,
|
||||||
|
focusPoint:
|
||||||
|
projectFixedPointOntoDiagonal(
|
||||||
|
arrow,
|
||||||
|
otherEndpoint,
|
||||||
|
otherBindableElement,
|
||||||
|
startDragged ? "end" : "start",
|
||||||
|
elementsMap,
|
||||||
|
) || otherEndpoint,
|
||||||
|
}
|
||||||
: { mode: undefined };
|
: { mode: undefined };
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -924,6 +966,8 @@ export const bindOrUnbindBindingElements = (
|
|||||||
bindOrUnbindBindingElement(
|
bindOrUnbindBindingElement(
|
||||||
arrow,
|
arrow,
|
||||||
new Map(), // No dragging points in this case
|
new Map(), // No dragging points in this case
|
||||||
|
Infinity,
|
||||||
|
Infinity,
|
||||||
scene,
|
scene,
|
||||||
appState,
|
appState,
|
||||||
);
|
);
|
||||||
@@ -1126,7 +1170,14 @@ export const updateBindings = (
|
|||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
if (isArrowElement(latestElement)) {
|
if (isArrowElement(latestElement)) {
|
||||||
bindOrUnbindBindingElement(latestElement, new Map(), scene, appState);
|
bindOrUnbindBindingElement(
|
||||||
|
latestElement,
|
||||||
|
new Map(),
|
||||||
|
Infinity,
|
||||||
|
Infinity,
|
||||||
|
scene,
|
||||||
|
appState,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
updateBoundElements(latestElement, scene, {
|
updateBoundElements(latestElement, scene, {
|
||||||
...options,
|
...options,
|
||||||
@@ -2289,3 +2340,434 @@ export const normalizeFixedPoint = <T extends FixedPoint | null>(
|
|||||||
}
|
}
|
||||||
return fixedPoint as any as T extends null ? null : FixedPoint;
|
return fixedPoint as any as T extends null ? null : FixedPoint;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Side =
|
||||||
|
| "top"
|
||||||
|
| "top-right"
|
||||||
|
| "right"
|
||||||
|
| "bottom-right"
|
||||||
|
| "bottom"
|
||||||
|
| "bottom-left"
|
||||||
|
| "left"
|
||||||
|
| "top-left";
|
||||||
|
type ShapeType = "rectangle" | "ellipse" | "diamond";
|
||||||
|
const getShapeType = (element: ExcalidrawBindableElement): ShapeType => {
|
||||||
|
if (element.type === "ellipse" || element.type === "diamond") {
|
||||||
|
return element.type;
|
||||||
|
}
|
||||||
|
return "rectangle";
|
||||||
|
};
|
||||||
|
|
||||||
|
interface SectorConfig {
|
||||||
|
// center angle of the sector in degrees
|
||||||
|
centerAngle: number;
|
||||||
|
// width of the sector in degrees
|
||||||
|
sectorWidth: number;
|
||||||
|
side: Side;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define sector configurations for different shape types
|
||||||
|
const SHAPE_CONFIGS: Record<ShapeType, SectorConfig[]> = {
|
||||||
|
// rectangle: 15° corners, 75° edges
|
||||||
|
rectangle: [
|
||||||
|
{ centerAngle: 0, sectorWidth: 75, side: "right" },
|
||||||
|
{ centerAngle: 45, sectorWidth: 15, side: "bottom-right" },
|
||||||
|
{ centerAngle: 90, sectorWidth: 75, side: "bottom" },
|
||||||
|
{ centerAngle: 135, sectorWidth: 15, side: "bottom-left" },
|
||||||
|
{ centerAngle: 180, sectorWidth: 75, side: "left" },
|
||||||
|
{ centerAngle: 225, sectorWidth: 15, side: "top-left" },
|
||||||
|
{ centerAngle: 270, sectorWidth: 75, side: "top" },
|
||||||
|
{ centerAngle: 315, sectorWidth: 15, side: "top-right" },
|
||||||
|
],
|
||||||
|
|
||||||
|
// diamond: 15° vertices, 75° edges
|
||||||
|
diamond: [
|
||||||
|
{ centerAngle: 0, sectorWidth: 15, side: "right" },
|
||||||
|
{ centerAngle: 45, sectorWidth: 75, side: "bottom-right" },
|
||||||
|
{ centerAngle: 90, sectorWidth: 15, side: "bottom" },
|
||||||
|
{ centerAngle: 135, sectorWidth: 75, side: "bottom-left" },
|
||||||
|
{ centerAngle: 180, sectorWidth: 15, side: "left" },
|
||||||
|
{ centerAngle: 225, sectorWidth: 75, side: "top-left" },
|
||||||
|
{ centerAngle: 270, sectorWidth: 15, side: "top" },
|
||||||
|
{ centerAngle: 315, sectorWidth: 75, side: "top-right" },
|
||||||
|
],
|
||||||
|
|
||||||
|
// ellipse: 15° cardinal points, 75° diagonals
|
||||||
|
ellipse: [
|
||||||
|
{ centerAngle: 0, sectorWidth: 15, side: "right" },
|
||||||
|
{ centerAngle: 45, sectorWidth: 75, side: "bottom-right" },
|
||||||
|
{ centerAngle: 90, sectorWidth: 15, side: "bottom" },
|
||||||
|
{ centerAngle: 135, sectorWidth: 75, side: "bottom-left" },
|
||||||
|
{ centerAngle: 180, sectorWidth: 15, side: "left" },
|
||||||
|
{ centerAngle: 225, sectorWidth: 75, side: "top-left" },
|
||||||
|
{ centerAngle: 270, sectorWidth: 15, side: "top" },
|
||||||
|
{ centerAngle: 315, sectorWidth: 75, side: "top-right" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSectorBoundaries = (
|
||||||
|
config: SectorConfig[],
|
||||||
|
): Array<{ start: number; end: number; side: Side }> => {
|
||||||
|
return config.map((sector, index) => {
|
||||||
|
const halfWidth = sector.sectorWidth / 2;
|
||||||
|
let start = sector.centerAngle - halfWidth;
|
||||||
|
let end = sector.centerAngle + halfWidth;
|
||||||
|
|
||||||
|
// normalize angles to [0, 360) range
|
||||||
|
start = ((start % 360) + 360) % 360;
|
||||||
|
end = ((end % 360) + 360) % 360;
|
||||||
|
|
||||||
|
return { start, end, side: sector.side };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// determine which side a point falls into using adaptive sectors
|
||||||
|
const getShapeSideAdaptive = (
|
||||||
|
fixedPoint: FixedPoint,
|
||||||
|
shapeType: ShapeType,
|
||||||
|
): Side => {
|
||||||
|
const [x, y] = fixedPoint;
|
||||||
|
|
||||||
|
// convert to centered coordinates
|
||||||
|
const centerX = x - 0.5;
|
||||||
|
const centerY = y - 0.5;
|
||||||
|
|
||||||
|
// calculate angle
|
||||||
|
let angle = Math.atan2(centerY, centerX);
|
||||||
|
if (angle < 0) {
|
||||||
|
angle += 2 * Math.PI;
|
||||||
|
}
|
||||||
|
const degrees = (angle * 180) / Math.PI;
|
||||||
|
|
||||||
|
// get sector configuration for this shape type
|
||||||
|
const config = SHAPE_CONFIGS[shapeType];
|
||||||
|
const boundaries = getSectorBoundaries(config);
|
||||||
|
|
||||||
|
// find which sector the angle falls into
|
||||||
|
for (const boundary of boundaries) {
|
||||||
|
if (boundary.start <= boundary.end) {
|
||||||
|
// Normal case: sector doesn't cross 0°
|
||||||
|
if (degrees >= boundary.start && degrees <= boundary.end) {
|
||||||
|
return boundary.side;
|
||||||
|
}
|
||||||
|
} else if (degrees >= boundary.start || degrees <= boundary.end) {
|
||||||
|
return boundary.side;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback - find nearest sector center
|
||||||
|
let minDiff = Infinity;
|
||||||
|
let nearestSide = config[0].side;
|
||||||
|
|
||||||
|
for (const sector of config) {
|
||||||
|
let diff = Math.abs(degrees - sector.centerAngle);
|
||||||
|
// handle wraparound
|
||||||
|
if (diff > 180) {
|
||||||
|
diff = 360 - diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diff < minDiff) {
|
||||||
|
minDiff = diff;
|
||||||
|
nearestSide = sector.side;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nearestSide;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getBindingSideMidPoint = (
|
||||||
|
binding: FixedPointBinding,
|
||||||
|
elementsMap: ElementsMap,
|
||||||
|
) => {
|
||||||
|
const bindableElement = elementsMap.get(binding.elementId);
|
||||||
|
if (
|
||||||
|
!bindableElement ||
|
||||||
|
bindableElement.isDeleted ||
|
||||||
|
!isBindableElement(bindableElement)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const center = elementCenterPoint(bindableElement, elementsMap);
|
||||||
|
const shapeType = getShapeType(bindableElement);
|
||||||
|
const side = getShapeSideAdaptive(
|
||||||
|
normalizeFixedPoint(binding.fixedPoint),
|
||||||
|
shapeType,
|
||||||
|
);
|
||||||
|
|
||||||
|
// small offset to avoid precision issues in elbow
|
||||||
|
const OFFSET = 0.01;
|
||||||
|
|
||||||
|
if (bindableElement.type === "diamond") {
|
||||||
|
const [sides, corners] = deconstructDiamondElement(bindableElement);
|
||||||
|
const [bottomRight, bottomLeft, topLeft, topRight] = sides;
|
||||||
|
|
||||||
|
let x: number;
|
||||||
|
let y: number;
|
||||||
|
switch (side) {
|
||||||
|
case "left": {
|
||||||
|
// left vertex - use the center of the left corner curve
|
||||||
|
if (corners.length >= 3) {
|
||||||
|
const leftCorner = corners[2];
|
||||||
|
const midPoint = leftCorner[1];
|
||||||
|
x = midPoint[0] - OFFSET;
|
||||||
|
y = midPoint[1];
|
||||||
|
} else {
|
||||||
|
// fallback for non-rounded diamond
|
||||||
|
const midPoint = getMidPoint(bottomLeft[1], topLeft[0]);
|
||||||
|
x = midPoint[0] - OFFSET;
|
||||||
|
y = midPoint[1];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "right": {
|
||||||
|
if (corners.length >= 1) {
|
||||||
|
const rightCorner = corners[0];
|
||||||
|
const midPoint = rightCorner[1];
|
||||||
|
x = midPoint[0] + OFFSET;
|
||||||
|
y = midPoint[1];
|
||||||
|
} else {
|
||||||
|
const midPoint = getMidPoint(topRight[1], bottomRight[0]);
|
||||||
|
x = midPoint[0] + OFFSET;
|
||||||
|
y = midPoint[1];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "top": {
|
||||||
|
if (corners.length >= 4) {
|
||||||
|
const topCorner = corners[3];
|
||||||
|
const midPoint = topCorner[1];
|
||||||
|
x = midPoint[0];
|
||||||
|
y = midPoint[1] - OFFSET;
|
||||||
|
} else {
|
||||||
|
const midPoint = getMidPoint(topLeft[1], topRight[0]);
|
||||||
|
x = midPoint[0];
|
||||||
|
y = midPoint[1] - OFFSET;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "bottom": {
|
||||||
|
if (corners.length >= 2) {
|
||||||
|
const bottomCorner = corners[1];
|
||||||
|
const midPoint = bottomCorner[1];
|
||||||
|
x = midPoint[0];
|
||||||
|
y = midPoint[1] + OFFSET;
|
||||||
|
} else {
|
||||||
|
const midPoint = getMidPoint(bottomRight[1], bottomLeft[0]);
|
||||||
|
x = midPoint[0];
|
||||||
|
y = midPoint[1] + OFFSET;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "top-right": {
|
||||||
|
const midPoint = getMidPoint(topRight[0], topRight[1]);
|
||||||
|
|
||||||
|
x = midPoint[0] + OFFSET * 0.707;
|
||||||
|
y = midPoint[1] - OFFSET * 0.707;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "bottom-right": {
|
||||||
|
const midPoint = getMidPoint(bottomRight[0], bottomRight[1]);
|
||||||
|
|
||||||
|
x = midPoint[0] + OFFSET * 0.707;
|
||||||
|
y = midPoint[1] + OFFSET * 0.707;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "bottom-left": {
|
||||||
|
const midPoint = getMidPoint(bottomLeft[0], bottomLeft[1]);
|
||||||
|
x = midPoint[0] - OFFSET * 0.707;
|
||||||
|
y = midPoint[1] + OFFSET * 0.707;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "top-left": {
|
||||||
|
const midPoint = getMidPoint(topLeft[0], topLeft[1]);
|
||||||
|
x = midPoint[0] - OFFSET * 0.707;
|
||||||
|
y = midPoint[1] - OFFSET * 0.707;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pointRotateRads(pointFrom(x, y), center, bindableElement.angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bindableElement.type === "ellipse") {
|
||||||
|
const ellipseCenterX = bindableElement.x + bindableElement.width / 2;
|
||||||
|
const ellipseCenterY = bindableElement.y + bindableElement.height / 2;
|
||||||
|
const radiusX = bindableElement.width / 2;
|
||||||
|
const radiusY = bindableElement.height / 2;
|
||||||
|
|
||||||
|
let x: number;
|
||||||
|
let y: number;
|
||||||
|
|
||||||
|
switch (side) {
|
||||||
|
case "top": {
|
||||||
|
x = ellipseCenterX;
|
||||||
|
y = ellipseCenterY - radiusY - OFFSET;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "right": {
|
||||||
|
x = ellipseCenterX + radiusX + OFFSET;
|
||||||
|
y = ellipseCenterY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "bottom": {
|
||||||
|
x = ellipseCenterX;
|
||||||
|
y = ellipseCenterY + radiusY + OFFSET;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "left": {
|
||||||
|
x = ellipseCenterX - radiusX - OFFSET;
|
||||||
|
y = ellipseCenterY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "top-right": {
|
||||||
|
const angle = -Math.PI / 4;
|
||||||
|
const ellipseX = radiusX * Math.cos(angle);
|
||||||
|
const ellipseY = radiusY * Math.sin(angle);
|
||||||
|
x = ellipseCenterX + ellipseX + OFFSET * 0.707;
|
||||||
|
y = ellipseCenterY + ellipseY - OFFSET * 0.707;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "bottom-right": {
|
||||||
|
const angle = Math.PI / 4;
|
||||||
|
const ellipseX = radiusX * Math.cos(angle);
|
||||||
|
const ellipseY = radiusY * Math.sin(angle);
|
||||||
|
x = ellipseCenterX + ellipseX + OFFSET * 0.707;
|
||||||
|
y = ellipseCenterY + ellipseY + OFFSET * 0.707;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "bottom-left": {
|
||||||
|
const angle = (3 * Math.PI) / 4;
|
||||||
|
const ellipseX = radiusX * Math.cos(angle);
|
||||||
|
const ellipseY = radiusY * Math.sin(angle);
|
||||||
|
x = ellipseCenterX + ellipseX - OFFSET * 0.707;
|
||||||
|
y = ellipseCenterY + ellipseY + OFFSET * 0.707;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "top-left": {
|
||||||
|
const angle = (-3 * Math.PI) / 4;
|
||||||
|
const ellipseX = radiusX * Math.cos(angle);
|
||||||
|
const ellipseY = radiusY * Math.sin(angle);
|
||||||
|
x = ellipseCenterX + ellipseX - OFFSET * 0.707;
|
||||||
|
y = ellipseCenterY + ellipseY - OFFSET * 0.707;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pointRotateRads(pointFrom(x, y), center, bindableElement.angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRectangularElement(bindableElement)) {
|
||||||
|
const [sides, corners] = deconstructRectanguloidElement(
|
||||||
|
bindableElement as ExcalidrawRectanguloidElement,
|
||||||
|
);
|
||||||
|
const [top, right, bottom, left] = sides;
|
||||||
|
|
||||||
|
let x: number;
|
||||||
|
let y: number;
|
||||||
|
switch (side) {
|
||||||
|
case "top": {
|
||||||
|
const midPoint = getMidPoint(top[0], top[1]);
|
||||||
|
x = midPoint[0];
|
||||||
|
y = midPoint[1] - OFFSET;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "right": {
|
||||||
|
const midPoint = getMidPoint(right[0], right[1]);
|
||||||
|
x = midPoint[0] + OFFSET;
|
||||||
|
y = midPoint[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "bottom": {
|
||||||
|
const midPoint = getMidPoint(bottom[0], bottom[1]);
|
||||||
|
x = midPoint[0];
|
||||||
|
y = midPoint[1] + OFFSET;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "left": {
|
||||||
|
const midPoint = getMidPoint(left[0], left[1]);
|
||||||
|
x = midPoint[0] - OFFSET;
|
||||||
|
y = midPoint[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "top-left": {
|
||||||
|
if (corners.length >= 1) {
|
||||||
|
const corner = corners[0];
|
||||||
|
|
||||||
|
const p1 = corner[0];
|
||||||
|
const p2 = corner[3];
|
||||||
|
const midPoint = getMidPoint(p1, p2);
|
||||||
|
|
||||||
|
x = midPoint[0] - OFFSET * 0.707;
|
||||||
|
y = midPoint[1] - OFFSET * 0.707;
|
||||||
|
} else {
|
||||||
|
x = bindableElement.x - OFFSET;
|
||||||
|
y = bindableElement.y - OFFSET;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "top-right": {
|
||||||
|
if (corners.length >= 2) {
|
||||||
|
const corner = corners[1];
|
||||||
|
const p1 = corner[0];
|
||||||
|
const p2 = corner[3];
|
||||||
|
const midPoint = getMidPoint(p1, p2);
|
||||||
|
|
||||||
|
x = midPoint[0] + OFFSET * 0.707;
|
||||||
|
y = midPoint[1] - OFFSET * 0.707;
|
||||||
|
} else {
|
||||||
|
x = bindableElement.x + bindableElement.width + OFFSET;
|
||||||
|
y = bindableElement.y - OFFSET;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "bottom-right": {
|
||||||
|
if (corners.length >= 3) {
|
||||||
|
const corner = corners[2];
|
||||||
|
const p1 = corner[0];
|
||||||
|
const p2 = corner[3];
|
||||||
|
const midPoint = getMidPoint(p1, p2);
|
||||||
|
|
||||||
|
x = midPoint[0] + OFFSET * 0.707;
|
||||||
|
y = midPoint[1] + OFFSET * 0.707;
|
||||||
|
} else {
|
||||||
|
x = bindableElement.x + bindableElement.width + OFFSET;
|
||||||
|
y = bindableElement.y + bindableElement.height + OFFSET;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "bottom-left": {
|
||||||
|
if (corners.length >= 4) {
|
||||||
|
const corner = corners[3];
|
||||||
|
const p1 = corner[0];
|
||||||
|
const p2 = corner[3];
|
||||||
|
const midPoint = getMidPoint(p1, p2);
|
||||||
|
|
||||||
|
x = midPoint[0] - OFFSET * 0.707;
|
||||||
|
y = midPoint[1] + OFFSET * 0.707;
|
||||||
|
} else {
|
||||||
|
x = bindableElement.x - OFFSET;
|
||||||
|
y = bindableElement.y + bindableElement.height + OFFSET;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pointRotateRads(pointFrom(x, y), center, bindableElement.angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMidPoint = (p1: GlobalPoint, p2: GlobalPoint): GlobalPoint => {
|
||||||
|
return pointFrom((p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2);
|
||||||
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import rough from "roughjs/bin/rough";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
arrayToMap,
|
arrayToMap,
|
||||||
|
type Bounds,
|
||||||
invariant,
|
invariant,
|
||||||
rescalePoints,
|
rescalePoints,
|
||||||
sizeOf,
|
sizeOf,
|
||||||
@@ -78,16 +79,6 @@ export type RectangleBox = {
|
|||||||
|
|
||||||
type MaybeQuadraticSolution = [number | null, number | null] | false;
|
type MaybeQuadraticSolution = [number | null, number | null] | false;
|
||||||
|
|
||||||
/**
|
|
||||||
* x and y position of top left corner, x and y position of bottom right corner
|
|
||||||
*/
|
|
||||||
export type Bounds = readonly [
|
|
||||||
minX: number,
|
|
||||||
minY: number,
|
|
||||||
maxX: number,
|
|
||||||
maxY: number,
|
|
||||||
];
|
|
||||||
|
|
||||||
export type SceneBounds = readonly [
|
export type SceneBounds = readonly [
|
||||||
sceneX: number,
|
sceneX: number,
|
||||||
sceneY: number,
|
sceneY: number,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { invariant, isTransparent } from "@excalidraw/common";
|
import { invariant, isTransparent, type Bounds } from "@excalidraw/common";
|
||||||
import {
|
import {
|
||||||
curveIntersectLineSegment,
|
curveIntersectLineSegment,
|
||||||
isPointWithinBounds,
|
isPointWithinBounds,
|
||||||
@@ -29,7 +29,6 @@ import type { FrameNameBounds } from "@excalidraw/excalidraw/types";
|
|||||||
|
|
||||||
import { isPathALoop } from "./utils";
|
import { isPathALoop } from "./utils";
|
||||||
import {
|
import {
|
||||||
type Bounds,
|
|
||||||
doBoundsIntersect,
|
doBoundsIntersect,
|
||||||
elementCenterPoint,
|
elementCenterPoint,
|
||||||
getCenterForBounds,
|
getCenterForBounds,
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ export const hasStrokeColor = (type: ElementOrToolType) =>
|
|||||||
type === "freedraw" ||
|
type === "freedraw" ||
|
||||||
type === "arrow" ||
|
type === "arrow" ||
|
||||||
type === "line" ||
|
type === "line" ||
|
||||||
type === "text";
|
type === "text" ||
|
||||||
|
type === "embeddable";
|
||||||
|
|
||||||
export const hasStrokeWidth = (type: ElementOrToolType) =>
|
export const hasStrokeWidth = (type: ElementOrToolType) =>
|
||||||
type === "rectangle" ||
|
type === "rectangle" ||
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
type Bounds,
|
||||||
TEXT_AUTOWRAP_THRESHOLD,
|
TEXT_AUTOWRAP_THRESHOLD,
|
||||||
getGridPoint,
|
getGridPoint,
|
||||||
getFontString,
|
getFontString,
|
||||||
@@ -29,7 +30,6 @@ import {
|
|||||||
|
|
||||||
import type { Scene } from "./Scene";
|
import type { Scene } from "./Scene";
|
||||||
|
|
||||||
import type { Bounds } from "./bounds";
|
|
||||||
import type { ExcalidrawElement } from "./types";
|
import type { ExcalidrawElement } from "./types";
|
||||||
|
|
||||||
export const dragSelectedElements = (
|
export const dragSelectedElements = (
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
type Bounds,
|
||||||
BinaryHeap,
|
BinaryHeap,
|
||||||
invariant,
|
invariant,
|
||||||
isAnyTrue,
|
isAnyTrue,
|
||||||
@@ -54,7 +55,6 @@ import {
|
|||||||
import { aabbForElement, pointInsideBounds } from "./bounds";
|
import { aabbForElement, pointInsideBounds } from "./bounds";
|
||||||
import { getHoveredElementForBinding } from "./collision";
|
import { getHoveredElementForBinding } from "./collision";
|
||||||
|
|
||||||
import type { Bounds } from "./bounds";
|
|
||||||
import type { Heading } from "./heading";
|
import type { Heading } from "./heading";
|
||||||
import type {
|
import type {
|
||||||
Arrowhead,
|
Arrowhead,
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { invariant, isDevEnv, isTestEnv } from "@excalidraw/common";
|
import {
|
||||||
|
invariant,
|
||||||
|
isDevEnv,
|
||||||
|
isTestEnv,
|
||||||
|
type Bounds,
|
||||||
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
pointFrom,
|
pointFrom,
|
||||||
@@ -19,7 +24,7 @@ import type {
|
|||||||
Vector,
|
Vector,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import { getCenterForBounds, type Bounds } from "./bounds";
|
import { getCenterForBounds } from "./bounds";
|
||||||
|
|
||||||
import type { ExcalidrawBindableElement } from "./types";
|
import type { ExcalidrawBindableElement } from "./types";
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ export * from "./store";
|
|||||||
export * from "./textElement";
|
export * from "./textElement";
|
||||||
export * from "./textMeasurements";
|
export * from "./textMeasurements";
|
||||||
export * from "./textWrapping";
|
export * from "./textWrapping";
|
||||||
|
export * from "./transform";
|
||||||
export * from "./transformHandles";
|
export * from "./transformHandles";
|
||||||
export * from "./typeChecks";
|
export * from "./typeChecks";
|
||||||
export * from "./utils";
|
export * from "./utils";
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import {
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
deconstructLinearOrFreeDrawElement,
|
deconstructLinearOrFreeDrawElement,
|
||||||
getHoveredElementForBinding,
|
|
||||||
isPathALoop,
|
isPathALoop,
|
||||||
moveArrowAboveBindable,
|
moveArrowAboveBindable,
|
||||||
projectFixedPointOntoDiagonal,
|
projectFixedPointOntoDiagonal,
|
||||||
@@ -43,6 +42,7 @@ import type {
|
|||||||
NullableGridSize,
|
NullableGridSize,
|
||||||
Zoom,
|
Zoom,
|
||||||
} from "@excalidraw/excalidraw/types";
|
} from "@excalidraw/excalidraw/types";
|
||||||
|
import type { Bounds } from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
calculateFixedPointForNonElbowArrowBinding,
|
calculateFixedPointForNonElbowArrowBinding,
|
||||||
@@ -69,7 +69,6 @@ import { isLineElement } from "./typeChecks";
|
|||||||
|
|
||||||
import type { Scene } from "./Scene";
|
import type { Scene } from "./Scene";
|
||||||
|
|
||||||
import type { Bounds } from "./bounds";
|
|
||||||
import type {
|
import type {
|
||||||
NonDeleted,
|
NonDeleted,
|
||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
@@ -306,21 +305,11 @@ export class LinearElementEditor {
|
|||||||
const customLineAngle =
|
const customLineAngle =
|
||||||
linearElementEditor.customLineAngle ??
|
linearElementEditor.customLineAngle ??
|
||||||
determineCustomLinearAngle(pivotPoint, element.points[idx]);
|
determineCustomLinearAngle(pivotPoint, element.points[idx]);
|
||||||
const hoveredElement = getHoveredElementForBinding(
|
|
||||||
pointFrom<GlobalPoint>(scenePointerX, scenePointerY),
|
|
||||||
elements,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Determine if point movement should happen and how much
|
// Determine if point movement should happen and how much
|
||||||
let deltaX = 0;
|
let deltaX = 0;
|
||||||
let deltaY = 0;
|
let deltaY = 0;
|
||||||
if (
|
if (shouldRotateWithDiscreteAngle(event)) {
|
||||||
shouldRotateWithDiscreteAngle(event) &&
|
|
||||||
!hoveredElement &&
|
|
||||||
!element.startBinding &&
|
|
||||||
!element.endBinding
|
|
||||||
) {
|
|
||||||
const [width, height] = LinearElementEditor._getShiftLockedDelta(
|
const [width, height] = LinearElementEditor._getShiftLockedDelta(
|
||||||
element,
|
element,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
@@ -354,11 +343,13 @@ export class LinearElementEditor {
|
|||||||
[idx],
|
[idx],
|
||||||
deltaX,
|
deltaX,
|
||||||
deltaY,
|
deltaY,
|
||||||
|
scenePointerX,
|
||||||
|
scenePointerY,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
element,
|
element,
|
||||||
elements,
|
elements,
|
||||||
app,
|
app,
|
||||||
event.shiftKey,
|
shouldRotateWithDiscreteAngle(event),
|
||||||
event.altKey,
|
event.altKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -492,22 +483,11 @@ export class LinearElementEditor {
|
|||||||
const endIsSelected = selectedPointsIndices.includes(
|
const endIsSelected = selectedPointsIndices.includes(
|
||||||
element.points.length - 1,
|
element.points.length - 1,
|
||||||
);
|
);
|
||||||
const hoveredElement = getHoveredElementForBinding(
|
|
||||||
pointFrom<GlobalPoint>(scenePointerX, scenePointerY),
|
|
||||||
elements,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Determine if point movement should happen and how much
|
// Determine if point movement should happen and how much
|
||||||
let deltaX = 0;
|
let deltaX = 0;
|
||||||
let deltaY = 0;
|
let deltaY = 0;
|
||||||
if (
|
if (shouldRotateWithDiscreteAngle(event) && singlePointDragged) {
|
||||||
shouldRotateWithDiscreteAngle(event) &&
|
|
||||||
singlePointDragged &&
|
|
||||||
!hoveredElement &&
|
|
||||||
!element.startBinding &&
|
|
||||||
!element.endBinding
|
|
||||||
) {
|
|
||||||
const [width, height] = LinearElementEditor._getShiftLockedDelta(
|
const [width, height] = LinearElementEditor._getShiftLockedDelta(
|
||||||
element,
|
element,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
@@ -520,7 +500,6 @@ export class LinearElementEditor {
|
|||||||
width + pivotPoint[0],
|
width + pivotPoint[0],
|
||||||
height + pivotPoint[1],
|
height + pivotPoint[1],
|
||||||
);
|
);
|
||||||
|
|
||||||
deltaX = target[0] - draggingPoint[0];
|
deltaX = target[0] - draggingPoint[0];
|
||||||
deltaY = target[1] - draggingPoint[1];
|
deltaY = target[1] - draggingPoint[1];
|
||||||
} else {
|
} else {
|
||||||
@@ -541,11 +520,13 @@ export class LinearElementEditor {
|
|||||||
selectedPointsIndices,
|
selectedPointsIndices,
|
||||||
deltaX,
|
deltaX,
|
||||||
deltaY,
|
deltaY,
|
||||||
|
scenePointerX,
|
||||||
|
scenePointerY,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
element,
|
element,
|
||||||
elements,
|
elements,
|
||||||
app,
|
app,
|
||||||
event.shiftKey,
|
shouldRotateWithDiscreteAngle(event) && singlePointDragged,
|
||||||
event.altKey,
|
event.altKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2088,11 +2069,13 @@ const pointDraggingUpdates = (
|
|||||||
selectedPointsIndices: readonly number[],
|
selectedPointsIndices: readonly number[],
|
||||||
deltaX: number,
|
deltaX: number,
|
||||||
deltaY: number,
|
deltaY: number,
|
||||||
|
scenePointerX: number,
|
||||||
|
scenePointerY: number,
|
||||||
elementsMap: NonDeletedSceneElementsMap,
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
elements: readonly Ordered<NonDeletedExcalidrawElement>[],
|
elements: readonly Ordered<NonDeletedExcalidrawElement>[],
|
||||||
app: AppClassProperties,
|
app: AppClassProperties,
|
||||||
shiftKey: boolean,
|
angleLocked: boolean,
|
||||||
altKey: boolean,
|
altKey: boolean,
|
||||||
): {
|
): {
|
||||||
positions: PointsPositionUpdates;
|
positions: PointsPositionUpdates;
|
||||||
@@ -2128,12 +2111,14 @@ const pointDraggingUpdates = (
|
|||||||
const { start, end } = getBindingStrategyForDraggingBindingElementEndpoints(
|
const { start, end } = getBindingStrategyForDraggingBindingElementEndpoints(
|
||||||
element,
|
element,
|
||||||
naiveDraggingPoints,
|
naiveDraggingPoints,
|
||||||
|
scenePointerX,
|
||||||
|
scenePointerY,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
elements,
|
elements,
|
||||||
app.state,
|
app.state,
|
||||||
{
|
{
|
||||||
newArrow: !!app.state.newElement,
|
newArrow: !!app.state.newElement,
|
||||||
shiftKey,
|
angleLocked,
|
||||||
altKey,
|
altKey,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -2250,10 +2235,15 @@ const pointDraggingUpdates = (
|
|||||||
// We need to use a custom intersector to ensure that if there is a big "jump"
|
// We need to use a custom intersector to ensure that if there is a big "jump"
|
||||||
// in the arrow's position, we can position it with outline avoidance
|
// in the arrow's position, we can position it with outline avoidance
|
||||||
// pixel-perfectly and avoid "dancing" arrows.
|
// pixel-perfectly and avoid "dancing" arrows.
|
||||||
const customIntersector =
|
// NOTE: Direction matters here, so we create two intersectors
|
||||||
|
const startCustomIntersector =
|
||||||
start.focusPoint && end.focusPoint
|
start.focusPoint && end.focusPoint
|
||||||
? lineSegment(start.focusPoint, end.focusPoint)
|
? lineSegment(start.focusPoint, end.focusPoint)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const endCustomIntersector =
|
||||||
|
start.focusPoint && end.focusPoint
|
||||||
|
? lineSegment(end.focusPoint, start.focusPoint)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
// Needed to handle a special case where an existing arrow is dragged over
|
// Needed to handle a special case where an existing arrow is dragged over
|
||||||
// the same element it is bound to on the other side
|
// the same element it is bound to on the other side
|
||||||
@@ -2290,7 +2280,7 @@ const pointDraggingUpdates = (
|
|||||||
nextArrow.endBinding,
|
nextArrow.endBinding,
|
||||||
endBindable,
|
endBindable,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
customIntersector,
|
endCustomIntersector,
|
||||||
) || nextArrow.points[nextArrow.points.length - 1]
|
) || nextArrow.points[nextArrow.points.length - 1]
|
||||||
: nextArrow.points[nextArrow.points.length - 1];
|
: nextArrow.points[nextArrow.points.length - 1];
|
||||||
|
|
||||||
@@ -2321,7 +2311,7 @@ const pointDraggingUpdates = (
|
|||||||
nextArrow.startBinding,
|
nextArrow.startBinding,
|
||||||
startBindable,
|
startBindable,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
customIntersector,
|
startCustomIntersector,
|
||||||
) || nextArrow.points[0]
|
) || nextArrow.points[0]
|
||||||
: nextArrow.points[0];
|
: nextArrow.points[0];
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
getResizedElementAbsoluteCoords,
|
getResizedElementAbsoluteCoords,
|
||||||
} from "./bounds";
|
} from "./bounds";
|
||||||
import { newElementWith } from "./mutateElement";
|
import { newElementWith } from "./mutateElement";
|
||||||
import { getBoundTextMaxWidth } from "./textElement";
|
import { getBoundTextMaxWidth, getInitialTextMetrics } from "./textElement";
|
||||||
import { normalizeText, measureText } from "./textMeasurements";
|
import { normalizeText, measureText } from "./textMeasurements";
|
||||||
import { wrapText } from "./textWrapping";
|
import { wrapText } from "./textWrapping";
|
||||||
|
|
||||||
@@ -236,27 +236,30 @@ const getTextElementPositionOffsets = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type NewTextElementOptions = {
|
||||||
|
text: string;
|
||||||
|
originalText?: string;
|
||||||
|
fontSize?: number;
|
||||||
|
fontFamily?: FontFamilyValues;
|
||||||
|
textAlign?: TextAlign;
|
||||||
|
verticalAlign?: VerticalAlign;
|
||||||
|
containerId?: ExcalidrawTextContainer["id"] | null;
|
||||||
|
lineHeight?: ExcalidrawTextElement["lineHeight"];
|
||||||
|
autoResize?: ExcalidrawTextElement["autoResize"];
|
||||||
|
} & ElementConstructorOpts;
|
||||||
|
|
||||||
export const newTextElement = (
|
export const newTextElement = (
|
||||||
opts: {
|
opts: NewTextElementOptions,
|
||||||
text: string;
|
|
||||||
originalText?: string;
|
|
||||||
fontSize?: number;
|
|
||||||
fontFamily?: FontFamilyValues;
|
|
||||||
textAlign?: TextAlign;
|
|
||||||
verticalAlign?: VerticalAlign;
|
|
||||||
containerId?: ExcalidrawTextContainer["id"] | null;
|
|
||||||
lineHeight?: ExcalidrawTextElement["lineHeight"];
|
|
||||||
autoResize?: ExcalidrawTextElement["autoResize"];
|
|
||||||
} & ElementConstructorOpts,
|
|
||||||
): NonDeleted<ExcalidrawTextElement> => {
|
): NonDeleted<ExcalidrawTextElement> => {
|
||||||
const fontFamily = opts.fontFamily || DEFAULT_FONT_FAMILY;
|
const fontFamily = opts.fontFamily || DEFAULT_FONT_FAMILY;
|
||||||
const fontSize = opts.fontSize || DEFAULT_FONT_SIZE;
|
const fontSize = opts.fontSize || DEFAULT_FONT_SIZE;
|
||||||
const lineHeight = opts.lineHeight || getLineHeight(fontFamily);
|
const lineHeight = opts.lineHeight || getLineHeight(fontFamily);
|
||||||
const text = normalizeText(opts.text);
|
const normalizedText = normalizeText(opts.text);
|
||||||
const metrics = measureText(
|
const originalText = opts.originalText ?? normalizedText;
|
||||||
text,
|
const metrics = getInitialTextMetrics(
|
||||||
getFontString({ fontFamily, fontSize }),
|
{ ...opts, text: normalizedText },
|
||||||
lineHeight,
|
fontFamily,
|
||||||
|
fontSize,
|
||||||
);
|
);
|
||||||
const textAlign = opts.textAlign || DEFAULT_TEXT_ALIGN;
|
const textAlign = opts.textAlign || DEFAULT_TEXT_ALIGN;
|
||||||
const verticalAlign = opts.verticalAlign || DEFAULT_VERTICAL_ALIGN;
|
const verticalAlign = opts.verticalAlign || DEFAULT_VERTICAL_ALIGN;
|
||||||
@@ -267,7 +270,7 @@ export const newTextElement = (
|
|||||||
|
|
||||||
const textElementProps: ExcalidrawTextElement = {
|
const textElementProps: ExcalidrawTextElement = {
|
||||||
..._newElementBase<ExcalidrawTextElement>("text", opts),
|
..._newElementBase<ExcalidrawTextElement>("text", opts),
|
||||||
text,
|
text: normalizedText,
|
||||||
fontSize,
|
fontSize,
|
||||||
fontFamily,
|
fontFamily,
|
||||||
textAlign,
|
textAlign,
|
||||||
@@ -277,7 +280,7 @@ export const newTextElement = (
|
|||||||
width: metrics.width,
|
width: metrics.width,
|
||||||
height: metrics.height,
|
height: metrics.height,
|
||||||
containerId: opts.containerId || null,
|
containerId: opts.containerId || null,
|
||||||
originalText: opts.originalText ?? text,
|
originalText,
|
||||||
autoResize: opts.autoResize ?? true,
|
autoResize: opts.autoResize ?? true,
|
||||||
lineHeight,
|
lineHeight,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import type { GlobalPoint, LineSegment, LocalPoint } from "@excalidraw/math";
|
import type { GlobalPoint, LineSegment, LocalPoint } from "@excalidraw/math";
|
||||||
|
|
||||||
import type { AppState, Zoom } from "@excalidraw/excalidraw/types";
|
import type { AppState, Zoom } from "@excalidraw/excalidraw/types";
|
||||||
|
import type { Bounds } from "@excalidraw/common";
|
||||||
|
|
||||||
import { getElementAbsoluteCoords } from "./bounds";
|
import { getElementAbsoluteCoords } from "./bounds";
|
||||||
import {
|
import {
|
||||||
@@ -23,7 +24,6 @@ import {
|
|||||||
} from "./transformHandles";
|
} from "./transformHandles";
|
||||||
import { isImageElement, isLinearElement } from "./typeChecks";
|
import { isImageElement, isLinearElement } from "./typeChecks";
|
||||||
|
|
||||||
import type { Bounds } from "./bounds";
|
|
||||||
import type {
|
import type {
|
||||||
TransformHandleType,
|
TransformHandleType,
|
||||||
TransformHandle,
|
TransformHandle,
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
getFontString,
|
getFontString,
|
||||||
isProdEnv,
|
isProdEnv,
|
||||||
invariant,
|
invariant,
|
||||||
|
DEFAULT_FONT_FAMILY,
|
||||||
|
getLineHeight,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import { pointFrom, pointRotateRads, type Radians } from "@excalidraw/math";
|
import { pointFrom, pointRotateRads, type Radians } from "@excalidraw/math";
|
||||||
@@ -30,6 +32,8 @@ import {
|
|||||||
isTextElement,
|
isTextElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
|
|
||||||
|
import type { NewTextElementOptions } from "./newElement";
|
||||||
|
|
||||||
import type { Scene } from "./Scene";
|
import type { Scene } from "./Scene";
|
||||||
|
|
||||||
import type { MaybeTransformHandleType } from "./transformHandles";
|
import type { MaybeTransformHandleType } from "./transformHandles";
|
||||||
@@ -40,6 +44,7 @@ import type {
|
|||||||
ExcalidrawTextContainer,
|
ExcalidrawTextContainer,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
ExcalidrawTextElementWithContainer,
|
ExcalidrawTextElementWithContainer,
|
||||||
|
FontFamilyValues,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
@@ -528,3 +533,24 @@ export const getTextFromElements = (
|
|||||||
.join(separator);
|
.join(separator);
|
||||||
return text;
|
return text;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** When text is already measured and wrapped, we want to respect those dimensions */
|
||||||
|
export const getInitialTextMetrics = (
|
||||||
|
text: NewTextElementOptions,
|
||||||
|
fontFamily: FontFamilyValues = DEFAULT_FONT_FAMILY,
|
||||||
|
fontSize: number = DEFAULT_FONT_SIZE,
|
||||||
|
) => {
|
||||||
|
const shouldUseProvidedDimensions =
|
||||||
|
text.autoResize === false && text.width && text.height;
|
||||||
|
|
||||||
|
return shouldUseProvidedDimensions
|
||||||
|
? {
|
||||||
|
width: text.width,
|
||||||
|
height: text.height,
|
||||||
|
}
|
||||||
|
: measureText(
|
||||||
|
text.text,
|
||||||
|
getFontString({ fontFamily, fontSize }),
|
||||||
|
text.lineHeight ?? getLineHeight(fontFamily),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ import {
|
|||||||
arrayToMap,
|
arrayToMap,
|
||||||
assertNever,
|
assertNever,
|
||||||
cloneJSON,
|
cloneJSON,
|
||||||
getFontString,
|
|
||||||
isDevEnv,
|
isDevEnv,
|
||||||
toBrandedType,
|
toBrandedType,
|
||||||
getLineHeight,
|
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import { bindBindingElement } from "@excalidraw/element";
|
import type { MarkOptional } from "@excalidraw/common/utility-types";
|
||||||
|
|
||||||
|
import { bindBindingElement } from "./binding";
|
||||||
import {
|
import {
|
||||||
newArrowElement,
|
newArrowElement,
|
||||||
newElement,
|
newElement,
|
||||||
@@ -25,21 +25,19 @@ import {
|
|||||||
newLinearElement,
|
newLinearElement,
|
||||||
newMagicFrameElement,
|
newMagicFrameElement,
|
||||||
newTextElement,
|
newTextElement,
|
||||||
} from "@excalidraw/element";
|
type ElementConstructorOpts,
|
||||||
import { measureText, normalizeText } from "@excalidraw/element";
|
} from "./newElement";
|
||||||
import { isArrowElement } from "@excalidraw/element";
|
import { isArrowElement } from "./typeChecks";
|
||||||
|
|
||||||
import { syncInvalidIndices } from "@excalidraw/element";
|
import { syncInvalidIndices } from "./fractionalIndex";
|
||||||
|
|
||||||
import { redrawTextBoundingBox } from "@excalidraw/element";
|
import { getInitialTextMetrics, redrawTextBoundingBox } from "./textElement";
|
||||||
|
|
||||||
import { LinearElementEditor } from "@excalidraw/element";
|
import { LinearElementEditor } from "./linearElementEditor";
|
||||||
|
|
||||||
import { getCommonBounds } from "@excalidraw/element";
|
import { getCommonBounds } from "./bounds";
|
||||||
|
|
||||||
import { Scene } from "@excalidraw/element";
|
import { Scene } from "./Scene";
|
||||||
|
|
||||||
import type { ElementConstructorOpts } from "@excalidraw/element";
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawArrowElement,
|
ExcalidrawArrowElement,
|
||||||
@@ -59,9 +57,7 @@ import type {
|
|||||||
NonDeletedSceneElementsMap,
|
NonDeletedSceneElementsMap,
|
||||||
TextAlign,
|
TextAlign,
|
||||||
VerticalAlign,
|
VerticalAlign,
|
||||||
} from "@excalidraw/element/types";
|
} from "./types";
|
||||||
|
|
||||||
import type { MarkOptional } from "@excalidraw/common/utility-types";
|
|
||||||
|
|
||||||
export type ValidLinearElement = {
|
export type ValidLinearElement = {
|
||||||
type: "arrow" | "line";
|
type: "arrow" | "line";
|
||||||
@@ -580,14 +576,7 @@ export const convertToExcalidrawElements = (
|
|||||||
case "text": {
|
case "text": {
|
||||||
const fontFamily = element?.fontFamily || DEFAULT_FONT_FAMILY;
|
const fontFamily = element?.fontFamily || DEFAULT_FONT_FAMILY;
|
||||||
const fontSize = element?.fontSize || DEFAULT_FONT_SIZE;
|
const fontSize = element?.fontSize || DEFAULT_FONT_SIZE;
|
||||||
const lineHeight = element?.lineHeight || getLineHeight(fontFamily);
|
const metrics = getInitialTextMetrics(element, fontFamily, fontSize);
|
||||||
const text = element.text ?? "";
|
|
||||||
const normalizedText = normalizeText(text);
|
|
||||||
const metrics = measureText(
|
|
||||||
normalizedText,
|
|
||||||
getFontString({ fontFamily, fontSize }),
|
|
||||||
lineHeight,
|
|
||||||
);
|
|
||||||
|
|
||||||
excalidrawElement = newTextElement({
|
excalidrawElement = newTextElement({
|
||||||
width: metrics.width,
|
width: metrics.width,
|
||||||
@@ -11,6 +11,7 @@ import type {
|
|||||||
InteractiveCanvasAppState,
|
InteractiveCanvasAppState,
|
||||||
Zoom,
|
Zoom,
|
||||||
} from "@excalidraw/excalidraw/types";
|
} from "@excalidraw/excalidraw/types";
|
||||||
|
import type { Bounds } from "@excalidraw/common";
|
||||||
|
|
||||||
import { getElementAbsoluteCoords } from "./bounds";
|
import { getElementAbsoluteCoords } from "./bounds";
|
||||||
import {
|
import {
|
||||||
@@ -20,7 +21,6 @@ import {
|
|||||||
isLinearElement,
|
isLinearElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
|
|
||||||
import type { Bounds } from "./bounds";
|
|
||||||
import type {
|
import type {
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import type { ElementOrToolType } from "@excalidraw/excalidraw/types";
|
|||||||
|
|
||||||
import type { MarkNonNullable } from "@excalidraw/common/utility-types";
|
import type { MarkNonNullable } from "@excalidraw/common/utility-types";
|
||||||
|
|
||||||
import type { Bounds } from "./bounds";
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
@@ -356,15 +355,6 @@ export const getDefaultRoundnessTypeForElement = (
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Move this to @excalidraw/math
|
|
||||||
export const isBounds = (box: unknown): box is Bounds =>
|
|
||||||
Array.isArray(box) &&
|
|
||||||
box.length === 4 &&
|
|
||||||
typeof box[0] === "number" &&
|
|
||||||
typeof box[1] === "number" &&
|
|
||||||
typeof box[2] === "number" &&
|
|
||||||
typeof box[3] === "number";
|
|
||||||
|
|
||||||
export const getLinearElementSubType = (
|
export const getLinearElementSubType = (
|
||||||
element: ExcalidrawLinearElement,
|
element: ExcalidrawLinearElement,
|
||||||
): ExcalidrawLinearElementSubType => {
|
): ExcalidrawLinearElementSubType => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { pointFrom } from "@excalidraw/math";
|
|||||||
|
|
||||||
import { Excalidraw } from "@excalidraw/excalidraw";
|
import { Excalidraw } from "@excalidraw/excalidraw";
|
||||||
import {
|
import {
|
||||||
|
type Bounds,
|
||||||
KEYS,
|
KEYS,
|
||||||
getSizeFromPoints,
|
getSizeFromPoints,
|
||||||
reseed,
|
reseed,
|
||||||
@@ -22,7 +23,6 @@ import { resizeSingleElement } from "../src/resizeElements";
|
|||||||
import { LinearElementEditor } from "../src/linearElementEditor";
|
import { LinearElementEditor } from "../src/linearElementEditor";
|
||||||
import { getElementPointsCoords } from "../src/bounds";
|
import { getElementPointsCoords } from "../src/bounds";
|
||||||
|
|
||||||
import type { Bounds } from "../src/bounds";
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElbowArrowElement,
|
ExcalidrawElbowArrowElement,
|
||||||
ExcalidrawFreeDrawElement,
|
ExcalidrawFreeDrawElement,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
KEYS,
|
KEYS,
|
||||||
arrayToMap,
|
arrayToMap,
|
||||||
invariant,
|
invariant,
|
||||||
|
shouldRotateWithDiscreteAngle,
|
||||||
updateActiveTool,
|
updateActiveTool,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
import { isPathALoop } from "@excalidraw/element";
|
import { isPathALoop } from "@excalidraw/element";
|
||||||
@@ -102,10 +103,19 @@ export const actionFinalize = register<FormData>({
|
|||||||
return map;
|
return map;
|
||||||
}, new Map()) ?? new Map();
|
}, new Map()) ?? new Map();
|
||||||
|
|
||||||
bindOrUnbindBindingElement(element, draggedPoints, scene, appState, {
|
bindOrUnbindBindingElement(
|
||||||
newArrow,
|
element,
|
||||||
altKey: event.altKey,
|
draggedPoints,
|
||||||
});
|
sceneCoords.x,
|
||||||
|
sceneCoords.y,
|
||||||
|
scene,
|
||||||
|
appState,
|
||||||
|
{
|
||||||
|
newArrow,
|
||||||
|
altKey: event.altKey,
|
||||||
|
angleLocked: shouldRotateWithDiscreteAngle(event),
|
||||||
|
},
|
||||||
|
);
|
||||||
} else if (isLineElement(element)) {
|
} else if (isLineElement(element)) {
|
||||||
if (
|
if (
|
||||||
appState.selectedLinearElement?.isEditing &&
|
appState.selectedLinearElement?.isEditing &&
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
isTransparent,
|
isTransparent,
|
||||||
reduceToCommonValue,
|
reduceToCommonValue,
|
||||||
invariant,
|
invariant,
|
||||||
|
FONT_SIZES,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import { canBecomePolygon, getNonDeletedElements } from "@excalidraw/element";
|
import { canBecomePolygon, getNonDeletedElements } from "@excalidraw/element";
|
||||||
@@ -758,25 +759,25 @@ export const actionChangeFontSize = register<ExcalidrawTextElement["fontSize"]>(
|
|||||||
group="font-size"
|
group="font-size"
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
value: 16,
|
value: FONT_SIZES.sm,
|
||||||
text: t("labels.small"),
|
text: t("labels.small"),
|
||||||
icon: FontSizeSmallIcon,
|
icon: FontSizeSmallIcon,
|
||||||
testId: "fontSize-small",
|
testId: "fontSize-small",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 20,
|
value: FONT_SIZES.md,
|
||||||
text: t("labels.medium"),
|
text: t("labels.medium"),
|
||||||
icon: FontSizeMediumIcon,
|
icon: FontSizeMediumIcon,
|
||||||
testId: "fontSize-medium",
|
testId: "fontSize-medium",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 28,
|
value: FONT_SIZES.lg,
|
||||||
text: t("labels.large"),
|
text: t("labels.large"),
|
||||||
icon: FontSizeLargeIcon,
|
icon: FontSizeLargeIcon,
|
||||||
testId: "fontSize-large",
|
testId: "fontSize-large",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 36,
|
value: FONT_SIZES.xl,
|
||||||
text: t("labels.veryLarge"),
|
text: t("labels.veryLarge"),
|
||||||
icon: FontSizeExtraLargeIcon,
|
icon: FontSizeExtraLargeIcon,
|
||||||
testId: "fontSize-veryLarge",
|
testId: "fontSize-veryLarge",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
VERTICAL_ALIGN,
|
VERTICAL_ALIGN,
|
||||||
randomId,
|
randomId,
|
||||||
isDevEnv,
|
isDevEnv,
|
||||||
|
FONT_SIZES,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -213,7 +214,7 @@ const chartXLabels = (
|
|||||||
y: y + BAR_GAP / 2,
|
y: y + BAR_GAP / 2,
|
||||||
width: BAR_WIDTH,
|
width: BAR_WIDTH,
|
||||||
angle: 5.87 as Radians,
|
angle: 5.87 as Radians,
|
||||||
fontSize: 16,
|
fontSize: FONT_SIZES.sm,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
verticalAlign: "top",
|
verticalAlign: "top",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ export const createPasteEvent = ({
|
|||||||
if (typeof value !== "string") {
|
if (typeof value !== "string") {
|
||||||
files = files || [];
|
files = files || [];
|
||||||
files.push(value);
|
files.push(value);
|
||||||
event.clipboardData?.items.add(value);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -248,6 +248,8 @@ import {
|
|||||||
doBoundsIntersect,
|
doBoundsIntersect,
|
||||||
isPointInElement,
|
isPointInElement,
|
||||||
maxBindingDistance_simple,
|
maxBindingDistance_simple,
|
||||||
|
convertToExcalidrawElements,
|
||||||
|
type ExcalidrawElementSkeleton,
|
||||||
} from "@excalidraw/element";
|
} from "@excalidraw/element";
|
||||||
|
|
||||||
import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math";
|
import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math";
|
||||||
@@ -395,7 +397,6 @@ import {
|
|||||||
SnapCache,
|
SnapCache,
|
||||||
isGridModeEnabled,
|
isGridModeEnabled,
|
||||||
} from "../snapping";
|
} from "../snapping";
|
||||||
import { convertToExcalidrawElements } from "../data/transform";
|
|
||||||
import { Renderer } from "../scene/Renderer";
|
import { Renderer } from "../scene/Renderer";
|
||||||
import {
|
import {
|
||||||
setEraserCursor,
|
setEraserCursor,
|
||||||
@@ -457,7 +458,7 @@ import type { ClipboardData, PastedMixedContent } from "../clipboard";
|
|||||||
import type { ExportedElements } from "../data";
|
import type { ExportedElements } from "../data";
|
||||||
import type { ContextMenuItems } from "./ContextMenu";
|
import type { ContextMenuItems } from "./ContextMenu";
|
||||||
import type { FileSystemHandle } from "../data/filesystem";
|
import type { FileSystemHandle } from "../data/filesystem";
|
||||||
import type { ExcalidrawElementSkeleton } from "../data/transform";
|
|
||||||
import type {
|
import type {
|
||||||
AppClassProperties,
|
AppClassProperties,
|
||||||
AppProps,
|
AppProps,
|
||||||
@@ -8617,9 +8618,16 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
]),
|
]),
|
||||||
|
point[0],
|
||||||
|
point[1],
|
||||||
this.scene,
|
this.scene,
|
||||||
this.state,
|
this.state,
|
||||||
{ newArrow: true, altKey: event.altKey, initialBinding: true },
|
{
|
||||||
|
newArrow: true,
|
||||||
|
altKey: event.altKey,
|
||||||
|
initialBinding: true,
|
||||||
|
angleLocked: shouldRotateWithDiscreteAngle(event.nativeEvent),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { hitElementBoundingBox } from "@excalidraw/element";
|
|||||||
|
|
||||||
import type { GlobalPoint, Radians } from "@excalidraw/math";
|
import type { GlobalPoint, Radians } from "@excalidraw/math";
|
||||||
|
|
||||||
import type { Bounds } from "@excalidraw/element";
|
import type { Bounds } from "@excalidraw/common";
|
||||||
import type {
|
import type {
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import type { LocalPoint, Radians } from "@excalidraw/math";
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
|
ElementsMapOrArray,
|
||||||
ExcalidrawArrowElement,
|
ExcalidrawArrowElement,
|
||||||
ExcalidrawBindableElement,
|
ExcalidrawBindableElement,
|
||||||
ExcalidrawElbowArrowElement,
|
ExcalidrawElbowArrowElement,
|
||||||
@@ -129,7 +130,8 @@ const getFontFamilyByName = (fontFamilyName: string): FontFamilyValues => {
|
|||||||
const repairBinding = <T extends ExcalidrawArrowElement>(
|
const repairBinding = <T extends ExcalidrawArrowElement>(
|
||||||
element: T,
|
element: T,
|
||||||
binding: FixedPointBinding | null,
|
binding: FixedPointBinding | null,
|
||||||
elementsMap: Readonly<ElementsMap>,
|
targetElementsMap: Readonly<ElementsMap>,
|
||||||
|
localElementsMap: Readonly<ElementsMap> | null | undefined,
|
||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
): FixedPointBinding | null => {
|
): FixedPointBinding | null => {
|
||||||
if (!binding) {
|
if (!binding) {
|
||||||
@@ -148,18 +150,27 @@ const repairBinding = <T extends ExcalidrawArrowElement>(
|
|||||||
return fixedPointBinding;
|
return fixedPointBinding;
|
||||||
}
|
}
|
||||||
|
|
||||||
const boundElement =
|
// Fallback if the bound element is missing but the binding is at least
|
||||||
(elementsMap.get(binding.elementId) as ExcalidrawBindableElement) ||
|
// looking like a valid one shape-wise
|
||||||
undefined;
|
if (binding.mode && binding.fixedPoint && binding.elementId) {
|
||||||
if (boundElement) {
|
return {
|
||||||
if (binding.mode) {
|
elementId: binding.elementId,
|
||||||
return {
|
mode: binding.mode,
|
||||||
elementId: binding.elementId,
|
fixedPoint: normalizeFixedPoint(binding.fixedPoint || [0.5, 0.5]),
|
||||||
mode: binding.mode || "orbit",
|
} as FixedPointBinding | null;
|
||||||
fixedPoint: normalizeFixedPoint(binding.fixedPoint || [0.5, 0.5]),
|
}
|
||||||
} as FixedPointBinding | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const targetBoundElement =
|
||||||
|
(targetElementsMap.get(binding.elementId) as ExcalidrawBindableElement) ||
|
||||||
|
undefined;
|
||||||
|
const boundElement =
|
||||||
|
targetBoundElement ||
|
||||||
|
(localElementsMap?.get(binding.elementId) as ExcalidrawBindableElement) ||
|
||||||
|
undefined;
|
||||||
|
const elementsMap = targetBoundElement ? targetElementsMap : localElementsMap;
|
||||||
|
|
||||||
|
// migrating legacy focus point bindings
|
||||||
|
if (boundElement && elementsMap) {
|
||||||
const p = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
const p = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
element,
|
element,
|
||||||
startOrEnd === "start" ? 0 : element.points.length - 1,
|
startOrEnd === "start" ? 0 : element.points.length - 1,
|
||||||
@@ -193,6 +204,8 @@ const repairBinding = <T extends ExcalidrawArrowElement>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.error(`could not repair binding for element`);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -284,7 +297,8 @@ const restoreElementWithProperties = <
|
|||||||
|
|
||||||
export const restoreElement = (
|
export const restoreElement = (
|
||||||
element: Exclude<ExcalidrawElement, ExcalidrawSelectionElement>,
|
element: Exclude<ExcalidrawElement, ExcalidrawSelectionElement>,
|
||||||
elementsMap: Readonly<ElementsMap>,
|
targetElementsMap: Readonly<ElementsMap>,
|
||||||
|
localElementsMap: Readonly<ElementsMap> | null | undefined,
|
||||||
opts?: {
|
opts?: {
|
||||||
deleteInvisibleElements?: boolean;
|
deleteInvisibleElements?: boolean;
|
||||||
},
|
},
|
||||||
@@ -405,13 +419,15 @@ export const restoreElement = (
|
|||||||
startBinding: repairBinding(
|
startBinding: repairBinding(
|
||||||
element as ExcalidrawArrowElement,
|
element as ExcalidrawArrowElement,
|
||||||
element.startBinding,
|
element.startBinding,
|
||||||
elementsMap,
|
targetElementsMap,
|
||||||
|
localElementsMap,
|
||||||
"start",
|
"start",
|
||||||
),
|
),
|
||||||
endBinding: repairBinding(
|
endBinding: repairBinding(
|
||||||
element as ExcalidrawArrowElement,
|
element as ExcalidrawArrowElement,
|
||||||
element.endBinding,
|
element.endBinding,
|
||||||
elementsMap,
|
targetElementsMap,
|
||||||
|
localElementsMap,
|
||||||
"end",
|
"end",
|
||||||
),
|
),
|
||||||
startArrowhead,
|
startArrowhead,
|
||||||
@@ -575,7 +591,7 @@ const repairFrameMembership = (
|
|||||||
export const restoreElements = (
|
export const restoreElements = (
|
||||||
targetElements: ImportedDataState["elements"],
|
targetElements: ImportedDataState["elements"],
|
||||||
/** NOTE doesn't serve for reconciliation */
|
/** NOTE doesn't serve for reconciliation */
|
||||||
localElements: readonly ExcalidrawElement[] | null | undefined,
|
localElements: Readonly<ElementsMapOrArray> | null | undefined,
|
||||||
opts?:
|
opts?:
|
||||||
| {
|
| {
|
||||||
refreshDimensions?: boolean;
|
refreshDimensions?: boolean;
|
||||||
@@ -586,7 +602,7 @@ export const restoreElements = (
|
|||||||
): OrderedExcalidrawElement[] => {
|
): OrderedExcalidrawElement[] => {
|
||||||
// used to detect duplicate top-level element ids
|
// used to detect duplicate top-level element ids
|
||||||
const existingIds = new Set<string>();
|
const existingIds = new Set<string>();
|
||||||
const elementsMap = arrayToMap(targetElements || []);
|
const targetElementsMap = arrayToMap(targetElements || []);
|
||||||
const localElementsMap = localElements ? arrayToMap(localElements) : null;
|
const localElementsMap = localElements ? arrayToMap(localElements) : null;
|
||||||
const restoredElements = syncInvalidIndices(
|
const restoredElements = syncInvalidIndices(
|
||||||
(targetElements || []).reduce((elements, element) => {
|
(targetElements || []).reduce((elements, element) => {
|
||||||
@@ -598,7 +614,8 @@ export const restoreElements = (
|
|||||||
|
|
||||||
let migratedElement: ExcalidrawElement | null = restoreElement(
|
let migratedElement: ExcalidrawElement | null = restoreElement(
|
||||||
element,
|
element,
|
||||||
elementsMap,
|
targetElementsMap,
|
||||||
|
localElementsMap,
|
||||||
{
|
{
|
||||||
deleteInvisibleElements: opts?.deleteInvisibleElements,
|
deleteInvisibleElements: opts?.deleteInvisibleElements,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { shouldTestInside } from "@excalidraw/element";
|
|||||||
import { hasBoundTextElement, isBoundToContainer } from "@excalidraw/element";
|
import { hasBoundTextElement, isBoundToContainer } from "@excalidraw/element";
|
||||||
import { getBoundTextElementId } from "@excalidraw/element";
|
import { getBoundTextElementId } from "@excalidraw/element";
|
||||||
|
|
||||||
import type { Bounds } from "@excalidraw/element";
|
import type { Bounds } from "@excalidraw/common";
|
||||||
|
|
||||||
import type { GlobalPoint, LineSegment } from "@excalidraw/math/types";
|
import type { GlobalPoint, LineSegment } from "@excalidraw/math/types";
|
||||||
import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types";
|
import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types";
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
CJK_HAND_DRAWN_FALLBACK_FONT,
|
CJK_HAND_DRAWN_FALLBACK_FONT,
|
||||||
WINDOWS_EMOJI_FALLBACK_FONT,
|
WINDOWS_EMOJI_FALLBACK_FONT,
|
||||||
getFontFamilyFallbacks,
|
getFontFamilyFallbacks,
|
||||||
|
FONT_SIZES,
|
||||||
} from "@excalidraw/common";
|
} from "@excalidraw/common";
|
||||||
import { getContainerElement } from "@excalidraw/element";
|
import { getContainerElement } from "@excalidraw/element";
|
||||||
import { charWidth } from "@excalidraw/element";
|
import { charWidth } from "@excalidraw/element";
|
||||||
@@ -240,7 +241,7 @@ export class Fonts {
|
|||||||
for (const [index, fontFamily] of fontFamilies.entries()) {
|
for (const [index, fontFamily] of fontFamilies.entries()) {
|
||||||
const font = getFontString({
|
const font = getFontString({
|
||||||
fontFamily,
|
fontFamily,
|
||||||
fontSize: 16,
|
fontSize: FONT_SIZES.sm,
|
||||||
});
|
});
|
||||||
|
|
||||||
// WARN: without "text" param it does not have to mean that all font faces are loaded as it could be just one irrelevant font face!
|
// WARN: without "text" param it does not have to mean that all font faces are loaded as it could be just one irrelevant font face!
|
||||||
|
|||||||
@@ -293,8 +293,11 @@ export { TTDDialog } from "./components/TTDDialog/TTDDialog";
|
|||||||
export { TTDDialogTrigger } from "./components/TTDDialog/TTDDialogTrigger";
|
export { TTDDialogTrigger } from "./components/TTDDialog/TTDDialogTrigger";
|
||||||
|
|
||||||
export { zoomToFitBounds } from "./actions/actionCanvas";
|
export { zoomToFitBounds } from "./actions/actionCanvas";
|
||||||
export { convertToExcalidrawElements } from "./data/transform";
|
export {
|
||||||
export { getCommonBounds, getVisibleSceneBounds } from "@excalidraw/element";
|
getCommonBounds,
|
||||||
|
getVisibleSceneBounds,
|
||||||
|
convertToExcalidrawElements,
|
||||||
|
} from "@excalidraw/element";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
elementsOverlappingBBox,
|
elementsOverlappingBBox,
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ import {
|
|||||||
polygonIncludesPointNonZero,
|
polygonIncludesPointNonZero,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
|
import { type Bounds } from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type Bounds,
|
|
||||||
computeBoundTextPosition,
|
computeBoundTextPosition,
|
||||||
doBoundsIntersect,
|
doBoundsIntersect,
|
||||||
getBoundTextElement,
|
getBoundTextElement,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import { type Mutable } from "@excalidraw/common/utility-types";
|
|||||||
|
|
||||||
import { newTextElement } from "@excalidraw/element";
|
import { newTextElement } from "@excalidraw/element";
|
||||||
|
|
||||||
import type { Bounds } from "@excalidraw/element";
|
import type { Bounds } from "@excalidraw/common";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
|
|
||||||
import type { InclusiveRange } from "@excalidraw/math";
|
import type { InclusiveRange } from "@excalidraw/math";
|
||||||
|
|
||||||
import type { Bounds } from "@excalidraw/element";
|
import type { Bounds } from "@excalidraw/common";
|
||||||
import type { MaybeTransformHandleType } from "@excalidraw/element";
|
import type { MaybeTransformHandleType } from "@excalidraw/element";
|
||||||
import type {
|
import type {
|
||||||
ElementsMap,
|
ElementsMap,
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import { isLinearElementType } from "@excalidraw/element";
|
|||||||
import { getSelectedElements } from "@excalidraw/element";
|
import { getSelectedElements } from "@excalidraw/element";
|
||||||
import { selectGroupsForSelectedElements } from "@excalidraw/element";
|
import { selectGroupsForSelectedElements } from "@excalidraw/element";
|
||||||
|
|
||||||
|
import { FONT_SIZES } from "@excalidraw/common";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawGenericElement,
|
ExcalidrawGenericElement,
|
||||||
@@ -406,7 +408,7 @@ export class API {
|
|||||||
text: opts?.label?.text || "sample-text",
|
text: opts?.label?.text || "sample-text",
|
||||||
width: 50,
|
width: 50,
|
||||||
height: 20,
|
height: 20,
|
||||||
fontSize: 16,
|
fontSize: FONT_SIZES.sm,
|
||||||
containerId: rectangle.id,
|
containerId: rectangle.id,
|
||||||
frameId:
|
frameId:
|
||||||
opts?.label?.frameId === undefined
|
opts?.label?.frameId === undefined
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
type LocalPoint,
|
type LocalPoint,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import type { Bounds } from "@excalidraw/element";
|
import type { Bounds } from "@excalidraw/common";
|
||||||
|
|
||||||
export type LineSegment<P extends LocalPoint | GlobalPoint> = [P, P];
|
export type LineSegment<P extends LocalPoint | GlobalPoint> = [P, P];
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { arrayToMap } from "@excalidraw/common";
|
import { arrayToMap, type Bounds } from "@excalidraw/common";
|
||||||
import { getElementBounds } from "@excalidraw/element";
|
import { getElementBounds } from "@excalidraw/element";
|
||||||
import {
|
import {
|
||||||
isArrowElement,
|
isArrowElement,
|
||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
rangeInclusive,
|
rangeInclusive,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import type { Bounds } from "@excalidraw/element";
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawFreeDrawElement,
|
ExcalidrawFreeDrawElement,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { API } from "@excalidraw/excalidraw/tests/helpers/api";
|
import { API } from "@excalidraw/excalidraw/tests/helpers/api";
|
||||||
|
|
||||||
import type { Bounds } from "@excalidraw/element";
|
import type { Bounds } from "@excalidraw/common";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
elementPartiallyOverlapsWithOrContainsBBox,
|
elementPartiallyOverlapsWithOrContainsBBox,
|
||||||
|
|||||||
Reference in New Issue
Block a user