refactor to include dimension and step size
parent
0a529bd2ed
commit
0987c5b770
@ -1,270 +0,0 @@
|
||||
import throttle from "lodash.throttle";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { EVENT } from "../constants";
|
||||
import { getTransformHandles } from "../element";
|
||||
import { mutateElement } from "../element/mutateElement";
|
||||
import { resizeSingleElement } from "../element/resizeElements";
|
||||
import type { ElementsMap, ExcalidrawElement } from "../element/types";
|
||||
import { KEYS } from "../keys";
|
||||
import { degreeToRadian, radianToDegree } from "../math";
|
||||
import type { AppState, Point } from "../types";
|
||||
import { deepCopyElement } from "../element/newElement";
|
||||
|
||||
const shouldKeepAspectRatio = (element: ExcalidrawElement) => {
|
||||
return element.type === "image";
|
||||
};
|
||||
|
||||
type AdjustableProperty = "width" | "height" | "angle" | "x" | "y";
|
||||
|
||||
interface DragInputProps {
|
||||
label: string | React.ReactNode;
|
||||
property: AdjustableProperty;
|
||||
element: ExcalidrawElement;
|
||||
elementsMap: ElementsMap;
|
||||
zoom: AppState["zoom"];
|
||||
}
|
||||
|
||||
const DragInput = ({
|
||||
label,
|
||||
property,
|
||||
element,
|
||||
elementsMap,
|
||||
zoom,
|
||||
}: DragInputProps) => {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const labelRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const originalElement = useRef<ExcalidrawElement>();
|
||||
const accumulatedDimensionChange = useRef(0);
|
||||
|
||||
const handleChange = useMemo(
|
||||
() =>
|
||||
(
|
||||
initialValue: number,
|
||||
delta: number,
|
||||
source: "pointerMove" | "keyDown",
|
||||
pointerOffset?: number,
|
||||
) => {
|
||||
if (inputRef.current && originalElement.current) {
|
||||
const keepAspectRatio = shouldKeepAspectRatio(element);
|
||||
|
||||
if (
|
||||
(property === "width" || property === "height") &&
|
||||
source === "pointerMove" &&
|
||||
pointerOffset
|
||||
) {
|
||||
const handles = getTransformHandles(
|
||||
originalElement.current,
|
||||
zoom,
|
||||
elementsMap,
|
||||
"mouse",
|
||||
{},
|
||||
);
|
||||
|
||||
let referencePoint: Point | undefined;
|
||||
let handleDirection: "e" | "s" | "se" | undefined;
|
||||
|
||||
if (keepAspectRatio && handles.se) {
|
||||
referencePoint = [handles.se[0], handles.se[1]];
|
||||
handleDirection = "se";
|
||||
} else if (property === "width" && handles.e) {
|
||||
referencePoint = [handles.e[0], handles.e[1]];
|
||||
handleDirection = "e";
|
||||
} else if (property === "height" && handles.s) {
|
||||
referencePoint = [handles.s[0], handles.s[1]];
|
||||
handleDirection = "s";
|
||||
}
|
||||
|
||||
if (referencePoint !== undefined && handleDirection !== undefined) {
|
||||
accumulatedDimensionChange.current += pointerOffset;
|
||||
|
||||
const pointer: Point = [
|
||||
referencePoint[0] +
|
||||
(property === "width"
|
||||
? accumulatedDimensionChange.current
|
||||
: 0),
|
||||
referencePoint[1] +
|
||||
(property === "height"
|
||||
? accumulatedDimensionChange.current
|
||||
: 0),
|
||||
];
|
||||
|
||||
resizeSingleElement(
|
||||
elementsMap,
|
||||
keepAspectRatio,
|
||||
element,
|
||||
elementsMap,
|
||||
handleDirection,
|
||||
false,
|
||||
pointer[0],
|
||||
pointer[1],
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
source === "keyDown" ||
|
||||
(source === "pointerMove" &&
|
||||
property !== "width" &&
|
||||
property !== "height")
|
||||
) {
|
||||
const incVal = Math.round(
|
||||
Math.sign(delta) * Math.pow(Math.abs(delta) / 10, 1.6),
|
||||
);
|
||||
let newVal = initialValue + incVal;
|
||||
|
||||
newVal =
|
||||
property === "angle"
|
||||
? // so the degree converted from radian is an integer
|
||||
degreeToRadian(
|
||||
Math.round(
|
||||
radianToDegree(
|
||||
degreeToRadian(
|
||||
Math.sign(newVal % 360) === -1
|
||||
? (newVal % 360) + 360
|
||||
: newVal % 360,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Math.round(newVal);
|
||||
|
||||
mutateElement(element, {
|
||||
[property]: newVal,
|
||||
});
|
||||
originalElement.current = deepCopyElement(element);
|
||||
}
|
||||
}
|
||||
},
|
||||
[element, property, zoom, elementsMap],
|
||||
);
|
||||
|
||||
const hangleChangeThrottled = useMemo(() => {
|
||||
return throttle(handleChange, 16);
|
||||
}, [handleChange]);
|
||||
|
||||
useEffect(() => {
|
||||
const value =
|
||||
Math.round(
|
||||
property === "angle"
|
||||
? radianToDegree(element[property]) * 100
|
||||
: element[property] * 100,
|
||||
) / 100;
|
||||
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = String(value);
|
||||
}
|
||||
}, [element, element.version, element.versionNonce, property]);
|
||||
|
||||
useEffect(() => {
|
||||
hangleChangeThrottled.cancel();
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
accumulatedDimensionChange.current = 0;
|
||||
originalElement.current = undefined;
|
||||
}, [element.id]);
|
||||
|
||||
return (
|
||||
<label className="color-input-container">
|
||||
<div
|
||||
className="color-picker-hash"
|
||||
ref={labelRef}
|
||||
style={{
|
||||
width: "20px",
|
||||
}}
|
||||
onPointerDown={(event) => {
|
||||
if (!originalElement.current) {
|
||||
originalElement.current = deepCopyElement(element);
|
||||
}
|
||||
|
||||
if (!accumulatedDimensionChange.current) {
|
||||
accumulatedDimensionChange.current = 0;
|
||||
}
|
||||
|
||||
if (inputRef.current) {
|
||||
const startPosition = event.clientX;
|
||||
let startValue = Number(inputRef.current.value);
|
||||
if (isNaN(startValue)) {
|
||||
startValue = 0;
|
||||
}
|
||||
|
||||
let lastPointerRef: {
|
||||
x: number;
|
||||
y: number;
|
||||
} | null = null;
|
||||
|
||||
document.body.classList.add("dragResize");
|
||||
|
||||
const onPointerMove = (event: PointerEvent) => {
|
||||
if (lastPointerRef) {
|
||||
hangleChangeThrottled(
|
||||
startValue,
|
||||
Math.ceil(event.clientX - startPosition),
|
||||
"pointerMove",
|
||||
event.clientX - lastPointerRef.x,
|
||||
);
|
||||
}
|
||||
|
||||
lastPointerRef = {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
};
|
||||
};
|
||||
|
||||
window.addEventListener(EVENT.POINTER_MOVE, onPointerMove, false);
|
||||
window.addEventListener(
|
||||
EVENT.POINTER_UP,
|
||||
() => {
|
||||
window.removeEventListener(
|
||||
EVENT.POINTER_MOVE,
|
||||
onPointerMove,
|
||||
false,
|
||||
);
|
||||
|
||||
lastPointerRef = null;
|
||||
|
||||
document.body.classList.remove("dragResize");
|
||||
},
|
||||
false,
|
||||
);
|
||||
}
|
||||
}}
|
||||
onPointerUp={(event) => {
|
||||
accumulatedDimensionChange.current = 0;
|
||||
originalElement.current = undefined;
|
||||
}}
|
||||
onPointerEnter={() => {
|
||||
if (labelRef.current) {
|
||||
labelRef.current.style.cursor = "ew-resize";
|
||||
}
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
<input
|
||||
className="color-picker-input"
|
||||
style={{
|
||||
width: "66px",
|
||||
fontSize: "12px",
|
||||
}}
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
onKeyDown={(event) => {
|
||||
const eventTarget = event.target;
|
||||
|
||||
if (eventTarget instanceof HTMLInputElement) {
|
||||
const value = Number(eventTarget.value);
|
||||
if (isNaN(value)) {
|
||||
return;
|
||||
}
|
||||
if (event.key === KEYS.ENTER) {
|
||||
handleChange(value, 0, "keyDown");
|
||||
}
|
||||
}
|
||||
}}
|
||||
ref={inputRef}
|
||||
></input>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
export default DragInput;
|
@ -0,0 +1,61 @@
|
||||
import { mutateElement } from "../../element/mutateElement";
|
||||
import type { ExcalidrawElement } from "../../element/types";
|
||||
import { degreeToRadian, radianToDegree } from "../../math";
|
||||
import DragInput from "./DragInput";
|
||||
import type { DragInputCallbackType } from "./DragInput";
|
||||
import { getStepSizedValue, isPropertyEditable } from "./utils";
|
||||
|
||||
interface AngleProps {
|
||||
element: ExcalidrawElement;
|
||||
}
|
||||
|
||||
const STEP_SIZE = 15;
|
||||
|
||||
const Angle = ({ element }: AngleProps) => {
|
||||
const handleDegreeChange: DragInputCallbackType = (
|
||||
accumulatedChange,
|
||||
instantChange,
|
||||
stateAtStart,
|
||||
shouldKeepAspectRatio,
|
||||
shouldChangeByStepSize,
|
||||
nextValue,
|
||||
) => {
|
||||
if (nextValue !== undefined) {
|
||||
const nextAngle = degreeToRadian(nextValue);
|
||||
mutateElement(element, {
|
||||
angle: nextAngle,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (stateAtStart) {
|
||||
const originalAngleInDegrees =
|
||||
Math.round(radianToDegree(stateAtStart.angle) * 100) / 100;
|
||||
const changeInDegrees = Math.round(accumulatedChange);
|
||||
let nextAngleInDegrees = (originalAngleInDegrees + changeInDegrees) % 360;
|
||||
if (shouldChangeByStepSize) {
|
||||
nextAngleInDegrees = getStepSizedValue(nextAngleInDegrees, STEP_SIZE);
|
||||
}
|
||||
|
||||
mutateElement(element, {
|
||||
angle: degreeToRadian(
|
||||
nextAngleInDegrees < 0
|
||||
? nextAngleInDegrees + 360
|
||||
: nextAngleInDegrees,
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DragInput
|
||||
label="A"
|
||||
value={Math.round(radianToDegree(element.angle) * 100) / 100}
|
||||
element={element}
|
||||
dragInputCallback={handleDegreeChange}
|
||||
editable={isPropertyEditable(element, "angle")}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Angle;
|
@ -0,0 +1,160 @@
|
||||
import type { ExcalidrawElement } from "../../element/types";
|
||||
import DragInput from "./DragInput";
|
||||
import type { DragInputCallbackType } from "./DragInput";
|
||||
import { getStepSizedValue, isPropertyEditable } from "./utils";
|
||||
import { mutateElement } from "../../element/mutateElement";
|
||||
|
||||
interface DimensionDragInputProps {
|
||||
property: "width" | "height";
|
||||
element: ExcalidrawElement;
|
||||
}
|
||||
|
||||
const STEP_SIZE = 10;
|
||||
const _shouldKeepAspectRatio = (element: ExcalidrawElement) => {
|
||||
return element.type === "image";
|
||||
};
|
||||
|
||||
const newOrigin = (
|
||||
x1: number,
|
||||
y1: number,
|
||||
w1: number,
|
||||
h1: number,
|
||||
w2: number,
|
||||
h2: number,
|
||||
angle: number,
|
||||
) => {
|
||||
/**
|
||||
* The formula below is the result of solving
|
||||
* rotate(x1, y1, cx1, cy1, angle) = rotate(x2, y2, cx2, cy2, angle)
|
||||
* where rotate is the function defined in math.ts
|
||||
*
|
||||
* This is so that the new origin (x2, y2),
|
||||
* when rotated against the new center (cx2, cy2),
|
||||
* coincides with (x1, y1) rotated against (cx1, cy1)
|
||||
*
|
||||
* The reason for doing this computation is so the element's top left corner
|
||||
* on the canvas remains fixed after any changes in its dimension.
|
||||
*/
|
||||
|
||||
return {
|
||||
x:
|
||||
x1 +
|
||||
(w1 - w2) / 2 +
|
||||
((w2 - w1) / 2) * Math.cos(angle) +
|
||||
((h1 - h2) / 2) * Math.sin(angle),
|
||||
y:
|
||||
y1 +
|
||||
(h1 - h2) / 2 +
|
||||
((w2 - w1) / 2) * Math.sin(angle) +
|
||||
((h2 - h1) / 2) * Math.cos(angle),
|
||||
};
|
||||
};
|
||||
|
||||
const DimensionDragInput = ({ property, element }: DimensionDragInputProps) => {
|
||||
const handleDimensionChange: DragInputCallbackType = (
|
||||
accumulatedChange,
|
||||
instantChange,
|
||||
stateAtStart,
|
||||
shouldKeepAspectRatio,
|
||||
shouldChangeByStepSize,
|
||||
nextValue,
|
||||
) => {
|
||||
if (stateAtStart) {
|
||||
const keepAspectRatio =
|
||||
shouldKeepAspectRatio || _shouldKeepAspectRatio(element);
|
||||
const aspectRatio = stateAtStart.width / stateAtStart.height;
|
||||
|
||||
if (nextValue !== undefined) {
|
||||
const nextWidth = Math.max(
|
||||
property === "width"
|
||||
? nextValue
|
||||
: keepAspectRatio
|
||||
? nextValue * aspectRatio
|
||||
: stateAtStart.width,
|
||||
0,
|
||||
);
|
||||
const nextHeight = Math.max(
|
||||
property === "height"
|
||||
? nextValue
|
||||
: keepAspectRatio
|
||||
? nextValue / aspectRatio
|
||||
: stateAtStart.height,
|
||||
0,
|
||||
);
|
||||
|
||||
mutateElement(element, {
|
||||
...newOrigin(
|
||||
element.x,
|
||||
element.y,
|
||||
element.width,
|
||||
element.height,
|
||||
nextWidth,
|
||||
nextHeight,
|
||||
element.angle,
|
||||
),
|
||||
width: nextWidth,
|
||||
height: nextHeight,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const changeInWidth = property === "width" ? accumulatedChange : 0;
|
||||
const changeInHeight = property === "height" ? accumulatedChange : 0;
|
||||
|
||||
let nextWidth = Math.max(0, stateAtStart.width + changeInWidth);
|
||||
if (property === "width") {
|
||||
if (shouldChangeByStepSize) {
|
||||
nextWidth = getStepSizedValue(nextWidth, STEP_SIZE);
|
||||
} else {
|
||||
nextWidth = Math.round(nextWidth);
|
||||
}
|
||||
}
|
||||
|
||||
let nextHeight = Math.max(0, stateAtStart.height + changeInHeight);
|
||||
if (property === "height") {
|
||||
if (shouldChangeByStepSize) {
|
||||
nextHeight = getStepSizedValue(nextHeight, STEP_SIZE);
|
||||
} else {
|
||||
nextHeight = Math.round(nextHeight);
|
||||
}
|
||||
}
|
||||
|
||||
if (keepAspectRatio) {
|
||||
if (property === "width") {
|
||||
nextHeight = Math.round((nextWidth / aspectRatio) * 100) / 100;
|
||||
} else {
|
||||
nextWidth = Math.round(nextHeight * aspectRatio * 100) / 100;
|
||||
}
|
||||
}
|
||||
|
||||
mutateElement(element, {
|
||||
width: nextWidth,
|
||||
height: nextHeight,
|
||||
...newOrigin(
|
||||
stateAtStart.x,
|
||||
stateAtStart.y,
|
||||
stateAtStart.width,
|
||||
stateAtStart.height,
|
||||
nextWidth,
|
||||
nextHeight,
|
||||
stateAtStart.angle,
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DragInput
|
||||
label={property === "width" ? "W" : "H"}
|
||||
element={element}
|
||||
dragInputCallback={handleDimensionChange}
|
||||
value={
|
||||
Math.round(
|
||||
(property === "width" ? element.width : element.height) * 100,
|
||||
) / 100
|
||||
}
|
||||
editable={isPropertyEditable(element, property)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DimensionDragInput;
|
@ -0,0 +1,75 @@
|
||||
.excalidraw {
|
||||
.drag-input-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
&:focus-within {
|
||||
box-shadow: 0 0 0 1px var(--color-primary-darkest);
|
||||
border-radius: var(--border-radius-lg);
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.drag-input-label {
|
||||
height: var(--default-button-size);
|
||||
flex-shrink: 0;
|
||||
padding: 0.5rem 0.5rem 0.5rem 0.75rem;
|
||||
border: 1px solid var(--default-border-color);
|
||||
border-right: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
:root[dir="ltr"] & {
|
||||
border-radius: var(--border-radius-lg) 0 0 var(--border-radius-lg);
|
||||
}
|
||||
|
||||
:root[dir="rtl"] & {
|
||||
border-radius: 0 var(--border-radius-lg) var(--border-radius-lg) 0;
|
||||
border-right: 1px solid var(--default-border-color);
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
color: var(--input-label-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.drag-input {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
font-family: inherit;
|
||||
background-color: transparent;
|
||||
color: var(--text-primary-color);
|
||||
border: 0;
|
||||
outline: none;
|
||||
height: var(--default-button-size);
|
||||
border: 1px solid var(--default-border-color);
|
||||
border-left: 0;
|
||||
letter-spacing: 0.4px;
|
||||
|
||||
:root[dir="ltr"] & {
|
||||
border-radius: 0 var(--border-radius-lg) var(--border-radius-lg) 0;
|
||||
}
|
||||
|
||||
:root[dir="rtl"] & {
|
||||
border-radius: var(--border-radius-lg) 0 0 var(--border-radius-lg);
|
||||
border-left: 1px solid var(--default-border-color);
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
padding: 0.5rem;
|
||||
padding-left: 0.25rem;
|
||||
appearance: none;
|
||||
|
||||
&:focus-visible {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,176 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import throttle from "lodash.throttle";
|
||||
import { EVENT } from "../../constants";
|
||||
import { KEYS } from "../../keys";
|
||||
import type { ExcalidrawElement } from "../../element/types";
|
||||
import { deepCopyElement } from "../../element/newElement";
|
||||
|
||||
import "./DragInput.scss";
|
||||
import clsx from "clsx";
|
||||
|
||||
export type DragInputCallbackType = (
|
||||
accumulatedChange: number,
|
||||
instantChange: number,
|
||||
stateAtStart: ExcalidrawElement,
|
||||
shouldKeepAspectRatio: boolean,
|
||||
shouldChangeByStepSize: boolean,
|
||||
nextValue?: number,
|
||||
) => void;
|
||||
|
||||
interface StatsDragInputProps {
|
||||
label: string | React.ReactNode;
|
||||
value: number;
|
||||
element: ExcalidrawElement;
|
||||
editable?: boolean;
|
||||
shouldKeepAspectRatio?: boolean;
|
||||
dragInputCallback: DragInputCallbackType;
|
||||
}
|
||||
|
||||
const StatsDragInput = ({
|
||||
label,
|
||||
dragInputCallback,
|
||||
value,
|
||||
element,
|
||||
editable = true,
|
||||
shouldKeepAspectRatio,
|
||||
}: StatsDragInputProps) => {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const labelRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const cbThrottled = useMemo(() => {
|
||||
return throttle(dragInputCallback, 16);
|
||||
}, [dragInputCallback]);
|
||||
|
||||
const [inputValue, setInputValue] = useState(value.toString());
|
||||
|
||||
useEffect(() => {
|
||||
setInputValue(value.toString());
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div className={clsx("drag-input-container", !editable && "disabled")}>
|
||||
<div
|
||||
className="drag-input-label"
|
||||
ref={labelRef}
|
||||
onPointerDown={(event) => {
|
||||
if (inputRef.current && editable) {
|
||||
let startValue = Number(inputRef.current.value);
|
||||
if (isNaN(startValue)) {
|
||||
startValue = 0;
|
||||
}
|
||||
|
||||
let lastPointer: {
|
||||
x: number;
|
||||
y: number;
|
||||
} | null = null;
|
||||
|
||||
let stateAtStart: ExcalidrawElement | null = null;
|
||||
|
||||
let accumulatedChange: number | null = null;
|
||||
|
||||
document.body.classList.add("dragResize");
|
||||
|
||||
const onPointerMove = (event: PointerEvent) => {
|
||||
if (!stateAtStart) {
|
||||
stateAtStart = deepCopyElement(element);
|
||||
}
|
||||
|
||||
if (!accumulatedChange) {
|
||||
accumulatedChange = 0;
|
||||
}
|
||||
|
||||
if (lastPointer && stateAtStart && accumulatedChange !== null) {
|
||||
const instantChange = event.clientX - lastPointer.x;
|
||||
accumulatedChange += instantChange;
|
||||
|
||||
cbThrottled(
|
||||
accumulatedChange,
|
||||
instantChange,
|
||||
stateAtStart,
|
||||
shouldKeepAspectRatio!!,
|
||||
event.shiftKey,
|
||||
);
|
||||
}
|
||||
|
||||
lastPointer = {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
};
|
||||
};
|
||||
|
||||
window.addEventListener(EVENT.POINTER_MOVE, onPointerMove, false);
|
||||
window.addEventListener(
|
||||
EVENT.POINTER_UP,
|
||||
() => {
|
||||
window.removeEventListener(
|
||||
EVENT.POINTER_MOVE,
|
||||
onPointerMove,
|
||||
false,
|
||||
);
|
||||
|
||||
lastPointer = null;
|
||||
accumulatedChange = null;
|
||||
stateAtStart = null;
|
||||
|
||||
document.body.classList.remove("dragResize");
|
||||
},
|
||||
false,
|
||||
);
|
||||
}
|
||||
}}
|
||||
onPointerEnter={() => {
|
||||
if (labelRef.current) {
|
||||
labelRef.current.style.cursor = "ew-resize";
|
||||
}
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
<input
|
||||
className="drag-input"
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
onKeyDown={(event) => {
|
||||
if (editable) {
|
||||
const eventTarget = event.target;
|
||||
|
||||
if (
|
||||
eventTarget instanceof HTMLInputElement &&
|
||||
event.key === KEYS.ENTER
|
||||
) {
|
||||
const v = Number(eventTarget.value);
|
||||
if (isNaN(v)) {
|
||||
setInputValue(value.toString());
|
||||
return;
|
||||
}
|
||||
dragInputCallback(
|
||||
0,
|
||||
0,
|
||||
element,
|
||||
shouldKeepAspectRatio!!,
|
||||
false,
|
||||
v,
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
ref={inputRef}
|
||||
value={inputValue}
|
||||
onChange={(event) => {
|
||||
const eventTarget = event.target;
|
||||
if (eventTarget instanceof HTMLInputElement) {
|
||||
setInputValue(event.target.value);
|
||||
}
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (!inputValue) {
|
||||
setInputValue(value.toString());
|
||||
}
|
||||
}}
|
||||
disabled={!editable}
|
||||
></input>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatsDragInput;
|
@ -0,0 +1,23 @@
|
||||
import { isFrameLikeElement, isTextElement } from "../../element/typeChecks";
|
||||
import type { ExcalidrawElement } from "../../element/types";
|
||||
|
||||
export const isPropertyEditable = (
|
||||
element: ExcalidrawElement,
|
||||
property: keyof ExcalidrawElement,
|
||||
) => {
|
||||
if (property === "height" && isTextElement(element)) {
|
||||
return false;
|
||||
}
|
||||
if (property === "width" && isTextElement(element)) {
|
||||
return false;
|
||||
}
|
||||
if (property === "angle" && isFrameLikeElement(element)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const getStepSizedValue = (value: number, stepSize: number) => {
|
||||
const v = value + stepSize / 2;
|
||||
return v - (v % stepSize);
|
||||
};
|
Loading…
Reference in New Issue