phoenixframework / phoenix_live_view

Rich, real-time user experiences with server-rendered HTML
https://hex.pm/packages/phoenix_live_view
MIT License
6.24k stars 934 forks source link

proof of concept: phx-portal #3478

Open SteffenDE opened 1 month ago

SteffenDE commented 1 month ago

This is a proof of concept PR implementing a new phx binding called phx-portal. This binding allows to designate an element to be rendered at another location in the DOM, designated by the ID that phx-portal points to. This can be useful to render things like dialogs at the top layer, outside any containers that might affect their rendering (e.g. overflow: hidden).

Nowadays there is also the Popover API and native <dialog> elements, so this might not be that important any more.

Because of this, I also don't feel like this is something we really want to support. This PR only shows how it could be implemented.

greven commented 1 month ago

Hey @SteffenDE,

I think this is a worthy addition, even with the upcoming Dialog API. Even though the Dialog API usage is already at 95%, there are use cases where a portal is needed outside of a dialog usage, anything that we need to break out of its container layout.

Now, my only doubt is if this should be user-land, but I would be perfectly happy to have it built-in. :)

tmjoen commented 1 month ago

I would love this to simplify "forms within forms". Say for instance you have a multi-select component in a form and you want to be able to create options from within the component using another predeclared form (which is our use case). Would this work for that? That's at least how we used Vue Teleport before migrating to Live View.

AlexKovynev commented 1 month ago

will be great to have this in 1.0 release. This is very necessary feature with which modals can finally will be live! :)

SteffenDE commented 2 weeks ago

Don't expect to see this in the upcoming 1.0 - maybe we can add it in a later release, but that's up to @chrismccord.

AlexKovynev commented 2 weeks ago

@SteffenDE it is a very useful feature which is exists in most of SPA frameworks. That why i want to see it here. But anyway i want to see 1.0 with or without it :) So many years without a release. Let it be as is but quicker :)

Gazler commented 1 week ago

I think given that container-queries are becoming more popular (https://github.com/tailwindlabs/tailwindcss-container-queries), and will be shipped with Tailwind 4 (https://tailwindcss.com/blog/tailwindcss-v4-alpha#designed-for-the-modern-web), this is something that will become increasingly useful.

greven commented 1 week ago

Also, imagine you have a dropdown menu in a header with a fix height. It's non-trivial to make it work without changing a lot of CSS positioning on the dropdown (that you might not want to just for this particular cases) without a portal.

I actually implemented a Portal like solution in a project of mine, but it's not DOM patch aware. :P

AlexKovynev commented 1 week ago

@chrismccord please push it :) any problems can be fixed in 1.0.1 :) Anyway 1.0 we don't know when will be released

SteffenDE commented 1 week ago

If anyone wants to help out here: try building something with phx-portal, you can use this in your mix.exs:

{:phoenix_live_view, github: "phoenixframework/phoenix_live_view", branch: "sd-phx-portal-assets", override: true}

and report any problems you find. This is very much a proof of concept and needs some exploration with real world and possibly complex use cases.

Valian commented 2 days ago

I wanted to add my 2 use-cases:

  1. Rendering page navigation in root layout. If we do it (eg to optimize the payload on "navigate"), that part can't be updated other than by using JS. Having portals would allow to selectively update parts of the root layout without the need to send it as a whole.

  2. I'm an author of LiveVue, a library bridging LiveView and Vue. There's one unsolved challenge related to slots. Currently we support passing slots from Phoenix LiveView to Vue, but it's a hack - I'm rendering slot content on a server and sending it over the wire. So it's not using optimized diffs, hooks are not evaluated etc. In particular, it makes it impossible to nest LiveVue component inside LiveVue component https://github.com/Valian/live_vue/issues/14 because LiveVue components are initialized using Hooks:

~H"""
<.vue v-component="Card">
  <!-- this is a slot. It's rendered on the server side, but Vue won't be initialized in nested -->
  <.vue v-component="Counter">
</.vue>
"""

I have a feeling having portals would make it possible - I could "teleport" each slot to a hidden element somewhere in DOM, and on update sync it with Vue slot content. The only alternative is to patch phoenix JS library to have more control when and how HTML diff is applied, but it would be tricky 😅