diff --git a/assets/js/keyboard_control/index.js b/assets/js/keyboard_control/index.js index 4ab986674..85ab45206 100644 --- a/assets/js/keyboard_control/index.js +++ b/assets/js/keyboard_control/index.js @@ -1,5 +1,5 @@ import { getAttributeOrThrow, parseBoolean } from "../lib/attribute"; -import { cancelEvent } from "../lib/utils"; +import { cancelEvent, isEditableElement } from "../lib/utils"; /** * A hook for ControlComponent to handle user keyboard interactions. @@ -30,6 +30,13 @@ const KeyboardControl = { }; window.addEventListener("keyup", this.handleDocumentKeyUp, true); + + this.handleDocumentFocus = (event) => { + handleDocumentFocus(this, event); + }; + + // Note: the focus event doesn't bubble, so we register for the capture phase + window.addEventListener("focus", this.handleDocumentFocus, true); }, updated() { @@ -39,6 +46,7 @@ const KeyboardControl = { destroyed() { window.removeEventListener("keydown", this.handleDocumentKeyDown, true); window.removeEventListener("keyup", this.handleDocumentKeyUp, true); + window.removeEventListener("focus", this.handleDocumentFocus, true); }, }; @@ -84,6 +92,12 @@ function handleDocumentKeyUp(hook, event) { } } +function handleDocumentFocus(hook, event) { + if (hook.props.isKeydownEnabled && isEditableElement(event.target)) { + hook.pushEventTo(hook.props.target, "disable_keyboard", {}); + } +} + function keyboardEnabled(hook) { return hook.props.isKeydownEnabled || hook.props.isKeyupEnabled; } diff --git a/lib/livebook_web/live/output/control_component.ex b/lib/livebook_web/live/output/control_component.ex index fa0620e6b..c6c2f8c05 100644 --- a/lib/livebook_web/live/output/control_component.ex +++ b/lib/livebook_web/live/output/control_component.ex @@ -61,10 +61,19 @@ defmodule LivebookWeb.Output.ControlComponent do @impl true def handle_event("toggle_keyboard", %{}, socket) do socket = update(socket, :keyboard_enabled, ¬/1) + maybe_report_status(socket) + {:noreply, socket} + end - if :status in socket.assigns.attrs.events do - report_event(socket, %{type: :status, enabled: socket.assigns.keyboard_enabled}) - end + def handle_event("disable_keyboard", %{}, socket) do + socket = + if socket.assigns.keyboard_enabled do + socket = assign(socket, keyboard_enabled: false) + maybe_report_status(socket) + socket + else + socket + end {:noreply, socket} end @@ -84,6 +93,12 @@ defmodule LivebookWeb.Output.ControlComponent do {:noreply, socket} end + defp maybe_report_status(socket) do + if :status in socket.assigns.attrs.events do + report_event(socket, %{type: :status, enabled: socket.assigns.keyboard_enabled}) + end + end + defp report_event(socket, attrs) do topic = socket.assigns.attrs.ref event = Map.merge(%{origin: self()}, attrs)