Dstar.Component (dstar v0.3.0)

Copy Markdown View Source

Shared UI + its event handlers in one module — for drawers, pickers, and other components used across many pages.

defmodule MyAppWeb.DetailDrawer do
  use Dstar.Component

  def drawer(assigns) do
    ~H"""
    <div id="detail-drawer">
      <input data-on:change={event("change_title:#{@item.id}")} />
    </div>
    """
  end

  def handle_event(conn, "change_title:" <> id, signals) do
    # The module allowlist only selects this handler. Authorize
    # the record *before* start/1 — after that the response is
    # already a 200 SSE stream.
    case Items.fetch_for_user(conn.assigns.current_user, id) do
      {:ok, item} ->
        {:ok, _} = Items.update_title(item, signals["title"])
        conn |> start() |> patch_signals(%{saved: true})

      :error ->
        conn
        |> Plug.Conn.send_resp(403, "Forbidden")
        |> Plug.Conn.halt()
    end
  end
end

Pages embed the UI as plain function components and need no handle_event clauses for it — events route to this module via Dstar.Plugs.Dispatch (wire it with Dstar.Router.dstar_components/2).

Unlike Dstar.Page, event/2 here targets the component's dispatch URL: <base>/<encoded-module>/<event>. The base defaults to /ds and is read client-side from <body data-ds-base="...">, so it must match the base you give dstar_components/2. The value must be a trusted local absolute application path beginning with one /; protocol-relative values, dot segments, backslashes, queries, fragments, and controls are rejected in the browser; percent escapes must decode successfully. Two common setups:

# Router: dstar_components "/ds", [...]   (the default — no layout
# attribute needed)

# Custom base and/or app path prefix — declare it once in the root
# layout, including the dispatch segment:
#   Router: scope "/:workspace_slug" do dstar_components "/ds", [...] end
<body data-ds-base={workspace_path(@current_scope.workspace.slug) <> "/ds"}>

Colocation only: no server-side component state, no lifecycle. State lives in signals, the DOM, and the database.