Skip to content

Commit

Permalink
Add tags_mode option and multiple_select variant
Browse files Browse the repository at this point in the history
The multiple select option for the new tags_mode makes it possible to select
multiple options at once without needing to search or force the dropdown
to appear. It is a kind of hybrid of the search variant and a normal
multi select input input.
  • Loading branch information
ringvold committed Aug 16, 2024
1 parent d1a0f52 commit 691c311
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 6 deletions.
5 changes: 5 additions & 0 deletions lib/live_select.ex
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,11 @@ defmodule LiveSelect do
default: Component.default_opts()[:mode],
doc: "either `:single` (for single selection), or `:tags` (for multiple selection using tags)"

attr :tags_mode, :atom,
values: [:default, :multiple_select],
default: Component.default_opts()[:tags_mode],
doc: "whether to use default tags mode or multi-select tags mode. Multi-select mode enables selecting multiple options without the options dropdown being hidden"

attr :options, :list,
doc:
~s(initial available options to select from. Note that, after the initial rendering of the component, options can only be updated using `Phoenix.LiveView.send_update/3` - See the "Options" section for details)
Expand Down
65 changes: 61 additions & 4 deletions lib/live_select/component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ defmodule LiveSelect.Component do
text_input_extra_class: nil,
text_input_selected_class: nil,
update_min_len: 1,
value: nil
value: nil,
tags_mode: :default
]

@styles [
Expand Down Expand Up @@ -427,6 +428,26 @@ defmodule LiveSelect.Component do

defp maybe_select(%{assigns: %{active_option: -1}} = socket, _extra_params), do: socket

defp maybe_select(
%{assigns: %{active_option: active_option, options: options, selection: selection}} =
socket,
extra_params
)
when active_option >= 0 do
option = Enum.at(socket.assigns.options, active_option)

if already_selected?(option, selection) do
pos = selection_index(option, selection)
unselect(socket, pos)
else
select(socket, Enum.at(socket.assigns.options, socket.assigns.active_option), extra_params)
end
end

defp selection_index(option, selection) do
Enum.find_index(selection, fn %{label: label} -> label == option.label end)
end

defp maybe_select(socket, extra_params) do
select(socket, Enum.at(socket.assigns.options, socket.assigns.active_option), extra_params)
end
Expand All @@ -443,9 +464,10 @@ defmodule LiveSelect.Component do

socket
|> assign(
active_option: -1,
active_option:
if(multi_select_mode?(socket), do: socket.assigns.active_option, else: -1),
selection: selection,
hide_dropdown: true
hide_dropdown: not multi_select_mode?(socket)
)
|> maybe_save_selection()
|> client_select(Map.merge(%{input_event: true}, extra_params))
Expand Down Expand Up @@ -662,10 +684,18 @@ defmodule LiveSelect.Component do

defp encode(value), do: Jason.encode!(value)

defp already_selected?(option, selection) do
def already_selected?(idx, selection) when is_integer(idx) do
Enum.at(selection, idx) != nil
end

def already_selected?(option, selection) do
option.label in Enum.map(selection, & &1.label)
end

defp multi_select_mode?(socket) do
socket.assigns.mode == :tags && socket.assigns.tags_mode == :multiple_select
end

defp next_selectable(%{
selection: selection,
active_option: active_option,
Expand All @@ -674,6 +704,19 @@ defmodule LiveSelect.Component do
when max_selectable > 0 and length(selection) >= max_selectable,
do: active_option

defp next_selectable(%{
options: options,
active_option: active_option,
selection: selection,
tags_mode: :multiple_select
}) do
options
|> Enum.with_index()
|> Enum.reject(fn {opt, _} -> active_option == opt end)
|> Enum.map(fn {_, idx} -> idx end)
|> Enum.find(active_option, &(&1 > active_option))
end

defp next_selectable(%{options: options, active_option: active_option, selection: selection}) do
options
|> Enum.with_index()
Expand All @@ -690,6 +733,20 @@ defmodule LiveSelect.Component do
when max_selectable > 0 and length(selection) >= max_selectable,
do: active_option

defp prev_selectable(%{
options: options,
active_option: active_option,
selection: selection,
tags_mode: :multiple_select
}) do
options
|> Enum.with_index()
|> Enum.reverse()
|> Enum.reject(fn {opt, _} -> active_option == opt end)
|> Enum.map(fn {_, idx} -> idx end)
|> Enum.find(active_option, &(&1 < active_option || active_option == -1))
end

defp prev_selectable(%{options: options, active_option: active_option, selection: selection}) do
options
|> Enum.with_index()
Expand Down
4 changes: 2 additions & 2 deletions lib/live_select/component.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,12 @@
)
)
}
data-idx={unless already_selected?(option, @selection), do: idx}
data-idx={idx}
>
<%= if @option == [] do %>
<%= option.label %>
<% else %>
<%= render_slot(@option, option) %>
<%= render_slot(@option, {option, already_selected?(option, @selection)}) %>
<% end %>
</div>
</li>
Expand Down

0 comments on commit 691c311

Please sign in to comment.