rewrite wysiwyg property updating (#1387)

* rewrite wysiwyg property updating

* reuse existing class

* fix case of focus being stolen by other UIs

* revert mistake csp removal

* ensure we don't run cleanup twice

* fix opacity updating

* add shape actions menu class to constants
This commit is contained in:
David Luzar 2020-04-12 15:57:57 +02:00 committed by GitHub
parent d79c859cd9
commit 6771b505ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 73 additions and 80 deletions

View file

@ -1,6 +1,8 @@
import { KEYS } from "../keys";
import { selectNode } from "../utils";
import { WysiwigElement } from "./types";
import { selectNode, isWritableElement } from "../utils";
import { globalSceneState } from "../scene";
import { isTextElement } from "./typeChecks";
import { CLASSES } from "../constants";
function trimText(text: string) {
// whitespace only → trim all because we'd end up inserting invisible element
@ -14,6 +16,7 @@ function trimText(text: string) {
}
type TextWysiwygParams = {
id: string;
initText: string;
x: number;
y: number;
@ -29,6 +32,7 @@ type TextWysiwygParams = {
};
export function textWysiwyg({
id,
initText,
x,
y,
@ -41,7 +45,7 @@ export function textWysiwyg({
textAlign,
onSubmit,
onCancel,
}: TextWysiwygParams): WysiwigElement {
}: TextWysiwygParams) {
const editable = document.createElement("div");
try {
editable.contentEditable = "plaintext-only";
@ -136,25 +140,74 @@ export function textWysiwyg({
}
function cleanup() {
if (isDestroyed) {
return;
}
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("wheel", stopEvent, true);
window.removeEventListener("pointerdown", onPointerDown);
window.removeEventListener("pointerup", rebindBlur);
window.removeEventListener("blur", handleSubmit);
unbindUpdate();
document.body.removeChild(editable);
}
const rebindBlur = () => {
window.removeEventListener("pointerup", rebindBlur);
// deferred to guard against focus traps on various UIs that steal focus
// upon pointerUp
setTimeout(() => {
editable.onblur = handleSubmit;
// case: clicking on the same property → no change → no update → no focus
editable.focus();
});
};
// prevent blur when changing properties from the menu
const onPointerDown = (event: MouseEvent) => {
if (
event.target instanceof HTMLElement &&
event.target.closest(CLASSES.SHAPE_ACTIONS_MENU) &&
!isWritableElement(event.target)
) {
editable.onblur = null;
window.addEventListener("pointerup", rebindBlur);
// handle edge-case where pointerup doesn't fire e.g. due to user
// alt-tabbing away
window.addEventListener("blur", handleSubmit);
}
};
// 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: editingElement.font,
textAlign: editingElement.textAlign,
color: editingElement.strokeColor,
opacity: editingElement.opacity / 100,
});
}
editable.focus();
});
let isDestroyed = false;
editable.onblur = handleSubmit;
window.addEventListener("pointerdown", onPointerDown);
window.addEventListener("wheel", stopEvent, true);
document.body.appendChild(editable);
editable.focus();
selectNode(editable);
return {
submit: handleSubmit,
changeStyle: (style: any) => {
Object.assign(editable.style, style);
editable.focus();
},
};
}