2022-01-08 02:46:01 +01:00
|
|
|
// Copyright: Ankitects Pty Ltd and contributors
|
|
|
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
|
2022-02-04 09:36:34 +01:00
|
|
|
import type { SelectionLocation } from "../domlib/location";
|
|
|
|
import { restoreSelection, saveSelection } from "../domlib/location";
|
2022-01-08 02:46:01 +01:00
|
|
|
import { placeCaretAfterContent } from "../domlib/place-caret";
|
2022-01-16 06:05:35 +01:00
|
|
|
import { bridgeCommand } from "../lib/bridgecommand";
|
2022-02-04 09:36:34 +01:00
|
|
|
import { on, preventDefault } from "../lib/events";
|
|
|
|
import { isApplePlatform } from "../lib/platform";
|
|
|
|
import { registerShortcut } from "../lib/shortcuts";
|
2022-01-08 02:46:01 +01:00
|
|
|
|
2022-01-11 23:39:41 +01:00
|
|
|
function safePlaceCaretAfterContent(editable: HTMLElement): void {
|
|
|
|
/**
|
|
|
|
* Workaround: If you try to invoke an IME after calling
|
|
|
|
* `placeCaretAfterContent` on a cE element, the IME will immediately
|
|
|
|
* end and the input character will be duplicated
|
|
|
|
*/
|
|
|
|
placeCaretAfterContent(editable);
|
|
|
|
restoreSelection(editable, saveSelection(editable)!);
|
|
|
|
}
|
2022-01-08 02:46:01 +01:00
|
|
|
|
2022-01-11 23:39:41 +01:00
|
|
|
function onFocus(location: SelectionLocation | null): () => void {
|
|
|
|
return function (this: HTMLElement): void {
|
|
|
|
if (!location) {
|
|
|
|
safePlaceCaretAfterContent(this);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
restoreSelection(this, location);
|
|
|
|
} catch {
|
|
|
|
safePlaceCaretAfterContent(this);
|
|
|
|
}
|
|
|
|
};
|
2022-01-08 02:46:01 +01:00
|
|
|
}
|
|
|
|
|
2022-01-19 01:17:53 +01:00
|
|
|
interface CustomFocusHandlingAPI {
|
|
|
|
setupFocusHandling(element: HTMLElement): { destroy(): void };
|
|
|
|
flushCaret(): void;
|
2022-01-08 02:46:01 +01:00
|
|
|
}
|
|
|
|
|
2022-01-19 01:17:53 +01:00
|
|
|
export function customFocusHandling(): CustomFocusHandlingAPI {
|
|
|
|
const focusHandlingEvents: (() => void)[] = [];
|
2022-01-08 02:46:01 +01:00
|
|
|
|
2022-01-19 01:17:53 +01:00
|
|
|
function flushEvents(): void {
|
|
|
|
let removeEvent: (() => void) | undefined;
|
|
|
|
|
|
|
|
while ((removeEvent = focusHandlingEvents.pop())) {
|
|
|
|
removeEvent();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function prepareFocusHandling(
|
|
|
|
editable: HTMLElement,
|
|
|
|
latestLocation: SelectionLocation | null = null,
|
|
|
|
): void {
|
|
|
|
const off = on(editable, "focus", onFocus(latestLocation), { once: true });
|
2022-01-11 23:39:41 +01:00
|
|
|
|
2022-01-19 01:17:53 +01:00
|
|
|
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();
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
2022-01-08 02:46:01 +01:00
|
|
|
|
|
|
|
return {
|
2022-01-19 01:17:53 +01:00
|
|
|
setupFocusHandling,
|
|
|
|
flushCaret: flushEvents,
|
2022-01-08 02:46:01 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-01-16 06:05:35 +01:00
|
|
|
if (isApplePlatform()) {
|
|
|
|
registerShortcut(() => bridgeCommand("paste"), "Control+Shift+V");
|
|
|
|
}
|
|
|
|
|
2022-01-08 02:46:01 +01:00
|
|
|
export function preventBuiltinContentEditableShortcuts(editable: HTMLElement): void {
|
|
|
|
for (const keyCombination of ["Control+B", "Control+U", "Control+I", "Control+R"]) {
|
|
|
|
registerShortcut(preventDefault, keyCombination, editable);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** API */
|
|
|
|
|
|
|
|
export interface ContentEditableAPI {
|
2022-01-19 01:17:53 +01:00
|
|
|
/**
|
|
|
|
* 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;
|
2022-01-08 02:46:01 +01:00
|
|
|
}
|