Packages
surface
0.9.2
0.12.3
0.12.2
0.12.1
0.12.0
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.1
0.1.0
0.1.0-alpha.2
0.1.0-alpha.1
0.1.0-alpha.0
A component based library for Phoenix LiveView
Current section
Files
Jump to
Current section
Files
lib/surface/component.ex
defmodule Surface.Component do
@moduledoc """
Defines a stateless component.
## Example
defmodule Button do
use Surface.Component
prop click, :event
def render(assigns) do
~F"\""
<button class="button" :on-click={@click}>
<#slot/>
</button>
"\""
end
end
> **Note**: Stateless components cannot handle Phoenix LiveView events.
If you need to handle them, please use a `Surface.LiveComponent` instead.
"""
alias Surface.IOHelper
@callback render(assigns :: Socket.assigns()) :: Phoenix.LiveView.Rendered.t()
defmacro __using__(opts \\ []) do
slot_name = Keyword.get(opts, :slot)
if slot_name do
validate_slot_name!(slot_name, __CALLER__)
end
slot_name = slot_name && String.to_atom(slot_name)
quote do
@before_compile Surface.Renderer
@before_compile unquote(__MODULE__)
use Phoenix.Component
import Phoenix.Component, except: [slot: 1, slot: 2]
@behaviour unquote(__MODULE__)
use Surface.BaseComponent, type: unquote(__MODULE__)
use Surface.API, include: [:prop, :slot, :data]
import Phoenix.HTML
@before_compile {Surface.BaseComponent, :__before_compile_init_slots__}
@before_compile {unquote(__MODULE__), :__before_compile_handle_from_context__}
alias Surface.Components.{Context, Raw}
alias Surface.Components.Dynamic.Component
@doc "Built-in assign"
data inner_block, :fun
defmacro __using__(opts) do
alias_opts = Keyword.take(opts, [:as])
quote do
alias unquote(__MODULE__), unquote(alias_opts)
Module.put_attribute(__MODULE__, :__compile_time_deps__, unquote(__MODULE__))
end
end
if unquote(slot_name) != nil do
Module.put_attribute(__MODULE__, :__slot_name__, unquote(slot_name))
def __slot_name__ do
unquote(slot_name)
end
end
end
end
defp validate_slot_name!(name, caller) do
if !is_binary(name) do
message = "invalid value for option :slot. Expected a string, got: #{inspect(name)}"
IOHelper.compile_error(message, caller.file, caller.line)
end
end
defmacro __before_compile__(env) do
quoted_render(env)
end
defmacro __before_compile_handle_from_context__(env) do
props_specs = env.module |> Surface.API.get_props() |> Enum.reverse()
data_specs = env.module |> Surface.API.get_data() |> Enum.reverse()
quoted_props_assigns =
for %{name: name, opts: opts} <- props_specs, key = opts[:from_context] do
quote do
var!(assigns) =
Surface.Components.Context.maybe_copy_assign(var!(assigns), unquote(key), as: unquote(name))
end
end
quoted_data_assigns =
for %{name: name, opts: opts} <- data_specs, key = opts[:from_context] do
quote do
var!(assigns) = Surface.Components.Context.copy_assign(var!(assigns), unquote(key), as: unquote(name))
end
end
quoted_caller_scope_id =
quote do
var!(assigns) = Phoenix.Component.assign_new(var!(assigns), :__caller_scope_id__, fn -> nil end)
end
quoted_assigns = {:__block__, [], [quoted_caller_scope_id] ++ quoted_data_assigns ++ quoted_props_assigns}
if Module.defines?(env.module, {:render, 1}) do
quote do
defoverridable render: 1
def render(var!(assigns)) do
unquote(quoted_assigns)
super(var!(assigns))
end
end
end
end
defp quoted_render(env) do
if !Module.defines?(env.module, {:__slot_name__, 0}) ||
Module.defines?(env.module, {:render, 1}) do
quote do
@doc false
def __renderless__? do
false
end
end
else
quote do
@doc false
def __renderless__? do
true
end
def render(var!(assigns)) do
~F()
end
end
end
end
end