Packages

phoenix_kit

1.7.174
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

5 files changed
+69 additions
-15 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.173">>}.
3 + {<<"version">>,<<"1.7.174">>}.
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,
  @@ -47,8 +47,14 @@ defmodule PhoenixKitWeb do
47 47
48 48 def live_view do
49 49 quote do
50 + # The native `:layout` is a pure passthrough (`PhoenixKitWeb.Layouts.app`
51 + # renders only `{@inner_content}`). PhoenixKit LiveViews apply the host's
52 + # configured `config :phoenix_kit, layout:` themselves, once, via
53 + # `LayoutWrapper.app_layout` in their render — so routing `:layout` at the
54 + # host layout too would render the host chrome twice. Keeping `:layout` a
55 + # passthrough makes `app_layout` the single owner of the host layout.
50 56 use Phoenix.LiveView,
51 - layout: PhoenixKit.LayoutConfig.get_layout()
57 + layout: {PhoenixKitWeb.Layouts, :app}
52 58
53 59 use Gettext, backend: PhoenixKitWeb.Gettext
  @@ -177,22 +177,40 @@ defmodule PhoenixKitWeb.Components.LayoutWrapper do
177 177 end
178 178
179 179 defp has_inner_content?(assigns), do: assigns[:inner_content] != nil
180 - defp has_inner_block?(assigns), do: assigns[:inner_block] && assigns[:inner_block] != []
180 +
181 + # Must return a strict boolean: `needs_inner_block_conversion?/1` calls
182 + # `not has_inner_block?(...)`. When `app_layout` is reached with only an
183 + # `inner_content` (the legacy Phoenix 1.7 flow) and no `inner_block` key at
184 + # all, the old `assigns[:inner_block] && ...` short-circuited to `nil`, and
185 + # `not nil` raised ArgumentError. `not in [nil, []]` normalizes both "absent"
186 + # (nil) and "declared-but-empty slot" ([]) to `false`.
187 + defp has_inner_block?(assigns), do: assigns[:inner_block] not in [nil, []]
181 188
182 189 defp convert_inner_content_to_block(assigns) do
183 190 inner_content = assigns[:inner_content]
184 191 inner_block = build_synthetic_inner_block(inner_content)
185 - Map.put(assigns, :inner_block, inner_block)
192 + # Use assign/3 (not Map.put) so `__changed__[:inner_block]` is force-marked.
193 + # Phoenix only force-marks `:inner_content` on a diff (renderer.ex), so a
194 + # host layout's `render_slot(@inner_block)` dynamic is guarded by
195 + # `changed_assign?(__changed__, :inner_block)` — which is false for a bare
196 + # Map.put — and would emit nil, freezing the page body after first paint on
197 + # connected updates. Mirrors the admin-nav path's `assign(..., :inner_block)`.
198 + assign(assigns, :inner_block, inner_block)
186 199 end
187 200
201 + # Synthesize a one-entry slot whose body yields `inner_content`. `inner_content`
202 + # may be a `%Phoenix.LiveView.Rendered{}` (return it verbatim — it is already
203 + # renderable; `Phoenix.HTML.raw/1` has no struct clause and would raise
204 + # FunctionClauseError on it) or a binary from the legacy Phoenix 1.7- flow
205 + # (mark it safe via `raw/1`).
188 206 defp build_synthetic_inner_block(inner_content) do
189 - [
190 - %{
191 - inner_block: fn _slot_assigns, _index ->
192 - Phoenix.HTML.raw(inner_content)
193 - end
194 - }
195 - ]
207 + body =
208 + case inner_content do
209 + %Phoenix.LiveView.Rendered{} = rendered -> rendered
210 + other -> Phoenix.HTML.raw(other)
211 + end
212 +
213 + [%{inner_block: fn _slot_assigns, _index -> body end}]
196 214 end
197 215
198 216 # Check if current page is an admin page that needs navigation.
  @@ -696,14 +714,44 @@ defmodule PhoenixKitWeb.Components.LayoutWrapper do
696 714 # Wrap inner content with admin navigation if needed
697 715 assigns = wrap_inner_block_with_admin_nav_if_needed(assigns)
698 716
699 - # Use apply/3 to dynamically call the parent layout function
700 - apply(module, function, [assigns])
717 + # `app_layout` is the single owner of the host layout (the native `:layout`
718 + # is a passthrough — see `PhoenixKitWeb.__using__(:live_view)`), so this is
719 + # the one place the host layout is applied.
720 + apply_host_layout(assigns, module, function)
721 + end
722 +
723 + # Apply the configured host layout, giving it BOTH inner conventions so it
724 + # works whether it renders `{@inner_content}` (the documented contract) or
725 + # `render_slot(@inner_block)` (the Phoenix 1.8 idiom). `app_layout` is invoked
726 + # with an `inner_block` slot; `ensure_inner_content_from_block/1` derives a
727 + # lazy `@inner_content` from it when absent. Rescues a bad
728 + # `config :phoenix_kit, layout:` (renamed/removed function) to PhoenixKit's own
729 + # layout instead of 500-ing every page. Public for regression testing.
730 + @doc false
731 + def apply_host_layout(assigns, module, function) do
732 + assigns
733 + |> ensure_inner_content_from_block()
734 + |> then(&apply(module, function, [&1]))
701 735 rescue
702 736 UndefinedFunctionError ->
703 - # Fallback to PhoenixKit layout if parent function doesn't exist
704 737 render_with_phoenix_kit_layout(assigns)
705 738 end
706 739
740 + # Derive `@inner_content` from the `inner_block` slot when the caller only
741 + # supplied a slot. Kept lazy (a `%Rendered{}`) so a host layout using
742 + # `{@inner_content}` still change-tracks correctly on connected updates.
743 + defp ensure_inner_content_from_block(assigns) do
744 + if has_inner_content?(assigns) do
745 + assigns
746 + else
747 + assign(assigns, :inner_content, render_inner_block(assigns))
748 + end
749 + end
750 +
751 + defp render_inner_block(assigns) do
752 + ~H"{render_slot(@inner_block)}"
753 + end
754 +
707 755 # Phoenix v1.7- approach - templates (legacy support)
708 756 defp render_legacy_parent_layout(assigns, _module, _function) do
709 757 # For legacy Phoenix, layouts are handled at router level
  @@ -1,7 +1,7 @@
1 1 defmodule PhoenixKit.MixProject do
2 2 use Mix.Project
3 3
4 - @version "1.7.173"
4 + @version "1.7.174"
5 5 @description "A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more"
6 6 @source_url "https://github.com/BeamLabEU/phoenix_kit"