diff --git a/public/index.html b/public/index.html
index 0e7353a26..c15ac97fa 100644
--- a/public/index.html
+++ b/public/index.html
@@ -112,8 +112,7 @@
Roboto, Helvetica, Arial, sans-serif;
font-family: var(--ui-font);
-webkit-text-size-adjust: 100%;
- -webkit-user-select: none;
- user-select: none;
+
width: 100vw;
height: 100vh;
}
diff --git a/src/components/App.tsx b/src/components/App.tsx
index 392e7059c..215304783 100644
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -1105,7 +1105,6 @@ class App extends React.Component {
};
private onTapEnd = (event: TouchEvent) => {
- event.preventDefault();
if (event.touches.length > 0) {
this.setState({
previousSelectedElementIds: {},
@@ -1631,10 +1630,12 @@ class App extends React.Component {
updateBoundElements(element);
}
}),
- onSubmit: withBatchedUpdates((text) => {
+ onSubmit: withBatchedUpdates(({ text, viaKeyboard }) => {
const isDeleted = !text.trim();
updateElement(text, isDeleted);
- if (!isDeleted) {
+ // select the created text element only if submitting via keyboard
+ // (when submitting via click it should act as signal to deselect)
+ if (!isDeleted && viaKeyboard) {
this.setState((prevState) => ({
selectedElementIds: {
...prevState.selectedElementIds,
@@ -2151,15 +2152,6 @@ class App extends React.Component {
this.updateGestureOnPointerDown(event);
- // fixes pointermove causing selection of UI texts #32
- event.preventDefault();
- // Preventing the event above disables default behavior
- // of defocusing potentially focused element, which is what we
- // want when clicking inside the canvas.
- if (document.activeElement instanceof HTMLElement) {
- document.activeElement.blur();
- }
-
// don't select while panning
if (gesture.pointers.size > 1) {
return;
diff --git a/src/css/styles.scss b/src/css/styles.scss
index dbe9ae827..863b11ccf 100644
--- a/src/css/styles.scss
+++ b/src/css/styles.scss
@@ -17,6 +17,13 @@
left: 0;
right: 0;
+ // serves 2 purposes:
+ // 1. prevent selecting text outside the component when double-clicking or
+ // dragging inside it (e.g. on canvas)
+ // 2. prevent selecting UI, both from the inside, and from outside the
+ // component (e.g. if you select text in a sidebar)
+ user-select: none;
+
a {
font-weight: 500;
text-decoration: none;
@@ -29,7 +36,6 @@
canvas {
touch-action: none;
- user-select: none;
// following props improve blurriness at certain devicePixelRatios.
// AFAIK it doesn't affect export (in fact, export seems sharp either way).
diff --git a/src/element/textWysiwyg.tsx b/src/element/textWysiwyg.tsx
index 5f0ca55a9..93ef78d04 100644
--- a/src/element/textWysiwyg.tsx
+++ b/src/element/textWysiwyg.tsx
@@ -47,7 +47,7 @@ export const textWysiwyg = ({
id: ExcalidrawElement["id"];
appState: AppState;
onChange?: (text: string) => void;
- onSubmit: (text: string) => void;
+ onSubmit: (data: { text: string; viaKeyboard: boolean }) => void;
getViewportCoords: (x: number, y: number) => [number, number];
element: ExcalidrawElement;
canvas: HTMLCanvasElement | null;
@@ -136,12 +136,14 @@ export const textWysiwyg = ({
editable.onkeydown = (event) => {
if (event.key === KEYS.ESCAPE) {
event.preventDefault();
+ submittedViaKeyboard = true;
handleSubmit();
} else if (event.key === KEYS.ENTER && event[KEYS.CTRL_OR_CMD]) {
event.preventDefault();
if (event.isComposing || event.keyCode === 229) {
return;
}
+ submittedViaKeyboard = true;
handleSubmit();
} else if (event.key === KEYS.ENTER && !event.altKey) {
event.stopPropagation();
@@ -153,8 +155,14 @@ export const textWysiwyg = ({
event.stopPropagation();
};
+ // using a state variable instead of passing it to the handleSubmit callback
+ // so that we don't need to create separate a callback for event handlers
+ let submittedViaKeyboard = false;
const handleSubmit = () => {
- onSubmit(normalizeText(editable.value));
+ onSubmit({
+ text: normalizeText(editable.value),
+ viaKeyboard: submittedViaKeyboard,
+ });
cleanup();
};
@@ -175,7 +183,7 @@ export const textWysiwyg = ({
window.removeEventListener("resize", updateWysiwygStyle);
window.removeEventListener("wheel", stopEvent, true);
window.removeEventListener("pointerdown", onPointerDown);
- window.removeEventListener("pointerup", rebindBlur);
+ window.removeEventListener("pointerup", bindBlurEvent);
window.removeEventListener("blur", handleSubmit);
unbindUpdate();
@@ -183,10 +191,12 @@ export const textWysiwyg = ({
editable.remove();
};
- const rebindBlur = () => {
- window.removeEventListener("pointerup", rebindBlur);
- // deferred to guard against focus traps on various UIs that steal focus
- // upon pointerUp
+ const bindBlurEvent = () => {
+ 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.
setTimeout(() => {
editable.onblur = handleSubmit;
// case: clicking on the same property → no change → no update → no focus
@@ -202,7 +212,7 @@ export const textWysiwyg = ({
!isWritableElement(event.target)
) {
editable.onblur = null;
- window.addEventListener("pointerup", rebindBlur);
+ window.addEventListener("pointerup", bindBlurEvent);
// handle edge-case where pointerup doesn't fire e.g. due to user
// alt-tabbing away
window.addEventListener("blur", handleSubmit);
@@ -215,9 +225,14 @@ export const textWysiwyg = ({
editable.focus();
});
+ // ---------------------------------------------------------------------------
+
let isDestroyed = false;
- editable.onblur = handleSubmit;
+ // select on init (focusing is done separately inside the bindBlurEvent()
+ // because we need it to happen *after* the blur event from `pointerdown`)
+ editable.select();
+ bindBlurEvent();
// reposition wysiwyg in case of canvas is resized. Using ResizeObserver
// is preferred so we catch changes from host, where window may not resize.
@@ -239,6 +254,4 @@ export const textWysiwyg = ({
document
.querySelector(".excalidraw-textEditorContainer")!
.appendChild(editable);
- editable.focus();
- editable.select();
};