Skip to content

Passing inner block content using LiveSvelte.Components macro does not work #101

Description

@ahacking

Expected Outcome

Use component helpers with inner blocks, just as we can with <.svelte .../>.

Issue

I added use LiveSvelte.Components to html_helpers in lib/myapp_web.ex:

defp html_helpers do
    quote do
      # HTML escaping functionality
      import Phoenix.HTML
      # Core UI components and translation
      import MyAppWeb.CoreComponents
      import MyAppWeb.Gettext
      import LiveSvelte
      use LiveSvelte.Components       # <--- added this

      # Shortcut for generating JS commands
      alias Phoenix.LiveView.JS

      # Routes generation with the ~p sigil
      unquote(verified_routes())
    end
  end

The generated component function fails to render the inner block when passing slot content to the LiveSvelte.svelte component, as it is not picked up in LiveSvelte.Slots.filter_slots_from_assigns/1.

I did some investigation and inspected the assigns:

defmodule LiveSvelte do

# ...

def svelte(assigns) do
    init = assigns.__changed__ == nil
    dead = assigns.socket == nil or not LiveView.connected?(assigns.socket)
    IO.inspect(assigns)
   # ...

With a test component HelloWorld.svelte:

Hello World
<slot />

Rendering with <.HelloWorld /> in our markup we get the following assigns:

%{
  name: "HelloWorld",
  socket: nil,
  props: %{},
  __changed__: nil,
  __given__: %{
    name: "HelloWorld",
    socket: nil,
    props: %{},
    __changed__: nil,
    ssr: true,
    class: nil
  },
  ssr: true,
  class: nil,
  inner_block: [],
  live_json_props: %{}
}

Rendering with <.svelte name="HelloWorld">Foo</.svelte> with a block we get working slot/assigns:

%{
  name: "HelloWorld",
  socket: nil,
  props: %{},
  __changed__: nil,
  __given__: %{
    name: "HelloWorld",
    __changed__: nil,
    inner_block: [
      %{
        inner_block: #Function<6.109048978/2 in MyAppWeb.PageHTML.home/1>,
        __slot__: :inner_block
      }
    ]
  },
  ssr: true,
  class: nil,
  inner_block: [
    %{
      inner_block: #Function<6.109048978/2 in MyAppWeb.PageHTML.home/1>,
      __slot__: :inner_block
    }
  ],
  live_json_props: %{}
}

Rendering with <.HelloWorld>Foo</.HelloWorld> the generated function component we get broken assigns with the slot content nested on props:

%{
  name: "HelloWorld",
  socket: nil,
  props: %{
    inner_block: [
      %{
        inner_block: #Function<8.96579134/2 in MyAppWeb.PageHTML.home/1>,
        __slot__: :inner_block
      }
    ]
  },
  __changed__: nil,
  __given__: %{
    name: "HelloWorld",
    socket: nil,
    props: %{
      inner_block: [
        %{
          inner_block: #Function<8.96579134/2 in MyAppWeb.PageHTML.home/1>,
          __slot__: :inner_block
        }
      ]
    },
    __changed__: nil,
    ssr: true,
    class: nil
  },
  ssr: true,
  class: nil,
  inner_block: [],
  live_json_props: %{}
}

I also discovered that Phoenix actually doesn't let one dynamically create components with attrs or slots, raising an error when attempting to add an inner_block slot on the component generator:

      slot(:inner_block, required: false)

could not define slots for function CounterExample/1. Components cannot be dynamically defined or have default arguments

What can we do?

It seems the obvious way forward is for LiveSvelte.Slots to also detect slots on the props assign in its filtering.

Did you have any further insights? Is there a better way to dynamically create the component functions that is more aligned with how phoenix components work?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions