mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Refactor plain/rich text input toggling code; fix focus loss (#2479)
* Refactor plain/rich text input toggling code; fix focus loss Fix: - Issue where field loses focus when plain/rich text input is closed Refactoring: - Call refocus() inside the reactive statement in Plain/RichTextInput.svelte to eliminate the need for polling with requestAnimationFrame - Introduce 'Flag' class - Move 'on:toggle' handlers from inline to functions defined in the <script> section for better readability * Improve code clarity based on feedback from code review - Rename method and add comment to it - Add 'private' access modifier to property
This commit is contained in:
parent
7c225fb5cd
commit
9123821131
4 changed files with 72 additions and 48 deletions
|
@ -317,6 +317,41 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
let plainTextInputs: PlainTextInput[] = [];
|
||||
$: plainTextInputs = plainTextInputs.filter(Boolean);
|
||||
|
||||
function toggleRichTextInput(index: number): void {
|
||||
const hidden = !richTextsHidden[index];
|
||||
richTextInputs[index].focusFlag.setFlag(!hidden);
|
||||
richTextsHidden[index] = hidden;
|
||||
if (hidden) {
|
||||
plainTextInputs[index].api.refocus();
|
||||
}
|
||||
}
|
||||
|
||||
function togglePlainTextInput(index: number): void {
|
||||
const hidden = !plainTextsHidden[index];
|
||||
plainTextInputs[index].focusFlag.setFlag(!hidden);
|
||||
plainTextsHidden[index] = hidden;
|
||||
if (hidden) {
|
||||
richTextInputs[index].api.refocus();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleField(index: number): void {
|
||||
const collapsed = !fieldsCollapsed[index];
|
||||
fieldsCollapsed[index] = collapsed;
|
||||
|
||||
const defaultInput = !plainTextDefaults[index]
|
||||
? richTextInputs[index]
|
||||
: plainTextInputs[index];
|
||||
|
||||
if (!collapsed) {
|
||||
defaultInput.api.refocus();
|
||||
} else if (!plainTextDefaults[index]) {
|
||||
plainTextsHidden[index] = true;
|
||||
} else {
|
||||
richTextsHidden[index] = true;
|
||||
}
|
||||
}
|
||||
|
||||
const toolbar: Partial<EditorToolbarAPI> = {};
|
||||
|
||||
function setShrinkImages(shrinkByDefault: boolean) {
|
||||
|
@ -332,7 +367,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
import { mathjaxConfig } from "../editable/mathjax-element";
|
||||
import CollapseLabel from "./CollapseLabel.svelte";
|
||||
import { refocusInput } from "./helpers";
|
||||
import * as oldEditorAdapter from "./old-editor-adapter";
|
||||
|
||||
onMount(() => {
|
||||
|
@ -457,21 +491,7 @@ the AddCards dialog) should be implemented in the user of this component.
|
|||
<svelte:fragment slot="field-label">
|
||||
<LabelContainer
|
||||
collapsed={fieldsCollapsed[index]}
|
||||
on:toggle={async () => {
|
||||
fieldsCollapsed[index] = !fieldsCollapsed[index];
|
||||
|
||||
const defaultInput = !plainTextDefaults[index]
|
||||
? richTextInputs[index]
|
||||
: plainTextInputs[index];
|
||||
|
||||
if (!fieldsCollapsed[index]) {
|
||||
refocusInput(defaultInput.api);
|
||||
} else if (!plainTextDefaults[index]) {
|
||||
plainTextsHidden[index] = true;
|
||||
} else {
|
||||
richTextsHidden[index] = true;
|
||||
}
|
||||
}}
|
||||
on:toggle={() => toggleField(index)}
|
||||
--icon-align="bottom"
|
||||
>
|
||||
<svelte:fragment slot="field-name">
|
||||
|
@ -489,14 +509,7 @@ the AddCards dialog) should be implemented in the user of this component.
|
|||
(fields[index] === $hoveredField ||
|
||||
fields[index] === $focusedField)}
|
||||
bind:off={richTextsHidden[index]}
|
||||
on:toggle={async () => {
|
||||
richTextsHidden[index] =
|
||||
!richTextsHidden[index];
|
||||
|
||||
if (!richTextsHidden[index]) {
|
||||
refocusInput(richTextInputs[index].api);
|
||||
}
|
||||
}}
|
||||
on:toggle={() => toggleRichTextInput(index)}
|
||||
/>
|
||||
{:else}
|
||||
<PlainTextBadge
|
||||
|
@ -504,14 +517,7 @@ the AddCards dialog) should be implemented in the user of this component.
|
|||
(fields[index] === $hoveredField ||
|
||||
fields[index] === $focusedField)}
|
||||
bind:off={plainTextsHidden[index]}
|
||||
on:toggle={async () => {
|
||||
plainTextsHidden[index] =
|
||||
!plainTextsHidden[index];
|
||||
|
||||
if (!plainTextsHidden[index]) {
|
||||
refocusInput(plainTextInputs[index].api);
|
||||
}
|
||||
}}
|
||||
on:toggle={() => togglePlainTextInput(index)}
|
||||
/>
|
||||
{/if}
|
||||
<slot
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
import type { PlainTextInputAPI } from "./plain-text-input";
|
||||
import type { RichTextInputAPI } from "./rich-text-input";
|
||||
|
||||
function isFontElement(element: Element): element is HTMLFontElement {
|
||||
return element.tagName === "FONT";
|
||||
}
|
||||
|
@ -23,14 +20,21 @@ export function withFontColor(
|
|||
return false;
|
||||
}
|
||||
|
||||
/***
|
||||
* Required for field inputs wrapped in Collapsible
|
||||
*/
|
||||
export async function refocusInput(
|
||||
api: RichTextInputAPI | PlainTextInputAPI,
|
||||
): Promise<void> {
|
||||
do {
|
||||
await new Promise(window.requestAnimationFrame);
|
||||
} while (!api.focusable);
|
||||
api.refocus();
|
||||
export class Flag {
|
||||
private flag: boolean;
|
||||
|
||||
constructor() {
|
||||
this.flag = false;
|
||||
}
|
||||
|
||||
setFlag(on: boolean): void {
|
||||
this.flag = on;
|
||||
}
|
||||
|
||||
/** Resets the flag to false and returns the previous value. */
|
||||
checkAndReset(): boolean {
|
||||
const val = this.flag;
|
||||
this.flag = false;
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,11 +37,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
import { baseOptions, gutterOptions, htmlanki } from "../code-mirror";
|
||||
import CodeMirror from "../CodeMirror.svelte";
|
||||
import { context as editingAreaContext } from "../EditingArea.svelte";
|
||||
import { Flag } from "../helpers";
|
||||
import { context as noteEditorContext } from "../NoteEditor.svelte";
|
||||
import removeProhibitedTags from "./remove-prohibited";
|
||||
import { storedToUndecorated, undecoratedToStored } from "./transform";
|
||||
|
||||
export let hidden = false;
|
||||
export const focusFlag = new Flag();
|
||||
|
||||
$: configuration = {
|
||||
mode: htmlanki,
|
||||
|
@ -115,7 +117,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
$: {
|
||||
pushUpdate(!hidden);
|
||||
tick().then(refresh);
|
||||
tick().then(() => {
|
||||
refresh();
|
||||
if (focusFlag.checkAndReset()) {
|
||||
refocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onChange({ detail: html }: CustomEvent<string>): void {
|
||||
|
|
|
@ -64,7 +64,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
import { directionKey, fontFamilyKey, fontSizeKey } from "@tslib/context-keys";
|
||||
import { promiseWithResolver } from "@tslib/promise";
|
||||
import { singleCallback } from "@tslib/typing";
|
||||
import { getAllContexts, getContext, onMount } from "svelte";
|
||||
import { getAllContexts, getContext, onMount, tick } from "svelte";
|
||||
import type { Readable } from "svelte/store";
|
||||
|
||||
import { placeCaretAfterContent } from "../../domlib/place-caret";
|
||||
|
@ -73,6 +73,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
import useInputHandler from "../../sveltelib/input-handler";
|
||||
import { pageTheme } from "../../sveltelib/theme";
|
||||
import { context as editingAreaContext } from "../EditingArea.svelte";
|
||||
import { Flag } from "../helpers";
|
||||
import { context as noteEditorContext } from "../NoteEditor.svelte";
|
||||
import getNormalizingNodeStore from "./normalizing-node-store";
|
||||
import useRichTextResolve from "./rich-text-resolve";
|
||||
|
@ -80,6 +81,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
import { fragmentToStored, storedToFragment } from "./transform";
|
||||
|
||||
export let hidden = false;
|
||||
export const focusFlag = new Flag();
|
||||
|
||||
const { focusedInput } = noteEditorContext.get();
|
||||
const { content, editingInputs } = editingAreaContext.get();
|
||||
|
@ -192,7 +194,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
$apiStore = null;
|
||||
}
|
||||
|
||||
$: pushUpdate(!hidden);
|
||||
$: {
|
||||
pushUpdate(!hidden);
|
||||
if (focusFlag.checkAndReset()) {
|
||||
tick().then(refocus);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
$editingInputs.push(api);
|
||||
|
|
Loading…
Reference in a new issue