fix: stop preventing canvas pointerdown/tapend events (#3207)

This commit is contained in:
David Luzar 2021-03-16 18:04:53 +01:00 committed by GitHub
parent edc62c550a
commit e90e56452f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 36 additions and 26 deletions

View File

@ -112,8 +112,7 @@
Roboto, Helvetica, Arial, sans-serif; Roboto, Helvetica, Arial, sans-serif;
font-family: var(--ui-font); font-family: var(--ui-font);
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
-webkit-user-select: none;
user-select: none;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
} }

View File

@ -1105,7 +1105,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
}; };
private onTapEnd = (event: TouchEvent) => { private onTapEnd = (event: TouchEvent) => {
event.preventDefault();
if (event.touches.length > 0) { if (event.touches.length > 0) {
this.setState({ this.setState({
previousSelectedElementIds: {}, previousSelectedElementIds: {},
@ -1631,10 +1630,12 @@ class App extends React.Component<ExcalidrawProps, AppState> {
updateBoundElements(element); updateBoundElements(element);
} }
}), }),
onSubmit: withBatchedUpdates((text) => { onSubmit: withBatchedUpdates(({ text, viaKeyboard }) => {
const isDeleted = !text.trim(); const isDeleted = !text.trim();
updateElement(text, isDeleted); 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) => ({ this.setState((prevState) => ({
selectedElementIds: { selectedElementIds: {
...prevState.selectedElementIds, ...prevState.selectedElementIds,
@ -2151,15 +2152,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
this.updateGestureOnPointerDown(event); 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 // don't select while panning
if (gesture.pointers.size > 1) { if (gesture.pointers.size > 1) {
return; return;

View File

@ -17,6 +17,13 @@
left: 0; left: 0;
right: 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 { a {
font-weight: 500; font-weight: 500;
text-decoration: none; text-decoration: none;
@ -29,7 +36,6 @@
canvas { canvas {
touch-action: none; touch-action: none;
user-select: none;
// following props improve blurriness at certain devicePixelRatios. // following props improve blurriness at certain devicePixelRatios.
// AFAIK it doesn't affect export (in fact, export seems sharp either way). // AFAIK it doesn't affect export (in fact, export seems sharp either way).

View File

@ -47,7 +47,7 @@ export const textWysiwyg = ({
id: ExcalidrawElement["id"]; id: ExcalidrawElement["id"];
appState: AppState; appState: AppState;
onChange?: (text: string) => void; onChange?: (text: string) => void;
onSubmit: (text: string) => void; onSubmit: (data: { text: string; viaKeyboard: boolean }) => void;
getViewportCoords: (x: number, y: number) => [number, number]; getViewportCoords: (x: number, y: number) => [number, number];
element: ExcalidrawElement; element: ExcalidrawElement;
canvas: HTMLCanvasElement | null; canvas: HTMLCanvasElement | null;
@ -136,12 +136,14 @@ export const textWysiwyg = ({
editable.onkeydown = (event) => { editable.onkeydown = (event) => {
if (event.key === KEYS.ESCAPE) { if (event.key === KEYS.ESCAPE) {
event.preventDefault(); event.preventDefault();
submittedViaKeyboard = true;
handleSubmit(); handleSubmit();
} else if (event.key === KEYS.ENTER && event[KEYS.CTRL_OR_CMD]) { } else if (event.key === KEYS.ENTER && event[KEYS.CTRL_OR_CMD]) {
event.preventDefault(); event.preventDefault();
if (event.isComposing || event.keyCode === 229) { if (event.isComposing || event.keyCode === 229) {
return; return;
} }
submittedViaKeyboard = true;
handleSubmit(); handleSubmit();
} else if (event.key === KEYS.ENTER && !event.altKey) { } else if (event.key === KEYS.ENTER && !event.altKey) {
event.stopPropagation(); event.stopPropagation();
@ -153,8 +155,14 @@ export const textWysiwyg = ({
event.stopPropagation(); 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 = () => { const handleSubmit = () => {
onSubmit(normalizeText(editable.value)); onSubmit({
text: normalizeText(editable.value),
viaKeyboard: submittedViaKeyboard,
});
cleanup(); cleanup();
}; };
@ -175,7 +183,7 @@ export const textWysiwyg = ({
window.removeEventListener("resize", updateWysiwygStyle); window.removeEventListener("resize", updateWysiwygStyle);
window.removeEventListener("wheel", stopEvent, true); window.removeEventListener("wheel", stopEvent, true);
window.removeEventListener("pointerdown", onPointerDown); window.removeEventListener("pointerdown", onPointerDown);
window.removeEventListener("pointerup", rebindBlur); window.removeEventListener("pointerup", bindBlurEvent);
window.removeEventListener("blur", handleSubmit); window.removeEventListener("blur", handleSubmit);
unbindUpdate(); unbindUpdate();
@ -183,10 +191,12 @@ export const textWysiwyg = ({
editable.remove(); editable.remove();
}; };
const rebindBlur = () => { const bindBlurEvent = () => {
window.removeEventListener("pointerup", rebindBlur); window.removeEventListener("pointerup", bindBlurEvent);
// deferred to guard against focus traps on various UIs that steal focus // Deferred so that the pointerdown that initiates the wysiwyg doesn't
// upon pointerUp // 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(() => { setTimeout(() => {
editable.onblur = handleSubmit; editable.onblur = handleSubmit;
// case: clicking on the same property → no change → no update → no focus // case: clicking on the same property → no change → no update → no focus
@ -202,7 +212,7 @@ export const textWysiwyg = ({
!isWritableElement(event.target) !isWritableElement(event.target)
) { ) {
editable.onblur = null; editable.onblur = null;
window.addEventListener("pointerup", rebindBlur); window.addEventListener("pointerup", bindBlurEvent);
// handle edge-case where pointerup doesn't fire e.g. due to user // handle edge-case where pointerup doesn't fire e.g. due to user
// alt-tabbing away // alt-tabbing away
window.addEventListener("blur", handleSubmit); window.addEventListener("blur", handleSubmit);
@ -215,9 +225,14 @@ export const textWysiwyg = ({
editable.focus(); editable.focus();
}); });
// ---------------------------------------------------------------------------
let isDestroyed = false; 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 // reposition wysiwyg in case of canvas is resized. Using ResizeObserver
// is preferred so we catch changes from host, where window may not resize. // is preferred so we catch changes from host, where window may not resize.
@ -239,6 +254,4 @@ export const textWysiwyg = ({
document document
.querySelector(".excalidraw-textEditorContainer")! .querySelector(".excalidraw-textEditorContainer")!
.appendChild(editable); .appendChild(editable);
editable.focus();
editable.select();
}; };