feat: support vertical text align for bound containers (#4852)
* feat: support vertical text align for bound containers * update icons * use const * fix lint * rename to and show when text editor active * don't update vertical align if not center * fix svgs * fix y coords when vertical align bottm Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
parent
c5a7723185
commit
8e26d5b500
@ -30,11 +30,15 @@ import {
|
|||||||
TextAlignCenterIcon,
|
TextAlignCenterIcon,
|
||||||
TextAlignLeftIcon,
|
TextAlignLeftIcon,
|
||||||
TextAlignRightIcon,
|
TextAlignRightIcon,
|
||||||
|
TextAlignTopIcon,
|
||||||
|
TextAlignBottomIcon,
|
||||||
|
TextAlignMiddleIcon,
|
||||||
} from "../components/icons";
|
} from "../components/icons";
|
||||||
import {
|
import {
|
||||||
DEFAULT_FONT_FAMILY,
|
DEFAULT_FONT_FAMILY,
|
||||||
DEFAULT_FONT_SIZE,
|
DEFAULT_FONT_SIZE,
|
||||||
FONT_FAMILY,
|
FONT_FAMILY,
|
||||||
|
VERTICAL_ALIGN,
|
||||||
} from "../constants";
|
} from "../constants";
|
||||||
import {
|
import {
|
||||||
getNonDeletedElements,
|
getNonDeletedElements,
|
||||||
@ -58,6 +62,7 @@ import {
|
|||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
FontFamilyValues,
|
FontFamilyValues,
|
||||||
TextAlign,
|
TextAlign,
|
||||||
|
VerticalAlign,
|
||||||
} from "../element/types";
|
} from "../element/types";
|
||||||
import { getLanguage, t } from "../i18n";
|
import { getLanguage, t } from "../i18n";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
@ -713,9 +718,7 @@ export const actionChangeTextAlign = register({
|
|||||||
if (isTextElement(oldElement)) {
|
if (isTextElement(oldElement)) {
|
||||||
const newElement: ExcalidrawTextElement = newElementWith(
|
const newElement: ExcalidrawTextElement = newElementWith(
|
||||||
oldElement,
|
oldElement,
|
||||||
{
|
{ textAlign: value },
|
||||||
textAlign: value,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
redrawTextBoundingBox(
|
redrawTextBoundingBox(
|
||||||
newElement,
|
newElement,
|
||||||
@ -736,47 +739,119 @@ export const actionChangeTextAlign = register({
|
|||||||
commitToHistory: true,
|
commitToHistory: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData }) => (
|
PanelComponent: ({ elements, appState, updateData }) => {
|
||||||
<fieldset>
|
return (
|
||||||
<legend>{t("labels.textAlign")}</legend>
|
<fieldset>
|
||||||
<ButtonIconSelect<TextAlign | false>
|
<legend>{t("labels.textAlign")}</legend>
|
||||||
group="text-align"
|
<ButtonIconSelect<TextAlign | false>
|
||||||
options={[
|
group="text-align"
|
||||||
{
|
options={[
|
||||||
value: "left",
|
{
|
||||||
text: t("labels.left"),
|
value: "left",
|
||||||
icon: <TextAlignLeftIcon theme={appState.theme} />,
|
text: t("labels.left"),
|
||||||
},
|
icon: <TextAlignLeftIcon theme={appState.theme} />,
|
||||||
{
|
},
|
||||||
value: "center",
|
{
|
||||||
text: t("labels.center"),
|
value: "center",
|
||||||
icon: <TextAlignCenterIcon theme={appState.theme} />,
|
text: t("labels.center"),
|
||||||
},
|
icon: <TextAlignCenterIcon theme={appState.theme} />,
|
||||||
{
|
},
|
||||||
value: "right",
|
{
|
||||||
text: t("labels.right"),
|
value: "right",
|
||||||
icon: <TextAlignRightIcon theme={appState.theme} />,
|
text: t("labels.right"),
|
||||||
},
|
icon: <TextAlignRightIcon theme={appState.theme} />,
|
||||||
]}
|
},
|
||||||
value={getFormValue(
|
]}
|
||||||
elements,
|
value={getFormValue(
|
||||||
appState,
|
elements,
|
||||||
(element) => {
|
appState,
|
||||||
if (isTextElement(element)) {
|
(element) => {
|
||||||
return element.textAlign;
|
if (isTextElement(element)) {
|
||||||
|
return element.textAlign;
|
||||||
|
}
|
||||||
|
const boundTextElement = getBoundTextElement(element);
|
||||||
|
if (boundTextElement) {
|
||||||
|
return boundTextElement.textAlign;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
appState.currentItemTextAlign,
|
||||||
|
)}
|
||||||
|
onChange={(value) => updateData(value)}
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
export const actionChangeVerticalAlign = register({
|
||||||
|
name: "changeVerticalAlign",
|
||||||
|
perform: (elements, appState, value) => {
|
||||||
|
return {
|
||||||
|
elements: changeProperty(
|
||||||
|
elements,
|
||||||
|
appState,
|
||||||
|
(oldElement) => {
|
||||||
|
if (isTextElement(oldElement)) {
|
||||||
|
const newElement: ExcalidrawTextElement = newElementWith(
|
||||||
|
oldElement,
|
||||||
|
{ verticalAlign: value },
|
||||||
|
);
|
||||||
|
|
||||||
|
redrawTextBoundingBox(
|
||||||
|
newElement,
|
||||||
|
getContainerElement(oldElement),
|
||||||
|
appState,
|
||||||
|
);
|
||||||
|
return newElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return oldElement;
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
appState: {
|
||||||
|
...appState,
|
||||||
|
},
|
||||||
|
commitToHistory: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
PanelComponent: ({ elements, appState, updateData }) => {
|
||||||
|
return (
|
||||||
|
<fieldset>
|
||||||
|
<ButtonIconSelect<VerticalAlign | false>
|
||||||
|
group="text-align"
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
value: VERTICAL_ALIGN.TOP,
|
||||||
|
text: t("labels.alignTop"),
|
||||||
|
icon: <TextAlignTopIcon theme={appState.theme} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: VERTICAL_ALIGN.MIDDLE,
|
||||||
|
text: t("labels.centerVertically"),
|
||||||
|
icon: <TextAlignMiddleIcon theme={appState.theme} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: VERTICAL_ALIGN.BOTTOM,
|
||||||
|
text: t("labels.alignBottom"),
|
||||||
|
icon: <TextAlignBottomIcon theme={appState.theme} />,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
value={getFormValue(elements, appState, (element) => {
|
||||||
|
if (isTextElement(element) && element.containerId) {
|
||||||
|
return element.verticalAlign;
|
||||||
}
|
}
|
||||||
const boundTextElement = getBoundTextElement(element);
|
const boundTextElement = getBoundTextElement(element);
|
||||||
if (boundTextElement) {
|
if (boundTextElement) {
|
||||||
return boundTextElement.textAlign;
|
return boundTextElement.verticalAlign;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
})}
|
||||||
appState.currentItemTextAlign,
|
onChange={(value) => updateData(value)}
|
||||||
)}
|
/>
|
||||||
onChange={(value) => updateData(value)}
|
</fieldset>
|
||||||
/>
|
);
|
||||||
</fieldset>
|
},
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const actionChangeSharpness = register({
|
export const actionChangeSharpness = register({
|
||||||
|
@ -17,6 +17,7 @@ export {
|
|||||||
actionChangeFontSize,
|
actionChangeFontSize,
|
||||||
actionChangeFontFamily,
|
actionChangeFontFamily,
|
||||||
actionChangeTextAlign,
|
actionChangeTextAlign,
|
||||||
|
actionChangeVerticalAlign,
|
||||||
} from "./actionProperties";
|
} from "./actionProperties";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@ -82,6 +82,7 @@ export type ActionName =
|
|||||||
| "zoomToSelection"
|
| "zoomToSelection"
|
||||||
| "changeFontFamily"
|
| "changeFontFamily"
|
||||||
| "changeTextAlign"
|
| "changeTextAlign"
|
||||||
|
| "changeVerticalAlign"
|
||||||
| "toggleFullScreen"
|
| "toggleFullScreen"
|
||||||
| "toggleShortcuts"
|
| "toggleShortcuts"
|
||||||
| "group"
|
| "group"
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import colors from "./colors";
|
import colors from "./colors";
|
||||||
import { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, ENV } from "./constants";
|
import {
|
||||||
|
DEFAULT_FONT_FAMILY,
|
||||||
|
DEFAULT_FONT_SIZE,
|
||||||
|
ENV,
|
||||||
|
VERTICAL_ALIGN,
|
||||||
|
} from "./constants";
|
||||||
import { newElement, newLinearElement, newTextElement } from "./element";
|
import { newElement, newLinearElement, newTextElement } from "./element";
|
||||||
import { NonDeletedExcalidrawElement } from "./element/types";
|
import { NonDeletedExcalidrawElement } from "./element/types";
|
||||||
import { randomId } from "./random";
|
import { randomId } from "./random";
|
||||||
@ -161,7 +166,7 @@ const commonProps = {
|
|||||||
strokeSharpness: "sharp",
|
strokeSharpness: "sharp",
|
||||||
strokeStyle: "solid",
|
strokeStyle: "solid",
|
||||||
strokeWidth: 1,
|
strokeWidth: 1,
|
||||||
verticalAlign: "middle",
|
verticalAlign: VERTICAL_ALIGN.MIDDLE,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const getChartDimentions = (spreadsheet: Spreadsheet) => {
|
const getChartDimentions = (spreadsheet: Spreadsheet) => {
|
||||||
|
@ -19,7 +19,7 @@ import { capitalizeString, isTransparent, setCursorForShape } from "../utils";
|
|||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
import { ToolButton } from "./ToolButton";
|
import { ToolButton } from "./ToolButton";
|
||||||
import { hasStrokeColor } from "../scene/comparisons";
|
import { hasStrokeColor } from "../scene/comparisons";
|
||||||
import { hasBoundTextElement } from "../element/typeChecks";
|
import { hasBoundTextElement, isBoundToContainer } from "../element/typeChecks";
|
||||||
|
|
||||||
export const SelectedShapeActions = ({
|
export const SelectedShapeActions = ({
|
||||||
appState,
|
appState,
|
||||||
@ -110,6 +110,10 @@ export const SelectedShapeActions = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{targetElements.every(
|
||||||
|
(element) =>
|
||||||
|
hasBoundTextElement(element) || isBoundToContainer(element),
|
||||||
|
) && <>{renderAction("changeVerticalAlign")}</>}
|
||||||
{(canHaveArrowheads(elementType) ||
|
{(canHaveArrowheads(elementType) ||
|
||||||
targetElements.some((element) => canHaveArrowheads(element.type))) && (
|
targetElements.some((element) => canHaveArrowheads(element.type))) && (
|
||||||
<>{renderAction("changeArrowhead")}</>
|
<>{renderAction("changeArrowhead")}</>
|
||||||
|
@ -69,6 +69,7 @@ import {
|
|||||||
TOUCH_CTX_MENU_TIMEOUT,
|
TOUCH_CTX_MENU_TIMEOUT,
|
||||||
URL_HASH_KEYS,
|
URL_HASH_KEYS,
|
||||||
URL_QUERY_KEYS,
|
URL_QUERY_KEYS,
|
||||||
|
VERTICAL_ALIGN,
|
||||||
ZOOM_STEP,
|
ZOOM_STEP,
|
||||||
} from "../constants";
|
} from "../constants";
|
||||||
import { loadFromBlob } from "../data";
|
import { loadFromBlob } from "../data";
|
||||||
@ -2225,7 +2226,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
? "center"
|
? "center"
|
||||||
: this.state.currentItemTextAlign,
|
: this.state.currentItemTextAlign,
|
||||||
verticalAlign: parentCenterPosition
|
verticalAlign: parentCenterPosition
|
||||||
? "middle"
|
? VERTICAL_ALIGN.MIDDLE
|
||||||
: DEFAULT_VERTICAL_ALIGN,
|
: DEFAULT_VERTICAL_ALIGN,
|
||||||
containerId: container?.id ?? undefined,
|
containerId: container?.id ?? undefined,
|
||||||
groupIds: container?.groupIds ?? [],
|
groupIds: container?.groupIds ?? [],
|
||||||
@ -2233,13 +2234,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
this.setState({ editingElement: element });
|
this.setState({ editingElement: element });
|
||||||
|
|
||||||
if (existingTextElement) {
|
if (!existingTextElement) {
|
||||||
// if text element is no longer centered to a container, reset
|
|
||||||
// verticalAlign to default because it's currently internal-only
|
|
||||||
if (!parentCenterPosition || element.textAlign !== "center") {
|
|
||||||
mutateElement(element, { verticalAlign: DEFAULT_VERTICAL_ALIGN });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.scene.replaceAllElements([
|
this.scene.replaceAllElements([
|
||||||
...this.scene.getElementsIncludingDeleted(),
|
...this.scene.getElementsIncludingDeleted(),
|
||||||
element,
|
element,
|
||||||
|
@ -885,6 +885,40 @@ export const TextAlignRightIcon = React.memo(({ theme }: { theme: Theme }) =>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const TextAlignTopIcon = React.memo(({ theme }: { theme: Theme }) =>
|
||||||
|
createIcon(
|
||||||
|
<path
|
||||||
|
d="m16,132l416,0c8.837,0 16,-7.163 16,-16l0,-40c0,-8.837 -7.163,-16 -16,-16l-416,0c-8.837,0 -16,7.163 -16,16l0,40c0,8.837 7.163,16 16,16zm0,160l416,0c8.837,0 16,-7.163 16,-16l0,-40c0,-8.837 -7.163,-16 -16,-16l-416,0c-8.837,0 -16,7.163 -16,16l0,40c0,8.837 7.163,16 16,16z"
|
||||||
|
fill={iconFillColor(theme)}
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>,
|
||||||
|
{ width: 448, height: 512 },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const TextAlignBottomIcon = React.memo(({ theme }: { theme: Theme }) =>
|
||||||
|
createIcon(
|
||||||
|
<path
|
||||||
|
d="M16,292L432,292C440.837,292 448,284.837 448,276L448,236C448,227.163 440.837,220 432,220L16,220C7.163,220 0,227.163 0,236L0,276C0,284.837 7.163,292 16,292ZM16,452L432,452C440.837,452 448,444.837 448,436L448,396C448,387.163 440.837,380 432,380L16,380C7.163,380 0,387.163 0,396L0,436C0,444.837 7.163,452 16,452Z"
|
||||||
|
fill={iconFillColor(theme)}
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>,
|
||||||
|
{ width: 448, height: 512 },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const TextAlignMiddleIcon = React.memo(({ theme }: { theme: Theme }) =>
|
||||||
|
createIcon(
|
||||||
|
<path
|
||||||
|
transform="matrix(1,0,0,1,0,80)"
|
||||||
|
d="M16,132L432,132C440.837,132 448,124.837 448,116L448,76C448,67.163 440.837,60 432,60L16,60C7.163,60 0,67.163 0,76L0,116C0,124.837 7.163,132 16,132ZM16,292L432,292C440.837,292 448,284.837 448,276L448,236C448,227.163 440.837,220 432,220L16,220C7.163,220 0,227.163 0,236L0,276C0,284.837 7.163,292 16,292Z"
|
||||||
|
fill={iconFillColor(theme)}
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>,
|
||||||
|
{ width: 448, height: 512 },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
export const publishIcon = createIcon(
|
export const publishIcon = createIcon(
|
||||||
<path
|
<path
|
||||||
d="M537.6 226.6c4.1-10.7 6.4-22.4 6.4-34.6 0-53-43-96-96-96-19.7 0-38.1 6-53.3 16.2C367 64.2 315.3 32 256 32c-88.4 0-160 71.6-160 160 0 2.7.1 5.4.2 8.1C40.2 219.8 0 273.2 0 336c0 79.5 64.5 144 144 144h368c70.7 0 128-57.3 128-128 0-61.9-44-113.6-102.4-125.4zM393.4 288H328v112c0 8.8-7.2 16-16 16h-48c-8.8 0-16-7.2-16-16V288h-65.4c-14.3 0-21.4-17.2-11.3-27.3l105.4-105.4c6.2-6.2 16.4-6.2 22.6 0l105.4 105.4c10.1 10.1 2.9 27.3-11.3 27.3z"
|
d="M537.6 226.6c4.1-10.7 6.4-22.4 6.4-34.6 0-53-43-96-96-96-19.7 0-38.1 6-53.3 16.2C367 64.2 315.3 32 256 32c-88.4 0-160 71.6-160 160 0 2.7.1 5.4.2 8.1C40.2 219.8 0 273.2 0 336c0 79.5 64.5 144 144 144h368c70.7 0 128-57.3 128-128 0-61.9-44-113.6-102.4-125.4zM393.4 288H328v112c0 8.8-7.2 16-16 16h-48c-8.8 0-16-7.2-16-16V288h-65.4c-14.3 0-21.4-17.2-11.3-27.3l105.4-105.4c6.2-6.2 16.4-6.2 22.6 0l105.4 105.4c10.1 10.1 2.9 27.3-11.3 27.3z"
|
||||||
|
@ -182,3 +182,9 @@ export const VERSIONS = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const BOUND_TEXT_PADDING = 5;
|
export const BOUND_TEXT_PADDING = 5;
|
||||||
|
|
||||||
|
export const VERTICAL_ALIGN = {
|
||||||
|
TOP: "top",
|
||||||
|
MIDDLE: "middle",
|
||||||
|
BOTTOM: "bottom",
|
||||||
|
};
|
||||||
|
@ -23,7 +23,7 @@ import { adjustXYWithRotation } from "../math";
|
|||||||
import { getResizedElementAbsoluteCoords } from "./bounds";
|
import { getResizedElementAbsoluteCoords } from "./bounds";
|
||||||
import { getContainerElement, measureText, wrapText } from "./textElement";
|
import { getContainerElement, measureText, wrapText } from "./textElement";
|
||||||
import { isBoundToContainer } from "./typeChecks";
|
import { isBoundToContainer } from "./typeChecks";
|
||||||
import { BOUND_TEXT_PADDING } from "../constants";
|
import { BOUND_TEXT_PADDING, VERTICAL_ALIGN } from "../constants";
|
||||||
|
|
||||||
type ElementConstructorOpts = MarkOptional<
|
type ElementConstructorOpts = MarkOptional<
|
||||||
Omit<ExcalidrawGenericElement, "id" | "type" | "isDeleted" | "updated">,
|
Omit<ExcalidrawGenericElement, "id" | "type" | "isDeleted" | "updated">,
|
||||||
@ -175,7 +175,7 @@ const getAdjustedDimensions = (
|
|||||||
let y: number;
|
let y: number;
|
||||||
if (
|
if (
|
||||||
textAlign === "center" &&
|
textAlign === "center" &&
|
||||||
verticalAlign === "middle" &&
|
verticalAlign === VERTICAL_ALIGN.MIDDLE &&
|
||||||
!element.containerId
|
!element.containerId
|
||||||
) {
|
) {
|
||||||
const prevMetrics = measureText(
|
const prevMetrics = measureText(
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { mutateElement } from "./mutateElement";
|
import { mutateElement } from "./mutateElement";
|
||||||
import { BOUND_TEXT_PADDING } from "../constants";
|
import { BOUND_TEXT_PADDING, VERTICAL_ALIGN } from "../constants";
|
||||||
import { MaybeTransformHandleType } from "./transformHandles";
|
import { MaybeTransformHandleType } from "./transformHandles";
|
||||||
import Scene from "../scene/Scene";
|
import Scene from "../scene/Scene";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
@ -39,11 +39,19 @@ export const redrawTextBoundingBox = (
|
|||||||
let coordY = element.y;
|
let coordY = element.y;
|
||||||
// Resize container and vertically center align the text
|
// Resize container and vertically center align the text
|
||||||
if (container) {
|
if (container) {
|
||||||
coordY = container.y + container.height / 2 - metrics.height / 2;
|
|
||||||
let nextHeight = container.height;
|
let nextHeight = container.height;
|
||||||
if (metrics.height > container.height - BOUND_TEXT_PADDING * 2) {
|
|
||||||
nextHeight = metrics.height + BOUND_TEXT_PADDING * 2;
|
if (element.verticalAlign === VERTICAL_ALIGN.TOP) {
|
||||||
coordY = container.y + nextHeight / 2 - metrics.height / 2;
|
coordY = container.y + BOUND_TEXT_PADDING;
|
||||||
|
} else if (element.verticalAlign === VERTICAL_ALIGN.BOTTOM) {
|
||||||
|
coordY =
|
||||||
|
container.y + container.height - metrics.height - BOUND_TEXT_PADDING;
|
||||||
|
} else {
|
||||||
|
coordY = container.y + container.height / 2 - metrics.height / 2;
|
||||||
|
if (metrics.height > container.height - BOUND_TEXT_PADDING * 2) {
|
||||||
|
nextHeight = metrics.height + BOUND_TEXT_PADDING * 2;
|
||||||
|
coordY = container.y + nextHeight / 2 - metrics.height / 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mutateElement(container, { height: nextHeight });
|
mutateElement(container, { height: nextHeight });
|
||||||
}
|
}
|
||||||
@ -142,7 +150,14 @@ export const handleBindTextResize = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedY = element.y + containerHeight / 2 - nextHeight / 2;
|
let updatedY;
|
||||||
|
if (textElement.verticalAlign === VERTICAL_ALIGN.TOP) {
|
||||||
|
updatedY = element.y + BOUND_TEXT_PADDING;
|
||||||
|
} else if (textElement.verticalAlign === VERTICAL_ALIGN.BOTTOM) {
|
||||||
|
updatedY = element.y + element.height - nextHeight - BOUND_TEXT_PADDING;
|
||||||
|
} else {
|
||||||
|
updatedY = element.y + element.height / 2 - nextHeight / 2;
|
||||||
|
}
|
||||||
|
|
||||||
mutateElement(textElement, {
|
mutateElement(textElement, {
|
||||||
text,
|
text,
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
} from "../utils";
|
} from "../utils";
|
||||||
import Scene from "../scene/Scene";
|
import Scene from "../scene/Scene";
|
||||||
import { isBoundToContainer, isTextElement } from "./typeChecks";
|
import { isBoundToContainer, isTextElement } from "./typeChecks";
|
||||||
import { CLASSES, BOUND_TEXT_PADDING } from "../constants";
|
import { CLASSES, BOUND_TEXT_PADDING, VERTICAL_ALIGN } from "../constants";
|
||||||
import {
|
import {
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawTextElement,
|
ExcalidrawTextElement,
|
||||||
@ -105,6 +105,8 @@ export const textWysiwyg = ({
|
|||||||
const updatedElement = Scene.getScene(element)?.getElement(
|
const updatedElement = Scene.getScene(element)?.getElement(
|
||||||
id,
|
id,
|
||||||
) as ExcalidrawTextElement;
|
) as ExcalidrawTextElement;
|
||||||
|
const { textAlign, verticalAlign } = updatedElement;
|
||||||
|
|
||||||
const approxLineHeight = getApproxLineHeight(getFontString(updatedElement));
|
const approxLineHeight = getApproxLineHeight(getFontString(updatedElement));
|
||||||
if (updatedElement && isTextElement(updatedElement)) {
|
if (updatedElement && isTextElement(updatedElement)) {
|
||||||
let coordX = updatedElement.x;
|
let coordX = updatedElement.x;
|
||||||
@ -140,7 +142,7 @@ export const textWysiwyg = ({
|
|||||||
maxHeight = container.height - BOUND_TEXT_PADDING * 2;
|
maxHeight = container.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
|
||||||
// 30px to preserve padding
|
// 5px to preserve padding
|
||||||
coordX = container.x + BOUND_TEXT_PADDING;
|
coordX = container.x + BOUND_TEXT_PADDING;
|
||||||
// autogrow container height if text exceeds
|
// autogrow container height if text exceeds
|
||||||
if (height > maxHeight) {
|
if (height > maxHeight) {
|
||||||
@ -160,11 +162,16 @@ export const textWysiwyg = ({
|
|||||||
// is reached
|
// is reached
|
||||||
else {
|
else {
|
||||||
// vertically center align the text
|
// vertically center align the text
|
||||||
coordY = container.y + container.height / 2 - height / 2;
|
if (verticalAlign === VERTICAL_ALIGN.MIDDLE) {
|
||||||
|
coordY = container.y + container.height / 2 - height / 2;
|
||||||
|
}
|
||||||
|
if (verticalAlign === VERTICAL_ALIGN.BOTTOM) {
|
||||||
|
coordY =
|
||||||
|
container.y + container.height - height - BOUND_TEXT_PADDING;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const [viewportX, viewportY] = getViewportCoords(coordX, coordY);
|
const [viewportX, viewportY] = getViewportCoords(coordX, coordY);
|
||||||
const { textAlign } = updatedElement;
|
|
||||||
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;
|
||||||
@ -212,6 +219,7 @@ export const textWysiwyg = ({
|
|||||||
editorMaxHeight,
|
editorMaxHeight,
|
||||||
),
|
),
|
||||||
textAlign,
|
textAlign,
|
||||||
|
verticalAlign,
|
||||||
color: updatedElement.strokeColor,
|
color: updatedElement.strokeColor,
|
||||||
opacity: updatedElement.opacity / 100,
|
opacity: updatedElement.opacity / 100,
|
||||||
filter: "var(--theme-filter)",
|
filter: "var(--theme-filter)",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Point } from "../types";
|
import { Point } from "../types";
|
||||||
import { FONT_FAMILY, THEME } from "../constants";
|
import { FONT_FAMILY, THEME, VERTICAL_ALIGN } from "../constants";
|
||||||
|
|
||||||
export type ChartType = "bar" | "line";
|
export type ChartType = "bar" | "line";
|
||||||
export type FillStyle = "hachure" | "cross-hatch" | "solid";
|
export type FillStyle = "hachure" | "cross-hatch" | "solid";
|
||||||
@ -12,7 +12,9 @@ export type PointerType = "mouse" | "pen" | "touch";
|
|||||||
export type StrokeSharpness = "round" | "sharp";
|
export type StrokeSharpness = "round" | "sharp";
|
||||||
export type StrokeStyle = "solid" | "dashed" | "dotted";
|
export type StrokeStyle = "solid" | "dashed" | "dotted";
|
||||||
export type TextAlign = "left" | "center" | "right";
|
export type TextAlign = "left" | "center" | "right";
|
||||||
export type VerticalAlign = "top" | "middle";
|
|
||||||
|
type VerticalAlignKeys = keyof typeof VERTICAL_ALIGN;
|
||||||
|
export type VerticalAlign = typeof VERTICAL_ALIGN[VerticalAlignKeys];
|
||||||
|
|
||||||
type _ExcalidrawElementBase = Readonly<{
|
type _ExcalidrawElementBase = Readonly<{
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -29,7 +29,13 @@ import { isPathALoop } from "../math";
|
|||||||
import rough from "roughjs/bin/rough";
|
import rough from "roughjs/bin/rough";
|
||||||
import { AppState, BinaryFiles, Zoom } from "../types";
|
import { AppState, BinaryFiles, Zoom } from "../types";
|
||||||
import { getDefaultAppState } from "../appState";
|
import { getDefaultAppState } from "../appState";
|
||||||
import { MAX_DECIMALS_FOR_SVG_EXPORT, MIME_TYPES, SVG_NS } from "../constants";
|
import {
|
||||||
|
BOUND_TEXT_PADDING,
|
||||||
|
MAX_DECIMALS_FOR_SVG_EXPORT,
|
||||||
|
MIME_TYPES,
|
||||||
|
SVG_NS,
|
||||||
|
VERTICAL_ALIGN,
|
||||||
|
} from "../constants";
|
||||||
import { getStroke, StrokeOptions } from "perfect-freehand";
|
import { getStroke, StrokeOptions } from "perfect-freehand";
|
||||||
import { getApproxLineHeight } from "../element/textElement";
|
import { getApproxLineHeight } from "../element/textElement";
|
||||||
|
|
||||||
@ -264,7 +270,11 @@ const drawElementOnCanvas = (
|
|||||||
const lineHeight = element.containerId
|
const lineHeight = element.containerId
|
||||||
? getApproxLineHeight(getFontString(element))
|
? getApproxLineHeight(getFontString(element))
|
||||||
: element.height / lines.length;
|
: element.height / lines.length;
|
||||||
const verticalOffset = element.height - element.baseline;
|
let verticalOffset = element.height - element.baseline;
|
||||||
|
if (element.verticalAlign === VERTICAL_ALIGN.BOTTOM) {
|
||||||
|
verticalOffset = BOUND_TEXT_PADDING;
|
||||||
|
}
|
||||||
|
|
||||||
const horizontalOffset =
|
const horizontalOffset =
|
||||||
element.textAlign === "center"
|
element.textAlign === "center"
|
||||||
? element.width / 2
|
? element.width / 2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user