Packages

phoenix_kit

1.7.199
1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

221 Versions

Jump to

Compare versions

6 files changed
+92 additions
-26 deletions
unknownCHANGELOG.md
File is too large to be displayed (100 KB limit).
  @@ -1,6 +1,6 @@
1 1 {<<"links">>,[{<<"GitHub">>,<<"https://github.com/BeamLabEU/phoenix_kit">>}]}.
2 2 {<<"name">>,<<"phoenix_kit">>}.
3 - {<<"version">>,<<"1.7.198">>}.
3 + {<<"version">>,<<"1.7.199">>}.
4 4 {<<"description">>,
5 5 <<65,32,102,111,117,110,100,97,116,105,111,110,32,102,111,114,32,98,117,105,
6 6 108,100,105,110,103,32,69,108,105,120,105,114,32,80,104,111,101,110,105,
  @@ -69,12 +69,18 @@ defmodule PhoenixKitWeb.Components.Core.NavTabs do
69 69 </span>
70 70 </.link>
71 71 <% else %>
72 + <%!-- While the switch round-trips, LiveView tags the clicked tab
73 + with phx-click-loadingpulse it so a switch whose content
74 + needs server work still gives instant feedback. --%>
72 75 <button
73 76 type="button"
74 77 role="tab"
75 78 phx-click={@on_change}
76 79 phx-value-tab={tab.id}
77 - class={["tab gap-2", tab.id == @active_tab && "tab-active"]}
80 + class={[
81 + "tab gap-2 [&.phx-click-loading]:animate-pulse",
82 + tab.id == @active_tab && "tab-active"
83 + ]}
78 84 >
79 85 <.icon :if={Map.has_key?(tab, :icon)} name={tab.icon} class="w-4 h-4" />
80 86 {tab.label}
  @@ -74,6 +74,14 @@ defmodule PhoenixKitWeb.Components.Core.SearchPicker do
74 74 The selector must actually match an element or every push is dropped
75 75 with a console error.
76 76 - `mode` — `"multi"` (default) or `"single"` (see moduledoc)
77 + - `search_on_focus` — when `true`, clicking/focusing the EMPTY input
78 + already opens the dropdown in browse mode (the hook searches with an
79 + empty query, so the server should answer it with a first pagethe
80 + workspace picker rule: offer options before any typing). Off by
81 + default; without it the dropdown only opens while typing, which also
82 + means a multi-mode picker won't reopen after a pick clears the input.
83 + (Previously only reachable as a raw `data-search-on-focus` rest attr —
84 + still honored for existing call sites.)
77 85 - `direction` — `"down"` (default) or `"up"`. Use `"up"` when the input
78 86 sits near the BOTTOM of a scrollable container (e.g. the last field
79 87 of a modal): a downward dropdown there extends the container's
  @@ -90,6 +98,7 @@ defmodule PhoenixKitWeb.Components.Core.SearchPicker do
90 98 attr :dropdown_id, :string, required: true
91 99 attr :target, :any, default: nil
92 100 attr :mode, :string, default: "multi", values: ["multi", "single"]
101 + attr :search_on_focus, :boolean, default: false
93 102 attr :direction, :string, default: "down", values: ["down", "up"]
94 103 attr :name, :string, default: nil
95 104 attr :value, :string, default: nil
  @@ -121,6 +130,7 @@ defmodule PhoenixKitWeb.Components.Core.SearchPicker do
121 130 data-target={@target}
122 131 data-dropdown={@dropdown_id}
123 132 data-mode={@mode}
133 + data-search-on-focus={@search_on_focus || nil}
124 134 data-search-event={@search_event}
125 135 data-results-event={@results_event}
126 136 data-pick-event={@pick_event}
  @@ -29,9 +29,9 @@ defmodule PhoenixKitWeb.Components.MultilangForm do
29 29
30 30 ### Events
31 31
32 - def handle_event("switch_language", %{"lang" => lang_code}, socket) do
33 - {:noreply, handle_switch_language(socket, lang_code)}
34 - end
32 + `mount_multilang/2` auto-handles the `"switch_language"` event, so you do
33 + NOT need a `handle_event("switch_language",)` clauseyou only wire your
34 + own form events:
35 35
36 36 def handle_event("validate", %{"record" => params}, socket) do
37 37 params = merge_translatable_params(params, socket, ["name", "description"],
  @@ -40,6 +40,18 @@ defmodule PhoenixKitWeb.Components.MultilangForm do
40 40 {:noreply, assign(socket, :changeset, changeset)}
41 41 end
42 42
43 + ## Storage caveat — the helper OWNS the `data` column
44 +
45 + `merge_translatable_params/4` (and `put_language_data/3`) store translations
46 + by RESTRUCTURING the schema's `data` JSONB into a multilang shape
47 + (`%{"_primary_language" => "en", "en" => %{…}, "et" => %{…}}`). On a schema
48 + whose `data` column already holds unrelated keys (e.g. per-record settings),
49 + this MOVES those keys into the primary-language sub-map and every top-level
50 + reader breaks. In that case do NOT point `merge_translatable_params` at
51 + `data`: keep translations in an isolated sub-key and use `translatable_field`
52 + in its settings-translations mode (`secondary_name` + `lang_data_key`) with
53 + your own small merge. See the `translatable_field` docs.
54 +
43 55 ### Template — whole-form translation
44 56
45 57 <.multilang_tabs
  @@ -105,10 +117,25 @@ defmodule PhoenixKitWeb.Components.MultilangForm do
105 117 Adds: `:multilang_enabled`, `:primary_language`, `:current_lang`,
106 118 `:language_tabs`, `:show_multilang_tabs`, `:switching_lang` (no-op
107 119 compat assign — kept because consumer templates pass it through to
108 - the wrapper). Also attaches an internal `:handle_info` hook that
109 - receives the debounced language-switch timer message.
120 + the wrapper).
121 +
122 + Also attaches two internal LiveView hooks so consumers don't have to
123 + wire the language-switching protocol by hand:
124 +
125 + * a `:handle_info` hook for the debounced language-switch timer, and
126 + * a `:handle_event` hook for the `"switch_language"` event that
127 + `<.multilang_tabs>` pushesit calls `handle_switch_language/2`
128 + and halts, so **you no longer need to add**
129 + `def handle_event("switch_language",)` yourself. Forgetting that
130 + clause used to crash the LiveView on the first tab click.
131 +
132 + ## Options
133 +
134 + * `:auto_switch_language` — when `false`, skips the `"switch_language"`
135 + event hook so you can handle the event yourself (e.g. to switch
136 + language immediately without the debounce). Default `true`.
110 137 """
111 - def mount_multilang(socket) do
138 + def mount_multilang(socket, opts \\ []) do
112 139 multilang_enabled = multilang_enabled?()
113 140 primary_language = if multilang_enabled, do: safe_primary_language(), else: nil
114 141
  @@ -126,21 +153,27 @@ defmodule PhoenixKitWeb.Components.MultilangForm do
126 153 # The wrapper no longer reads it — visibility is client-side JS.
127 154 switching_lang: false
128 155 )
129 - |> attach_multilang_hook()
156 + |> attach_multilang_hooks(opts)
130 157 end
131 158
132 - # Attaches a `:handle_info` hook that intercepts the internal
133 - # `{:__multilang_apply_lang__, lang}` message. The hook lives on the
134 - # consumer's socket but is invisible to them — no `handle_info/2`
135 - # clause needed. Returning `{:halt, socket}` prevents the message
136 - # from reaching the consumer's own handle_info (and suppresses the
137 - # "unhandled message" warning). Other messages pass through with
138 - # `{:cont, socket}`.
139 - #
140 - # `attach_hook/4` is idempotent-by-name within a process — re-running
141 - # `mount_multilang/1` (e.g. across reconnects) with the same hook id
142 - # simply replaces the prior callback, so we don't need a guard.
143 - defp attach_multilang_hook(socket) do
159 + # Attaches the internal hooks. Both are idempotent-by-name within a
160 + # process — re-running `mount_multilang/2` (e.g. across reconnects) with
161 + # the same hook ids simply replaces the prior callbacks, so no guard is
162 + # needed. `attach_hook/4` only works on LiveView sockets (not
163 + # LiveComponents), so each attach is guarded — a LiveComponent consumer
164 + # would need to wire the handlers itself.
165 + defp attach_multilang_hooks(socket, opts) do
166 + socket = attach_apply_lang_hook(socket)
167 +
168 + if Keyword.get(opts, :auto_switch_language, true),
169 + do: attach_switch_language_hook(socket),
170 + else: socket
171 + end
172 +
173 + # Intercepts the debounced `{:__multilang_apply_lang__, lang}` message so
174 + # the consumer needs no `handle_info/2` clause. Other messages pass
175 + # through with `{:cont, socket}`.
176 + defp attach_apply_lang_hook(socket) do
144 177 Phoenix.LiveView.attach_hook(
145 178 socket,
146 179 :__phoenix_kit_multilang_apply_lang__,
  @@ -154,10 +187,27 @@ defmodule PhoenixKitWeb.Components.MultilangForm do
154 187 end
155 188 )
156 189 rescue
157 - # `attach_hook/4` only works on LiveView sockets, not LiveComponent
158 - # sockets. Multilang consumers are all `Phoenix.LiveView` today, but
159 - # if someone wires it into a component in the future, fall back
160 - # silently — they'll need to add the `handle_info` themselves.
190 + ArgumentError -> socket
191 + end
192 +
193 + # Intercepts the `"switch_language"` event that `<.multilang_tabs>`
194 + # pushes, so the consumer needs no `handle_event/3` clause. Halting stops
195 + # propagation to the consumer's own callbacks; every other event passes
196 + # through with `{:cont, socket}`.
197 + defp attach_switch_language_hook(socket) do
198 + Phoenix.LiveView.attach_hook(
199 + socket,
200 + :__phoenix_kit_multilang_switch_language__,
201 + :handle_event,
202 + fn
203 + "switch_language", %{"lang" => lang_code}, socket ->
204 + {:halt, handle_switch_language(socket, lang_code)}
205 +
206 + _event, _params, socket ->
207 + {:cont, socket}
208 + end
209 + )
210 + rescue
161 211 ArgumentError -> socket
162 212 end
Loading more files…