refactor(web): share member-dropdown keyboard navigation between LiveViews
This commit is contained in:
parent
070d9d1fc3
commit
bc1dbb1d11
3 changed files with 89 additions and 119 deletions
83
lib/mv_web/live/member_dropdown_nav.ex
Normal file
83
lib/mv_web/live/member_dropdown_nav.ex
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue