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:
parent
1ff55475b9
commit
f2e9c73b31
@ -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");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
/>
|
/>
|
||||||
|
@ -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}>
|
||||||
|
34
ts/sveltelib/modal-closing.ts
Normal file
34
ts/sveltelib/modal-closing.ts
Normal 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 };
|
Loading…
Reference in New Issue
Block a user