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
This commit is contained in:
Henrik Giesel 2022-01-19 01:17:53 +01:00 committed by GitHub
parent 0bb04e2951
commit f90895995f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 54 additions and 49 deletions

View file

@ -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 type { Writable } from "svelte/store";
import { updateAllState } from "../components/WithState.svelte"; import { updateAllState } from "../components/WithState.svelte";
import actionList from "../sveltelib/action-list"; import actionList from "../sveltelib/action-list";
import contentEditableAPI, { import {
saveLocation, customFocusHandling,
initialFocusHandling,
preventBuiltinContentEditableShortcuts, preventBuiltinContentEditableShortcuts,
} from "./content-editable"; } from "./content-editable";
import type { ContentEditableAPI } 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<ContentEditableAPI>; export let api: Partial<ContentEditableAPI>;
Object.assign(api, contentEditableAPI); const { setupFocusHandling, flushCaret } = customFocusHandling();
Object.assign(api, { flushCaret });
</script> </script>
<anki-editable <anki-editable
contenteditable="true" contenteditable="true"
use:resolve use:resolve
use:saveLocation use:setupFocusHandling
use:initialFocusHandling
use:preventBuiltinContentEditableShortcuts use:preventBuiltinContentEditableShortcuts
use:mirrorAction={mirrorOptions} use:mirrorAction={mirrorOptions}
use:managerAction={{}} use:managerAction={{}}

View file

@ -9,16 +9,6 @@ import { isApplePlatform } from "../lib/platform";
import { bridgeCommand } from "../lib/bridgecommand"; import { bridgeCommand } from "../lib/bridgecommand";
import type { SelectionLocation } from "../domlib/location"; import type { SelectionLocation } from "../domlib/location";
const locationEvents: (() => void)[] = [];
function flushLocation(): void {
let removeEvent: (() => void) | undefined;
while ((removeEvent = locationEvents.pop())) {
removeEvent();
}
}
function safePlaceCaretAfterContent(editable: HTMLElement): void { function safePlaceCaretAfterContent(editable: HTMLElement): void {
/** /**
* Workaround: If you try to invoke an IME after calling * Workaround: If you try to invoke an IME after calling
@ -44,38 +34,54 @@ function onFocus(location: SelectionLocation | null): () => void {
}; };
} }
function onBlur(this: HTMLElement): void { interface CustomFocusHandlingAPI {
prepareFocusHandling(this, saveSelection(this)); setupFocusHandling(element: HTMLElement): { destroy(): void };
flushCaret(): void;
} }
function prepareFocusHandling( export function customFocusHandling(): CustomFocusHandlingAPI {
const focusHandlingEvents: (() => void)[] = [];
function flushEvents(): void {
let removeEvent: (() => void) | undefined;
while ((removeEvent = focusHandlingEvents.pop())) {
removeEvent();
}
}
function prepareFocusHandling(
editable: HTMLElement, editable: HTMLElement,
latestLocation: SelectionLocation | null = null, latestLocation: SelectionLocation | null = null,
): void { ): void {
const removeOnFocus = on(editable, "focus", onFocus(latestLocation), { const off = on(editable, "focus", onFocus(latestLocation), { once: true });
once: true,
});
locationEvents.push( focusHandlingEvents.push(off, on(editable, "pointerdown", off, { once: true }));
removeOnFocus, }
on(editable, "pointerdown", removeOnFocus, { once: true }),
);
}
export function initialFocusHandling(editable: HTMLElement): void { /**
* Must execute before DOMMirror.
*/
function onBlur(this: HTMLElement): void {
prepareFocusHandling(this, saveSelection(this));
}
function setupFocusHandling(editable: HTMLElement): { destroy(): void } {
prepareFocusHandling(editable); prepareFocusHandling(editable);
} const off = on(editable, "blur", onBlur);
/* Must execute before DOMMirror */
export function saveLocation(editable: HTMLElement): { destroy(): void } {
const removeOnBlur = on(editable, "blur", onBlur);
return { return {
destroy() { destroy() {
removeOnBlur(); flushEvents();
flushLocation(); off();
}, },
}; };
}
return {
setupFocusHandling,
flushCaret: flushEvents,
};
} }
if (isApplePlatform()) { if (isApplePlatform()) {
@ -91,11 +97,10 @@ export function preventBuiltinContentEditableShortcuts(editable: HTMLElement): v
/** API */ /** API */
export interface ContentEditableAPI { 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;

View file

@ -16,7 +16,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { Mathjax } from "../../editable/mathjax-element"; import { Mathjax } from "../../editable/mathjax-element";
const { container, api } = getRichTextInput(); const { container, api } = getRichTextInput();
const { flushLocation, preventResubscription } = api; const { flushCaret, preventResubscription } = api;
const code = writable(""); const code = writable("");
@ -40,7 +40,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let selectAll = false; let selectAll = false;
function placeHandle(after: boolean): void { function placeHandle(after: boolean): void {
flushLocation(); flushCaret();
if (after) { if (after) {
(mathjaxElement as any).placeCaretAfter(); (mathjaxElement as any).placeCaretAfter();