wip
This commit is contained in:
parent
55be6de5ec
commit
e5a9786af3
@ -25,7 +25,7 @@ VITE_APP_ENABLE_TRACKING=true
|
||||
FAST_REFRESH=false
|
||||
|
||||
# The port the run the dev server
|
||||
VITE_APP_PORT=3000
|
||||
VITE_APP_PORT=3001
|
||||
|
||||
#Debug flags
|
||||
|
||||
|
@ -169,7 +169,7 @@ export default defineConfig(({ mode }) => {
|
||||
},
|
||||
],
|
||||
start_url: "/",
|
||||
id:"excalidraw",
|
||||
id: "excalidraw",
|
||||
display: "standalone",
|
||||
theme_color: "#121212",
|
||||
background_color: "#ffffff",
|
||||
|
@ -4845,6 +4845,12 @@ class App extends React.Component<AppProps, AppState> {
|
||||
) {
|
||||
const elementsMap = this.scene.getElementsMapIncludingDeleted();
|
||||
|
||||
// flushSync(() => {
|
||||
// this.setState({
|
||||
// editingTextElement: element,
|
||||
// });
|
||||
// });
|
||||
|
||||
const updateElement = (nextOriginalText: string, isDeleted: boolean) => {
|
||||
this.scene.replaceAllElements([
|
||||
// Not sure why we include deleted elements as well hence using deleted elements map
|
||||
@ -6278,6 +6284,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||
private handleCanvasPointerDown = (
|
||||
event: React.PointerEvent<HTMLElement>,
|
||||
) => {
|
||||
console.log("(1)", document.activeElement);
|
||||
console.time();
|
||||
this.focusContainer();
|
||||
console.timeEnd();
|
||||
console.log("(2)", document.activeElement);
|
||||
this.maybeCleanupAfterMissingPointerUp(event.nativeEvent);
|
||||
this.maybeUnfollowRemoteUser();
|
||||
|
||||
@ -6722,17 +6733,16 @@ class App extends React.Component<AppProps, AppState> {
|
||||
}
|
||||
isPanning = true;
|
||||
|
||||
// due to event.preventDefault below, container wouldn't get focus
|
||||
// automatically
|
||||
this.focusContainer();
|
||||
|
||||
// preventing defualt while text editing messes with cursor/focus
|
||||
if (!this.state.editingTextElement) {
|
||||
// necessary to prevent browser from scrolling the page if excalidraw
|
||||
// not full-page #4489
|
||||
//
|
||||
// as such, the above is broken when panning canvas while in wysiwyg
|
||||
// note, this fix won't work when panning canvas while in wysiwyg since
|
||||
// we don't execute it while in wysiwyg
|
||||
event.preventDefault();
|
||||
// focus explicitly due to the event.preventDefault above
|
||||
this.focusContainer();
|
||||
}
|
||||
|
||||
let nextPastePrevented = false;
|
||||
|
@ -19,6 +19,7 @@ import { jotaiScope } from "../../jotai";
|
||||
import { ColorInput } from "./ColorInput";
|
||||
import { activeEyeDropperAtom } from "../EyeDropper";
|
||||
import { PropertiesPopover } from "../PropertiesPopover";
|
||||
import { CLASSES } from "../../constants";
|
||||
|
||||
import "./ColorPicker.scss";
|
||||
|
||||
@ -186,9 +187,13 @@ const ColorPickerTrigger = ({
|
||||
return (
|
||||
<Popover.Trigger
|
||||
type="button"
|
||||
className={clsx("color-picker__button active-color properties-trigger", {
|
||||
"is-transparent": color === "transparent" || !color,
|
||||
})}
|
||||
className={clsx(
|
||||
"color-picker__button active-color",
|
||||
CLASSES.PROPERTIES_POPOVER_TRIGGER,
|
||||
{
|
||||
"is-transparent": color === "transparent" || !color,
|
||||
},
|
||||
)}
|
||||
aria-label={label}
|
||||
style={color ? { "--swatch-color": color } : undefined}
|
||||
title={
|
||||
|
@ -1,6 +1,7 @@
|
||||
import clsx from "clsx";
|
||||
import { useAtom } from "jotai";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useUIAppState } from "../../context/ui-appState";
|
||||
import { activeColorPickerSectionAtom } from "./colorPickerUtils";
|
||||
import HotkeyLabel from "./HotkeyLabel";
|
||||
|
||||
@ -17,6 +18,7 @@ export const CustomColorList = ({
|
||||
onChange,
|
||||
label,
|
||||
}: CustomColorListProps) => {
|
||||
const appState = useUIAppState();
|
||||
const [activeColorPickerSection, setActiveColorPickerSection] = useAtom(
|
||||
activeColorPickerSectionAtom,
|
||||
);
|
||||
@ -54,7 +56,9 @@ export const CustomColorList = ({
|
||||
key={i}
|
||||
>
|
||||
<div className="color-picker__button-outline" />
|
||||
<HotkeyLabel color={c} keyLabel={i + 1} isCustomColor />
|
||||
{!appState.editingTextElement && (
|
||||
<HotkeyLabel color={c} keyLabel={i + 1} isCustomColor />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
@ -10,6 +10,7 @@ import HotkeyLabel from "./HotkeyLabel";
|
||||
import type { ColorPaletteCustom } from "../../colors";
|
||||
import type { TranslationKeys } from "../../i18n";
|
||||
import { t } from "../../i18n";
|
||||
import { useUIAppState } from "../../context/ui-appState";
|
||||
|
||||
interface PickerColorListProps {
|
||||
palette: ColorPaletteCustom;
|
||||
@ -26,6 +27,8 @@ const PickerColorList = ({
|
||||
label,
|
||||
activeShade,
|
||||
}: PickerColorListProps) => {
|
||||
const appState = useUIAppState();
|
||||
|
||||
const colorObj = getColorNameAndShadeFromColor({
|
||||
color: color || "transparent",
|
||||
palette,
|
||||
@ -80,7 +83,9 @@ const PickerColorList = ({
|
||||
key={key}
|
||||
>
|
||||
<div className="color-picker__button-outline" />
|
||||
<HotkeyLabel color={color} keyLabel={keybinding} />
|
||||
{!appState.editingTextElement && (
|
||||
<HotkeyLabel color={color} keyLabel={keybinding} />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
import HotkeyLabel from "./HotkeyLabel";
|
||||
import { t } from "../../i18n";
|
||||
import type { ColorPaletteCustom } from "../../colors";
|
||||
import { useUIAppState } from "../../context/ui-appState";
|
||||
|
||||
interface ShadeListProps {
|
||||
hex: string;
|
||||
@ -16,6 +17,8 @@ interface ShadeListProps {
|
||||
}
|
||||
|
||||
export const ShadeList = ({ hex, onChange, palette }: ShadeListProps) => {
|
||||
const appState = useUIAppState();
|
||||
|
||||
const colorObj = getColorNameAndShadeFromColor({
|
||||
color: hex || "transparent",
|
||||
palette,
|
||||
@ -31,7 +34,7 @@ export const ShadeList = ({ hex, onChange, palette }: ShadeListProps) => {
|
||||
if (btnRef.current && activeColorPickerSection === "shades") {
|
||||
btnRef.current.focus();
|
||||
}
|
||||
}, [colorObj, activeColorPickerSection]);
|
||||
}, [colorObj?.colorName, colorObj?.shade, activeColorPickerSection]);
|
||||
|
||||
if (colorObj) {
|
||||
const { colorName, shade } = colorObj;
|
||||
@ -64,7 +67,9 @@ export const ShadeList = ({ hex, onChange, palette }: ShadeListProps) => {
|
||||
}}
|
||||
>
|
||||
<div className="color-picker__button-outline" />
|
||||
<HotkeyLabel color={color} keyLabel={i + 1} isShade />
|
||||
{!appState.editingTextElement && (
|
||||
<HotkeyLabel color={color} keyLabel={i + 1} isShade />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
@ -56,6 +56,7 @@ export const TopPicks = ({
|
||||
title={color}
|
||||
onClick={() => onChange(color)}
|
||||
data-testid={`color-top-pick-${color}`}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<div className="color-picker__button-outline" />
|
||||
</button>
|
||||
|
@ -250,6 +250,10 @@ export const FontPickerList = React.memo(
|
||||
onClose={onClose}
|
||||
onPointerLeave={onLeave}
|
||||
onKeyDown={onKeyDown}
|
||||
onFocusOutside={(event) => {
|
||||
// so we don't close when refocusing wysiwyg while editing
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
<QuickSearch
|
||||
ref={inputRef}
|
||||
|
@ -5,6 +5,7 @@ import { TextIcon } from "../icons";
|
||||
import type { FontFamilyValues } from "../../element/types";
|
||||
import { t } from "../../i18n";
|
||||
import { isDefaultFont } from "./FontPicker";
|
||||
import { CLASSES } from "../../constants";
|
||||
|
||||
interface FontPickerTriggerProps {
|
||||
selectedFontFamily: FontFamilyValues | null;
|
||||
@ -26,7 +27,7 @@ export const FontPickerTrigger = ({
|
||||
standalone
|
||||
icon={TextIcon}
|
||||
title={t("labels.showFonts")}
|
||||
className="properties-trigger"
|
||||
className={CLASSES.PROPERTIES_POPOVER_TRIGGER}
|
||||
testId={"font-family-show-fonts"}
|
||||
active={isTriggerActive}
|
||||
// no-op
|
||||
|
@ -5,6 +5,7 @@ import * as Popover from "@radix-ui/react-popover";
|
||||
import { useDevice } from "./App";
|
||||
import { Island } from "./Island";
|
||||
import { isInteractive } from "../utils";
|
||||
import { CLASSES } from "../constants";
|
||||
|
||||
interface PropertiesPopoverProps {
|
||||
className?: string;
|
||||
@ -42,7 +43,11 @@ export const PropertiesPopover = React.forwardRef<
|
||||
<Popover.Portal container={container}>
|
||||
<Popover.Content
|
||||
ref={ref}
|
||||
className={clsx("focus-visible-none", className)}
|
||||
className={clsx(
|
||||
"focus-visible-none",
|
||||
CLASSES.PROPERTIES_POPOVER,
|
||||
className,
|
||||
)}
|
||||
data-prevent-outside-click
|
||||
side={
|
||||
device.editor.isMobile && !device.viewport.isLandscape
|
||||
|
@ -115,6 +115,8 @@ export const CLASSES = {
|
||||
SHAPE_ACTIONS_MENU: "App-menu__left",
|
||||
ZOOM_ACTIONS: "zoom-actions",
|
||||
SEARCH_MENU_INPUT_WRAPPER: "layer-ui__search-inputWrapper",
|
||||
PROPERTIES_POPOVER: "properties-popover",
|
||||
PROPERTIES_POPOVER_TRIGGER: "properties-popover-trigger",
|
||||
};
|
||||
|
||||
export const CJK_HAND_DRAWN_FALLBACK_FONT = "Xiaolai";
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
isBoundToContainer,
|
||||
isTextElement,
|
||||
} from "./typeChecks";
|
||||
import { CLASSES, POINTER_BUTTON } from "../constants";
|
||||
import { CLASSES, EVENT, isSafari, POINTER_BUTTON } from "../constants";
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawLinearElement,
|
||||
@ -50,6 +50,8 @@ import {
|
||||
originalContainerCache,
|
||||
updateOriginalContainerCache,
|
||||
} from "./containerCache";
|
||||
import { activeEyeDropperAtom } from "../components/EyeDropper";
|
||||
import { jotaiStore } from "../jotai";
|
||||
|
||||
const getTransform = (
|
||||
width: number,
|
||||
@ -524,6 +526,7 @@ export const textWysiwyg = ({
|
||||
// so that we don't need to create separate a callback for event handlers
|
||||
let submittedViaKeyboard = false;
|
||||
const handleSubmit = () => {
|
||||
console.warn("handleSubmit");
|
||||
// prevent double submit
|
||||
if (isDestroyed) {
|
||||
return;
|
||||
@ -581,62 +584,96 @@ export const textWysiwyg = ({
|
||||
});
|
||||
};
|
||||
|
||||
const onBlur = () => {
|
||||
console.warn("onBlur", document.activeElement);
|
||||
const isColorPicking = jotaiStore.get(activeEyeDropperAtom);
|
||||
if (isColorPicking) {
|
||||
focusEditable(null);
|
||||
} else if (document.activeElement !== editable) {
|
||||
handleSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
// remove events to ensure they don't late-fire
|
||||
editable.onblur = null;
|
||||
editable.oninput = null;
|
||||
editable.onkeydown = null;
|
||||
editable.onpointerdown = null;
|
||||
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
}
|
||||
|
||||
window.removeEventListener("resize", updateWysiwygStyle);
|
||||
window.removeEventListener("wheel", stopEvent, true);
|
||||
window.removeEventListener("pointerdown", onPointerDown);
|
||||
window.removeEventListener("pointerup", bindBlurEvent);
|
||||
window.removeEventListener("blur", handleSubmit);
|
||||
window.removeEventListener("beforeunload", handleSubmit);
|
||||
window.removeEventListener(EVENT.RESIZE, updateWysiwygStyle);
|
||||
window.removeEventListener(EVENT.WHEEL, stopEvent, true);
|
||||
window.removeEventListener(EVENT.POINTER_DOWN, onPointerDown, {
|
||||
capture: true,
|
||||
});
|
||||
window.removeEventListener(EVENT.POINTER_UP, onPointerUp);
|
||||
window.removeEventListener(EVENT.BLUR, onBlur);
|
||||
window.removeEventListener(EVENT.BEFORE_UNLOAD, handleSubmit);
|
||||
unbindUpdate();
|
||||
unbindOnScroll();
|
||||
|
||||
editable.remove();
|
||||
};
|
||||
|
||||
const bindBlurEvent = (event?: MouseEvent) => {
|
||||
window.removeEventListener("pointerup", bindBlurEvent);
|
||||
// Deferred so that the pointerdown that initiates the wysiwyg doesn't
|
||||
// trigger the blur on ensuing pointerup.
|
||||
// Also to handle cases such as picking a color which would trigger a blur
|
||||
// in that same tick.
|
||||
const focusEditable = (event: MouseEvent | FocusEvent | null) => {
|
||||
const target = event?.target;
|
||||
|
||||
const isPropertiesTrigger =
|
||||
target instanceof HTMLElement &&
|
||||
target.classList.contains("properties-trigger");
|
||||
const shouldSkipRefocus =
|
||||
target &&
|
||||
// don't steal focus if user is focusing an input such as HEX input
|
||||
((isWritableElement(target) && document.activeElement !== editable) ||
|
||||
// refocusing while clicking on popver breaks safari
|
||||
(isSafari &&
|
||||
target instanceof HTMLElement &&
|
||||
target.classList.contains(CLASSES.PROPERTIES_POPOVER_TRIGGER)));
|
||||
|
||||
setTimeout(() => {
|
||||
editable.onblur = handleSubmit;
|
||||
|
||||
// case: clicking on the same property → no change → no update → no focus
|
||||
if (!isPropertiesTrigger) {
|
||||
editable.focus();
|
||||
}
|
||||
});
|
||||
if (!shouldSkipRefocus) {
|
||||
// Deferred so that the pointerdown that initiates the wysiwyg doesn't
|
||||
// trigger the blur on ensuing pointerup.
|
||||
// Also to handle cases such as picking a color which would trigger a blur
|
||||
// in that same tick.
|
||||
setTimeout(() => {
|
||||
// double deferred because on onUpdate/color picker shennanings
|
||||
setTimeout(() => {
|
||||
editable.focus();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const temporarilyDisableSubmit = () => {
|
||||
const onPointerUp = (event: PointerEvent | FocusEvent) => {
|
||||
window.removeEventListener(EVENT.POINTER_UP, onPointerUp);
|
||||
window.removeEventListener(EVENT.FOCUS, onPointerUp);
|
||||
// needs to be deferred due to Safari
|
||||
setTimeout(() => {
|
||||
editable.onblur = onBlur;
|
||||
});
|
||||
focusEditable(event);
|
||||
};
|
||||
|
||||
const disableBlurUntilNextPointerUp = () => {
|
||||
editable.onblur = null;
|
||||
window.addEventListener("pointerup", bindBlurEvent);
|
||||
window.addEventListener(EVENT.POINTER_UP, onPointerUp);
|
||||
// handle edge-case where pointerup doesn't fire e.g. due to user
|
||||
// alt-tabbing away
|
||||
window.addEventListener("blur", handleSubmit);
|
||||
window.addEventListener(EVENT.FOCUS, onPointerUp);
|
||||
};
|
||||
|
||||
// prevent blur when changing properties from the menu
|
||||
const onPointerDown = (event: MouseEvent) => {
|
||||
const target = event?.target;
|
||||
|
||||
// ugly hack to close popups such as color picker when clicking back
|
||||
// into the wysiwyg editor (it won't autoclose as blur won't trigger
|
||||
// since we perpetually keep focus inside the wysiwyg)
|
||||
if (target === editable && app.state.openPopup) {
|
||||
app.setState({ openPopup: null });
|
||||
}
|
||||
|
||||
// panning canvas
|
||||
if (event.button === POINTER_BUTTON.WHEEL) {
|
||||
// trying to pan by clicking inside text area itself -> handle here
|
||||
@ -644,24 +681,18 @@ export const textWysiwyg = ({
|
||||
event.preventDefault();
|
||||
app.handleCanvasPanUsingWheelOrSpaceDrag(event);
|
||||
}
|
||||
temporarilyDisableSubmit();
|
||||
disableBlurUntilNextPointerUp();
|
||||
return;
|
||||
}
|
||||
|
||||
const isPropertiesTrigger =
|
||||
target instanceof HTMLElement &&
|
||||
target.classList.contains("properties-trigger");
|
||||
|
||||
if (
|
||||
((event.target instanceof HTMLElement ||
|
||||
(event.target instanceof HTMLElement ||
|
||||
event.target instanceof SVGElement) &&
|
||||
event.target.closest(
|
||||
`.${CLASSES.SHAPE_ACTIONS_MENU}, .${CLASSES.ZOOM_ACTIONS}`,
|
||||
) &&
|
||||
!isWritableElement(event.target)) ||
|
||||
isPropertiesTrigger
|
||||
event.target.closest(
|
||||
`.${CLASSES.SHAPE_ACTIONS_MENU}, .${CLASSES.ZOOM_ACTIONS}, .${CLASSES.PROPERTIES_POPOVER}`,
|
||||
)
|
||||
) {
|
||||
temporarilyDisableSubmit();
|
||||
disableBlurUntilNextPointerUp();
|
||||
} else if (
|
||||
event.target instanceof HTMLCanvasElement &&
|
||||
// Vitest simply ignores stopPropagation, capture-mode, or rAF
|
||||
@ -684,9 +715,11 @@ export const textWysiwyg = ({
|
||||
const unbindUpdate = app.scene.onUpdate(() => {
|
||||
updateWysiwygStyle();
|
||||
const isPopupOpened = !!document.activeElement?.closest(
|
||||
".properties-content",
|
||||
CLASSES.PROPERTIES_POPOVER,
|
||||
);
|
||||
if (!isPopupOpened) {
|
||||
// we need to keep this code path for safari (iPadOS) bs reasons
|
||||
// (also Vitest)
|
||||
editable.focus();
|
||||
}
|
||||
});
|
||||
@ -704,8 +737,11 @@ export const textWysiwyg = ({
|
||||
// because we need it to happen *after* the blur event from `pointerdown`)
|
||||
editable.select();
|
||||
}
|
||||
bindBlurEvent();
|
||||
|
||||
focusEditable(null);
|
||||
setTimeout(() => {
|
||||
editable.onblur = onBlur;
|
||||
});
|
||||
console.log(">>>>>>>>", app.state.editingTextElement);
|
||||
// reposition wysiwyg in case of canvas is resized. Using ResizeObserver
|
||||
// is preferred so we catch changes from host, where window may not resize.
|
||||
let observer: ResizeObserver | null = null;
|
||||
@ -715,7 +751,7 @@ export const textWysiwyg = ({
|
||||
});
|
||||
observer.observe(canvas);
|
||||
} else {
|
||||
window.addEventListener("resize", updateWysiwygStyle);
|
||||
window.addEventListener(EVENT.RESIZE, updateWysiwygStyle);
|
||||
}
|
||||
|
||||
editable.onpointerdown = (event) => event.stopPropagation();
|
||||
@ -723,9 +759,11 @@ export const textWysiwyg = ({
|
||||
// rAF (+ capture to by doubly sure) so we don't catch te pointerdown that
|
||||
// triggered the wysiwyg
|
||||
requestAnimationFrame(() => {
|
||||
window.addEventListener("pointerdown", onPointerDown, { capture: true });
|
||||
window.addEventListener(EVENT.POINTER_DOWN, onPointerDown, {
|
||||
capture: true,
|
||||
});
|
||||
});
|
||||
window.addEventListener("beforeunload", handleSubmit);
|
||||
window.addEventListener(EVENT.BEFORE_UNLOAD, handleSubmit);
|
||||
excalidrawContainer
|
||||
?.querySelector(".excalidraw-textEditorContainer")!
|
||||
.appendChild(editable);
|
||||
|
@ -635,7 +635,7 @@ exports[`<Excalidraw/> > Test UIOptions prop > Test canvasActions > should rende
|
||||
aria-expanded="false"
|
||||
aria-haspopup="dialog"
|
||||
aria-label="Canvas background"
|
||||
class="color-picker__button active-color properties-trigger"
|
||||
class="color-picker__button active-color properties-popover-trigger"
|
||||
data-state="closed"
|
||||
style="--swatch-color: #ffffff;"
|
||||
title="Show background color picker"
|
||||
|
@ -17,6 +17,7 @@ exports[`Test Linear Elements > Test bound text element > should match styles fo
|
||||
class="excalidraw-wysiwyg"
|
||||
data-type="wysiwyg"
|
||||
dir="auto"
|
||||
placeholder=" "
|
||||
style="position: absolute; display: inline-block; min-height: 1em; backface-visibility: hidden; margin: 0px; padding: 0px; border: 0px; outline: 0; resize: none; background: transparent; overflow: hidden; z-index: var(--zIndex-wysiwyg); word-break: break-word; white-space: pre-wrap; overflow-wrap: break-word; box-sizing: content-box; width: 10.5px; height: 26.25px; left: 35px; top: 7.5px; transform: translate(0px, 0px) scale(1) rotate(0deg); text-align: center; vertical-align: middle; color: rgb(30, 30, 30); opacity: 1; filter: var(--theme-filter); max-height: 992.5px; font: Emoji 20px 20px; line-height: 1.25; font-family: Excalifont, Xiaolai, Segoe UI Emoji;"
|
||||
tabindex="0"
|
||||
wrap="off"
|
||||
|
@ -38,6 +38,8 @@ import { pointFrom, pointRotateRads } from "../../../math";
|
||||
import { cropElement } from "../../element/cropElement";
|
||||
import type { ToolType } from "../../types";
|
||||
|
||||
const TEXT_EDITOR_SELECTOR = ".excalidraw-textEditorContainer > textarea";
|
||||
|
||||
// so that window.h is available when App.tsx is not imported as well.
|
||||
createTestHook();
|
||||
|
||||
@ -477,12 +479,20 @@ export class UI {
|
||||
pointFrom(0, 0),
|
||||
pointFrom(width, height),
|
||||
];
|
||||
|
||||
UI.clickTool(type);
|
||||
|
||||
if (type === "text") {
|
||||
mouse.reset();
|
||||
mouse.click(x, y);
|
||||
|
||||
const openedEditor =
|
||||
document.querySelector<HTMLTextAreaElement>(TEXT_EDITOR_SELECTOR);
|
||||
|
||||
// NOTE this is a hack to make sure the editor is focused on edit
|
||||
// which for some reason doesn't work in tests after latest changes.
|
||||
// This means that a regression in wysiwyg editor might not be caught
|
||||
// tests.
|
||||
openedEditor?.focus();
|
||||
} else if ((type === "line" || type === "arrow") && points.length > 2) {
|
||||
points.forEach((point) => {
|
||||
mouse.reset();
|
||||
@ -518,20 +528,25 @@ export class UI {
|
||||
static async editText<
|
||||
T extends ExcalidrawTextElement | ExcalidrawTextContainer,
|
||||
>(element: T, text: string) {
|
||||
const textEditorSelector = ".excalidraw-textEditorContainer > textarea";
|
||||
const openedEditor =
|
||||
document.querySelector<HTMLTextAreaElement>(textEditorSelector);
|
||||
document.querySelector<HTMLTextAreaElement>(TEXT_EDITOR_SELECTOR);
|
||||
|
||||
if (!openedEditor) {
|
||||
mouse.select(element);
|
||||
Keyboard.keyPress(KEYS.ENTER);
|
||||
}
|
||||
|
||||
const editor = await getTextEditor(textEditorSelector);
|
||||
const editor = await getTextEditor();
|
||||
if (!editor) {
|
||||
throw new Error("Can't find wysiwyg text editor in the dom");
|
||||
}
|
||||
|
||||
// NOTE this is a hack to make sure the editor is focused on edit
|
||||
// which for some reason doesn't work in tests after latest changes.
|
||||
// This means that a regression in wysiwyg editor might not be caught
|
||||
// tests.
|
||||
editor.focus();
|
||||
|
||||
fireEvent.input(editor, { target: { value: text } });
|
||||
act(() => {
|
||||
editor.blur();
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { waitFor } from "@testing-library/dom";
|
||||
import { fireEvent } from "@testing-library/react";
|
||||
|
||||
export const getTextEditor = async (selector: string, waitForEditor = true) => {
|
||||
export const getTextEditor = async (
|
||||
selector = ".excalidraw-textEditorContainer > textarea",
|
||||
waitForEditor = true,
|
||||
) => {
|
||||
const query = () => document.querySelector(selector) as HTMLTextAreaElement;
|
||||
if (waitForEditor) {
|
||||
await waitFor(() => expect(query()).not.toBe(null));
|
||||
|
@ -1126,7 +1126,7 @@ describe("multiple selection", () => {
|
||||
expect(bottomArrowLabel.fontSize).toBeCloseTo(28 * scale);
|
||||
});
|
||||
|
||||
it("resizes with text elements", async () => {
|
||||
it.only("resizes with text elements", async () => {
|
||||
const topText = UI.createElement("text", { position: 0 });
|
||||
await UI.editText(topText, "lorem ipsum");
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user