PhoenixKitProjects.Web.PopupHostLive (PhoenixKitProjects v0.5.0)

Copy Markdown View Source

Opinionated wrapper LV that pairs the <.popup_host> function component with the emit-mode PubSub contract.

A host app mounts this LV (typically via live_render) and gets popup-driven UX for free — no host-side handle_info subscription, no modal-stack state management, no frame-ref bookkeeping.

What it does

  1. Subscribes to a host-supplied PubSub topic on connect.
  2. Optionally renders a "root view" inline (the always-visible LV; OverviewLive is the typical pick) by passing session["root_view"].
  3. On {:projects, :opened, ...} — pushes a frame onto the modal stack and renders the target LV inside a <dialog> overlay.
  4. On {:projects, :closed | :saved | :deleted, %{frame_ref: ref}} — pops the top frame iff ref matches (race-safe against stale events).

  5. Generates a unique frame_ref per push and stamps it into the child LV's session along with mode: "emit" and the host topic, so the child's own emits flow back through this LV.
  6. Caps stack depth at session["max_stack_depth"] (defaults to 5; accepts an integer in 1..20, anything outside that band resets to the default) to prevent runaway recursion if a misbehaved LV emits :opened on every mount.

Session contract

  • "pubsub_topic" (required) — PubSub topic string. The host owns the topic name; this LV does not invent it (so two embeds on the same page can use different topics if needed).
  • "root_view" (optional) — %{"lv" => "Module.Name", "session" => %{...}}. The always-visible LV. :lv is whitelist-validated.
  • "wrapper_class" (optional) — outer div class. Defaults to "flex flex-col w-full".
  • "max_stack_depth" (optional) — positive integer in 1..20 overriding the default 5-frame cap. Values outside that band are clamped to the default with a logged warning.
  • "modal_box_class" (optional) — daisyUI modal-box sizing overrides. Defaults to "w-11/12 max-w-6xl" (91% viewport, capped at 72rem). Pass a different size class ("max-w-4xl", "max-w-7xl", etc.) if a host page wants a narrower or wider modal.

Example: dashboard root view (OverviewLive)

For a dedicated admin page that lists projects/tasks/templates with modal-stacked detail views:

live "/orders/:id/projects", MyApp.OrderProjectsLive

# ... and in MyApp.OrderProjectsLive's render:
{Phoenix.Component.live_render(@socket, PhoenixKitProjects.Web.PopupHostLive,
   id: "projects-popup-host",
   session: %{
     "pubsub_topic" => "host:orders:" <> @order_id,
     "root_view" => %{
       "lv" => "PhoenixKitProjects.Web.OverviewLive",
       "session" => %{
         "wrapper_class" => "flex flex-col w-full px-4 py-6 gap-6"
       }
     }
   })}

Example: single project show inside a host record's edit page

The common host-app shape — a host record (order, ticket, etc.) has one linked project and the edit page embeds its detail view:

{Phoenix.Component.live_render(@socket, PhoenixKitProjects.Web.PopupHostLive,
   id: "embedded-project-host-#{@host_record.uuid}",
   session: %{
     "pubsub_topic" => "host:foo:" <> @host_record.uuid,
     "root_view" => %{
       "lv" => "PhoenixKitProjects.Web.ProjectShowLive",
       "session" => %{"id" => @host_record.project_uuid}
     },
     "locale" => @locale
   })}

Whenever the embedded LV (or anything it transitively opens) emits :opened, this LV renders the target inside a modal on the host's existing page. No URL change. No DOM replacement.