defmodule MvWeb.Live.MemberDropdownNav do @moduledoc """ Shared keyboard-navigation logic for the member-search dropdown used by the user form and the group show LiveViews. Both views keep their own `member_dropdown_keydown` event entry point and their own `select_focused_member/1` (the selection effect differs per view), but the arrow/enter/escape navigation over `:focused_member_index` and the dropdown-open guard are identical and live here. The caller passes a zero-arity `select_focused` callback that performs the view-specific selection of the currently focused entry. """ import Phoenix.Component, only: [assign: 2] @type socket :: Phoenix.LiveView.Socket.t() @type result :: {:noreply, socket} @doc """ Handles a `member_dropdown_keydown` event for the shared dropdown. Navigation keys move `:focused_member_index` within `[0, length(available_members) - 1]`; Enter invokes the view-specific `select_focused` callback; Escape closes the dropdown; any other key is a no-op. All key handling is guarded so that keystrokes while the dropdown is closed are ignored. """ @spec handle_keydown(map(), socket, (-> result)) :: result def handle_keydown(%{"key" => "ArrowDown"}, socket, _select_focused) do return_if_dropdown_closed(socket, fn -> max_index = length(socket.assigns.available_members) - 1 current = socket.assigns.focused_member_index new_index = case current do nil -> 0 index when index < max_index -> index + 1 _ -> current end {:noreply, assign(socket, focused_member_index: new_index)} end) end def handle_keydown(%{"key" => "ArrowUp"}, socket, _select_focused) do return_if_dropdown_closed(socket, fn -> current = socket.assigns.focused_member_index new_index = case current do nil -> 0 0 -> 0 index -> index - 1 end {:noreply, assign(socket, focused_member_index: new_index)} end) end def handle_keydown(%{"key" => "Enter"}, socket, select_focused) do return_if_dropdown_closed(socket, select_focused) end def handle_keydown(%{"key" => "Escape"}, socket, _select_focused) do return_if_dropdown_closed(socket, fn -> {:noreply, assign(socket, show_member_dropdown: false, focused_member_index: nil)} end) end def handle_keydown(_params, socket, _select_focused) do # Ignore other keys {:noreply, socket} end defp return_if_dropdown_closed(socket, func) do if socket.assigns.show_member_dropdown do func.() else {:noreply, socket} end end end