defmodule Surface.LiveComponent do
@moduledoc """
A live stateful component. A wrapper around `Phoenix.LiveComponent`.
## Example
defmodule Dialog do
use Surface.LiveComponent
prop title, :string, required: true
def mount(socket) do
{:ok, assign(socket, show: false)}
end
def render(assigns) do
~F"\""
<div class={"modal", "is-active": @show}>
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">{@title}</p>
</header>
<section class="modal-card-body">
<#slot/>
</section>
<footer class="modal-card-foot" style="justify-content: flex-end">
<Button click="hide">Ok</Button>
</footer>
</div>
</div>
"\""
end
# Public API
def show(dialog_id) do
send_update(__MODULE__, id: dialog_id, show: true)
end
# Event handlers
def handle_event("show", _, socket) do
{:noreply, assign(socket, show: true)}
end
def handle_event("hide", _, socket) do
{:noreply, assign(socket, show: false)}
end
end
"""
alias Surface.BaseComponent
defmacro __using__(_) do
quote do
@before_compile Surface.Renderer
use Phoenix.LiveComponent
import Phoenix.LiveView.Helpers, except: [slot: 2]
use Surface.BaseComponent, type: unquote(__MODULE__)
@before_compile unquote(__MODULE__)
use Surface.API, include: [:prop, :slot, :data]
import Phoenix.HTML
@before_compile {Surface.BaseComponent, :__before_compile_init_slots__}
alias Surface.Components.{Context, Raw}
alias Surface.Components.Dynamic.Component
alias Surface.Components.Dynamic.LiveComponent
@doc """
The id of the live component (required by LiveView for stateful components).
"""
prop id, :string, required: true
@doc "Built-in assign"
data socket, :struct
@doc "Built-in assign"
data myself, :struct
end
end
defmacro __before_compile__(env) do
[quoted_mount(env), quoted_update(env)]
end
defp quoted_update(env) do
if Module.defines?(env.module, {:update, 2}) do
quote do
defoverridable update: 2
def update(assigns, socket) do
{:ok, socket} = super(assigns, socket)
{:ok, BaseComponent.restore_private_assigns(socket, assigns)}
end
end
end
end
defp quoted_mount(env) do
defaults = env.module |> Surface.API.get_defaults() |> Macro.escape()
if Module.defines?(env.module, {:mount, 1}) do
quote do
defoverridable mount: 1
def mount(socket) do
super(
socket
|> Surface.init()
|> assign(unquote(defaults))
)
end
end
else
quote do
def mount(socket) do
{:ok,
socket
|> Surface.init()
|> assign(unquote(defaults))}
end
end
end
end
end