diff --git a/lib/live_select.ex b/lib/live_select.ex index cca59d1..dc47185 100644 --- a/lib/live_select.ex +++ b/lib/live_select.ex @@ -370,14 +370,10 @@ defmodule LiveSelect do ~S(an id to assign to the component. If none is provided, `#{form_name}_#{field}_live_select_component` will be used) attr :mode, :atom, - values: [:single, :tags], + values: [:single, :tags, :quick_tags], 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" + doc: + "either `:single` (for single selection), `:tags` (for multiple selection using tags), or :quick_tags (tags mode but can select multiple at a time)" attr :options, :list, doc: diff --git a/lib/live_select/component.ex b/lib/live_select/component.ex index 44cfee8..3e93c32 100644 --- a/lib/live_select/component.ex +++ b/lib/live_select/component.ex @@ -41,8 +41,7 @@ defmodule LiveSelect.Component do text_input_extra_class: nil, text_input_selected_class: nil, update_min_len: 1, - value: nil, - tags_mode: :default + value: nil ] @styles [ @@ -79,7 +78,7 @@ defmodule LiveSelect.Component do none: [] ] - @modes ~w(single tags)a + @modes ~w(single tags quick_tags)a @impl true def mount(socket) do @@ -199,11 +198,7 @@ defmodule LiveSelect.Component do &if &1.assigns.mode == :single do clear(&1, %{input_event: false, parent_event: &1.assigns[:"phx-focus"]}) else - if &1.assigns.tags_mode == :multiple_select do - &1 - else - parent_event(&1, &1.assigns[:"phx-focus"], %{id: &1.assigns.id}) - end + parent_event(&1, &1.assigns[:"phx-focus"], %{id: &1.assigns.id}) end ) |> assign(hide_dropdown: false) @@ -441,36 +436,39 @@ defmodule LiveSelect.Component do option = Enum.at(socket.assigns.options, active_option) if already_selected?(option, selection) do - pos = selection_index(option, selection) + pos = get_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 + defp get_selection_index(option, selection) do + Enum.find_index(selection, fn %{label: label} -> label == option.label end) + end + defp select(socket, selected, extra_params) do selection = case socket.assigns.mode do :tags -> socket.assigns.selection ++ [selected] + :quick_tags -> + socket.assigns.selection ++ [selected] + _ -> [selected] end socket |> assign( - active_option: if(multi_select_mode?(socket), do: socket.assigns.active_option, else: -1), + active_option: if(quick_tags_mode?(socket), do: socket.assigns.active_option, else: -1), selection: selection, - hide_dropdown: not multi_select_mode?(socket) + hide_dropdown: not quick_tags_mode?(socket) ) |> maybe_save_selection() |> client_select(Map.merge(%{input_event: true}, extra_params)) @@ -544,7 +542,7 @@ defmodule LiveSelect.Component do List.wrap(normalize_selection_value(value, options ++ current_selection, value_mapper)) end - defp update_selection(value, current_selection, options, :tags, value_mapper) do + defp update_selection(value, current_selection, options, _mode, value_mapper) do value = if Enumerable.impl_for(value), do: value, else: [value] Enum.map(value, &normalize_selection_value(&1, options ++ current_selection, value_mapper)) @@ -695,8 +693,8 @@ defmodule LiveSelect.Component do Enum.any?(selection, fn item -> item.label == option.label end) end - defp multi_select_mode?(socket) do - socket.assigns.mode == :tags && socket.assigns.tags_mode == :multiple_select + defp quick_tags_mode?(socket) do + socket.assigns.mode == :quick_tags end defp next_selectable(%{ @@ -711,7 +709,7 @@ defmodule LiveSelect.Component do options: options, active_option: active_option, selection: selection, - tags_mode: :multiple_select + mode: :quick_tags }) do options |> Enum.with_index() @@ -740,7 +738,7 @@ defmodule LiveSelect.Component do options: options, active_option: active_option, selection: selection, - tags_mode: :multiple_select + mode: :quick_tags }) do options |> Enum.with_index() diff --git a/lib/live_select/component.html.heex b/lib/live_select/component.html.heex index d0841c7..a55681a 100644 --- a/lib/live_select/component.html.heex +++ b/lib/live_select/component.html.heex @@ -8,10 +8,11 @@ data-field={@field.id} data-debounce={@debounce} > - <%= if @mode == :tags && Enum.any?(@selection) do %> +
+ <%= if (@mode == :tags || @mode == :quick_tags) && Enum.any?(@selection) do %> <%= for {option, idx} <- Enum.with_index(@selection) do %>
<%= if @tag == [] do %> @@ -40,8 +41,9 @@
<% end %> + <% end %>
- <% end %> +
<%= text_input(@field.form, @text_input_field, class: @@ -127,7 +129,7 @@ <%= if @option == [] do %> <%= option.label %> <% else %> - <%= render_slot(@option, {option, already_selected?(option, @selection)}) %> + <%= render_slot(@option, Map.merge(option, %{selected: already_selected?(option, @selection)})) %> <% end %>
diff --git a/lib/support/live_select_web/live/live_component_test.ex b/lib/support/live_select_web/live/live_component_test.ex index 03fd0a6..286da4d 100644 --- a/lib/support/live_select_web/live/live_component_test.ex +++ b/lib/support/live_select_web/live/live_component_test.ex @@ -8,6 +8,7 @@ defmodule LiveSelectWeb.LiveComponentTest do @impl true def render(assigns) do ~H""" +

ølksdjf

<.live_component module={LiveComponentForm} id="lc_form" /> """ end diff --git a/lib/support/live_select_web/live/showcase_live.ex b/lib/support/live_select_web/live/showcase_live.ex index a4dc611..2ccc710 100644 --- a/lib/support/live_select_web/live/showcase_live.ex +++ b/lib/support/live_select_web/live/showcase_live.ex @@ -67,10 +67,15 @@ defmodule LiveSelectWeb.ShowcaseLive do field(:allow_clear, :boolean) field(:debounce, :integer, default: Component.default_opts()[:debounce]) field(:disabled, :boolean) + field(:custom_option_html, :boolean) field(:max_selectable, :integer, default: Component.default_opts()[:max_selectable]) field(:user_defined_options, :boolean) - field(:mode, Ecto.Enum, values: [:single, :tags], default: Component.default_opts()[:mode]) - field(:tags_mode, Ecto.Enum, values: [:default, :multiple_select], default: Component.default_opts()[:tags_mode]) + + field(:mode, Ecto.Enum, + values: [:single, :tags, :quick_tags], + default: Component.default_opts()[:mode] + ) + field(:new, :boolean, default: true) field(:placeholder, :string, default: "Search for a city") field(:search_delay, :integer, default: 10) @@ -94,10 +99,10 @@ defmodule LiveSelectWeb.ShowcaseLive do :allow_clear, :debounce, :disabled, + :custom_option_html, :max_selectable, :user_defined_options, :mode, - :tags_mode, :options, :selection, :placeholder, @@ -121,7 +126,7 @@ defmodule LiveSelectWeb.ShowcaseLive do default_opts = Component.default_opts() settings - |> Map.drop([:search_delay, :new, :selection]) + |> Map.drop([:search_delay, :new, :selection, :custom_option_html]) |> Map.from_struct() |> then( &if is_nil(&1.style) do @@ -140,8 +145,12 @@ defmodule LiveSelectWeb.ShowcaseLive do end defp maybe_set_classes_for_multiselect(opts) do - if LiveSelectWeb.ShowcaseLive.multiple_select?(opts) |> dbg do - Keyword.put(opts, :selected_option_class, "cursor-pointer font-bold hover:bg-gray-400 rounded") + if LiveSelectWeb.ShowcaseLive.quick_tags?(opts[:mode]) do + Keyword.put( + opts, + :selected_option_class, + "cursor-pointer font-bold hover:bg-gray-400 rounded" + ) else opts end @@ -250,20 +259,55 @@ defmodule LiveSelectWeb.ShowcaseLive do assigns = assign(assigns, opts: opts, format_value: format_value) ~H""" -
- <.live_select -
   field={my_form[:city_search]} - <%= for {key, value} <- @opts, !is_nil(value) do %> - <%= if value == true do %> -
   <%= key %> - <% else %> -
   <%= key %>=<%= @format_value.(value) %> + <%= if @custom_option_html do %> +
+ <.live_select +
   field={my_form[:city_search]} + <%= for {key, value} <- @opts, !is_nil(value) do %> + <%= if value == true do %> +
   <%= key %> + <% else %> +
   <%= key %>=<%= @format_value.(value) %> + <% end %> + <% end %> +
> + +
   + <:option :let={%{label: label, value: value, selected: selected}} + + > + +
<%= indent(2) %> <div class="flex justify-content items-center"> +
<%= indent(3) %> <input +
<%= indent(4) %> class="rounded w-4 h-4 mr-3 border border-border" +
<%= indent(4) %> type="checkbox" +
<%= indent(4) %> checked={selected} +
<%= indent(3) %> /> +
<%= indent(4) %> <span class="text-sm"><%= label %> +
<%= indent(2) %> </div> +
<%= indent(1) %> </:option> +
<.live_select/> +
+ <% else %> +
+ <.live_select +
   field={my_form[:city_search]} + <%= for {key, value} <- @opts, !is_nil(value) do %> + <%= if value == true do %> +
   <%= key %> + <% else %> +
   <%= key %>=<%= @format_value.(value) %> + <% end %> <% end %> - <% end %> - /> -
+ /> +
+ <% end %> """ end + + defp indent(amount) do + raw(for _ <- 1..amount, do: "   ") + end end @impl true @@ -310,11 +354,12 @@ defmodule LiveSelectWeb.ShowcaseLive do {:ok, settings} -> socket.assigns - attrs = if settings.tags_mode == :multiple_select do - %{selected_option_class: "cursor-pointer font-bold hover:bg-gray-400 rounded"} - else - %{} - end + attrs = + if settings.mode == :quick_select do + %{selected_option_class: "cursor-pointer font-bold hover:bg-gray-400 rounded"} + else + %{} + end socket = socket @@ -505,9 +550,8 @@ defmodule LiveSelectWeb.ShowcaseLive do {:noreply, socket} end - def multiple_select?(assigns) do - assigns[:mode] == :tags && Keyword.has_key?(assigns, :tags_mode) && - assigns[:tags_mode] == :multiple_select + def quick_tags?(mode) do + mode == :quick_tags end defp value_mapper(%City{name: name} = value), do: %{label: name, value: Map.from_struct(value)} diff --git a/lib/support/live_select_web/live/showcase_live.html.heex b/lib/support/live_select_web/live/showcase_live.html.heex index 52880d6..9b20cde 100644 --- a/lib/support/live_select_web/live/showcase_live.html.heex +++ b/lib/support/live_select_web/live/showcase_live.html.heex @@ -40,13 +40,7 @@
<%= label(@settings_form, :mode, "Mode:", class: "label label-text font-semibold") %> - <%= select(@settings_form, :mode, [:single, :tags], - class: "select select-sm select-bordered text-xs" - ) %> -
-
- <%= label(@settings_form, :tags_mode, "Tags mode:", class: "label label-text font-semibold") %> - <%= select(@settings_form, :tags_mode, [:default, :multiple_select], + <%= select(@settings_form, :mode, [:single, :tags, :quick_tags], class: "select select-sm select-bordered text-xs" ) %>
@@ -77,6 +71,10 @@ Disabled:  <%= checkbox(@settings_form, :disabled, class: "toggle") %> <% end %> + <%= label class: "label cursor-pointer" do %> + Custom option HTML:  + <%= checkbox(@settings_form, :custom_option_html, class: "toggle") %> + <% end %>
<%= label(@settings_form, :search_delay, "Search delay in ms:", @@ -292,7 +290,22 @@ field={@live_select_form[:city_search]} value_mapper={&value_mapper/1} {live_select_assigns(@settings_form.source)} - /> + > + <:option :let={%{label: label, value: value, selected: selected}}> + <%= if @settings_form[:custom_option_html].value do %> +
+ + <%= label %> +
+ <% else %> + <%= label %> + <% end %> + +
<%= submit("Submit", @@ -316,7 +329,7 @@
- +
diff --git a/test/live_select/component_test.exs b/test/live_select/component_test.exs index b6a8090..c36a49e 100644 --- a/test/live_select/component_test.exs +++ b/test/live_select/component_test.exs @@ -427,7 +427,7 @@ defmodule LiveSelect.ComponentTest do test "raises if unknown mode is given", %{form: form} do assert_raise( RuntimeError, - ~s(Invalid mode: "not_a_valid_mode". Mode must be one of: [:single, :tags]), + ~s(Invalid mode: "not_a_valid_mode". Mode must be one of: [:single, :tags, :quick_tags]), fn -> render_component(&LiveSelect.live_select/1, field: form[:input], @@ -539,6 +539,11 @@ defmodule LiveSelect.ComponentTest do ] end + describe "in quick_tags mode" do + test "" do + end + end + for style <- [:daisyui, :tailwind, :none, nil] do @style style