Do not close web pages when Esc is pressed and a modal is open (#2894)

* Prefer key over keyCode

* Do not close TS pages on Esc when floating elements are open

* Close pop-up when Escape is pressed regardless of keepOnKeyup

* Close help modals when Escape is pressed

* Avoid duplicate handling of Esc in WithFloating

* Formatting

* Handle closing of preset management modals

* Reset text input modal to initial value
This commit is contained in:
Abdo 2023-12-21 06:59:52 +03:00 committed by GitHub
parent 1ff55475b9
commit f2e9c73b31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 94 additions and 10 deletions

View File

@ -295,7 +295,7 @@ class AnkiWebView(QWebEngineView):
self.eval( self.eval(
""" """
document.addEventListener("keydown", function(evt) { document.addEventListener("keydown", function(evt) {
if (evt.keyCode === 27) { if (evt.key === "Escape") {
pycmd("close"); pycmd("close");
} }
}); });

View File

@ -7,8 +7,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { renderMarkdown } from "@tslib/helpers"; import { renderMarkdown } from "@tslib/helpers";
import Carousel from "bootstrap/js/dist/carousel"; import Carousel from "bootstrap/js/dist/carousel";
import Modal from "bootstrap/js/dist/modal"; import Modal from "bootstrap/js/dist/modal";
import { createEventDispatcher, getContext, onMount } from "svelte"; import { createEventDispatcher, getContext, onDestroy, onMount } from "svelte";
import { registerModalClosingHandler } from "../sveltelib/modal-closing";
import { pageTheme } from "../sveltelib/theme"; import { pageTheme } from "../sveltelib/theme";
import Badge from "./Badge.svelte"; import Badge from "./Badge.svelte";
import Col from "./Col.svelte"; import Col from "./Col.svelte";
@ -40,8 +41,21 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const { set: setModalOpen, remove: removeModalClosingHandler } =
registerModalClosingHandler(onOkClicked);
function onShown() {
setModalOpen(true);
}
function onHidden() {
setModalOpen(false);
}
onMount(() => { onMount(() => {
modal = new Modal(modalRef); modalRef.addEventListener("shown.bs.modal", onShown);
modalRef.addEventListener("hidden.bs.modal", onHidden);
modal = new Modal(modalRef, { keyboard: false });
carousel = new Carousel(carouselRef, { interval: false, ride: false }); carousel = new Carousel(carouselRef, { interval: false, ride: false });
/* Bootstrap's Carousel.Event interface doesn't seem to work as a type here */ /* Bootstrap's Carousel.Event interface doesn't seem to work as a type here */
carouselRef.addEventListener("slide.bs.carousel", (e: any) => { carouselRef.addEventListener("slide.bs.carousel", (e: any) => {
@ -51,6 +65,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
modals.set(modalKey, modal); modals.set(modalKey, modal);
}); });
onDestroy(() => {
removeModalClosingHandler();
modalRef.removeEventListener("shown.bs.modal", onShown);
modalRef.removeEventListener("hidden.bs.modal", onHidden);
});
let activeIndex = startIndex; let activeIndex = startIndex;
</script> </script>

View File

@ -18,6 +18,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import isClosingKeyup from "../sveltelib/closing-keyup"; import isClosingKeyup from "../sveltelib/closing-keyup";
import type { EventPredicateResult } from "../sveltelib/event-predicate"; import type { EventPredicateResult } from "../sveltelib/event-predicate";
import { documentClick, documentKeyup } from "../sveltelib/event-store"; import { documentClick, documentKeyup } from "../sveltelib/event-store";
import { registerModalClosingHandler } from "../sveltelib/modal-closing";
import portal from "../sveltelib/portal"; import portal from "../sveltelib/portal";
import type { PositioningCallback } from "../sveltelib/position/auto-update"; import type { PositioningCallback } from "../sveltelib/position/auto-update";
import autoUpdate from "../sveltelib/position/auto-update"; import autoUpdate from "../sveltelib/position/auto-update";
@ -54,6 +55,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let arrow: HTMLElement; let arrow: HTMLElement;
const { set: setModalOpen, remove: removeModalClosingHandler } =
registerModalClosingHandler(() => dispatch("close", { reason: "esc" }));
$: positionCurried = positionFloating({ $: positionCurried = positionFloating({
placement, placement,
offset, offset,
@ -124,7 +128,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
) { ) {
cleanup?.(); cleanup?.();
cleanup = null; cleanup = null;
setModalOpen(isShowing);
if (!reference || !floating || !isShowing) { if (!reference || !floating || !isShowing) {
return; return;
} }
@ -164,7 +168,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
); );
} }
cleanup = singleCallback(...subscribers, autoAction.destroy!); cleanup = singleCallback(
...subscribers,
autoAction.destroy!,
removeModalClosingHandler,
);
} }
$: updateFloating(reference, floating, show); $: updateFloating(reference, floating, show);

View File

@ -86,7 +86,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<TextInputModal <TextInputModal
title={modalTitle} title={modalTitle}
prompt={tr.deckConfigNamePrompt()} prompt={tr.deckConfigNamePrompt()}
value={modalStartingValue} initialValue={modalStartingValue}
onOk={modalSuccess} onOk={modalSuccess}
bind:modalKey bind:modalKey
/> />

View File

@ -5,14 +5,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<script lang="ts"> <script lang="ts">
import Modal from "bootstrap/js/dist/modal"; import Modal from "bootstrap/js/dist/modal";
import { getContext, onDestroy, onMount } from "svelte"; import { getContext, onDestroy, onMount } from "svelte";
import { registerModalClosingHandler } from "sveltelib/modal-closing";
import { modalsKey } from "../components/context-keys"; import { modalsKey } from "../components/context-keys";
import { pageTheme } from "../sveltelib/theme"; import { pageTheme } from "../sveltelib/theme";
export let title: string; export let title: string;
export let prompt: string; export let prompt: string;
export let value = ""; export let initialValue = "";
export let onOk: (text: string) => void; export let onOk: (text: string) => void;
$: value = initialValue;
export const modalKey: string = Math.random().toString(36).substring(2); export const modalKey: string = Math.random().toString(36).substring(2);
@ -26,21 +28,37 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
function onOkClicked(): void { function onOkClicked(): void {
onOk(inputRef.value); onOk(inputRef.value);
modal.hide(); modal.hide();
value = ""; value = initialValue;
}
function onCancelClicked(): void {
modal.hide();
value = initialValue;
} }
function onShown(): void { function onShown(): void {
inputRef.focus(); inputRef.focus();
setModalOpen(true);
} }
function onHidden() {
setModalOpen(false);
}
const { set: setModalOpen, remove: removeModalClosingHandler } =
registerModalClosingHandler(onCancelClicked);
onMount(() => { onMount(() => {
modalRef.addEventListener("shown.bs.modal", onShown); modalRef.addEventListener("shown.bs.modal", onShown);
modal = new Modal(modalRef); modalRef.addEventListener("hidden.bs.modal", onHidden);
modal = new Modal(modalRef, { keyboard: false });
modals.set(modalKey, modal); modals.set(modalKey, modal);
}); });
onDestroy(() => { onDestroy(() => {
removeModalClosingHandler();
modalRef.removeEventListener("shown.bs.modal", onShown); modalRef.removeEventListener("shown.bs.modal", onShown);
modalRef.removeEventListener("hidden.bs.modal", onHidden);
}); });
</script> </script>
@ -81,7 +99,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> <button
type="button"
class="btn btn-secondary"
on:click={onCancelClicked}
>
Cancel Cancel
</button> </button>
<button type="button" class="btn btn-primary" on:click={onOkClicked}> <button type="button" class="btn btn-primary" on:click={onOkClicked}>

View File

@ -0,0 +1,34 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { on } from "@tslib/events";
interface ModalClosingHandler {
set: (value: boolean) => void;
remove: () => void;
}
/**
* Register a keydown handler on the document that can optionally stop propagation to other handlers if Escape is pressed and the associated flag is set.
* Intended to override the general handler in webview.py when a modal is open.
*/
function registerModalClosingHandler(callback?: () => void): ModalClosingHandler {
let modalIsOpen = false;
function set(value: boolean) {
modalIsOpen = value;
}
const remove = on(document, "keydown", (event) => {
if (event.key === "Escape" && modalIsOpen) {
event.stopImmediatePropagation();
if (callback) {
callback();
}
}
}, { capture: true });
return { set, remove };
}
export { registerModalClosingHandler };