diff --git a/frontend/components/Editor.js b/frontend/components/Editor.js index 0cbaf8045e..5c7ae43cfe 100644 --- a/frontend/components/Editor.js +++ b/frontend/components/Editor.js @@ -868,6 +868,7 @@ patch: ${JSON.stringify( }} >Stay here`, + should_focus: true, }) } } diff --git a/frontend/components/PkgStatusMark.js b/frontend/components/PkgStatusMark.js index ce52e99659..fd2eae561d 100644 --- a/frontend/components/PkgStatusMark.js +++ b/frontend/components/PkgStatusMark.js @@ -139,6 +139,7 @@ export const PkgStatusMark = ({ package_name, pluto_actions, notebook_id, nbpkg source_element: event.currentTarget.parentElement, package_name: package_name, is_disable_pkg: false, + should_focus: true, }) }} > @@ -165,6 +166,7 @@ export const PkgActivateMark = ({ package_name }) => { source_element: event.currentTarget.parentElement, package_name: package_name, is_disable_pkg: true, + should_focus: true, }) }} > diff --git a/frontend/components/PkgTerminalView.js b/frontend/components/PkgTerminalView.js index 070806342d..14f8e6277f 100644 --- a/frontend/components/PkgTerminalView.js +++ b/frontend/components/PkgTerminalView.js @@ -13,7 +13,7 @@ const TerminalViewAnsiUp = ({ value }) => { return !!value ? html`
` : null } diff --git a/frontend/components/Popup.js b/frontend/components/Popup.js index 59b5acb8f8..e49b895828 100644 --- a/frontend/components/Popup.js +++ b/frontend/components/Popup.js @@ -1,4 +1,4 @@ -import { html, useState, useRef, useEffect, useContext, useCallback } from "../imports/Preact.js" +import { html, useState, useRef, useEffect, useContext, useCallback, useLayoutEffect } from "../imports/Preact.js" import { cl } from "../common/ClassTable.js" import { PlutoActionsContext } from "../common/PlutoContext.js" @@ -21,6 +21,7 @@ export const help_circle_icon = new URL("https://cdn.jsdelivr.net/gh/ionic-team/ * @property {"nbpkg"} type * @property {HTMLElement} [source_element] * @property {Boolean} [big] + * @property {Boolean} [should_focus] Should the popup receive keyboard focus after opening? Rule of thumb: yes if the popup opens on a click, no if it opens spontaneously. * @property {string} package_name * @property {boolean} is_disable_pkg */ @@ -31,6 +32,7 @@ export const help_circle_icon = new URL("https://cdn.jsdelivr.net/gh/ionic-team/ * @property {import("../imports/Preact.js").ReactElement} body * @property {HTMLElement?} [source_element] * @property {Boolean} [big] + * @property {Boolean} [should_focus] Should the popup receive keyboard focus after opening? Rule of thumb: yes if the popup opens on a click, no if it opens spontaneously. */ export const Popup = ({ notebook, disable_input }) => { @@ -87,26 +89,75 @@ export const Popup = ({ notebook, disable_input }) => { [close] ) + // focus the popup when it opens + const element_focused_before_popup = useRef(/** @type {any} */ (null)) + useLayoutEffect(() => { + if (recent_event != null) { + console.log(recent_event) + if (recent_event.should_focus === true) { + console.log(element_ref.current?.querySelector("a") ?? element_ref.current) + requestAnimationFrame(() => { + element_focused_before_popup.current = document.activeElement + ;(element_ref.current?.querySelector("a") ?? element_ref.current)?.focus?.() + }) + } else { + element_focused_before_popup.current = null + } + } + }, [recent_event != null]) + + const element_ref = useRef(/** @type {HTMLElement?} */ (null)) + + // if the popup was focused on opening: + // when the popup loses focus (and the focus did not move to the source element): + // 1. close the popup + // 2. return focus to the element that was focused before the popup opened + useEventListener( + element_ref.current, + "focusout", + (e) => { + if (recent_event_ref.current != null && recent_event_ref.current.should_focus === true) { + if (element_ref.current?.matches(":focus-within")) return + if ( + recent_source_element_ref.current != null && + (recent_source_element_ref.current.contains(e.relatedTarget) || recent_source_element_ref.current.matches(":focus-within")) + ) + return + close() + e.preventDefault() + element_focused_before_popup.current?.focus?.() + } + }, + [close] + ) + const type = recent_event?.type return html` - ${type === "nbpkg" - ? html`<${PkgPopup} - notebook=${notebook} - disable_input=${disable_input} - recent_event=${recent_event} - clear_recent_event=${() => set_recent_event(null)} - />` - : type === "info" || type === "warn" - ? html`
${recent_event?.body}
` - : null} -
` + class=${cl({ + visible: recent_event != null, + [type ?? ""]: type != null, + big: recent_event?.big === true, + })} + style="${pos_ref.current}" + ref=${element_ref} + tabindex=${ + "0" /* this makes the popup itself focusable (not just its buttons), just like a element. It also makes the `.matches(":focus-within")` trick work. */ + } + > + ${type === "nbpkg" + ? html`<${PkgPopup} + notebook=${notebook} + disable_input=${disable_input} + recent_event=${recent_event} + clear_recent_event=${() => set_recent_event(null)} + />` + : type === "info" || type === "warn" + ? html`
${recent_event?.body}
` + : null} + +
+ +
` } /** @@ -187,26 +238,27 @@ const PkgPopup = ({ notebook, recent_event, clear_recent_event, disable_input }) ` : null}
- { - if (busy) { - alert("Pkg is currently busy with other packages... come back later!") - } else { - if (confirm("Would you like to check for updates and install them? A backup of the notebook file will be created.")) { - console.warn("Pkg.updating!") - pluto_actions.send("pkg_update", {}, { notebook_id: notebook.notebook_id }) - } - } - e.preventDefault() - }} - >⬆️ + ${recent_event?.is_disable_pkg || disable_input || notebook.nbpkg?.waiting_for_permission + ? null + : html` { + if (busy) { + alert("Pkg is currently busy with other packages... come back later!") + } else { + if (confirm("Would you like to check for updates and install them? A backup of the notebook file will be created.")) { + console.warn("Pkg.updating!") + pluto_actions.send("pkg_update", {}, { notebook_id: notebook.notebook_id }) + } + } + e.preventDefault() + }} + >⬆️`} Safe preview

You are reading and editing this file without running Julia code.