|
@@ -17,6 +17,7 @@ import {
|
|
|
ExcalidrawLinearElement,
|
|
|
ExcalidrawTextElementWithContainer,
|
|
|
ExcalidrawTextElement,
|
|
|
+ ExcalidrawTextContainer,
|
|
|
} from "./types";
|
|
|
import { AppState } from "../types";
|
|
|
import { mutateElement } from "./mutateElement";
|
|
@@ -60,6 +61,38 @@ const getTransform = (
|
|
|
return `translate(${translateX}px, ${translateY}px) scale(${zoom.value}) rotate(${degree}deg)`;
|
|
|
};
|
|
|
|
|
|
+const originalContainerCache: {
|
|
|
+ [id: ExcalidrawTextContainer["id"]]:
|
|
|
+ | {
|
|
|
+ height: ExcalidrawTextContainer["height"];
|
|
|
+ }
|
|
|
+ | undefined;
|
|
|
+} = {};
|
|
|
+
|
|
|
+export const updateOriginalContainerCache = (
|
|
|
+ id: ExcalidrawTextContainer["id"],
|
|
|
+ height: ExcalidrawTextContainer["height"],
|
|
|
+) => {
|
|
|
+ const data =
|
|
|
+ originalContainerCache[id] || (originalContainerCache[id] = { height });
|
|
|
+ data.height = height;
|
|
|
+ return data;
|
|
|
+};
|
|
|
+
|
|
|
+export const resetOriginalContainerCache = (
|
|
|
+ id: ExcalidrawTextContainer["id"],
|
|
|
+) => {
|
|
|
+ if (originalContainerCache[id]) {
|
|
|
+ delete originalContainerCache[id];
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+export const getOriginalContainerHeightFromCache = (
|
|
|
+ id: ExcalidrawTextContainer["id"],
|
|
|
+) => {
|
|
|
+ return originalContainerCache[id]?.height ?? null;
|
|
|
+};
|
|
|
+
|
|
|
export const textWysiwyg = ({
|
|
|
id,
|
|
|
onChange,
|
|
@@ -87,6 +120,9 @@ export const textWysiwyg = ({
|
|
|
updatedTextElement: ExcalidrawTextElement,
|
|
|
editable: HTMLTextAreaElement,
|
|
|
) => {
|
|
|
+ if (!editable.style.fontFamily || !editable.style.fontSize) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
const currentFont = editable.style.fontFamily.replace(/"/g, "");
|
|
|
if (
|
|
|
getFontFamilyString({ fontFamily: updatedTextElement.fontFamily }) !==
|
|
@@ -99,7 +135,6 @@ export const textWysiwyg = ({
|
|
|
}
|
|
|
return false;
|
|
|
};
|
|
|
- let originalContainerHeight: number;
|
|
|
|
|
|
const updateWysiwygStyle = () => {
|
|
|
const appState = app.state;
|
|
@@ -123,7 +158,7 @@ export const textWysiwyg = ({
|
|
|
const width = updatedTextElement.width;
|
|
|
// Set to element height by default since that's
|
|
|
// what is going to be used for unbounded text
|
|
|
- let height = updatedTextElement.height;
|
|
|
+ let textElementHeight = updatedTextElement.height;
|
|
|
if (container && updatedTextElement.containerId) {
|
|
|
if (isArrowElement(container)) {
|
|
|
const boundTextCoords =
|
|
@@ -142,34 +177,52 @@ export const textWysiwyg = ({
|
|
|
// using editor.style.height to get the accurate height of text editor
|
|
|
const editorHeight = Number(editable.style.height.slice(0, -2));
|
|
|
if (editorHeight > 0) {
|
|
|
- height = editorHeight;
|
|
|
+ textElementHeight = editorHeight;
|
|
|
}
|
|
|
if (propertiesUpdated) {
|
|
|
- originalContainerHeight = containerDims.height;
|
|
|
-
|
|
|
// update height of the editor after properties updated
|
|
|
- height = updatedTextElement.height;
|
|
|
+ textElementHeight = updatedTextElement.height;
|
|
|
}
|
|
|
- if (!originalContainerHeight) {
|
|
|
- originalContainerHeight = containerDims.height;
|
|
|
+
|
|
|
+ let originalContainerData;
|
|
|
+ if (propertiesUpdated) {
|
|
|
+ originalContainerData = updateOriginalContainerCache(
|
|
|
+ container.id,
|
|
|
+ containerDims.height,
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ originalContainerData = originalContainerCache[container.id];
|
|
|
+ if (!originalContainerData) {
|
|
|
+ originalContainerData = updateOriginalContainerCache(
|
|
|
+ container.id,
|
|
|
+ containerDims.height,
|
|
|
+ );
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
maxWidth = getMaxContainerWidth(container);
|
|
|
maxHeight = getMaxContainerHeight(container);
|
|
|
|
|
|
// autogrow container height if text exceeds
|
|
|
|
|
|
- if (!isArrowElement(container) && height > maxHeight) {
|
|
|
- const diff = Math.min(height - maxHeight, approxLineHeight);
|
|
|
+ if (!isArrowElement(container) && textElementHeight > maxHeight) {
|
|
|
+ const diff = Math.min(
|
|
|
+ textElementHeight - maxHeight,
|
|
|
+ approxLineHeight,
|
|
|
+ );
|
|
|
mutateElement(container, { height: containerDims.height + diff });
|
|
|
return;
|
|
|
} else if (
|
|
|
// autoshrink container height until original container height
|
|
|
// is reached when text is removed
|
|
|
!isArrowElement(container) &&
|
|
|
- containerDims.height > originalContainerHeight &&
|
|
|
- height < maxHeight
|
|
|
+ containerDims.height > originalContainerData.height &&
|
|
|
+ textElementHeight < maxHeight
|
|
|
) {
|
|
|
- const diff = Math.min(maxHeight - height, approxLineHeight);
|
|
|
+ const diff = Math.min(
|
|
|
+ maxHeight - textElementHeight,
|
|
|
+ approxLineHeight,
|
|
|
+ );
|
|
|
mutateElement(container, { height: containerDims.height - diff });
|
|
|
}
|
|
|
// Start pushing text upward until a diff of 30px (padding)
|
|
@@ -178,14 +231,15 @@ export const textWysiwyg = ({
|
|
|
// vertically center align the text
|
|
|
if (verticalAlign === VERTICAL_ALIGN.MIDDLE) {
|
|
|
if (!isArrowElement(container)) {
|
|
|
- coordY = container.y + containerDims.height / 2 - height / 2;
|
|
|
+ coordY =
|
|
|
+ container.y + containerDims.height / 2 - textElementHeight / 2;
|
|
|
}
|
|
|
}
|
|
|
if (verticalAlign === VERTICAL_ALIGN.BOTTOM) {
|
|
|
coordY =
|
|
|
container.y +
|
|
|
containerDims.height -
|
|
|
- height -
|
|
|
+ textElementHeight -
|
|
|
getBoundTextElementOffset(updatedTextElement);
|
|
|
}
|
|
|
}
|
|
@@ -226,12 +280,12 @@ export const textWysiwyg = ({
|
|
|
// must be defined *after* font ¯\_(ツ)_/¯
|
|
|
lineHeight: `${lineHeight}px`,
|
|
|
width: `${Math.min(width, maxWidth)}px`,
|
|
|
- height: `${height}px`,
|
|
|
+ height: `${textElementHeight}px`,
|
|
|
left: `${viewportX}px`,
|
|
|
top: `${viewportY}px`,
|
|
|
transform: getTransform(
|
|
|
width,
|
|
|
- height,
|
|
|
+ textElementHeight,
|
|
|
getTextElementAngle(updatedTextElement),
|
|
|
appState,
|
|
|
maxWidth,
|