From f90895995f8a38399067a8584b695c44023b3173 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 19 Jan 2022 01:17:53 +0100 Subject: [PATCH] Fix the caret flushing behavior when moving out of a Mathjax element (#1605) * Fix the caret flushing behavior when moving out of a Mathjax element * Fix typing issue and add docs for ContentEditableAPI --- ts/editable/ContentEditable.svelte | 12 +-- ts/editable/content-editable.ts | 87 ++++++++++--------- .../mathjax-overlay/MathjaxHandle.svelte | 4 +- 3 files changed, 54 insertions(+), 49 deletions(-) diff --git a/ts/editable/ContentEditable.svelte b/ts/editable/ContentEditable.svelte index d1a6bb02b..3931b47a7 100644 --- a/ts/editable/ContentEditable.svelte +++ b/ts/editable/ContentEditable.svelte @@ -10,9 +10,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import type { Writable } from "svelte/store"; import { updateAllState } from "../components/WithState.svelte"; import actionList from "../sveltelib/action-list"; - import contentEditableAPI, { - saveLocation, - initialFocusHandling, + import { + customFocusHandling, preventBuiltinContentEditableShortcuts, } from "./content-editable"; import type { ContentEditableAPI } from "./content-editable"; @@ -33,14 +32,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html export let api: Partial; - Object.assign(api, contentEditableAPI); + const { setupFocusHandling, flushCaret } = customFocusHandling(); + + Object.assign(api, { flushCaret }); void)[] = []; - -function flushLocation(): void { - let removeEvent: (() => void) | undefined; - - while ((removeEvent = locationEvents.pop())) { - removeEvent(); - } -} - function safePlaceCaretAfterContent(editable: HTMLElement): void { /** * Workaround: If you try to invoke an IME after calling @@ -44,37 +34,53 @@ function onFocus(location: SelectionLocation | null): () => void { }; } -function onBlur(this: HTMLElement): void { - prepareFocusHandling(this, saveSelection(this)); +interface CustomFocusHandlingAPI { + setupFocusHandling(element: HTMLElement): { destroy(): void }; + flushCaret(): void; } -function prepareFocusHandling( - editable: HTMLElement, - latestLocation: SelectionLocation | null = null, -): void { - const removeOnFocus = on(editable, "focus", onFocus(latestLocation), { - once: true, - }); +export function customFocusHandling(): CustomFocusHandlingAPI { + const focusHandlingEvents: (() => void)[] = []; - locationEvents.push( - removeOnFocus, - on(editable, "pointerdown", removeOnFocus, { once: true }), - ); -} + function flushEvents(): void { + let removeEvent: (() => void) | undefined; -export function initialFocusHandling(editable: HTMLElement): void { - prepareFocusHandling(editable); -} + while ((removeEvent = focusHandlingEvents.pop())) { + removeEvent(); + } + } -/* Must execute before DOMMirror */ -export function saveLocation(editable: HTMLElement): { destroy(): void } { - const removeOnBlur = on(editable, "blur", onBlur); + function prepareFocusHandling( + editable: HTMLElement, + latestLocation: SelectionLocation | null = null, + ): void { + const off = on(editable, "focus", onFocus(latestLocation), { once: true }); + + focusHandlingEvents.push(off, on(editable, "pointerdown", off, { once: true })); + } + + /** + * Must execute before DOMMirror. + */ + function onBlur(this: HTMLElement): void { + prepareFocusHandling(this, saveSelection(this)); + } + + function setupFocusHandling(editable: HTMLElement): { destroy(): void } { + prepareFocusHandling(editable); + const off = on(editable, "blur", onBlur); + + return { + destroy() { + flushEvents(); + off(); + }, + }; + } return { - destroy() { - removeOnBlur(); - flushLocation(); - }, + setupFocusHandling, + flushCaret: flushEvents, }; } @@ -91,11 +97,10 @@ export function preventBuiltinContentEditableShortcuts(editable: HTMLElement): v /** API */ export interface ContentEditableAPI { - flushLocation(): void; + /** + * Can be used to turn off the caret restoring functionality of + * the ContentEditable. Can be used when you want to set the caret + * yourself. + */ + flushCaret(): void; } - -const contentEditableApi: ContentEditableAPI = { - flushLocation, -}; - -export default contentEditableApi; diff --git a/ts/editor/mathjax-overlay/MathjaxHandle.svelte b/ts/editor/mathjax-overlay/MathjaxHandle.svelte index 28e10e3a3..cfcd117ae 100644 --- a/ts/editor/mathjax-overlay/MathjaxHandle.svelte +++ b/ts/editor/mathjax-overlay/MathjaxHandle.svelte @@ -16,7 +16,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { Mathjax } from "../../editable/mathjax-element"; const { container, api } = getRichTextInput(); - const { flushLocation, preventResubscription } = api; + const { flushCaret, preventResubscription } = api; const code = writable(""); @@ -40,7 +40,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html let selectAll = false; function placeHandle(after: boolean): void { - flushLocation(); + flushCaret(); if (after) { (mathjaxElement as any).placeCaretAfter();