refactor: create a util to compute container dimensions for bound text container (#5708)
This commit is contained in:
parent
3a776f8795
commit
8636ef1017
@ -252,6 +252,7 @@ import {
|
|||||||
getApproxMinLineHeight,
|
getApproxMinLineHeight,
|
||||||
getApproxMinLineWidth,
|
getApproxMinLineWidth,
|
||||||
getBoundTextElement,
|
getBoundTextElement,
|
||||||
|
getContainerDims,
|
||||||
} from "../element/textElement";
|
} from "../element/textElement";
|
||||||
import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
|
import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
|
||||||
import {
|
import {
|
||||||
@ -2385,8 +2386,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
};
|
};
|
||||||
const minWidth = getApproxMinLineWidth(getFontString(fontString));
|
const minWidth = getApproxMinLineWidth(getFontString(fontString));
|
||||||
const minHeight = getApproxMinLineHeight(getFontString(fontString));
|
const minHeight = getApproxMinLineHeight(getFontString(fontString));
|
||||||
const newHeight = Math.max(container.height, minHeight);
|
const containerDims = getContainerDims(container);
|
||||||
const newWidth = Math.max(container.width, minWidth);
|
const newHeight = Math.max(containerDims.height, minHeight);
|
||||||
|
const newWidth = Math.max(containerDims.width, minWidth);
|
||||||
mutateElement(container, { height: newHeight, width: newWidth });
|
mutateElement(container, { height: newHeight, width: newWidth });
|
||||||
sceneX = container.x + newWidth / 2;
|
sceneX = container.x + newWidth / 2;
|
||||||
sceneY = container.y + newHeight / 2;
|
sceneY = container.y + newHeight / 2;
|
||||||
|
@ -21,7 +21,12 @@ import { AppState } from "../types";
|
|||||||
import { getElementAbsoluteCoords } from ".";
|
import { getElementAbsoluteCoords } from ".";
|
||||||
import { adjustXYWithRotation } from "../math";
|
import { adjustXYWithRotation } from "../math";
|
||||||
import { getResizedElementAbsoluteCoords } from "./bounds";
|
import { getResizedElementAbsoluteCoords } from "./bounds";
|
||||||
import { getContainerElement, measureText, wrapText } from "./textElement";
|
import {
|
||||||
|
getContainerDims,
|
||||||
|
getContainerElement,
|
||||||
|
measureText,
|
||||||
|
wrapText,
|
||||||
|
} from "./textElement";
|
||||||
import { BOUND_TEXT_PADDING, VERTICAL_ALIGN } from "../constants";
|
import { BOUND_TEXT_PADDING, VERTICAL_ALIGN } from "../constants";
|
||||||
|
|
||||||
type ElementConstructorOpts = MarkOptional<
|
type ElementConstructorOpts = MarkOptional<
|
||||||
@ -164,7 +169,8 @@ const getAdjustedDimensions = (
|
|||||||
let maxWidth = null;
|
let maxWidth = null;
|
||||||
const container = getContainerElement(element);
|
const container = getContainerElement(element);
|
||||||
if (container) {
|
if (container) {
|
||||||
maxWidth = container.width - BOUND_TEXT_PADDING * 2;
|
const containerDims = getContainerDims(container);
|
||||||
|
maxWidth = containerDims.width - BOUND_TEXT_PADDING * 2;
|
||||||
}
|
}
|
||||||
const {
|
const {
|
||||||
width: nextWidth,
|
width: nextWidth,
|
||||||
@ -224,15 +230,16 @@ const getAdjustedDimensions = (
|
|||||||
// make sure container dimensions are set properly when
|
// make sure container dimensions are set properly when
|
||||||
// text editor overflows beyond viewport dimensions
|
// text editor overflows beyond viewport dimensions
|
||||||
if (container) {
|
if (container) {
|
||||||
let height = container.height;
|
const containerDims = getContainerDims(container);
|
||||||
let width = container.width;
|
let height = containerDims.height;
|
||||||
|
let width = containerDims.width;
|
||||||
if (nextHeight > height - BOUND_TEXT_PADDING * 2) {
|
if (nextHeight > height - BOUND_TEXT_PADDING * 2) {
|
||||||
height = nextHeight + BOUND_TEXT_PADDING * 2;
|
height = nextHeight + BOUND_TEXT_PADDING * 2;
|
||||||
}
|
}
|
||||||
if (nextWidth > width - BOUND_TEXT_PADDING * 2) {
|
if (nextWidth > width - BOUND_TEXT_PADDING * 2) {
|
||||||
width = nextWidth + BOUND_TEXT_PADDING * 2;
|
width = nextWidth + BOUND_TEXT_PADDING * 2;
|
||||||
}
|
}
|
||||||
if (height !== container.height || width !== container.width) {
|
if (height !== containerDims.height || width !== containerDims.width) {
|
||||||
mutateElement(container, { height, width });
|
mutateElement(container, { height, width });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -259,7 +266,8 @@ export const updateTextElement = (
|
|||||||
): ExcalidrawTextElement => {
|
): ExcalidrawTextElement => {
|
||||||
const container = getContainerElement(element);
|
const container = getContainerElement(element);
|
||||||
if (container) {
|
if (container) {
|
||||||
text = wrapText(text, getFontString(element), container.width);
|
const containerDims = getContainerDims(container);
|
||||||
|
text = wrapText(text, getFontString(element), containerDims.width);
|
||||||
}
|
}
|
||||||
const dimensions = getAdjustedDimensions(element, text);
|
const dimensions = getAdjustedDimensions(element, text);
|
||||||
return newElementWith(element, {
|
return newElementWith(element, {
|
||||||
|
@ -16,16 +16,16 @@ export const redrawTextBoundingBox = (
|
|||||||
element: ExcalidrawTextElement,
|
element: ExcalidrawTextElement,
|
||||||
container: ExcalidrawElement | null,
|
container: ExcalidrawElement | null,
|
||||||
) => {
|
) => {
|
||||||
const maxWidth = container
|
let maxWidth = undefined;
|
||||||
? container.width - BOUND_TEXT_PADDING * 2
|
|
||||||
: undefined;
|
|
||||||
let text = element.text;
|
let text = element.text;
|
||||||
|
|
||||||
if (container) {
|
if (container) {
|
||||||
|
const containerDims = getContainerDims(container);
|
||||||
|
maxWidth = containerDims.width - BOUND_TEXT_PADDING * 2;
|
||||||
text = wrapText(
|
text = wrapText(
|
||||||
element.originalText,
|
element.originalText,
|
||||||
getFontString(element),
|
getFontString(element),
|
||||||
container.width,
|
containerDims.width,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const metrics = measureText(
|
const metrics = measureText(
|
||||||
@ -37,16 +37,20 @@ export const redrawTextBoundingBox = (
|
|||||||
let coordX = element.x;
|
let coordX = element.x;
|
||||||
// Resize container and vertically center align the text
|
// Resize container and vertically center align the text
|
||||||
if (container) {
|
if (container) {
|
||||||
let nextHeight = container.height;
|
const containerDims = getContainerDims(container);
|
||||||
|
let nextHeight = containerDims.height;
|
||||||
coordX = container.x + BOUND_TEXT_PADDING;
|
coordX = container.x + BOUND_TEXT_PADDING;
|
||||||
if (element.verticalAlign === VERTICAL_ALIGN.TOP) {
|
if (element.verticalAlign === VERTICAL_ALIGN.TOP) {
|
||||||
coordY = container.y + BOUND_TEXT_PADDING;
|
coordY = container.y + BOUND_TEXT_PADDING;
|
||||||
} else if (element.verticalAlign === VERTICAL_ALIGN.BOTTOM) {
|
} else if (element.verticalAlign === VERTICAL_ALIGN.BOTTOM) {
|
||||||
coordY =
|
coordY =
|
||||||
container.y + container.height - metrics.height - BOUND_TEXT_PADDING;
|
container.y +
|
||||||
|
containerDims.height -
|
||||||
|
metrics.height -
|
||||||
|
BOUND_TEXT_PADDING;
|
||||||
} else {
|
} else {
|
||||||
coordY = container.y + container.height / 2 - metrics.height / 2;
|
coordY = container.y + containerDims.height / 2 - metrics.height / 2;
|
||||||
if (metrics.height > container.height - BOUND_TEXT_PADDING * 2) {
|
if (metrics.height > containerDims.height - BOUND_TEXT_PADDING * 2) {
|
||||||
nextHeight = metrics.height + BOUND_TEXT_PADDING * 2;
|
nextHeight = metrics.height + BOUND_TEXT_PADDING * 2;
|
||||||
coordY = container.y + nextHeight / 2 - metrics.height / 2;
|
coordY = container.y + nextHeight / 2 - metrics.height / 2;
|
||||||
}
|
}
|
||||||
@ -474,3 +478,7 @@ export const getContainerElement = (
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getContainerDims = (element: ExcalidrawElement) => {
|
||||||
|
return { width: element.width, height: element.height };
|
||||||
|
};
|
||||||
|
@ -18,6 +18,7 @@ import { mutateElement } from "./mutateElement";
|
|||||||
import {
|
import {
|
||||||
getApproxLineHeight,
|
getApproxLineHeight,
|
||||||
getBoundTextElementId,
|
getBoundTextElementId,
|
||||||
|
getContainerDims,
|
||||||
getContainerElement,
|
getContainerElement,
|
||||||
wrapText,
|
wrapText,
|
||||||
} from "./textElement";
|
} from "./textElement";
|
||||||
@ -83,17 +84,17 @@ export const textWysiwyg = ({
|
|||||||
app: App;
|
app: App;
|
||||||
}) => {
|
}) => {
|
||||||
const textPropertiesUpdated = (
|
const textPropertiesUpdated = (
|
||||||
updatedElement: ExcalidrawTextElement,
|
updatedTextElement: ExcalidrawTextElement,
|
||||||
editable: HTMLTextAreaElement,
|
editable: HTMLTextAreaElement,
|
||||||
) => {
|
) => {
|
||||||
const currentFont = editable.style.fontFamily.replace(/"/g, "");
|
const currentFont = editable.style.fontFamily.replace(/"/g, "");
|
||||||
if (
|
if (
|
||||||
getFontFamilyString({ fontFamily: updatedElement.fontFamily }) !==
|
getFontFamilyString({ fontFamily: updatedTextElement.fontFamily }) !==
|
||||||
currentFont
|
currentFont
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (`${updatedElement.fontSize}px` !== editable.style.fontSize) {
|
if (`${updatedTextElement.fontSize}px` !== editable.style.fontSize) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -102,46 +103,49 @@ export const textWysiwyg = ({
|
|||||||
|
|
||||||
const updateWysiwygStyle = () => {
|
const updateWysiwygStyle = () => {
|
||||||
const appState = app.state;
|
const appState = app.state;
|
||||||
const updatedElement =
|
const updatedTextElement =
|
||||||
Scene.getScene(element)?.getElement<ExcalidrawTextElement>(id);
|
Scene.getScene(element)?.getElement<ExcalidrawTextElement>(id);
|
||||||
if (!updatedElement) {
|
if (!updatedTextElement) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { textAlign, verticalAlign } = updatedElement;
|
const { textAlign, verticalAlign } = updatedTextElement;
|
||||||
|
|
||||||
const approxLineHeight = getApproxLineHeight(getFontString(updatedElement));
|
const approxLineHeight = getApproxLineHeight(
|
||||||
if (updatedElement && isTextElement(updatedElement)) {
|
getFontString(updatedTextElement),
|
||||||
let coordX = updatedElement.x;
|
);
|
||||||
let coordY = updatedElement.y;
|
if (updatedTextElement && isTextElement(updatedTextElement)) {
|
||||||
const container = getContainerElement(updatedElement);
|
let coordX = updatedTextElement.x;
|
||||||
let maxWidth = updatedElement.width;
|
let coordY = updatedTextElement.y;
|
||||||
|
const container = getContainerElement(updatedTextElement);
|
||||||
|
let maxWidth = updatedTextElement.width;
|
||||||
|
|
||||||
let maxHeight = updatedElement.height;
|
let maxHeight = updatedTextElement.height;
|
||||||
let width = updatedElement.width;
|
let width = updatedTextElement.width;
|
||||||
// Set to element height by default since that's
|
// Set to element height by default since that's
|
||||||
// what is going to be used for unbounded text
|
// what is going to be used for unbounded text
|
||||||
let height = updatedElement.height;
|
let height = updatedTextElement.height;
|
||||||
if (container && updatedElement.containerId) {
|
if (container && updatedTextElement.containerId) {
|
||||||
const propertiesUpdated = textPropertiesUpdated(
|
const propertiesUpdated = textPropertiesUpdated(
|
||||||
updatedElement,
|
updatedTextElement,
|
||||||
editable,
|
editable,
|
||||||
);
|
);
|
||||||
|
const containerDims = getContainerDims(container);
|
||||||
// using editor.style.height to get the accurate height of text editor
|
// using editor.style.height to get the accurate height of text editor
|
||||||
const editorHeight = Number(editable.style.height.slice(0, -2));
|
const editorHeight = Number(editable.style.height.slice(0, -2));
|
||||||
if (editorHeight > 0) {
|
if (editorHeight > 0) {
|
||||||
height = editorHeight;
|
height = editorHeight;
|
||||||
}
|
}
|
||||||
if (propertiesUpdated) {
|
if (propertiesUpdated) {
|
||||||
originalContainerHeight = container.height;
|
originalContainerHeight = containerDims.height;
|
||||||
|
|
||||||
// update height of the editor after properties updated
|
// update height of the editor after properties updated
|
||||||
height = updatedElement.height;
|
height = updatedTextElement.height;
|
||||||
}
|
}
|
||||||
if (!originalContainerHeight) {
|
if (!originalContainerHeight) {
|
||||||
originalContainerHeight = container.height;
|
originalContainerHeight = containerDims.height;
|
||||||
}
|
}
|
||||||
maxWidth = container.width - BOUND_TEXT_PADDING * 2;
|
maxWidth = containerDims.width - BOUND_TEXT_PADDING * 2;
|
||||||
maxHeight = container.height - BOUND_TEXT_PADDING * 2;
|
maxHeight = containerDims.height - BOUND_TEXT_PADDING * 2;
|
||||||
width = maxWidth;
|
width = maxWidth;
|
||||||
// The coordinates of text box set a distance of
|
// The coordinates of text box set a distance of
|
||||||
// 5px to preserve padding
|
// 5px to preserve padding
|
||||||
@ -149,27 +153,27 @@ export const textWysiwyg = ({
|
|||||||
// autogrow container height if text exceeds
|
// autogrow container height if text exceeds
|
||||||
if (height > maxHeight) {
|
if (height > maxHeight) {
|
||||||
const diff = Math.min(height - maxHeight, approxLineHeight);
|
const diff = Math.min(height - maxHeight, approxLineHeight);
|
||||||
mutateElement(container, { height: container.height + diff });
|
mutateElement(container, { height: containerDims.height + diff });
|
||||||
return;
|
return;
|
||||||
} else if (
|
} else if (
|
||||||
// autoshrink container height until original container height
|
// autoshrink container height until original container height
|
||||||
// is reached when text is removed
|
// is reached when text is removed
|
||||||
container.height > originalContainerHeight &&
|
containerDims.height > originalContainerHeight &&
|
||||||
height < maxHeight
|
height < maxHeight
|
||||||
) {
|
) {
|
||||||
const diff = Math.min(maxHeight - height, approxLineHeight);
|
const diff = Math.min(maxHeight - height, approxLineHeight);
|
||||||
mutateElement(container, { height: container.height - diff });
|
mutateElement(container, { height: containerDims.height - diff });
|
||||||
}
|
}
|
||||||
// Start pushing text upward until a diff of 30px (padding)
|
// Start pushing text upward until a diff of 30px (padding)
|
||||||
// is reached
|
// is reached
|
||||||
else {
|
else {
|
||||||
// vertically center align the text
|
// vertically center align the text
|
||||||
if (verticalAlign === VERTICAL_ALIGN.MIDDLE) {
|
if (verticalAlign === VERTICAL_ALIGN.MIDDLE) {
|
||||||
coordY = container.y + container.height / 2 - height / 2;
|
coordY = container.y + containerDims.height / 2 - height / 2;
|
||||||
}
|
}
|
||||||
if (verticalAlign === VERTICAL_ALIGN.BOTTOM) {
|
if (verticalAlign === VERTICAL_ALIGN.BOTTOM) {
|
||||||
coordY =
|
coordY =
|
||||||
container.y + container.height - height - BOUND_TEXT_PADDING;
|
container.y + containerDims.height - height - BOUND_TEXT_PADDING;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -177,7 +181,7 @@ export const textWysiwyg = ({
|
|||||||
const initialSelectionStart = editable.selectionStart;
|
const initialSelectionStart = editable.selectionStart;
|
||||||
const initialSelectionEnd = editable.selectionEnd;
|
const initialSelectionEnd = editable.selectionEnd;
|
||||||
const initialLength = editable.value.length;
|
const initialLength = editable.value.length;
|
||||||
editable.value = updatedElement.originalText;
|
editable.value = updatedTextElement.originalText;
|
||||||
|
|
||||||
// restore cursor position after value updated so it doesn't
|
// restore cursor position after value updated so it doesn't
|
||||||
// go to the end of text when container auto expanded
|
// go to the end of text when container auto expanded
|
||||||
@ -192,10 +196,10 @@ export const textWysiwyg = ({
|
|||||||
editable.selectionEnd = editable.value.length - diff;
|
editable.selectionEnd = editable.value.length - diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lines = updatedElement.originalText.split("\n");
|
const lines = updatedTextElement.originalText.split("\n");
|
||||||
const lineHeight = updatedElement.containerId
|
const lineHeight = updatedTextElement.containerId
|
||||||
? approxLineHeight
|
? approxLineHeight
|
||||||
: updatedElement.height / lines.length;
|
: updatedTextElement.height / lines.length;
|
||||||
if (!container) {
|
if (!container) {
|
||||||
maxWidth = (appState.width - 8 - viewportX) / appState.zoom.value;
|
maxWidth = (appState.width - 8 - viewportX) / appState.zoom.value;
|
||||||
}
|
}
|
||||||
@ -203,9 +207,9 @@ export const textWysiwyg = ({
|
|||||||
// Make sure text editor height doesn't go beyond viewport
|
// Make sure text editor height doesn't go beyond viewport
|
||||||
const editorMaxHeight =
|
const editorMaxHeight =
|
||||||
(appState.height - viewportY) / appState.zoom.value;
|
(appState.height - viewportY) / appState.zoom.value;
|
||||||
const angle = container ? container.angle : updatedElement.angle;
|
const angle = container ? container.angle : updatedTextElement.angle;
|
||||||
Object.assign(editable.style, {
|
Object.assign(editable.style, {
|
||||||
font: getFontString(updatedElement),
|
font: getFontString(updatedTextElement),
|
||||||
// must be defined *after* font ¯\_(ツ)_/¯
|
// must be defined *after* font ¯\_(ツ)_/¯
|
||||||
lineHeight: `${lineHeight}px`,
|
lineHeight: `${lineHeight}px`,
|
||||||
width: `${width}px`,
|
width: `${width}px`,
|
||||||
@ -222,8 +226,8 @@ export const textWysiwyg = ({
|
|||||||
),
|
),
|
||||||
textAlign,
|
textAlign,
|
||||||
verticalAlign,
|
verticalAlign,
|
||||||
color: updatedElement.strokeColor,
|
color: updatedTextElement.strokeColor,
|
||||||
opacity: updatedElement.opacity / 100,
|
opacity: updatedTextElement.opacity / 100,
|
||||||
filter: "var(--theme-filter)",
|
filter: "var(--theme-filter)",
|
||||||
maxWidth: `${maxWidth}px`,
|
maxWidth: `${maxWidth}px`,
|
||||||
maxHeight: `${editorMaxHeight}px`,
|
maxHeight: `${editorMaxHeight}px`,
|
||||||
@ -231,9 +235,9 @@ export const textWysiwyg = ({
|
|||||||
// For some reason updating font attribute doesn't set font family
|
// For some reason updating font attribute doesn't set font family
|
||||||
// hence updating font family explicitly for test environment
|
// hence updating font family explicitly for test environment
|
||||||
if (isTestEnv()) {
|
if (isTestEnv()) {
|
||||||
editable.style.fontFamily = getFontFamilyString(updatedElement);
|
editable.style.fontFamily = getFontFamilyString(updatedTextElement);
|
||||||
}
|
}
|
||||||
mutateElement(updatedElement, { x: coordX, y: coordY });
|
mutateElement(updatedTextElement, { x: coordX, y: coordY });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -276,10 +280,10 @@ export const textWysiwyg = ({
|
|||||||
|
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
editable.oninput = () => {
|
editable.oninput = () => {
|
||||||
const updatedElement = Scene.getScene(element)?.getElement(
|
const updatedTextElement = Scene.getScene(element)?.getElement(
|
||||||
id,
|
id,
|
||||||
) as ExcalidrawTextElement;
|
) as ExcalidrawTextElement;
|
||||||
const font = getFontString(updatedElement);
|
const font = getFontString(updatedTextElement);
|
||||||
// using scrollHeight here since we need to calculate
|
// using scrollHeight here since we need to calculate
|
||||||
// number of lines so cannot use editable.style.height
|
// number of lines so cannot use editable.style.height
|
||||||
// as that gets updated below
|
// as that gets updated below
|
||||||
|
Loading…
x
Reference in New Issue
Block a user