Align component model with HEEx/Surface (#182)
* Align component model with HEEx/Surface This change aligns the component model with HEEx/Surface. This shoudl allow one to interop components created in any syntax with any other syntax. The advantage of this is folks can utilize component packages created using a different syntax. This includes several enhancements and breaking changes, please see the changelog and the migration guide for further details. Closes #130
This commit is contained in:
parent
dca47b9802
commit
db231e7b6b
40 changed files with 497 additions and 235 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -2,6 +2,19 @@
|
|||
|
||||
## Main
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Temple components are now compatible with HEEx/Surface components! Some small tweaks to the component implementation has made this possible. Please see the guides for more information.
|
||||
- Multiple instances of the same slot name can now be declared and then rendered inside the component (similar to HEEx and Surface).
|
||||
- You can now pass arbitrary data to slots, and it does not need to be a map or a keyword list. I don't think this is a breaking change, but please submit an issue if you notice it is.
|
||||
- Slot attributes. You can now pass data into a slot from the definition site and use it at the call site (inside the component).
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Rendering slots is now done by passing the assign with the slot name to the `slot` keyword instead of name as an atom. If this slot has multiple definitions, you can loop through them and render each one individually, or render them all at once. Please see the migration guide for more information.
|
||||
- The `:default` slot has been renamed to `:inner_block`. This is to be easily compatible with HEEx/Surface. Please see the migration guide for more information.
|
||||
- Capturing the data being passed into a slot is now defined using the `:let` attribute. Please see the migration guide for more information.
|
||||
|
||||
### 0.10.0
|
||||
|
||||
### Enhancements
|
||||
|
|
|
@ -67,6 +67,8 @@ Temple components are simple to write and easy to use.
|
|||
|
||||
Unlike normal partials, Temple components have the concept of "slots", which are similar [Vue](https://v3.vuejs.org/guide/component-slots.html#named-slots). You can also refer to HEEx and Surface for examples of templates that have the "slot" concept.
|
||||
|
||||
Temple components are compatible with HEEx and Surface components and can be shared.
|
||||
|
||||
Please see the [guides](https://hexdocs.pm/temple/components.html) for more details.
|
||||
|
||||
```elixir
|
||||
|
@ -77,15 +79,15 @@ defmodule MyAppWeb.Component do
|
|||
temple do
|
||||
section do
|
||||
div do
|
||||
slot :header
|
||||
slot @header
|
||||
end
|
||||
|
||||
div do
|
||||
slot :default
|
||||
slot @inner_block
|
||||
end
|
||||
|
||||
div do
|
||||
slot :footer
|
||||
slot @footer
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -54,7 +54,7 @@ Slots are defined and rendered using the `slot` keyword. This is similar to the
|
|||
|
||||
### Default Slot
|
||||
|
||||
The default slot can be rendered from within your component by passing the `slot` the atom `:default`. Let's redefine our button component using slots.
|
||||
The default slot can be rendered from within your component by passing the `slot` the `@inner_block` assign. Let's redefine our button component using slots.
|
||||
|
||||
```elixir
|
||||
defmodule MyApp.Components do
|
||||
|
@ -63,7 +63,7 @@ defmodule MyApp.Components do
|
|||
def button(assigns) do
|
||||
temple do
|
||||
button type: "button", class: "bg-blue-800 text-white rounded #{@class}" do
|
||||
slot :default
|
||||
slot @inner_block
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -109,18 +109,18 @@ defmodule MyApp.Components do
|
|||
div class: "card" do
|
||||
header class: "card-header", style: "background-color: @f5f5f5" do
|
||||
p class: "card-header-title" do
|
||||
slot :header
|
||||
slot @header
|
||||
end
|
||||
end
|
||||
|
||||
div class: "card-content" do
|
||||
div class: "content" do
|
||||
slot :default
|
||||
slot @inner_block
|
||||
end
|
||||
end
|
||||
|
||||
footer class: "card-footer", style: "background-color: #f5f5f5" do
|
||||
slot :footer
|
||||
slot @footer
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -145,8 +145,8 @@ def MyApp.CardExample do
|
|||
"This example demonstrates how to create components with multiple, named slots"
|
||||
|
||||
slot :footer do
|
||||
a href="#", class: "card-footer-item", do: "Footer Item 1"
|
||||
a href="#", class: "card-footer-item", do: "Footer Item 2"
|
||||
a href: "#", class: "card-footer-item", do: "Footer Item 1"
|
||||
a href: "#", class: "card-footer-item", do: "Footer Item 2"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -154,11 +154,15 @@ def MyApp.CardExample do
|
|||
end
|
||||
```
|
||||
|
||||
## Passing Data Through Slots
|
||||
## Passing data to and through Slots
|
||||
|
||||
Sometimes it is necessary to pass data from a component definition back to the call site.
|
||||
Sometimes it is necessary to pass data _into_ a slot (hereby known as *slot attributes*) from the call site and _from_ a component definition (hereby known as *slot arguments*) back to the call site.
|
||||
|
||||
Let's look at what a `table` component could look like.
|
||||
Let's look at what a `table` component could look like. Here we observe we access an attribute in the slot in the header with `col.label`.
|
||||
|
||||
This example is taken from the HEEx documentation to demonstrate how you can build the same thing with Temple.
|
||||
|
||||
Note: Slot attributes can only be accessed on an individual slot, so if you define a single slot definition, you still need to loop through it to access it, as they are stored as a list.
|
||||
|
||||
#### Definition
|
||||
|
||||
|
@ -166,30 +170,23 @@ Let's look at what a `table` component could look like.
|
|||
defmodule MyApp.Components do
|
||||
import Temple
|
||||
|
||||
def cols(items) do
|
||||
items
|
||||
|> List.first()
|
||||
|> Map.keys()
|
||||
|> Enum.sort()
|
||||
end
|
||||
|
||||
def table(assigns) do
|
||||
temple do
|
||||
table do
|
||||
thead do
|
||||
tr do
|
||||
for col <- cols(@entries) do
|
||||
tr do: String.upcase(to_string(col))
|
||||
for col <- @col do
|
||||
th do: col.label # 👈 accessing a slot attribute
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
tbody do
|
||||
for row <- @entries do
|
||||
for row <- @rows do
|
||||
tr do
|
||||
for col <- cols(@entries) do
|
||||
for col <- @col do
|
||||
td do
|
||||
slot :cell, %{value: row[cell]}
|
||||
slot col, row
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -203,7 +200,7 @@ end
|
|||
|
||||
#### Usage
|
||||
|
||||
When we render the slot, we can pattern match on the data passed through the slot. If this seems familiar, it's because this is the same syntax you use when writing your tests using `ExUnit.Case.test/3`.
|
||||
When we render the slot, we can pattern match on the data passed through the slot via the `:let` attribute.
|
||||
|
||||
```elixir
|
||||
def MyApp.TableExample do
|
||||
|
@ -213,24 +210,16 @@ def MyApp.TableExample do
|
|||
def render(assigns) do
|
||||
temple do
|
||||
section do
|
||||
h2 do: "Inventory Levels"
|
||||
h2 do: "Users"
|
||||
|
||||
c &table/1, entries: @item_inventories do
|
||||
slot :cell, %{value: value} do
|
||||
case value do
|
||||
0 ->
|
||||
span class: "font-bold" do
|
||||
"Out of stock!"
|
||||
c &table/1, rows: @users do
|
||||
# 👇 defining the parameter for the slot argument
|
||||
slot :col, let: user, label: "Name" do # 👈 passing a slot attribute
|
||||
user.name
|
||||
end
|
||||
|
||||
level when is_number(level) ->
|
||||
span do
|
||||
"#{level} in stock"
|
||||
end
|
||||
|
||||
_ ->
|
||||
span do: value
|
||||
end
|
||||
slot :col, let: user, label: "Address" do
|
||||
user.address
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
95
guides/migrating/0.10-to-0.11.md
Normal file
95
guides/migrating/0.10-to-0.11.md
Normal file
|
@ -0,0 +1,95 @@
|
|||
# Migrating from 0.10 to 0.11
|
||||
|
||||
Most of the changes in this release are related to tweaking Temple's component model to align with HEEx & Surface.
|
||||
|
||||
## Rendering Slots
|
||||
|
||||
Slots are now available as assigns in the component and are rendered as such.
|
||||
|
||||
### Before
|
||||
|
||||
```elixir
|
||||
def my_component(assign) do
|
||||
temple do
|
||||
span do
|
||||
slot :a_slot
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### After
|
||||
|
||||
```elixir
|
||||
def my_component(assign) do
|
||||
temple do
|
||||
span do
|
||||
slot @a_slot
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## :default slot has been renamed to :inner_block
|
||||
|
||||
The main body of a component has been renamed from `:default` to `:inner_block`.
|
||||
|
||||
Note: The "after" example also includes the necessary change specified above.
|
||||
|
||||
### Before
|
||||
|
||||
```elixir
|
||||
def my_component(assign) do
|
||||
temple do
|
||||
span do
|
||||
slot :default
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### After
|
||||
|
||||
```elixir
|
||||
def my_component(assign) do
|
||||
temple do
|
||||
span do
|
||||
slot @inner_block
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Passing data into slots
|
||||
|
||||
The syntax for capturing data being passed from the call site of a slot to the definition of a slot (or put another way, from the definition of a component to the call site of the component) has changed.
|
||||
|
||||
You now capture it as the value of the `:let` attribute on the slot definition.
|
||||
|
||||
### Before
|
||||
|
||||
```elixir
|
||||
def my_component(assign) do
|
||||
temple do
|
||||
c &my_component/1 do
|
||||
slot :a_slot, %{some: value} do
|
||||
"I'm using some #{value}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### After
|
||||
|
||||
```elixir
|
||||
def my_component(assign) do
|
||||
temple do
|
||||
c &my_component/1 do
|
||||
slot :a_slot, let: %{some: value} do
|
||||
"I'm using some #{value}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
|
@ -93,6 +93,8 @@ defmodule Temple do
|
|||
<link href="/css/site.css">
|
||||
```
|
||||
"""
|
||||
@doc false
|
||||
def engine(), do: @engine
|
||||
|
||||
defmacro temple(block) do
|
||||
opts = [engine: engine()]
|
||||
|
@ -104,10 +106,69 @@ defmodule Temple do
|
|||
end
|
||||
|
||||
@doc false
|
||||
def component(func, assigns) do
|
||||
def component(func, assigns, _) do
|
||||
apply(func, [assigns])
|
||||
end
|
||||
|
||||
defmacro inner_block(_name, do: do_block) do
|
||||
__inner_block__(do_block)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def engine(), do: @engine
|
||||
def __inner_block__([{:->, meta, _} | _] = do_block) do
|
||||
inner_fun = {:fn, meta, do_block}
|
||||
|
||||
quote do
|
||||
fn arg ->
|
||||
_ = var!(assigns)
|
||||
unquote(inner_fun).(arg)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def __inner_block__(do_block) do
|
||||
quote do
|
||||
fn arg ->
|
||||
_ = var!(assigns)
|
||||
unquote(do_block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro render_slot(slot, arg) do
|
||||
quote do
|
||||
unquote(__MODULE__).__render_slot__(unquote(slot), unquote(arg))
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __render_slot__([], _), do: nil
|
||||
|
||||
def __render_slot__([entry], argument) do
|
||||
call_inner_block!(entry, argument)
|
||||
end
|
||||
|
||||
def __render_slot__(entries, argument) when is_list(entries) do
|
||||
assigns = %{}
|
||||
_ = assigns
|
||||
|
||||
temple do
|
||||
for entry <- entries do
|
||||
call_inner_block!(entry, argument)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def __render_slot__(entry, argument) when is_map(entry) do
|
||||
entry.inner_block.(argument)
|
||||
end
|
||||
|
||||
defp call_inner_block!(entry, argument) do
|
||||
if !entry.inner_block do
|
||||
message = "attempted to render slot #{entry.__slot__} but the slot has no inner content"
|
||||
raise RuntimeError, message
|
||||
end
|
||||
|
||||
entry.inner_block.(argument)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,17 +2,17 @@ defmodule Temple.Ast do
|
|||
@moduledoc false
|
||||
|
||||
@type t ::
|
||||
Temple.Parser.Empty.t()
|
||||
| Temple.Parser.Text.t()
|
||||
| Temple.Parser.Components.t()
|
||||
| Temple.Parser.Slot.t()
|
||||
| Temple.Parser.NonvoidElementsAliases.t()
|
||||
| Temple.Parser.VoidElementsAliases.t()
|
||||
| Temple.Parser.AnonymousFunctions.t()
|
||||
| Temple.Parser.RightArrow.t()
|
||||
| Temple.Parser.DoExpressions.t()
|
||||
| Temple.Parser.Match.t()
|
||||
| Temple.Parser.Default.t()
|
||||
Temple.Ast.Empty.t()
|
||||
| Temple.Ast.Text.t()
|
||||
| Temple.Ast.Components.t()
|
||||
| Temple.Ast.Slot.t()
|
||||
| Temple.Ast.NonvoidElementsAliases.t()
|
||||
| Temple.Ast.VoidElementsAliases.t()
|
||||
| Temple.Ast.AnonymousFunctions.t()
|
||||
| Temple.Ast.RightArrow.t()
|
||||
| Temple.Ast.DoExpressions.t()
|
||||
| Temple.Ast.Match.t()
|
||||
| Temple.Ast.Default.t()
|
||||
|
||||
def new(module, opts \\ []) do
|
||||
struct(module, opts)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Temple.Parser.AnonymousFunctions do
|
||||
defmodule Temple.Ast.AnonymousFunctions do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
|
@ -11,7 +11,7 @@ defmodule Temple.Parser.AnonymousFunctions do
|
|||
|
||||
@impl true
|
||||
def applicable?({_, _, args}) do
|
||||
import Temple.Parser.Utils, only: [split_args: 1]
|
||||
import Temple.Ast.Utils, only: [split_args: 1]
|
||||
|
||||
args
|
||||
|> split_args()
|
||||
|
@ -23,9 +23,9 @@ defmodule Temple.Parser.AnonymousFunctions do
|
|||
|
||||
@impl true
|
||||
def run({_name, _, args} = expression) do
|
||||
{_do_and_else, args} = Temple.Parser.Utils.split_args(args)
|
||||
{_do_and_else, args} = Temple.Ast.Utils.split_args(args)
|
||||
|
||||
{_args, func_arg, _args2} = Temple.Parser.Utils.split_on_fn(args, {[], nil, []})
|
||||
{_args, func_arg, _args2} = Temple.Ast.Utils.split_on_fn(args, {[], nil, []})
|
||||
|
||||
{_func, _, [{_arrow, _, [[{_arg, _, _}], block]}]} = func_arg
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Temple.Parser.Components do
|
||||
defmodule Temple.Ast.Components do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
|
@ -6,8 +6,7 @@ defmodule Temple.Parser.Components do
|
|||
|
||||
typedstruct do
|
||||
field :function, function()
|
||||
field :assigns, map()
|
||||
field :children, [map()]
|
||||
field :arguments, map()
|
||||
field :slots, [function()]
|
||||
end
|
||||
|
||||
|
@ -22,27 +21,28 @@ defmodule Temple.Parser.Components do
|
|||
def run({:c, _meta, [component_function | args]}) do
|
||||
{do_and_else, args} =
|
||||
args
|
||||
|> Temple.Parser.Utils.split_args()
|
||||
|> Temple.Ast.Utils.split_args()
|
||||
|
||||
{do_and_else, assigns} = Temple.Parser.Utils.consolidate_blocks(do_and_else, args)
|
||||
{do_and_else, arguments} = Temple.Ast.Utils.consolidate_blocks(do_and_else, args)
|
||||
|
||||
{default_slot, {_, named_slots}} =
|
||||
if children = do_and_else[:do] do
|
||||
Macro.prewalk(
|
||||
children,
|
||||
{component_function, %{}},
|
||||
{component_function, []},
|
||||
fn
|
||||
{:c, _, [name | _]} = node, {_, named_slots} ->
|
||||
{node, {name, named_slots}}
|
||||
|
||||
{:slot, _, [name | args]} = node, {^component_function, named_slots} ->
|
||||
{assigns, slot} = split_assigns_and_children(args, Macro.escape(%{}))
|
||||
{arguments, slot} = split_assigns_and_children(args, nil)
|
||||
|
||||
if is_nil(slot) do
|
||||
{node, {component_function, named_slots}}
|
||||
else
|
||||
{nil,
|
||||
{component_function, Map.put(named_slots, name, %{assigns: assigns, slot: slot})}}
|
||||
{parameter, attributes} = Keyword.pop(arguments || [], :let)
|
||||
new_slot = {name, %{parameter: parameter, slot: slot, attributes: attributes}}
|
||||
{nil, {component_function, named_slots ++ [new_slot]}}
|
||||
end
|
||||
|
||||
node, acc ->
|
||||
|
@ -57,37 +57,45 @@ defmodule Temple.Parser.Components do
|
|||
if default_slot == nil do
|
||||
[]
|
||||
else
|
||||
Temple.Parser.parse(default_slot)
|
||||
[
|
||||
Temple.Ast.new(
|
||||
Temple.Ast.Slottable,
|
||||
name: :inner_block,
|
||||
content: Temple.Parser.parse(default_slot)
|
||||
)
|
||||
]
|
||||
end
|
||||
|
||||
slots =
|
||||
for {name, %{slot: slot, assigns: assigns}} <- named_slots do
|
||||
for {name, %{slot: slot, parameter: parameter, attributes: attributes}} <- named_slots do
|
||||
Temple.Ast.new(
|
||||
Temple.Parser.Slottable,
|
||||
Temple.Ast.Slottable,
|
||||
name: name,
|
||||
content: Temple.Parser.parse(slot),
|
||||
assigns: assigns
|
||||
parameter: parameter,
|
||||
attributes: attributes
|
||||
)
|
||||
end
|
||||
|
||||
slots = children ++ slots
|
||||
|
||||
Temple.Ast.new(__MODULE__,
|
||||
function: component_function,
|
||||
assigns: assigns,
|
||||
slots: slots,
|
||||
children: children
|
||||
arguments: arguments,
|
||||
slots: slots
|
||||
)
|
||||
end
|
||||
|
||||
defp split_assigns_and_children(args, empty) do
|
||||
case args do
|
||||
[assigns, [do: block]] ->
|
||||
{assigns, block}
|
||||
[arguments, [do: block]] ->
|
||||
{arguments, block}
|
||||
|
||||
[[do: block]] ->
|
||||
{empty, block}
|
||||
|
||||
[assigns] ->
|
||||
{assigns, nil}
|
||||
[arguments] ->
|
||||
{arguments, nil}
|
||||
|
||||
_ ->
|
||||
{empty, nil}
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Temple.Parser.Default do
|
||||
defmodule Temple.Ast.Default do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Temple.Parser.DoExpressions do
|
||||
defmodule Temple.Ast.DoExpressions do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
|
@ -18,7 +18,7 @@ defmodule Temple.Parser.DoExpressions do
|
|||
|
||||
@impl true
|
||||
def run({name, meta, args}) do
|
||||
{do_and_else, args} = Temple.Parser.Utils.split_args(args)
|
||||
{do_and_else, args} = Temple.Ast.Utils.split_args(args)
|
||||
|
||||
do_body = Temple.Parser.parse(do_and_else[:do])
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Temple.Parser.ElementList do
|
||||
defmodule Temple.Ast.ElementList do
|
||||
@moduledoc false
|
||||
|
||||
@behaviour Temple.Parser
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Temple.Parser.Empty do
|
||||
defmodule Temple.Ast.Empty do
|
||||
@moduledoc false
|
||||
|
||||
use TypedStruct
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Temple.Parser.Match do
|
||||
defmodule Temple.Ast.Match do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Temple.Parser.NonvoidElementsAliases do
|
||||
defmodule Temple.Ast.NonvoidElementsAliases do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
|
@ -24,9 +24,9 @@ defmodule Temple.Parser.NonvoidElementsAliases do
|
|||
def run({name, meta, args}) do
|
||||
name = Parser.nonvoid_elements_lookup()[name]
|
||||
|
||||
{do_and_else, args} = Temple.Parser.Utils.split_args(args)
|
||||
{do_and_else, args} = Temple.Ast.Utils.split_args(args)
|
||||
|
||||
{do_and_else, args} = Temple.Parser.Utils.consolidate_blocks(do_and_else, args)
|
||||
{do_and_else, args} = Temple.Ast.Utils.consolidate_blocks(do_and_else, args)
|
||||
|
||||
children = Temple.Parser.parse(do_and_else[:do])
|
||||
|
||||
|
@ -35,7 +35,7 @@ defmodule Temple.Parser.NonvoidElementsAliases do
|
|||
attrs: args,
|
||||
meta: %{whitespace: whitespace(meta)},
|
||||
children:
|
||||
Temple.Ast.new(Temple.Parser.ElementList,
|
||||
Temple.Ast.new(Temple.Ast.ElementList,
|
||||
children: children,
|
||||
whitespace: whitespace(meta)
|
||||
)
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Temple.Parser.RightArrow do
|
||||
defmodule Temple.Ast.RightArrow do
|
||||
@moduledoc false
|
||||
|
||||
@behaviour Temple.Parser
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Temple.Parser.Slot do
|
||||
defmodule Temple.Ast.Slot do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
use TypedStruct
|
12
lib/temple/ast/slottable.ex
Normal file
12
lib/temple/ast/slottable.ex
Normal file
|
@ -0,0 +1,12 @@
|
|||
defmodule Temple.Ast.Slottable do
|
||||
@moduledoc false
|
||||
|
||||
use TypedStruct
|
||||
|
||||
typedstruct do
|
||||
field :content, [Temple.Ast.t()]
|
||||
field :parameter, Macro.t()
|
||||
field :name, atom()
|
||||
field :attributes, Macro.t(), default: []
|
||||
end
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Temple.Parser.TempleNamespaceNonvoid do
|
||||
defmodule Temple.Ast.TempleNamespaceNonvoid do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
|
@ -14,6 +14,6 @@ defmodule Temple.Parser.TempleNamespaceNonvoid do
|
|||
@impl true
|
||||
def run({name, meta, args}) do
|
||||
{:., _, [{:__aliases__, _, [:Temple]}, name]} = name
|
||||
Temple.Parser.NonvoidElementsAliases.run({name, meta, args})
|
||||
Temple.Ast.NonvoidElementsAliases.run({name, meta, args})
|
||||
end
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Temple.Parser.TempleNamespaceVoid do
|
||||
defmodule Temple.Ast.TempleNamespaceVoid do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
|
@ -13,6 +13,6 @@ defmodule Temple.Parser.TempleNamespaceVoid do
|
|||
def run({name, meta, args}) do
|
||||
{:., _, [{:__aliases__, _, [:Temple]}, name]} = name
|
||||
|
||||
Temple.Parser.VoidElementsAliases.run({name, meta, args})
|
||||
Temple.Ast.VoidElementsAliases.run({name, meta, args})
|
||||
end
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Temple.Parser.Text do
|
||||
defmodule Temple.Ast.Text do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Temple.Parser.Utils do
|
||||
defmodule Temple.Ast.Utils do
|
||||
@moduledoc false
|
||||
|
||||
def snake_to_kebab(stringable),
|
||||
|
@ -25,7 +25,7 @@ defmodule Temple.Parser.Utils do
|
|||
[{:text, " " <> name <> "=\"" <> to_string(value) <> "\""} | acc]
|
||||
else
|
||||
true ->
|
||||
nodes = Temple.Parser.Utils.build_attr(name, value)
|
||||
nodes = Temple.Ast.Utils.build_attr(name, value)
|
||||
Enum.reverse(nodes) ++ acc
|
||||
end
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Temple.Parser.VoidElementsAliases do
|
||||
defmodule Temple.Ast.VoidElementsAliases do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
|
@ -19,7 +19,7 @@ defmodule Temple.Parser.VoidElementsAliases do
|
|||
@impl true
|
||||
def run({name, _, args}) do
|
||||
args =
|
||||
case Temple.Parser.Utils.split_args(args) do
|
||||
case Temple.Ast.Utils.split_args(args) do
|
||||
{_, [args]} when is_list(args) ->
|
||||
args
|
||||
|
|
@ -1,19 +1,19 @@
|
|||
defmodule Temple.Parser do
|
||||
@moduledoc false
|
||||
|
||||
alias Temple.Parser.AnonymousFunctions
|
||||
alias Temple.Parser.Components
|
||||
alias Temple.Parser.Default
|
||||
alias Temple.Parser.DoExpressions
|
||||
alias Temple.Parser.Empty
|
||||
alias Temple.Parser.Match
|
||||
alias Temple.Parser.NonvoidElementsAliases
|
||||
alias Temple.Parser.RightArrow
|
||||
alias Temple.Parser.Slot
|
||||
alias Temple.Parser.TempleNamespaceNonvoid
|
||||
alias Temple.Parser.TempleNamespaceVoid
|
||||
alias Temple.Parser.Text
|
||||
alias Temple.Parser.VoidElementsAliases
|
||||
alias Temple.Ast.AnonymousFunctions
|
||||
alias Temple.Ast.Components
|
||||
alias Temple.Ast.Default
|
||||
alias Temple.Ast.DoExpressions
|
||||
alias Temple.Ast.Empty
|
||||
alias Temple.Ast.Match
|
||||
alias Temple.Ast.NonvoidElementsAliases
|
||||
alias Temple.Ast.RightArrow
|
||||
alias Temple.Ast.Slot
|
||||
alias Temple.Ast.TempleNamespaceNonvoid
|
||||
alias Temple.Ast.TempleNamespaceVoid
|
||||
alias Temple.Ast.Text
|
||||
alias Temple.Ast.VoidElementsAliases
|
||||
|
||||
@aliases Application.compile_env(:temple, :aliases, [])
|
||||
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
defmodule Temple.Parser.Slottable do
|
||||
@moduledoc false
|
||||
|
||||
use TypedStruct
|
||||
|
||||
typedstruct do
|
||||
field :content, any()
|
||||
field :assigns, map(), default: Macro.escape(%{})
|
||||
field :name, atom()
|
||||
end
|
||||
end
|
|
@ -1,20 +1,21 @@
|
|||
defmodule Temple.Renderer do
|
||||
@moduledoc false
|
||||
|
||||
alias Temple.Parser.ElementList
|
||||
alias Temple.Parser.Text
|
||||
alias Temple.Parser.Components
|
||||
alias Temple.Parser.Slot
|
||||
alias Temple.Parser.NonvoidElementsAliases
|
||||
alias Temple.Parser.VoidElementsAliases
|
||||
alias Temple.Parser.AnonymousFunctions
|
||||
alias Temple.Parser.RightArrow
|
||||
alias Temple.Parser.DoExpressions
|
||||
alias Temple.Parser.Match
|
||||
alias Temple.Parser.Default
|
||||
alias Temple.Parser.Empty
|
||||
alias Temple.Ast.ElementList
|
||||
alias Temple.Ast.Text
|
||||
alias Temple.Ast.Components
|
||||
alias Temple.Ast.Slot
|
||||
alias Temple.Ast.Slottable
|
||||
alias Temple.Ast.NonvoidElementsAliases
|
||||
alias Temple.Ast.VoidElementsAliases
|
||||
alias Temple.Ast.AnonymousFunctions
|
||||
alias Temple.Ast.RightArrow
|
||||
alias Temple.Ast.DoExpressions
|
||||
alias Temple.Ast.Match
|
||||
alias Temple.Ast.Default
|
||||
alias Temple.Ast.Empty
|
||||
|
||||
alias Temple.Parser.Utils
|
||||
alias Temple.Ast.Utils
|
||||
|
||||
@default_engine EEx.SmartEngine
|
||||
|
||||
|
@ -23,7 +24,7 @@ defmodule Temple.Renderer do
|
|||
|> Temple.Parser.parse()
|
||||
|> Temple.Renderer.render(opts)
|
||||
|
||||
# |> Temple.Parser.Utils.inspect_ast()
|
||||
# |> Temple.Ast.Utils.inspect_ast()
|
||||
end
|
||||
|
||||
def render(asts, opts \\ [])
|
||||
|
@ -37,7 +38,7 @@ defmodule Temple.Renderer do
|
|||
terminal_node: false
|
||||
}
|
||||
|
||||
buffer = engine.init(%{})
|
||||
buffer = engine.init([])
|
||||
|
||||
buffer =
|
||||
for ast <- asts, reduce: buffer do
|
||||
|
@ -73,25 +74,11 @@ defmodule Temple.Renderer do
|
|||
|
||||
def render(buffer, state, %Components{
|
||||
function: function,
|
||||
assigns: assigns,
|
||||
children: children,
|
||||
arguments: arguments,
|
||||
slots: slots
|
||||
}) do
|
||||
child_quoted =
|
||||
if Enum.any?(children) do
|
||||
children_buffer = state.engine.handle_begin(buffer)
|
||||
|
||||
children_buffer =
|
||||
for child <- children(children), reduce: children_buffer do
|
||||
children_buffer ->
|
||||
render(children_buffer, state, child)
|
||||
end
|
||||
|
||||
state.engine.handle_end(children_buffer)
|
||||
end
|
||||
|
||||
slot_quotes =
|
||||
for slot <- slots do
|
||||
Enum.group_by(slots, & &1.name, fn %Slottable{} = slot ->
|
||||
slot_buffer = state.engine.handle_begin(buffer)
|
||||
|
||||
slot_buffer =
|
||||
|
@ -102,29 +89,34 @@ defmodule Temple.Renderer do
|
|||
|
||||
ast = state.engine.handle_end(slot_buffer)
|
||||
|
||||
[quoted] =
|
||||
inner_block =
|
||||
quote do
|
||||
{unquote(slot.name), unquote(slot.assigns)} ->
|
||||
inner_block unquote(slot.name) do
|
||||
unquote(slot.parameter || quote(do: _)) ->
|
||||
unquote(ast)
|
||||
end
|
||||
|
||||
quoted
|
||||
end
|
||||
|
||||
{:fn, meta, clauses} =
|
||||
quote do
|
||||
fn
|
||||
{:default, _} -> unquote(child_quoted)
|
||||
end
|
||||
end
|
||||
{:%{}, [],
|
||||
[
|
||||
__slot__: slot.name,
|
||||
inner_block: inner_block
|
||||
] ++ slot.attributes}
|
||||
end)
|
||||
|
||||
slot_func = {:fn, meta, slot_quotes ++ clauses}
|
||||
component_arguments =
|
||||
{:%{}, [],
|
||||
arguments
|
||||
|> Map.new()
|
||||
|> Map.merge(slot_quotes)
|
||||
|> Enum.to_list()}
|
||||
|
||||
expr =
|
||||
quote do
|
||||
component(
|
||||
unquote(function),
|
||||
Map.put(Map.new(unquote(assigns)), :__slots__, unquote(slot_func))
|
||||
unquote(component_arguments),
|
||||
{__MODULE__, __ENV__.function, __ENV__.file, __ENV__.line}
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -134,7 +126,7 @@ defmodule Temple.Renderer do
|
|||
def render(buffer, state, %Slot{} = ast) do
|
||||
render_slot_func =
|
||||
quote do
|
||||
var!(assigns).__slots__.({unquote(ast.name), Map.new(unquote(ast.args))})
|
||||
render_slot(unquote(ast.name), unquote(ast.args))
|
||||
end
|
||||
|
||||
state.engine.handle_expr(buffer, "=", render_slot_func)
|
||||
|
@ -242,7 +234,7 @@ defmodule Temple.Renderer do
|
|||
{name, meta, args} = ast.elixir_ast
|
||||
|
||||
{args, {func, fmeta, [{arrow, arrowmeta, [first, _block]}]}, args2} =
|
||||
Temple.Parser.Utils.split_on_fn(args, {[], nil, []})
|
||||
Temple.Ast.Utils.split_on_fn(args, {[], nil, []})
|
||||
|
||||
full_ast =
|
||||
{name, meta, args ++ [{func, fmeta, [{arrow, arrowmeta, [first, inner_quoted]}]}] ++ args2}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
defmodule Temple.Parser.AnonymousFunctionsTest do
|
||||
defmodule Temple.Ast.AnonymousFunctionsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Temple.Parser.AnonymousFunctions
|
||||
alias Temple.Ast.AnonymousFunctions
|
||||
|
||||
describe "applicable?/1" do
|
||||
test "returns true when the node contains an anonymous function as an argument to a function" do
|
||||
|
@ -57,7 +57,7 @@ defmodule Temple.Parser.AnonymousFunctionsTest do
|
|||
assert %AnonymousFunctions{
|
||||
elixir_ast: _,
|
||||
children: [
|
||||
%Temple.Parser.Default{
|
||||
%Temple.Ast.Default{
|
||||
elixir_ast: ^expected_child
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
defmodule Temple.Parser.ComponentsTest do
|
||||
use ExUnit.Case, async: false
|
||||
alias Temple.Parser.Components
|
||||
alias Temple.Parser.Slottable
|
||||
defmodule Temple.Ast.ComponentsTest do
|
||||
use ExUnit.Case, async: true
|
||||
alias Temple.Ast.Components
|
||||
alias Temple.Ast.Slottable
|
||||
|
||||
describe "applicable?/1" do
|
||||
test "runs when using the `c` ast with a block" do
|
||||
|
@ -57,8 +57,7 @@ defmodule Temple.Parser.ComponentsTest do
|
|||
|
||||
assert %Components{
|
||||
function: ^func,
|
||||
assigns: [],
|
||||
children: _
|
||||
arguments: []
|
||||
} = ast
|
||||
end
|
||||
|
||||
|
@ -72,8 +71,7 @@ defmodule Temple.Parser.ComponentsTest do
|
|||
|
||||
assert %Components{
|
||||
function: ^func,
|
||||
assigns: [foo: :bar],
|
||||
children: _
|
||||
arguments: [foo: :bar]
|
||||
} = ast
|
||||
end
|
||||
|
||||
|
@ -91,8 +89,7 @@ defmodule Temple.Parser.ComponentsTest do
|
|||
|
||||
assert %Components{
|
||||
function: ^func,
|
||||
assigns: [foo: :bar],
|
||||
children: _
|
||||
arguments: [foo: :bar]
|
||||
} = ast
|
||||
end
|
||||
|
||||
|
@ -106,8 +103,7 @@ defmodule Temple.Parser.ComponentsTest do
|
|||
|
||||
assert %Components{
|
||||
function: ^func,
|
||||
assigns: [foo: :bar],
|
||||
children: []
|
||||
arguments: [foo: :bar]
|
||||
} = ast
|
||||
end
|
||||
|
||||
|
@ -115,7 +111,7 @@ defmodule Temple.Parser.ComponentsTest do
|
|||
raw_ast =
|
||||
quote do
|
||||
c unquote(func), foo: :bar do
|
||||
slot :foo, %{form: form} do
|
||||
slot :foo, let: %{form: form} do
|
||||
"in the slot"
|
||||
end
|
||||
end
|
||||
|
@ -125,15 +121,40 @@ defmodule Temple.Parser.ComponentsTest do
|
|||
|
||||
assert %Components{
|
||||
function: ^func,
|
||||
assigns: [foo: :bar],
|
||||
arguments: [foo: :bar],
|
||||
slots: [
|
||||
%Slottable{
|
||||
name: :foo,
|
||||
content: [%Temple.Parser.Text{}],
|
||||
assigns: {:%{}, _, [form: _]}
|
||||
content: [%Temple.Ast.Text{}],
|
||||
parameter: {:%{}, _, [form: _]}
|
||||
}
|
||||
],
|
||||
children: []
|
||||
]
|
||||
} = ast
|
||||
end
|
||||
|
||||
test "slot attributes", %{func: func} do
|
||||
raw_ast =
|
||||
quote do
|
||||
c unquote(func), foo: :bar do
|
||||
slot :foo, let: %{form: form}, label: the_label do
|
||||
"in the slot"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ast = Components.run(raw_ast)
|
||||
|
||||
assert %Components{
|
||||
function: ^func,
|
||||
arguments: [foo: :bar],
|
||||
slots: [
|
||||
%Slottable{
|
||||
name: :foo,
|
||||
content: [%Temple.Ast.Text{}],
|
||||
parameter: {:%{}, _, [form: _]},
|
||||
attributes: [label: {:the_label, [], Temple.Ast.ComponentsTest}]
|
||||
}
|
||||
]
|
||||
} = ast
|
||||
end
|
||||
|
||||
|
@ -149,7 +170,7 @@ defmodule Temple.Parser.ComponentsTest do
|
|||
c unquote(list), socials: @user.socials do
|
||||
"hello"
|
||||
|
||||
slot :default, %{text: text, url: url} do
|
||||
slot :foo, let: %{text: text, url: url} do
|
||||
a class: "text-blue-500 hover:underline", href: url do
|
||||
text
|
||||
end
|
||||
|
@ -161,16 +182,32 @@ defmodule Temple.Parser.ComponentsTest do
|
|||
|
||||
ast = Components.run(raw_ast)
|
||||
|
||||
assert Kernel.==(ast.slots, [])
|
||||
assert [
|
||||
%Slottable{
|
||||
name: :inner_block,
|
||||
parameter: nil
|
||||
}
|
||||
] = ast.slots
|
||||
|
||||
assert %Components{
|
||||
children: [
|
||||
%Components{
|
||||
children: [
|
||||
slots: [
|
||||
%Slottable{
|
||||
content: [
|
||||
%Components{
|
||||
slots: [
|
||||
%Slottable{
|
||||
name: :default
|
||||
content: [
|
||||
%Components{
|
||||
slots: [
|
||||
%Slottable{
|
||||
name: :inner_block
|
||||
},
|
||||
%Slottable{
|
||||
name: :foo
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
defmodule Temple.Parser.DefaultTest do
|
||||
defmodule Temple.Ast.DefaultTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Temple.Parser.Default
|
||||
alias Temple.Ast.Default
|
||||
|
||||
describe "applicable?/1" do
|
||||
test "returns true when the node is an elixir expression" do
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
defmodule Temple.Parser.DoExpressionsTest do
|
||||
defmodule Temple.Ast.DoExpressionsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Temple.Parser.DoExpressions
|
||||
alias Temple.Ast.DoExpressions
|
||||
|
||||
describe "applicable?/1" do
|
||||
test "returns true when the node contains a do expression" do
|
||||
|
@ -30,7 +30,7 @@ defmodule Temple.Parser.DoExpressionsTest do
|
|||
assert %DoExpressions{
|
||||
elixir_ast: _,
|
||||
children: [
|
||||
[%Temple.Parser.Text{text: "bob"}],
|
||||
[%Temple.Ast.Text{text: "bob"}],
|
||||
nil
|
||||
]
|
||||
} = ast
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
defmodule Temple.Parser.EmptyTest do
|
||||
defmodule Temple.Ast.EmptyTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Temple.Parser.Empty
|
||||
alias Temple.Ast.Empty
|
||||
|
||||
describe "applicable?/1" do
|
||||
test "returns true when the node is non-content" do
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
defmodule Temple.Parser.MatchTest do
|
||||
defmodule Temple.Ast.MatchTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Temple.Parser.Match
|
||||
alias Temple.Ast.Match
|
||||
|
||||
describe "applicable?/1" do
|
||||
test "returns true when the node is an elixir match expression" do
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
defmodule Temple.Parser.NonvoidElementsAliasesTest do
|
||||
defmodule Temple.Ast.NonvoidElementsAliasesTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Temple.Parser.NonvoidElementsAliases
|
||||
alias Temple.Parser.ElementList
|
||||
alias Temple.Ast.NonvoidElementsAliases
|
||||
alias Temple.Ast.ElementList
|
||||
alias Temple.Ast.Text
|
||||
|
||||
describe "applicable?/1" do
|
||||
test "returns true when the node is a nonvoid element or alias" do
|
||||
|
@ -74,7 +75,7 @@ defmodule Temple.Parser.NonvoidElementsAliasesTest do
|
|||
name: "option",
|
||||
children: %ElementList{
|
||||
children: [
|
||||
%Temple.Parser.Text{text: "foo"}
|
||||
%Text{text: "foo"}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
defmodule Temple.Parser.RightArrowTest do
|
||||
defmodule Temple.Ast.RightArrowTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Temple.Parser.RightArrow
|
||||
alias Temple.Ast.RightArrow
|
||||
|
||||
describe "applicable?/1" do
|
||||
test "returns true when the node contains a right arrow" do
|
||||
|
@ -52,7 +52,7 @@ defmodule Temple.Parser.RightArrowTest do
|
|||
assert %RightArrow{
|
||||
elixir_ast: {:->, [newlines: 1], [[:bing]]},
|
||||
children: [
|
||||
%Temple.Parser.Default{
|
||||
%Temple.Ast.Default{
|
||||
elixir_ast: ^bong
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
defmodule Temple.Parser.SlotTest do
|
||||
use ExUnit.Case, async: false
|
||||
alias Temple.Parser.Slot
|
||||
defmodule Temple.Ast.SlotTest do
|
||||
use ExUnit.Case, async: true
|
||||
alias Temple.Ast.Slot
|
||||
|
||||
describe "applicable?/1" do
|
||||
test "runs when using the `c` ast with a block" do
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
defmodule Temple.Parser.TempleNamespaceNonvoidTest do
|
||||
defmodule Temple.Ast.TempleNamespaceNonvoidTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Temple.Parser.NonvoidElementsAliases
|
||||
alias Temple.Parser.TempleNamespaceNonvoid
|
||||
alias Temple.Ast.ElementList
|
||||
alias Temple.Ast.NonvoidElementsAliases
|
||||
alias Temple.Ast.TempleNamespaceNonvoid
|
||||
alias Temple.Ast.Text
|
||||
|
||||
describe "applicable?/1" do
|
||||
test "returns true when the node is a Temple aliased nonvoid element" do
|
||||
|
@ -50,8 +52,8 @@ defmodule Temple.Parser.TempleNamespaceNonvoidTest do
|
|||
assert %NonvoidElementsAliases{
|
||||
name: "div",
|
||||
attrs: [class: "foo", id: {:var, [], _}],
|
||||
children: %Temple.Parser.ElementList{
|
||||
children: [%Temple.Parser.Text{text: "foo"}],
|
||||
children: %ElementList{
|
||||
children: [%Text{text: "foo"}],
|
||||
whitespace: :loose
|
||||
}
|
||||
} = ast
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
defmodule Temple.Parser.TempleNamespaceVoidTest do
|
||||
defmodule Temple.Ast.TempleNamespaceVoidTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Temple.Parser.TempleNamespaceVoid
|
||||
alias Temple.Parser.VoidElementsAliases
|
||||
alias Temple.Ast.TempleNamespaceVoid
|
||||
alias Temple.Ast.VoidElementsAliases
|
||||
|
||||
describe "applicable?/1" do
|
||||
test "returns true when the node is a Temple aliased nonvoid element" do
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
defmodule Temple.Parser.TextTest do
|
||||
defmodule Temple.Ast.TextTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Temple.Parser.Text
|
||||
alias Temple.Ast.Text
|
||||
|
||||
describe "applicable?/1" do
|
||||
test "returns true when the node is a string literal" do
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
defmodule Temple.Parser.UtilsTest do
|
||||
defmodule Temple.Ast.UtilsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Temple.Parser.Utils
|
||||
alias Temple.Ast.Utils
|
||||
|
||||
describe "compile_attrs/1" do
|
||||
test "returns a list of text nodes for static attributes" do
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
defmodule Temple.Parser.VoidElementsAliasesTest do
|
||||
defmodule Temple.Ast.VoidElementsAliasesTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Temple.Parser.VoidElementsAliases
|
||||
alias Temple.Ast.VoidElementsAliases
|
||||
|
||||
describe "applicable?/1" do
|
||||
test "returns true when the node is a nonvoid element or alias" do
|
||||
|
|
|
@ -368,12 +368,14 @@ defmodule Temple.RendererTest do
|
|||
temple do
|
||||
div do
|
||||
"I am above the slot"
|
||||
slot :default
|
||||
slot @inner_block
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "component with default slot" do
|
||||
assigns = %{}
|
||||
|
||||
result =
|
||||
Renderer.compile do
|
||||
div do
|
||||
|
@ -404,23 +406,28 @@ defmodule Temple.RendererTest do
|
|||
temple do
|
||||
div do
|
||||
"#{@name} is above the slot"
|
||||
slot :default
|
||||
slot @inner_block
|
||||
end
|
||||
|
||||
footer do
|
||||
slot :footer, %{name: @name}
|
||||
for f <- @footer do
|
||||
span do: f[:label]
|
||||
slot f, %{name: @name}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "component with a named slot" do
|
||||
assigns = %{label: "i'm a slot attribute"}
|
||||
|
||||
result =
|
||||
Renderer.compile do
|
||||
div do
|
||||
c &named_slot/1, name: "motchy boi" do
|
||||
span do: "i'm a slot"
|
||||
|
||||
slot :footer, %{name: name} do
|
||||
slot :footer, let: %{name: name}, label: @label, expr: 1 + 1 do
|
||||
p do
|
||||
"#{name}'s in the footer!"
|
||||
end
|
||||
|
@ -439,6 +446,7 @@ defmodule Temple.RendererTest do
|
|||
</div>
|
||||
|
||||
<footer>
|
||||
<span>i'm a slot attribute</span>
|
||||
<p>
|
||||
motchy boi's in the footer!
|
||||
</p>
|
||||
|
@ -501,5 +509,58 @@ defmodule Temple.RendererTest do
|
|||
|
||||
assert expected == result
|
||||
end
|
||||
|
||||
test "multiple slots" do
|
||||
assigns = %{}
|
||||
|
||||
result =
|
||||
Renderer.compile do
|
||||
div do
|
||||
c &named_slot/1, name: "motchy boi" do
|
||||
span do: "i'm a slot"
|
||||
|
||||
slot :footer, let: %{name: name} do
|
||||
p do
|
||||
"#{name}'s in the footer!"
|
||||
end
|
||||
end
|
||||
|
||||
slot :footer, let: %{name: name} do
|
||||
p do
|
||||
"#{name} is the second footer!"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# heex
|
||||
expected = """
|
||||
<div>
|
||||
<div>
|
||||
motchy boi is above the slot
|
||||
<span>i'm a slot</span>
|
||||
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<span></span>
|
||||
<p>
|
||||
motchy boi's in the footer!
|
||||
</p>
|
||||
<span></span>
|
||||
<p>
|
||||
motchy boi is the second footer!
|
||||
</p>
|
||||
|
||||
</footer>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
"""
|
||||
|
||||
assert expected == result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Reference in a new issue