feat: support shrinking text containers to original height when text removed (#6025)

* fix:cache bind text containers height so that it could autoshrink to original height when text deleted

* revert

* rename

* reset cache when resized

* safe check

* restore original containr height when text is unbind

* update cache when redrawing bounding box

* reset cache when unbind

* make type-safe

* add specs

* skip one test

* remoe mock

* fix

Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
Aakansha Doshi 2022-12-23 11:57:48 +05:30 committed by GitHub
parent 9086674b27
commit 8ec5f7b982
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 200 additions and 19 deletions

View file

@ -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,