mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
feat: support Stats bound text fontSize
editing (#8187)
This commit is contained in:
parent
ba8c09d529
commit
abbeed3d5f
5 changed files with 215 additions and 150 deletions
|
@ -15,33 +15,42 @@ import "./DragInput.scss";
|
|||
import type { AppState } from "../../types";
|
||||
import { cloneJSON } from "../../utils";
|
||||
|
||||
export type DragInputCallbackType<T extends StatsInputProperty> = (props: {
|
||||
export type DragInputCallbackType<
|
||||
P extends StatsInputProperty,
|
||||
E = ExcalidrawElement,
|
||||
> = (props: {
|
||||
accumulatedChange: number;
|
||||
instantChange: number;
|
||||
originalElements: readonly ExcalidrawElement[];
|
||||
originalElements: readonly E[];
|
||||
originalElementsMap: ElementsMap;
|
||||
shouldKeepAspectRatio: boolean;
|
||||
shouldChangeByStepSize: boolean;
|
||||
nextValue?: number;
|
||||
property: T;
|
||||
property: P;
|
||||
scene: Scene;
|
||||
originalAppState: AppState;
|
||||
}) => void;
|
||||
|
||||
interface StatsDragInputProps<T extends StatsInputProperty> {
|
||||
interface StatsDragInputProps<
|
||||
T extends StatsInputProperty,
|
||||
E = ExcalidrawElement,
|
||||
> {
|
||||
label: string | React.ReactNode;
|
||||
icon?: React.ReactNode;
|
||||
value: number | "Mixed";
|
||||
elements: readonly ExcalidrawElement[];
|
||||
elements: readonly E[];
|
||||
editable?: boolean;
|
||||
shouldKeepAspectRatio?: boolean;
|
||||
dragInputCallback: DragInputCallbackType<T>;
|
||||
dragInputCallback: DragInputCallbackType<T, E>;
|
||||
property: T;
|
||||
scene: Scene;
|
||||
appState: AppState;
|
||||
}
|
||||
|
||||
const StatsDragInput = <T extends StatsInputProperty>({
|
||||
const StatsDragInput = <
|
||||
T extends StatsInputProperty,
|
||||
E extends ExcalidrawElement = ExcalidrawElement,
|
||||
>({
|
||||
label,
|
||||
icon,
|
||||
dragInputCallback,
|
||||
|
@ -52,7 +61,7 @@ const StatsDragInput = <T extends StatsInputProperty>({
|
|||
property,
|
||||
scene,
|
||||
appState,
|
||||
}: StatsDragInputProps<T>) => {
|
||||
}: StatsDragInputProps<T, E>) => {
|
||||
const app = useApp();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const labelRef = useRef<HTMLDivElement>(null);
|
||||
|
@ -61,7 +70,7 @@ const StatsDragInput = <T extends StatsInputProperty>({
|
|||
|
||||
const stateRef = useRef<{
|
||||
originalAppState: AppState;
|
||||
originalElements: readonly ExcalidrawElement[];
|
||||
originalElements: readonly E[];
|
||||
lastUpdatedValue: string;
|
||||
updatePending: boolean;
|
||||
}>(null!);
|
||||
|
@ -82,7 +91,7 @@ const StatsDragInput = <T extends StatsInputProperty>({
|
|||
|
||||
const handleInputValue = (
|
||||
updatedValue: string,
|
||||
elements: readonly ExcalidrawElement[],
|
||||
elements: readonly E[],
|
||||
appState: AppState,
|
||||
) => {
|
||||
if (!stateRef.current.updatePending) {
|
||||
|
@ -173,9 +182,18 @@ const StatsDragInput = <T extends StatsInputProperty>({
|
|||
y: number;
|
||||
} | null = null;
|
||||
|
||||
let originalElements: ExcalidrawElement[] | null = null;
|
||||
let originalElementsMap: Map<string, ExcalidrawElement> | null =
|
||||
null;
|
||||
app.scene
|
||||
.getNonDeletedElements()
|
||||
.reduce((acc: ElementsMap, element) => {
|
||||
acc.set(element.id, deepCopyElement(element));
|
||||
return acc;
|
||||
}, new Map());
|
||||
|
||||
let originalElements: readonly E[] | null = elements.map(
|
||||
(element) => originalElementsMap!.get(element.id) as E,
|
||||
);
|
||||
|
||||
const originalAppState: AppState = cloneJSON(appState);
|
||||
|
||||
let accumulatedChange: number | null = null;
|
||||
|
@ -183,21 +201,6 @@ const StatsDragInput = <T extends StatsInputProperty>({
|
|||
document.body.classList.add("excalidraw-cursor-resize");
|
||||
|
||||
const onPointerMove = (event: PointerEvent) => {
|
||||
if (!originalElementsMap) {
|
||||
originalElementsMap = app.scene
|
||||
.getNonDeletedElements()
|
||||
.reduce((acc, element) => {
|
||||
acc.set(element.id, deepCopyElement(element));
|
||||
return acc;
|
||||
}, new Map() as ElementsMap);
|
||||
}
|
||||
|
||||
if (!originalElements) {
|
||||
originalElements = elements.map(
|
||||
(element) => originalElementsMap!.get(element.id)!,
|
||||
);
|
||||
}
|
||||
|
||||
if (!accumulatedChange) {
|
||||
accumulatedChange = 0;
|
||||
}
|
||||
|
@ -205,6 +208,7 @@ const StatsDragInput = <T extends StatsInputProperty>({
|
|||
if (
|
||||
lastPointer &&
|
||||
originalElementsMap !== null &&
|
||||
originalElements !== null &&
|
||||
accumulatedChange !== null
|
||||
) {
|
||||
const instantChange = event.clientX - lastPointer.x;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import type { ExcalidrawTextElement } from "../../element/types";
|
||||
import { refreshTextDimensions } from "../../element/newElement";
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawTextElement,
|
||||
} from "../../element/types";
|
||||
import StatsDragInput from "./DragInput";
|
||||
import type { DragInputCallbackType } from "./DragInput";
|
||||
import { mutateElement } from "../../element/mutateElement";
|
||||
|
@ -7,10 +9,12 @@ import { getStepSizedValue } from "./utils";
|
|||
import { fontSizeIcon } from "../icons";
|
||||
import type Scene from "../../scene/Scene";
|
||||
import type { AppState } from "../../types";
|
||||
import { isTextElement } from "../../element";
|
||||
import { isTextElement, redrawTextBoundingBox } from "../../element";
|
||||
import { hasBoundTextElement } from "../../element/typeChecks";
|
||||
import { getBoundTextElement } from "../../element/textElement";
|
||||
|
||||
interface FontSizeProps {
|
||||
element: ExcalidrawTextElement;
|
||||
element: ExcalidrawElement;
|
||||
scene: Scene;
|
||||
appState: AppState;
|
||||
property: "fontSize";
|
||||
|
@ -20,7 +24,8 @@ const MIN_FONT_SIZE = 4;
|
|||
const STEP_SIZE = 4;
|
||||
|
||||
const handleFontSizeChange: DragInputCallbackType<
|
||||
FontSizeProps["property"]
|
||||
FontSizeProps["property"],
|
||||
ExcalidrawTextElement
|
||||
> = ({
|
||||
accumulatedChange,
|
||||
originalElements,
|
||||
|
@ -36,50 +41,52 @@ const handleFontSizeChange: DragInputCallbackType<
|
|||
if (!latestElement || !isTextElement(latestElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let nextFontSize;
|
||||
|
||||
if (nextValue !== undefined) {
|
||||
const nextFontSize = Math.max(Math.round(nextValue), MIN_FONT_SIZE);
|
||||
|
||||
const newElement = {
|
||||
...latestElement,
|
||||
fontSize: nextFontSize,
|
||||
};
|
||||
const updates = refreshTextDimensions(newElement, null, elementsMap);
|
||||
mutateElement(latestElement, {
|
||||
...updates,
|
||||
fontSize: nextFontSize,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (origElement.type === "text") {
|
||||
nextFontSize = Math.max(Math.round(nextValue), MIN_FONT_SIZE);
|
||||
} else if (origElement.type === "text") {
|
||||
const originalFontSize = Math.round(origElement.fontSize);
|
||||
const changeInFontSize = Math.round(accumulatedChange);
|
||||
let nextFontSize = Math.max(
|
||||
nextFontSize = Math.max(
|
||||
originalFontSize + changeInFontSize,
|
||||
MIN_FONT_SIZE,
|
||||
);
|
||||
if (shouldChangeByStepSize) {
|
||||
nextFontSize = getStepSizedValue(nextFontSize, STEP_SIZE);
|
||||
}
|
||||
const newElement = {
|
||||
...latestElement,
|
||||
fontSize: nextFontSize,
|
||||
};
|
||||
const updates = refreshTextDimensions(newElement, null, elementsMap);
|
||||
}
|
||||
|
||||
if (nextFontSize) {
|
||||
mutateElement(latestElement, {
|
||||
...updates,
|
||||
fontSize: nextFontSize,
|
||||
});
|
||||
redrawTextBoundingBox(
|
||||
latestElement,
|
||||
scene.getContainerElement(latestElement),
|
||||
scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const FontSize = ({ element, scene, appState, property }: FontSizeProps) => {
|
||||
const _element = isTextElement(element)
|
||||
? element
|
||||
: hasBoundTextElement(element)
|
||||
? getBoundTextElement(element, scene.getNonDeletedElementsMap())
|
||||
: null;
|
||||
|
||||
if (!_element) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StatsDragInput
|
||||
label="F"
|
||||
value={Math.round(element.fontSize * 10) / 10}
|
||||
elements={[element]}
|
||||
value={Math.round(_element.fontSize * 10) / 10}
|
||||
elements={[_element]}
|
||||
dragInputCallback={handleFontSizeChange}
|
||||
icon={fontSizeIcon}
|
||||
appState={appState}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { isTextElement, refreshTextDimensions } from "../../element";
|
||||
import { isTextElement, redrawTextBoundingBox } from "../../element";
|
||||
import { mutateElement } from "../../element/mutateElement";
|
||||
import { isBoundToContainer } from "../../element/typeChecks";
|
||||
import { hasBoundTextElement } from "../../element/typeChecks";
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawTextElement,
|
||||
NonDeletedSceneElementsMap,
|
||||
} from "../../element/types";
|
||||
import { isInGroup } from "../../groups";
|
||||
import type Scene from "../../scene/Scene";
|
||||
|
@ -12,10 +13,12 @@ import StatsDragInput from "./DragInput";
|
|||
import type { DragInputCallbackType } from "./DragInput";
|
||||
import { getStepSizedValue } from "./utils";
|
||||
import type { AppState } from "../../types";
|
||||
import { getBoundTextElement } from "../../element/textElement";
|
||||
|
||||
interface MultiFontSizeProps {
|
||||
elements: readonly ExcalidrawElement[];
|
||||
scene: Scene;
|
||||
elementsMap: NonDeletedSceneElementsMap;
|
||||
appState: AppState;
|
||||
property: "fontSize";
|
||||
}
|
||||
|
@ -25,14 +28,34 @@ const STEP_SIZE = 4;
|
|||
|
||||
const getApplicableTextElements = (
|
||||
elements: readonly (ExcalidrawElement | undefined)[],
|
||||
elementsMap: NonDeletedSceneElementsMap,
|
||||
) =>
|
||||
elements.filter(
|
||||
(el) =>
|
||||
el && !isInGroup(el) && isTextElement(el) && !isBoundToContainer(el),
|
||||
) as ExcalidrawTextElement[];
|
||||
elements.reduce(
|
||||
(acc: ExcalidrawTextElement[], el) => {
|
||||
if (!el || isInGroup(el)) {
|
||||
return acc;
|
||||
}
|
||||
if (isTextElement(el)) {
|
||||
acc.push(el);
|
||||
return acc;
|
||||
}
|
||||
if (hasBoundTextElement(el)) {
|
||||
const boundTextElement = getBoundTextElement(el, elementsMap);
|
||||
if (boundTextElement) {
|
||||
acc.push(boundTextElement);
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
|
||||
[],
|
||||
);
|
||||
|
||||
const handleFontSizeChange: DragInputCallbackType<
|
||||
MultiFontSizeProps["property"]
|
||||
MultiFontSizeProps["property"],
|
||||
ExcalidrawTextElement
|
||||
> = ({
|
||||
accumulatedChange,
|
||||
originalElements,
|
||||
|
@ -41,71 +64,67 @@ const handleFontSizeChange: DragInputCallbackType<
|
|||
scene,
|
||||
}) => {
|
||||
const elementsMap = scene.getNonDeletedElementsMap();
|
||||
const latestTextElements = getApplicableTextElements(
|
||||
originalElements.map((el) => elementsMap.get(el.id)),
|
||||
);
|
||||
const latestTextElements = originalElements.map((el) =>
|
||||
elementsMap.get(el.id),
|
||||
) as ExcalidrawTextElement[];
|
||||
|
||||
let nextFontSize;
|
||||
|
||||
if (nextValue) {
|
||||
const nextFontSize = Math.max(Math.round(nextValue), MIN_FONT_SIZE);
|
||||
nextFontSize = Math.max(Math.round(nextValue), MIN_FONT_SIZE);
|
||||
|
||||
for (const textElement of latestTextElements.map((el) =>
|
||||
elementsMap.get(el.id),
|
||||
)) {
|
||||
if (!textElement || !isTextElement(textElement)) {
|
||||
continue;
|
||||
}
|
||||
const newElement = {
|
||||
...textElement,
|
||||
fontSize: nextFontSize,
|
||||
};
|
||||
const updates = refreshTextDimensions(newElement, null, elementsMap);
|
||||
for (const textElement of latestTextElements) {
|
||||
mutateElement(
|
||||
textElement,
|
||||
{
|
||||
...updates,
|
||||
fontSize: nextFontSize,
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
redrawTextBoundingBox(
|
||||
textElement,
|
||||
scene.getContainerElement(textElement),
|
||||
elementsMap,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
scene.triggerUpdate();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const originalTextElements = originalElements as ExcalidrawTextElement[];
|
||||
|
||||
const originalTextElements = originalElements.filter(
|
||||
(el) => !isInGroup(el) && isTextElement(el) && !isBoundToContainer(el),
|
||||
) as ExcalidrawTextElement[];
|
||||
for (let i = 0; i < latestTextElements.length; i++) {
|
||||
const latestElement = latestTextElements[i];
|
||||
const originalElement = originalTextElements[i];
|
||||
|
||||
for (let i = 0; i < latestTextElements.length; i++) {
|
||||
const latestElement = latestTextElements[i];
|
||||
const originalElement = originalTextElements[i];
|
||||
const originalFontSize = Math.round(originalElement.fontSize);
|
||||
const changeInFontSize = Math.round(accumulatedChange);
|
||||
let nextFontSize = Math.max(
|
||||
originalFontSize + changeInFontSize,
|
||||
MIN_FONT_SIZE,
|
||||
);
|
||||
if (shouldChangeByStepSize) {
|
||||
nextFontSize = getStepSizedValue(nextFontSize, STEP_SIZE);
|
||||
}
|
||||
mutateElement(
|
||||
latestElement,
|
||||
{
|
||||
fontSize: nextFontSize,
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
const originalFontSize = Math.round(originalElement.fontSize);
|
||||
const changeInFontSize = Math.round(accumulatedChange);
|
||||
let nextFontSize = Math.max(
|
||||
originalFontSize + changeInFontSize,
|
||||
MIN_FONT_SIZE,
|
||||
);
|
||||
if (shouldChangeByStepSize) {
|
||||
nextFontSize = getStepSizedValue(nextFontSize, STEP_SIZE);
|
||||
redrawTextBoundingBox(
|
||||
latestElement,
|
||||
scene.getContainerElement(latestElement),
|
||||
elementsMap,
|
||||
false,
|
||||
);
|
||||
}
|
||||
const newElement = {
|
||||
...latestElement,
|
||||
fontSize: nextFontSize,
|
||||
};
|
||||
const updates = refreshTextDimensions(newElement, null, elementsMap);
|
||||
mutateElement(
|
||||
latestElement,
|
||||
{
|
||||
...updates,
|
||||
fontSize: nextFontSize,
|
||||
},
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
scene.triggerUpdate();
|
||||
scene.triggerUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
const MultiFontSize = ({
|
||||
|
@ -113,8 +132,14 @@ const MultiFontSize = ({
|
|||
scene,
|
||||
appState,
|
||||
property,
|
||||
elementsMap,
|
||||
}: MultiFontSizeProps) => {
|
||||
const latestTextElements = getApplicableTextElements(elements);
|
||||
const latestTextElements = getApplicableTextElements(elements, elementsMap);
|
||||
|
||||
if (!latestTextElements.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fontSizes = latestTextElements.map(
|
||||
(textEl) => Math.round(textEl.fontSize * 10) / 10,
|
||||
);
|
||||
|
@ -125,7 +150,7 @@ const MultiFontSize = ({
|
|||
<StatsDragInput
|
||||
label="F"
|
||||
icon={fontSizeIcon}
|
||||
elements={elements}
|
||||
elements={latestTextElements}
|
||||
dragInputCallback={handleFontSizeChange}
|
||||
value={value}
|
||||
editable={editable}
|
||||
|
|
|
@ -21,7 +21,6 @@ import type Scene from "../../scene/Scene";
|
|||
import { useExcalidrawAppState, useExcalidrawSetAppState } from "../App";
|
||||
import { getAtomicUnits } from "./utils";
|
||||
import { STATS_PANELS } from "../../constants";
|
||||
import { isTextElement } from "../../element";
|
||||
|
||||
interface StatsProps {
|
||||
scene: Scene;
|
||||
|
@ -216,14 +215,12 @@ export const StatsInner = memo(
|
|||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
{singleElement.type === "text" && (
|
||||
<FontSize
|
||||
property="fontSize"
|
||||
element={singleElement}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
)}
|
||||
<FontSize
|
||||
property="fontSize"
|
||||
element={singleElement}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@ -278,14 +275,13 @@ export const StatsInner = memo(
|
|||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
{multipleElements.some((el) => isTextElement(el)) && (
|
||||
<MultiFontSize
|
||||
property="fontSize"
|
||||
elements={multipleElements}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
)}
|
||||
<MultiFontSize
|
||||
property="fontSize"
|
||||
elements={multipleElements}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
elementsMap={elementsMap}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -11,7 +11,7 @@ import * as StaticScene from "../../renderer/staticScene";
|
|||
import { vi } from "vitest";
|
||||
import { reseed } from "../../random";
|
||||
import { setDateTimeForTests } from "../../utils";
|
||||
import { Excalidraw } from "../..";
|
||||
import { Excalidraw, mutateElement } from "../..";
|
||||
import { t } from "../../i18n";
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
|
@ -37,10 +37,14 @@ const editInput = (input: HTMLInputElement, value: string) => {
|
|||
};
|
||||
|
||||
const getStatsProperty = (label: string) => {
|
||||
const elementStats = UI.queryStats()?.querySelector("#elementStats");
|
||||
|
||||
if (elementStats) {
|
||||
const properties = elementStats?.querySelector(".statsItem");
|
||||
return properties?.querySelector?.(
|
||||
`.drag-input-container[data-testid="${label}"]`,
|
||||
return (
|
||||
properties?.querySelector?.(
|
||||
`.drag-input-container[data-testid="${label}"]`,
|
||||
) || null
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -57,7 +61,7 @@ const testInputProperty = (
|
|||
const input = getStatsProperty(label)?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(input).not.toBeNull();
|
||||
expect(input).toBeDefined();
|
||||
expect(input.value).toBe(initialValue.toString());
|
||||
editInput(input, String(nextValue));
|
||||
if (property === "angle") {
|
||||
|
@ -131,8 +135,8 @@ describe("stats for a generic element", () => {
|
|||
});
|
||||
|
||||
it("should open stats", () => {
|
||||
expect(stats).not.toBeNull();
|
||||
expect(elementStats).not.toBeNull();
|
||||
expect(stats).toBeDefined();
|
||||
expect(elementStats).toBeDefined();
|
||||
|
||||
// title
|
||||
const title = elementStats?.querySelector("h3");
|
||||
|
@ -140,18 +144,18 @@ describe("stats for a generic element", () => {
|
|||
|
||||
// element type
|
||||
const elementType = elementStats?.querySelector(".elementType");
|
||||
expect(elementType).not.toBeNull();
|
||||
expect(elementType).toBeDefined();
|
||||
expect(elementType?.lastChild?.nodeValue).toBe(t("element.rectangle"));
|
||||
|
||||
// properties
|
||||
const properties = elementStats?.querySelector(".statsItem");
|
||||
expect(properties?.childNodes).not.toBeNull();
|
||||
expect(properties?.childNodes).toBeDefined();
|
||||
["X", "Y", "W", "H", "A"].forEach((label) => () => {
|
||||
expect(
|
||||
properties?.querySelector?.(
|
||||
`.drag-input-container[data-testid="${label}"]`,
|
||||
),
|
||||
).not.toBeNull();
|
||||
).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -174,7 +178,7 @@ describe("stats for a generic element", () => {
|
|||
const input = getStatsProperty("W")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(input).not.toBeNull();
|
||||
expect(input).toBeDefined();
|
||||
expect(input.value).toBe(rectangle.width.toString());
|
||||
editInput(input, "123.123");
|
||||
expect(h.elements.length).toBe(1);
|
||||
|
@ -333,7 +337,7 @@ describe("stats for a non-generic element", () => {
|
|||
const input = getStatsProperty("F")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(input).not.toBeNull();
|
||||
expect(input).toBeDefined();
|
||||
expect(input.value).toBe(text.fontSize.toString());
|
||||
editInput(input, "36");
|
||||
expect(text.fontSize).toBe(36);
|
||||
|
@ -366,7 +370,7 @@ describe("stats for a non-generic element", () => {
|
|||
|
||||
elementStats = stats?.querySelector("#elementStats");
|
||||
|
||||
expect(elementStats).not.toBeNull();
|
||||
expect(elementStats).toBeDefined();
|
||||
|
||||
// cannot change angle
|
||||
const angle = getStatsProperty("A")?.querySelector(".drag-input");
|
||||
|
@ -387,7 +391,7 @@ describe("stats for a non-generic element", () => {
|
|||
},
|
||||
});
|
||||
elementStats = stats?.querySelector("#elementStats");
|
||||
expect(elementStats).not.toBeNull();
|
||||
expect(elementStats).toBeDefined();
|
||||
const widthToHeight = image.width / image.height;
|
||||
|
||||
// when width or height is changed, the aspect ratio is preserved
|
||||
|
@ -399,6 +403,35 @@ describe("stats for a non-generic element", () => {
|
|||
expect(image.height).toBe(80);
|
||||
expect(image.width / image.height).toBe(widthToHeight);
|
||||
});
|
||||
|
||||
it("should display fontSize for bound text", () => {
|
||||
const container = API.createElement({
|
||||
type: "rectangle",
|
||||
width: 200,
|
||||
height: 100,
|
||||
});
|
||||
const text = API.createElement({
|
||||
type: "text",
|
||||
width: 200,
|
||||
height: 100,
|
||||
containerId: container.id,
|
||||
fontSize: 20,
|
||||
});
|
||||
mutateElement(container, {
|
||||
boundElements: [{ type: "text", id: text.id }],
|
||||
});
|
||||
h.elements = [container, text];
|
||||
|
||||
API.setSelectedElements([container]);
|
||||
const fontSize = getStatsProperty("F")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(fontSize).toBeDefined();
|
||||
|
||||
editInput(fontSize, "40");
|
||||
|
||||
expect(text.fontSize).toBe(40);
|
||||
});
|
||||
});
|
||||
|
||||
// multiple elements
|
||||
|
@ -515,25 +548,25 @@ describe("stats for multiple elements", () => {
|
|||
const width = getStatsProperty("W")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(width).not.toBeNull();
|
||||
expect(width).toBeDefined();
|
||||
expect(width.value).toBe("Mixed");
|
||||
|
||||
const height = getStatsProperty("H")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(height).not.toBeNull();
|
||||
expect(height).toBeDefined();
|
||||
expect(height.value).toBe("Mixed");
|
||||
|
||||
const angle = getStatsProperty("A")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(angle).not.toBeNull();
|
||||
expect(angle).toBeDefined();
|
||||
expect(angle.value).toBe("0");
|
||||
|
||||
const fontSize = getStatsProperty("F")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(fontSize).not.toBeNull();
|
||||
expect(fontSize).toBeDefined();
|
||||
|
||||
// changing width does not affect text
|
||||
editInput(width, "200");
|
||||
|
@ -579,7 +612,7 @@ describe("stats for multiple elements", () => {
|
|||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
|
||||
expect(x).not.toBeNull();
|
||||
expect(x).toBeDefined();
|
||||
expect(Number(x.value)).toBe(x1);
|
||||
|
||||
editInput(x, "300");
|
||||
|
@ -592,7 +625,7 @@ describe("stats for multiple elements", () => {
|
|||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
|
||||
expect(y).not.toBeNull();
|
||||
expect(y).toBeDefined();
|
||||
expect(Number(y.value)).toBe(y1);
|
||||
|
||||
editInput(y, "200");
|
||||
|
@ -604,13 +637,13 @@ describe("stats for multiple elements", () => {
|
|||
const width = getStatsProperty("W")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(width).not.toBeNull();
|
||||
expect(width).toBeDefined();
|
||||
expect(Number(width.value)).toBe(200);
|
||||
|
||||
const height = getStatsProperty("H")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(height).not.toBeNull();
|
||||
expect(height).toBeDefined();
|
||||
expect(Number(height.value)).toBe(200);
|
||||
|
||||
editInput(width, "400");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue