Move caretToEnd logic out of focus handling

This commit is contained in:
Henrik Giesel 2021-03-08 20:40:23 +01:00
parent 6db7897601
commit 972993b42e
5 changed files with 36 additions and 45 deletions

View file

@ -6,16 +6,12 @@ import type { EditingArea } from ".";
import { getCurrentField } from "."; import { getCurrentField } from ".";
import { bridgeCommand } from "./lib"; import { bridgeCommand } from "./lib";
import { getNoteId } from "./noteId"; import { getNoteId } from "./noteId";
import { updateButtonState } from "./toolbar";
let changeTimer: number | null = null; let changeTimer: number | null = null;
export function triggerChangeTimer(currentField: EditingArea): void { export function triggerChangeTimer(currentField: EditingArea): void {
clearChangeTimer(); clearChangeTimer();
changeTimer = setTimeout(function () { changeTimer = setTimeout(() => saveField(currentField, "key"), 600);
updateButtonState();
saveField(currentField, "key");
}, 600);
} }
function clearChangeTimer(): void { function clearChangeTimer(): void {

View file

@ -1,55 +1,23 @@
/* Copyright: Ankitects Pty Ltd and contributors /* Copyright: Ankitects Pty Ltd and contributors
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
import { EditingArea } from "."; import type { EditingArea } from ".";
import { bridgeCommand } from "./lib"; import { bridgeCommand } from "./lib";
import { enableButtons, disableButtons } from "./toolbar"; import { enableButtons, disableButtons } from "./toolbar";
import { saveField } from "./changeTimer"; import { saveField } from "./changeTimer";
function caretToEnd(currentField: EditingArea): void {
const range = document.createRange();
range.selectNodeContents(currentField.editable);
range.collapse(false);
const selection = currentField.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
function focusField(field: EditingArea, moveCaretToEnd: boolean): void {
field.focusEditable();
bridgeCommand(`focus:${field.ord}`);
if (moveCaretToEnd) {
caretToEnd(field);
}
enableButtons();
}
// For distinguishing focus by refocusing window from deliberate focus
let previousActiveElement: EditingArea | null = null;
export function onFocus(evt: FocusEvent): void { export function onFocus(evt: FocusEvent): void {
const currentField = evt.currentTarget as EditingArea; const currentField = evt.currentTarget as EditingArea;
const previousFocus = evt.relatedTarget as EditingArea; currentField.focusEditable();
bridgeCommand(`focus:${currentField.ord}`);
if ( enableButtons();
!(previousFocus instanceof EditingArea) ||
previousFocus === previousActiveElement
) {
const moveCaretToEnd =
Boolean(previousFocus) || !Boolean(previousActiveElement);
focusField(currentField, moveCaretToEnd);
}
} }
export function onBlur(evt: FocusEvent): void { export function onBlur(evt: FocusEvent): void {
const previousFocus = evt.currentTarget as EditingArea; const previousFocus = evt.currentTarget as EditingArea;
const currentFieldUnchanged = previousFocus === document.activeElement;
saveField(previousFocus, previousFocus === document.activeElement ? "key" : "blur"); saveField(previousFocus, currentFieldUnchanged ? "key" : "blur");
// other widget or window focused; current field unchanged
previousActiveElement = previousFocus;
disableButtons(); disableButtons();
} }

View file

@ -1,6 +1,8 @@
/* Copyright: Ankitects Pty Ltd and contributors /* Copyright: Ankitects Pty Ltd and contributors
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
import type { EditingArea } from ".";
export function nodeIsElement(node: Node): node is Element { export function nodeIsElement(node: Node): node is Element {
return node.nodeType === Node.ELEMENT_NODE; return node.nodeType === Node.ELEMENT_NODE;
} }
@ -66,3 +68,12 @@ const INLINE_TAGS = [
export function nodeIsInline(node: Node): boolean { export function nodeIsInline(node: Node): boolean {
return !nodeIsElement(node) || INLINE_TAGS.includes(node.tagName); return !nodeIsElement(node) || INLINE_TAGS.includes(node.tagName);
} }
export function caretToEnd(currentField: EditingArea): void {
const range = document.createRange();
range.selectNodeContents(currentField.editable);
range.collapse(false);
const selection = currentField.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}

View file

@ -1,7 +1,7 @@
/* Copyright: Ankitects Pty Ltd and contributors /* Copyright: Ankitects Pty Ltd and contributors
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
import { nodeIsInline } from "./helpers"; import { nodeIsInline, caretToEnd } from "./helpers";
import { bridgeCommand } from "./lib"; import { bridgeCommand } from "./lib";
import { saveField } from "./changeTimer"; import { saveField } from "./changeTimer";
import { filterHTML } from "./htmlFilter"; import { filterHTML } from "./htmlFilter";
@ -34,6 +34,8 @@ export function focusField(n: number): void {
if (field) { if (field) {
field.editingArea.focusEditable(); field.editingArea.focusEditable();
caretToEnd(field.editingArea);
updateButtonState();
} }
} }

View file

@ -2,8 +2,9 @@
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
import { EditingArea } from "."; import { EditingArea } from ".";
import { nodeIsElement } from "./helpers"; import { caretToEnd, nodeIsElement } from "./helpers";
import { triggerChangeTimer } from "./changeTimer"; import { triggerChangeTimer } from "./changeTimer";
import { updateButtonState } from "./toolbar";
function inListItem(currentField: EditingArea): boolean { function inListItem(currentField: EditingArea): boolean {
const anchor = currentField.getSelection()!.anchorNode!; const anchor = currentField.getSelection()!.anchorNode!;
@ -21,6 +22,7 @@ function inListItem(currentField: EditingArea): boolean {
export function onInput(event: Event): void { export function onInput(event: Event): void {
// make sure IME changes get saved // make sure IME changes get saved
triggerChangeTimer(event.currentTarget as EditingArea); triggerChangeTimer(event.currentTarget as EditingArea);
updateButtonState();
} }
export function onKey(evt: KeyboardEvent): void { export function onKey(evt: KeyboardEvent): void {
@ -59,6 +61,18 @@ export function onKey(evt: KeyboardEvent): void {
triggerChangeTimer(currentField); triggerChangeTimer(currentField);
} }
globalThis.addEventListener("keydown", (evt: KeyboardEvent) => {
if (evt.code === "Tab") {
globalThis.addEventListener("focusin", (evt: FocusEvent) => {
const newFocusTarget = evt.target;
if (newFocusTarget instanceof EditingArea) {
caretToEnd(newFocusTarget);
updateButtonState();
}
}, { once: true })
}
})
export function onKeyUp(evt: KeyboardEvent): void { export function onKeyUp(evt: KeyboardEvent): void {
const currentField = evt.currentTarget as EditingArea; const currentField = evt.currentTarget as EditingArea;