Merge remote-tracking branch 'origin/release' into danieljgeiger-mathjax-maint-stage

This commit is contained in:
Daniel J. Geiger
2023-11-03 19:11:26 -05:00
163 changed files with 9277 additions and 3561 deletions
+1 -1
View File
@@ -392,7 +392,7 @@ export const getLinkHandleFromCoords = (
[x1, y1, x2, y2]: Bounds,
angle: number,
appState: Pick<UIAppState, "zoom">,
): [x: number, y: number, width: number, height: number] => {
): Bounds => {
const size = DEFAULT_LINK_SIZE;
const linkWidth = size / appState.zoom.value;
const linkHeight = size / appState.zoom.value;
+16 -16
View File
@@ -34,7 +34,12 @@ export type RectangleBox = {
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 [x1: number, y1: number, x2: number, y2: number];
export type Bounds = readonly [
minX: number,
minY: number,
maxX: number,
maxY: number,
];
export class ElementBounds {
private static boundsCache = new WeakMap<
@@ -63,7 +68,7 @@ export class ElementBounds {
}
private static calculateBounds(element: ExcalidrawElement): Bounds {
let bounds: [number, number, number, number];
let bounds: Bounds;
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(element);
@@ -387,7 +392,7 @@ const getCubicBezierCurveBound = (
export const getMinMaxXYFromCurvePathOps = (
ops: Op[],
transformXY?: (x: number, y: number) => [number, number],
): [number, number, number, number] => {
): Bounds => {
let currentP: Point = [0, 0];
const { minX, minY, maxX, maxY } = ops.reduce(
@@ -435,9 +440,9 @@ export const getMinMaxXYFromCurvePathOps = (
return [minX, minY, maxX, maxY];
};
const getBoundsFromPoints = (
export const getBoundsFromPoints = (
points: ExcalidrawFreeDrawElement["points"],
): [number, number, number, number] => {
): Bounds => {
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
@@ -589,7 +594,7 @@ const getLinearElementRotatedBounds = (
element: ExcalidrawLinearElement,
cx: number,
cy: number,
): [number, number, number, number] => {
): Bounds => {
if (element.points.length < 2) {
const [pointX, pointY] = element.points[0];
const [x, y] = rotate(
@@ -600,7 +605,7 @@ const getLinearElementRotatedBounds = (
element.angle,
);
let coords: [number, number, number, number] = [x, y, x, y];
let coords: Bounds = [x, y, x, y];
const boundTextElement = getBoundTextElement(element);
if (boundTextElement) {
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
@@ -625,12 +630,7 @@ const getLinearElementRotatedBounds = (
const transformXY = (x: number, y: number) =>
rotate(element.x + x, element.y + y, cx, cy, element.angle);
const res = getMinMaxXYFromCurvePathOps(ops, transformXY);
let coords: [number, number, number, number] = [
res[0],
res[1],
res[2],
res[3],
];
let coords: Bounds = [res[0], res[1], res[2], res[3]];
const boundTextElement = getBoundTextElement(element);
if (boundTextElement) {
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
@@ -692,7 +692,7 @@ export const getResizedElementAbsoluteCoords = (
nextWidth: number,
nextHeight: number,
normalizePoints: boolean,
): [number, number, number, number] => {
): Bounds => {
if (!(isLinearElement(element) || isFreeDrawElement(element))) {
return [
element.x,
@@ -709,7 +709,7 @@ export const getResizedElementAbsoluteCoords = (
normalizePoints,
);
let bounds: [number, number, number, number];
let bounds: Bounds;
if (isFreeDrawElement(element)) {
// Free Draw
@@ -740,7 +740,7 @@ export const getResizedElementAbsoluteCoords = (
export const getElementPointsCoords = (
element: ExcalidrawLinearElement,
points: readonly (readonly [number, number])[],
): [number, number, number, number] => {
): Bounds => {
// This might be computationally heavey
const gen = rough.generator();
const curve =
+3 -1
View File
@@ -494,7 +494,9 @@ const hitTestFreeDrawElement = (
// for filled freedraw shapes, support
// selecting from inside
if (shape && shape.sets.length) {
return hitTestRoughShape(shape, x, y, threshold);
return element.fillStyle === "solid"
? hitTestCurveInside(shape, x, y, "round")
: hitTestRoughShape(shape, x, y, threshold);
}
return false;
+51 -37
View File
@@ -1,5 +1,5 @@
import { updateBoundElements } from "./binding";
import { getCommonBounds } from "./bounds";
import { Bounds, getCommonBounds } from "./bounds";
import { mutateElement } from "./mutateElement";
import { getPerfectElementSize } from "./sizeHelpers";
import { NonDeletedExcalidrawElement } from "./types";
@@ -8,7 +8,11 @@ import { getBoundTextElement } from "./textElement";
import { isSelectedViaGroup } from "../groups";
import { getGridPoint } from "../math";
import Scene from "../scene/Scene";
import { isFrameElement } from "./typeChecks";
import {
isArrowElement,
isBoundToContainer,
isFrameElement,
} from "./typeChecks";
export const dragSelectedElements = (
pointerDownState: PointerDownState,
@@ -35,44 +39,41 @@ export const dragSelectedElements = (
if (frames.length > 0) {
const elementsInFrames = scene
.getNonDeletedElements()
.filter((e) => !isBoundToContainer(e))
.filter((e) => e.frameId !== null)
.filter((e) => frames.includes(e.frameId!));
elementsInFrames.forEach((element) => elementsToUpdate.add(element));
}
const commonBounds = getCommonBounds(
Array.from(elementsToUpdate).map(
(el) => pointerDownState.originalElements.get(el.id) ?? el,
),
);
const adjustedOffset = calculateOffset(
commonBounds,
offset,
snapOffset,
gridSize,
);
elementsToUpdate.forEach((element) => {
updateElementCoords(
pointerDownState,
element,
offset,
snapOffset,
gridSize,
);
updateElementCoords(pointerDownState, element, adjustedOffset);
// update coords of bound text only if we're dragging the container directly
// (we don't drag the group that it's part of)
if (
// Don't update coords of arrow label since we calculate its position during render
!isArrowElement(element) &&
// container isn't part of any group
// (perf optim so we don't check `isSelectedViaGroup()` in every case)
!element.groupIds.length ||
// container is part of a group, but we're dragging the container directly
(appState.editingGroupId && !isSelectedViaGroup(appState, element))
(!element.groupIds.length ||
// container is part of a group, but we're dragging the container directly
(appState.editingGroupId && !isSelectedViaGroup(appState, element)))
) {
const textElement = getBoundTextElement(element);
if (
textElement &&
// when container is added to a frame, so will its bound text
// so the text is already in `elementsToUpdate` and we should avoid
// updating its coords again
(!textElement.frameId || !frames.includes(textElement.frameId))
) {
updateElementCoords(
pointerDownState,
textElement,
offset,
snapOffset,
gridSize,
);
if (textElement) {
updateElementCoords(pointerDownState, textElement, adjustedOffset);
}
}
updateBoundElements(element, {
@@ -81,23 +82,20 @@ export const dragSelectedElements = (
});
};
const updateElementCoords = (
pointerDownState: PointerDownState,
element: NonDeletedExcalidrawElement,
const calculateOffset = (
commonBounds: Bounds,
dragOffset: { x: number; y: number },
snapOffset: { x: number; y: number },
gridSize: AppState["gridSize"],
) => {
const originalElement =
pointerDownState.originalElements.get(element.id) ?? element;
let nextX = originalElement.x + dragOffset.x + snapOffset.x;
let nextY = originalElement.y + dragOffset.y + snapOffset.y;
): { x: number; y: number } => {
const [x, y] = commonBounds;
let nextX = x + dragOffset.x + snapOffset.x;
let nextY = y + dragOffset.y + snapOffset.y;
if (snapOffset.x === 0 || snapOffset.y === 0) {
const [nextGridX, nextGridY] = getGridPoint(
originalElement.x + dragOffset.x,
originalElement.y + dragOffset.y,
x + dragOffset.x,
y + dragOffset.y,
gridSize,
);
@@ -109,6 +107,22 @@ const updateElementCoords = (
nextY = nextGridY;
}
}
return {
x: nextX - x,
y: nextY - y,
};
};
const updateElementCoords = (
pointerDownState: PointerDownState,
element: NonDeletedExcalidrawElement,
dragOffset: { x: number; y: number },
) => {
const originalElement =
pointerDownState.originalElements.get(element.id) ?? element;
const nextX = originalElement.x + dragOffset.x;
const nextY = originalElement.y + dragOffset.y;
mutateElement(element, {
x: nextX,
+9
View File
@@ -28,6 +28,7 @@ const embeddedLinkCache = new Map<string, EmbeddedLink>();
const RE_YOUTUBE =
/^(?:http(?:s)?:\/\/)?(?:www\.)?youtu(?:be\.com|\.be)\/(embed\/|watch\?v=|shorts\/|playlist\?list=|embed\/videoseries\?list=)?([a-zA-Z0-9_-]+)(?:\?t=|&t=|\?start=|&start=)?([a-zA-Z0-9_-]+)?[^\s]*$/;
const RE_VIMEO =
/^(?:http(?:s)?:\/\/)?(?:(?:w){3}.)?(?:player\.)?vimeo\.com\/(?:video\/)?([^?\s]+)(?:\?.*)?$/;
const RE_FIGMA = /^https:\/\/(?:www\.)?figma\.com/;
@@ -47,6 +48,9 @@ const RE_VALTOWN =
const RE_GENERIC_EMBED =
/^<(?:iframe|blockquote)[\s\S]*?\s(?:src|href)=["']([^"']*)["'][\s\S]*?>$/i;
const RE_GIPHY =
/giphy.com\/(?:clips|embed|gifs)\/[a-zA-Z0-9]*?-?([a-zA-Z0-9]+)(?:[^a-zA-Z0-9]|$)/;
const ALLOWED_DOMAINS = new Set([
"youtube.com",
"youtu.be",
@@ -59,6 +63,7 @@ const ALLOWED_DOMAINS = new Set([
"*.simplepdf.eu",
"stackblitz.com",
"val.town",
"giphy.com",
]);
const createSrcDoc = (body: string) => {
@@ -308,6 +313,10 @@ export const extractSrc = (htmlString: string): string => {
return gistMatch[1];
}
if (RE_GIPHY.test(htmlString)) {
return `https://giphy.com/embed/${RE_GIPHY.exec(htmlString)![1]}`;
}
const match = htmlString.match(RE_GENERIC_EMBED);
if (match && match.length === 2) {
return match[1];
+2 -1
View File
@@ -21,6 +21,7 @@ import {
} from "../math";
import { getElementAbsoluteCoords, getLockedLinearCursorAlignSize } from ".";
import {
Bounds,
getCurvePathOps,
getElementPointsCoords,
getMinMaxXYFromCurvePathOps,
@@ -1316,7 +1317,7 @@ export class LinearElementEditor {
static getMinMaxXYWithBoundText = (
element: ExcalidrawLinearElement,
elementBounds: [number, number, number, number],
elementBounds: Bounds,
boundTextElement: ExcalidrawTextElementWithContainer,
): [number, number, number, number, number, number] => {
let [x1, y1, x2, y2] = elementBounds;
+2 -2
View File
@@ -163,8 +163,8 @@ export const newElementWith = <TElement extends ExcalidrawElement>(
*
* NOTE: does not trigger re-render.
*/
export const bumpVersion = (
element: Mutable<ExcalidrawElement>,
export const bumpVersion = <T extends Mutable<ExcalidrawElement>>(
element: T,
version?: ExcalidrawElement["version"],
) => {
element.version = (version ?? element.version) + 1;
+2 -1
View File
@@ -13,6 +13,7 @@ import {
MaybeTransformHandleType,
} from "./transformHandles";
import { AppState, Zoom } from "../types";
import { Bounds } from "./bounds";
const isInsideTransformHandle = (
transformHandle: TransformHandle,
@@ -87,7 +88,7 @@ export const getElementWithTransformHandleType = (
};
export const getTransformHandleTypeFromCoords = (
[x1, y1, x2, y2]: readonly [number, number, number, number],
[x1, y1, x2, y2]: Bounds,
scenePointerX: number,
scenePointerY: number,
zoom: Zoom,
@@ -1493,6 +1493,7 @@ const createMathActions = () => {
: element;
return isMathElement(el) && el.customData?.mathOnly;
},
true,
null,
);
if (mathOnly === null) {
@@ -1554,6 +1555,7 @@ const createMathActions = () => {
? getMathProps.ensureMathProps(el.customData).mathOnly
: null;
},
true,
getMathProps.getMathOnly(appState),
);
return (
+61 -79
View File
@@ -17,8 +17,8 @@ import {
} from "./types";
import { API } from "../tests/helpers/api";
import { mutateElement } from "./mutateElement";
import { resize } from "../tests/utils";
import { getOriginalContainerHeightFromCache } from "./textWysiwyg";
import { getTextEditor } from "../tests/queries/dom";
// Unmount ReactDOM from root
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
@@ -26,12 +26,6 @@ ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
const tab = " ";
const mouse = new Pointer("mouse");
const getTextEditor = () => {
return document.querySelector(
".excalidraw-textEditorContainer > textarea",
) as HTMLTextAreaElement;
};
const updateTextEditor = (editor: HTMLTextAreaElement, value: string) => {
fireEvent.change(editor, { target: { value } });
editor.dispatchEvent(new Event("input"));
@@ -186,7 +180,7 @@ describe("textWysiwyg", () => {
expect(h.state.editingElement?.id).toBe(boundText.id);
});
it("should edit text under cursor when clicked with text tool", () => {
it("should edit text under cursor when clicked with text tool", async () => {
const text = API.createElement({
type: "text",
text: "ola",
@@ -201,14 +195,14 @@ describe("textWysiwyg", () => {
mouse.clickAt(text.x + 50, text.y + 50);
const editor = getTextEditor();
const editor = await getTextEditor(false);
expect(editor).not.toBe(null);
expect(h.state.editingElement?.id).toBe(text.id);
expect(h.elements.length).toBe(1);
});
it("should edit text under cursor when double-clicked with selection tool", () => {
it("should edit text under cursor when double-clicked with selection tool", async () => {
const text = API.createElement({
type: "text",
text: "ola",
@@ -223,12 +217,26 @@ describe("textWysiwyg", () => {
mouse.doubleClickAt(text.x + 50, text.y + 50);
const editor = getTextEditor();
const editor = await getTextEditor(false);
expect(editor).not.toBe(null);
expect(h.state.editingElement?.id).toBe(text.id);
expect(h.elements.length).toBe(1);
});
// FIXME too flaky. No one knows why.
it.skip("should bump the version of a labeled arrow when the label is updated", async () => {
const arrow = UI.createElement("arrow", {
width: 300,
height: 0,
});
await UI.editText(arrow, "Hello");
const { version } = arrow;
await UI.editText(arrow, "Hello\nworld!");
expect(arrow.version).toEqual(version + 1);
});
});
describe("Test container-unbound text", () => {
@@ -250,7 +258,7 @@ describe("textWysiwyg", () => {
textElement = UI.createElement("text");
mouse.clickOn(textElement);
textarea = getTextEditor();
textarea = await getTextEditor(true);
});
afterAll(() => {
@@ -460,7 +468,7 @@ describe("textWysiwyg", () => {
UI.clickTool("text");
mouse.clickAt(750, 300);
textarea = getTextEditor();
textarea = await getTextEditor(true);
updateTextEditor(
textarea,
"Excalidraw is an opensource virtual collaborative whiteboard for sketching hand-drawn like diagrams!",
@@ -512,7 +520,7 @@ describe("textWysiwyg", () => {
{ id: text.id, type: "text" },
]);
mouse.down();
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
@@ -540,7 +548,7 @@ describe("textWysiwyg", () => {
]);
expect(text.angle).toBe(rectangle.angle);
mouse.down();
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
@@ -567,7 +575,7 @@ describe("textWysiwyg", () => {
API.setSelectedElements([diamond]);
Keyboard.keyPress(KEYS.ENTER);
const editor = getTextEditor();
const editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
const value = new Array(1000).fill("1").join("\n");
@@ -602,7 +610,7 @@ describe("textWysiwyg", () => {
expect(text.type).toBe("text");
expect(text.containerId).toBe(null);
mouse.down();
let editor = getTextEditor();
let editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
editor.blur();
@@ -617,7 +625,7 @@ describe("textWysiwyg", () => {
expect(text.containerId).toBe(rectangle.id);
mouse.down();
editor = getTextEditor();
editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
await new Promise((r) => setTimeout(r, 0));
@@ -639,7 +647,7 @@ describe("textWysiwyg", () => {
const text = h.elements[1] as ExcalidrawTextElementWithContainer;
expect(text.type).toBe("text");
expect(text.containerId).toBe(rectangle.id);
const editor = getTextEditor();
const editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
@@ -674,7 +682,7 @@ describe("textWysiwyg", () => {
{ id: text.id, type: "text" },
]);
mouse.down();
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
await new Promise((r) => setTimeout(r, 0));
@@ -699,7 +707,7 @@ describe("textWysiwyg", () => {
freedraw.y + freedraw.height / 2,
);
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
fireEvent.keyDown(editor, { key: KEYS.ESCAPE });
@@ -733,7 +741,7 @@ describe("textWysiwyg", () => {
expect(text.type).toBe("text");
expect(text.containerId).toBe(null);
mouse.down();
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
@@ -748,7 +756,7 @@ describe("textWysiwyg", () => {
UI.clickTool("text");
mouse.clickAt(20, 30);
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(
editor,
@@ -793,7 +801,7 @@ describe("textWysiwyg", () => {
mouse.down();
const text = h.elements[1] as ExcalidrawTextElementWithContainer;
let editor = getTextEditor();
let editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello World!");
@@ -806,7 +814,7 @@ describe("textWysiwyg", () => {
rectangle.y + rectangle.height / 2,
);
mouse.down();
editor = getTextEditor();
editor = await getTextEditor(true);
editor.select();
fireEvent.click(screen.getByTitle(/code/i));
@@ -839,7 +847,7 @@ describe("textWysiwyg", () => {
Keyboard.keyDown(KEYS.ENTER);
let text = h.elements[1] as ExcalidrawTextElementWithContainer;
let editor = getTextEditor();
let editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
@@ -860,7 +868,7 @@ describe("textWysiwyg", () => {
mouse.select(rectangle);
Keyboard.keyPress(KEYS.ENTER);
editor = getTextEditor();
editor = await getTextEditor(true);
updateTextEditor(editor, "Hello");
await new Promise((r) => setTimeout(r, 0));
@@ -889,7 +897,7 @@ describe("textWysiwyg", () => {
const text = h.elements[1] as ExcalidrawTextElementWithContainer;
expect(text.containerId).toBe(rectangle.id);
const editor = getTextEditor();
const editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
@@ -926,7 +934,7 @@ describe("textWysiwyg", () => {
// Bind first text
const text = h.elements[1] as ExcalidrawTextElementWithContainer;
expect(text.containerId).toBe(rectangle.id);
const editor = getTextEditor();
const editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello World!");
editor.blur();
@@ -947,13 +955,13 @@ describe("textWysiwyg", () => {
it("should respect text alignment when resizing", async () => {
Keyboard.keyPress(KEYS.ENTER);
let editor = getTextEditor();
let editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello");
editor.blur();
// should center align horizontally and vertically by default
resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
UI.resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
[
85,
@@ -964,7 +972,7 @@ describe("textWysiwyg", () => {
mouse.select(rectangle);
Keyboard.keyPress(KEYS.ENTER);
editor = getTextEditor();
editor = await getTextEditor(true);
editor.select();
@@ -977,7 +985,7 @@ describe("textWysiwyg", () => {
editor.blur();
// should left align horizontally and bottom vertically after resize
resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
UI.resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
[
15,
@@ -987,7 +995,7 @@ describe("textWysiwyg", () => {
mouse.select(rectangle);
Keyboard.keyPress(KEYS.ENTER);
editor = getTextEditor();
editor = await getTextEditor(true);
editor.select();
@@ -999,7 +1007,7 @@ describe("textWysiwyg", () => {
editor.blur();
// should right align horizontally and top vertically after resize
resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
UI.resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
[
374.99999999999994,
@@ -1025,7 +1033,7 @@ describe("textWysiwyg", () => {
expect(text.type).toBe("text");
expect(text.containerId).toBe(rectangle.id);
mouse.down();
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
@@ -1040,7 +1048,7 @@ describe("textWysiwyg", () => {
it("should scale font size correctly when resizing using shift", async () => {
Keyboard.keyPress(KEYS.ENTER);
const editor = getTextEditor();
const editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello");
editor.blur();
@@ -1049,7 +1057,7 @@ describe("textWysiwyg", () => {
expect(rectangle.height).toBe(75);
expect(textElement.fontSize).toBe(20);
resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 50], {
UI.resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 50], {
shift: true,
});
expect(rectangle.width).toBe(200);
@@ -1060,7 +1068,7 @@ describe("textWysiwyg", () => {
it("should bind text correctly when container duplicated with alt-drag", async () => {
Keyboard.keyPress(KEYS.ENTER);
const editor = getTextEditor();
const editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello");
editor.blur();
@@ -1092,7 +1100,7 @@ describe("textWysiwyg", () => {
it("undo should work", async () => {
Keyboard.keyPress(KEYS.ENTER);
const editor = getTextEditor();
const editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello");
editor.blur();
@@ -1129,7 +1137,7 @@ describe("textWysiwyg", () => {
it("should not allow bound text with only whitespaces", async () => {
Keyboard.keyPress(KEYS.ENTER);
const editor = getTextEditor();
const editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, " ");
@@ -1184,19 +1192,19 @@ describe("textWysiwyg", () => {
it("should reset the container height cache when resizing", async () => {
Keyboard.keyPress(KEYS.ENTER);
expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(75);
let editor = getTextEditor();
let editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello");
editor.blur();
resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
UI.resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
expect(rectangle.height).toBeCloseTo(155, 8);
expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(null);
mouse.select(rectangle);
Keyboard.keyPress(KEYS.ENTER);
editor = getTextEditor();
editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
editor.blur();
@@ -1212,7 +1220,7 @@ describe("textWysiwyg", () => {
Keyboard.keyPress(KEYS.ENTER);
expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(75);
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
editor.blur();
@@ -1237,7 +1245,7 @@ describe("textWysiwyg", () => {
Keyboard.keyPress(KEYS.ENTER);
expect(getOriginalContainerHeightFromCache(rectangle.id)).toBe(75);
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(editor, "Hello World!");
editor.blur();
expect(
@@ -1269,12 +1277,12 @@ describe("textWysiwyg", () => {
beforeEach(async () => {
Keyboard.keyPress(KEYS.ENTER);
editor = getTextEditor();
editor = await getTextEditor(true);
updateTextEditor(editor, "Hello");
editor.blur();
mouse.select(rectangle);
Keyboard.keyPress(KEYS.ENTER);
editor = getTextEditor();
editor = await getTextEditor(true);
editor.select();
});
@@ -1385,7 +1393,7 @@ describe("textWysiwyg", () => {
it("should wrap text in a container when wrap text in container triggered from context menu", async () => {
UI.clickTool("text");
mouse.clickAt(20, 30);
const editor = getTextEditor();
const editor = await getTextEditor(true);
updateTextEditor(
editor,
@@ -1431,7 +1439,7 @@ describe("textWysiwyg", () => {
type: "text",
},
],
fillStyle: "hachure",
fillStyle: "solid",
groupIds: [],
height: 35,
isDeleted: false,
@@ -1444,7 +1452,7 @@ describe("textWysiwyg", () => {
},
strokeColor: "#1e1e1e",
strokeStyle: "solid",
strokeWidth: 1,
strokeWidth: 2,
type: "rectangle",
updated: 1,
version: 1,
@@ -1473,7 +1481,7 @@ describe("textWysiwyg", () => {
// Bind first text
let text = h.elements[1] as ExcalidrawTextElementWithContainer;
expect(text.containerId).toBe(rectangle.id);
let editor = getTextEditor();
let editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello!");
expect(
@@ -1498,7 +1506,7 @@ describe("textWysiwyg", () => {
rectangle.x + rectangle.width / 2,
rectangle.y + rectangle.height / 2,
);
editor = getTextEditor();
editor = await getTextEditor(true);
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Excalidraw");
editor.blur();
@@ -1512,30 +1520,4 @@ describe("textWysiwyg", () => {
expect(text.text).toBe("Excalidraw");
});
});
it("should bump the version of labelled arrow when label updated", async () => {
await render(<Excalidraw handleKeyboardGlobally={true} />);
const arrow = UI.createElement("arrow", {
width: 300,
height: 0,
});
mouse.select(arrow);
Keyboard.keyPress(KEYS.ENTER);
let editor = getTextEditor();
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello");
editor.blur();
const { version } = arrow;
mouse.select(arrow);
Keyboard.keyPress(KEYS.ENTER);
editor = getTextEditor();
await new Promise((r) => setTimeout(r, 0));
updateTextEditor(editor, "Hello\nworld!");
editor.blur();
expect(arrow.version).toEqual(version + 1);
});
});
+2 -1
View File
@@ -634,7 +634,7 @@ export const textWysiwyg = ({
window.removeEventListener("pointerdown", onPointerDown);
window.removeEventListener("pointerup", bindBlurEvent);
window.removeEventListener("blur", handleSubmit);
window.removeEventListener("beforeunload", handleSubmit);
unbindUpdate();
editable.remove();
@@ -751,6 +751,7 @@ export const textWysiwyg = ({
passive: false,
capture: true,
});
window.addEventListener("beforeunload", handleSubmit);
excalidrawContainer
?.querySelector(".excalidraw-textEditorContainer")!
.appendChild(editable);
+2 -2
View File
@@ -4,7 +4,7 @@ import {
PointerType,
} from "./types";
import { getElementAbsoluteCoords } from "./bounds";
import { Bounds, getElementAbsoluteCoords } from "./bounds";
import { rotate } from "../math";
import { InteractiveCanvasAppState, Zoom } from "../types";
import { isTextElement } from ".";
@@ -23,7 +23,7 @@ export type TransformHandleDirection =
export type TransformHandleType = TransformHandleDirection | "rotation";
export type TransformHandle = [number, number, number, number];
export type TransformHandle = Bounds;
export type TransformHandles = Partial<{
[T in TransformHandleType]: TransformHandle;
}>;