mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
do not center text when not applicable (#1783)
This commit is contained in:
parent
9c89504b6f
commit
cd87bd6901
16 changed files with 418 additions and 321 deletions
|
@ -1,116 +1,111 @@
|
|||
import { KEYS } from "../keys";
|
||||
import { selectNode, isWritableElement, getFontString } from "../utils";
|
||||
import { isWritableElement, getFontString } from "../utils";
|
||||
import { globalSceneState } from "../scene";
|
||||
import { isTextElement } from "./typeChecks";
|
||||
import { CLASSES } from "../constants";
|
||||
import { FontFamily } from "./types";
|
||||
import { ExcalidrawElement } from "./types";
|
||||
|
||||
const trimText = (text: string) => {
|
||||
// whitespace only → trim all because we'd end up inserting invisible element
|
||||
if (!text.trim()) {
|
||||
return "";
|
||||
}
|
||||
// replace leading/trailing newlines (only) otherwise it messes up bounding
|
||||
// box calculation (there's also a bug in FF which inserts trailing newline
|
||||
// for multiline texts)
|
||||
return text.replace(/^\n+|\n+$/g, "");
|
||||
const normalizeText = (text: string) => {
|
||||
return (
|
||||
text
|
||||
// replace tabs with spaces so they render and measure correctly
|
||||
.replace(/\t/g, " ")
|
||||
// normalize newlines
|
||||
.replace(/\r?\n|\r/g, "\n")
|
||||
);
|
||||
};
|
||||
|
||||
type TextWysiwygParams = {
|
||||
id: string;
|
||||
initText: string;
|
||||
x: number;
|
||||
y: number;
|
||||
strokeColor: string;
|
||||
fontSize: number;
|
||||
fontFamily: FontFamily;
|
||||
opacity: number;
|
||||
zoom: number;
|
||||
angle: number;
|
||||
textAlign: string;
|
||||
onChange?: (text: string) => void;
|
||||
onSubmit: (text: string) => void;
|
||||
onCancel: () => void;
|
||||
const getTransform = (
|
||||
width: number,
|
||||
height: number,
|
||||
angle: number,
|
||||
zoom: number,
|
||||
) => {
|
||||
const degree = (180 * angle) / Math.PI;
|
||||
return `translate(${(width * (zoom - 1)) / 2}px, ${
|
||||
(height * (zoom - 1)) / 2
|
||||
}px) scale(${zoom}) rotate(${degree}deg)`;
|
||||
};
|
||||
|
||||
export const textWysiwyg = ({
|
||||
id,
|
||||
initText,
|
||||
x,
|
||||
y,
|
||||
strokeColor,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
opacity,
|
||||
zoom,
|
||||
angle,
|
||||
onChange,
|
||||
textAlign,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
}: TextWysiwygParams) => {
|
||||
const editable = document.createElement("div");
|
||||
try {
|
||||
editable.contentEditable = "plaintext-only";
|
||||
} catch {
|
||||
editable.contentEditable = "true";
|
||||
getViewportCoords,
|
||||
}: {
|
||||
id: ExcalidrawElement["id"];
|
||||
zoom: number;
|
||||
onChange?: (text: string) => void;
|
||||
onSubmit: (text: string) => void;
|
||||
onCancel: () => void;
|
||||
getViewportCoords: (x: number, y: number) => [number, number];
|
||||
}) => {
|
||||
function updateWysiwygStyle() {
|
||||
const updatedElement = globalSceneState.getElement(id);
|
||||
if (isTextElement(updatedElement)) {
|
||||
const [viewportX, viewportY] = getViewportCoords(
|
||||
updatedElement.x,
|
||||
updatedElement.y,
|
||||
);
|
||||
const { textAlign, angle } = updatedElement;
|
||||
|
||||
editable.value = updatedElement.text;
|
||||
|
||||
const lines = updatedElement.text.replace(/\r\n?/g, "\n").split("\n");
|
||||
const lineHeight = updatedElement.height / lines.length;
|
||||
|
||||
Object.assign(editable.style, {
|
||||
font: getFontString(updatedElement),
|
||||
// must be defined *after* font ¯\_(ツ)_/¯
|
||||
lineHeight: `${lineHeight}px`,
|
||||
width: `${updatedElement.width}px`,
|
||||
height: `${updatedElement.height}px`,
|
||||
left: `${viewportX}px`,
|
||||
top: `${viewportY}px`,
|
||||
transform: getTransform(
|
||||
updatedElement.width,
|
||||
updatedElement.height,
|
||||
angle,
|
||||
zoom,
|
||||
),
|
||||
textAlign: textAlign,
|
||||
color: updatedElement.strokeColor,
|
||||
opacity: updatedElement.opacity / 100,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const editable = document.createElement("textarea");
|
||||
|
||||
editable.dir = "auto";
|
||||
editable.tabIndex = 0;
|
||||
editable.innerText = initText;
|
||||
editable.dataset.type = "wysiwyg";
|
||||
|
||||
const degree = (180 * angle) / Math.PI;
|
||||
// prevent line wrapping on Safari
|
||||
editable.wrap = "off";
|
||||
|
||||
Object.assign(editable.style, {
|
||||
color: strokeColor,
|
||||
position: "fixed",
|
||||
opacity: opacity / 100,
|
||||
top: `${y}px`,
|
||||
left: `${x}px`,
|
||||
transform: `translate(-50%, -50%) scale(${zoom}) rotate(${degree}deg)`,
|
||||
textAlign: textAlign,
|
||||
display: "inline-block",
|
||||
font: getFontString({ fontSize, fontFamily }),
|
||||
padding: "4px",
|
||||
// This needs to have "1px solid" otherwise the carret doesn't show up
|
||||
// the first time on Safari and Chrome!
|
||||
outline: "1px solid transparent",
|
||||
whiteSpace: "nowrap",
|
||||
minHeight: "1em",
|
||||
backfaceVisibility: "hidden",
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
border: 0,
|
||||
outline: 0,
|
||||
resize: "none",
|
||||
background: "transparent",
|
||||
overflow: "hidden",
|
||||
// prevent line wrapping (`whitespace: nowrap` doesn't work on FF)
|
||||
whiteSpace: "pre",
|
||||
});
|
||||
|
||||
editable.onpaste = (event) => {
|
||||
try {
|
||||
const selection = window.getSelection();
|
||||
if (!selection?.rangeCount) {
|
||||
return;
|
||||
}
|
||||
selection.deleteFromDocument();
|
||||
|
||||
const text = event.clipboardData!.getData("text").replace(/\r\n?/g, "\n");
|
||||
|
||||
const span = document.createElement("span");
|
||||
span.innerText = text;
|
||||
const range = selection.getRangeAt(0);
|
||||
range.insertNode(span);
|
||||
|
||||
// deselect
|
||||
window.getSelection()!.removeAllRanges();
|
||||
range.setStart(span, span.childNodes.length);
|
||||
range.setEnd(span, span.childNodes.length);
|
||||
selection.addRange(range);
|
||||
|
||||
event.preventDefault();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
updateWysiwygStyle();
|
||||
|
||||
if (onChange) {
|
||||
editable.oninput = () => {
|
||||
onChange(trimText(editable.innerText));
|
||||
onChange(normalizeText(editable.value));
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -134,8 +129,8 @@ export const textWysiwyg = ({
|
|||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (editable.innerText) {
|
||||
onSubmit(trimText(editable.innerText));
|
||||
if (editable.value) {
|
||||
onSubmit(normalizeText(editable.value));
|
||||
} else {
|
||||
onCancel();
|
||||
}
|
||||
|
@ -149,10 +144,10 @@ export const textWysiwyg = ({
|
|||
isDestroyed = true;
|
||||
// remove events to ensure they don't late-fire
|
||||
editable.onblur = null;
|
||||
editable.onpaste = null;
|
||||
editable.oninput = null;
|
||||
editable.onkeydown = null;
|
||||
|
||||
window.removeEventListener("resize", updateWysiwygStyle);
|
||||
window.removeEventListener("wheel", stopEvent, true);
|
||||
window.removeEventListener("pointerdown", onPointerDown);
|
||||
window.removeEventListener("pointerup", rebindBlur);
|
||||
|
@ -191,26 +186,19 @@ export const textWysiwyg = ({
|
|||
|
||||
// handle updates of textElement properties of editing element
|
||||
const unbindUpdate = globalSceneState.addCallback(() => {
|
||||
const editingElement = globalSceneState
|
||||
.getElementsIncludingDeleted()
|
||||
.find((element) => element.id === id);
|
||||
if (editingElement && isTextElement(editingElement)) {
|
||||
Object.assign(editable.style, {
|
||||
font: getFontString(editingElement),
|
||||
textAlign: editingElement.textAlign,
|
||||
color: editingElement.strokeColor,
|
||||
opacity: editingElement.opacity / 100,
|
||||
});
|
||||
}
|
||||
updateWysiwygStyle();
|
||||
editable.focus();
|
||||
});
|
||||
|
||||
let isDestroyed = false;
|
||||
|
||||
editable.onblur = handleSubmit;
|
||||
// reposition wysiwyg in case of window resize. Happens on mobile when
|
||||
// device keyboard is opened.
|
||||
window.addEventListener("resize", updateWysiwygStyle);
|
||||
window.addEventListener("pointerdown", onPointerDown);
|
||||
window.addEventListener("wheel", stopEvent, true);
|
||||
document.body.appendChild(editable);
|
||||
editable.focus();
|
||||
selectNode(editable);
|
||||
editable.select();
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue