Skip to content

Commit 95fd934

Browse files
SteffenDEgmile
andauthored
add portal section to changelog (#3847)
* add portal section to changelog * mention vue and react * Update CHANGELOG.md Co-authored-by: Ievgen Pyrogov <207112+gmile@users.noreply.github.com> * use body in example --------- Co-authored-by: Ievgen Pyrogov <207112+gmile@users.noreply.github.com>
1 parent 23b2e54 commit 95fd934

1 file changed

Lines changed: 54 additions & 39 deletions

File tree

CHANGELOG.md

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,51 @@ Here is a quick summary of the changes necessary to upgrade to LiveView v1.1:
3737
env: %{"NODE_PATH" => [Path.expand("../deps", __DIR__), Mix.Project.build_path()]},
3838
```
3939
40+
## Macro components and colocated hooks
41+
42+
A `Phoenix.Component.MacroComponent` defines a compile-time transformation of a HEEx tag. This can be used to transform a tag and its content into something else, for example to perform compile time syntax highlighting, or even remove tags from the template entirely and write them elsewhere. `Phoenix.LiveView.ColocatedHook` is a macro component that allows you to co-locate LiveView [JavaScript hooks](https://hexdocs.pm/phoenix_live_view/js-interop.html#client-hooks-via-phx-hook) next to the component code that uses them, while ensuring they are included in your regular JavaScript bundle. A colocated hook is defined by placing the special `:type` attribute on a `<script>` tag:
43+
44+
```elixir
45+
alias Phoenix.LiveView.ColocatedHook
46+
47+
def input(%{type: "phone-number"} = assigns) do
48+
~H"""
49+
<input type="text" name={@name} id={@id} value={@value} phx-hook=".PhoneNumber" />
50+
<script :type={ColocatedHook} name=".PhoneNumber">
51+
export default {
52+
mounted() {
53+
this.el.addEventListener("input", e => {
54+
let match = this.el.value.replace(/\D/g, "").match(/^(\d{3})(\d{3})(\d{4})$/)
55+
if(match) {
56+
this.el.value = `${match[1]}-${match[2]}-${match[3]}`
57+
}
58+
})
59+
}
60+
}
61+
</script>
62+
"""
63+
end
64+
```
65+
66+
Important: LiveView now supports the `phx-hook` attribute to start with a dot (`.PhoneNumber` above) for namespacing. Any hook name starting with a dot is prefixed at compile time with the module name of the component. If you named your hooks with a leading dot in the past, you'll need to adjust this for your hooks to work properly on LiveView 1.1.
67+
68+
Colocated hooks are extracted to a `phoenix-colocated` folder inside your `_build/$MIX_ENV` directory (`Mix.Project.build_path()`). See the quick update section at the top of the changelog on how to adjust your `esbuild` configuration to handle this. With everything configured, you can import your colocated hooks inside of your `app.js` like this:
69+
70+
```diff
71+
...
72+
import {LiveSocket} from "phoenix_live_view"
73+
+ import {hooks as colocatedHooks} from "phoenix-colocated/my_app"
74+
import topbar from "../vendor/topbar"
75+
...
76+
const liveSocket = new LiveSocket("/live", Socket, {
77+
longPollFallbackMs: 2500,
78+
+ params: {_csrf_token, csrfToken},
79+
+ hooks: {...colocatedHooks}
80+
})
81+
```
82+
83+
The `phoenix-colocated` folder has subfolders for each application that uses colocated hooks, therefore you'll need to adjust the `my_app` part of the import depending on the name of your project (defined in your `mix.exs`). You can read more about colocated hooks in the module documentation of `Phoenix.LiveView.ColocatedHook` and `Phoenix.LiveView.ColocatedJS`.
84+
4085
## Types for public interfaces
4186

4287
LiveView 1.1 adds official types to the JavaScript client. This allows IntelliSense to work in editors that support it and is a massive improvement to the user experience when writing JavaScript hooks.
@@ -106,50 +151,20 @@ let liveSocket = new LiveSocket(..., {
106151

107152
Using [`@types/phoenix_live_view`](https://www.npmjs.com/package/@types/phoenix_live_view) (not maintained by the Phoenix team) is not necessary any more.
108153

109-
## Macro components and colocated hooks
110-
111-
A `Phoenix.Component.MacroComponent` defines a compile-time transformation of a HEEx tag. This can be used to transform a tag and its content into something else, for example to perform compile time syntax highlighting, or even remove tags from the template entirely and write them to somewhere else. `Phoenix.LiveView.ColocatedHook` is a macro component that allows you to co-locate LiveView [JavaScript hooks](https://hexdocs.pm/phoenix_live_view/js-interop.html#client-hooks-via-phx-hook) next to the component code that uses them, while ensuring they are included in your regular JavaScript bundle. A colocated hook is defined by placing the special `:type` attribute on a `<script>` tag:
154+
## Phoenix.Component.portal
112155

113-
```elixir
114-
alias Phoenix.LiveView.ColocatedHook
156+
When designing reusable HTML components for UI elements like tooltips or dialogs, it is sometimes necessary to render a part of a component's template outside of the regular DOM hierarchy of that component, for example to prevent clipping due to CSS rules like `overflow: hidden` that are not controlled by the component itself. Modern browser APIs for rendering elements in [the top layer](https://developer.mozilla.org/en-US/docs/Glossary/Top_layer) can help in many cases, but if you cannot use those for whatever reasons, LiveView previously did not have a solution to solve that problem. In LiveView 1.1, we introduce a new `Phoenix.Component.portal/1` component:
115157

116-
def input(%{type: "phone-number"} = assigns) do
117-
~H"""
118-
<input type="text" name={@name} id={@id} value={@value} phx-hook=".PhoneNumber" />
119-
<script :type={ColocatedHook} name=".PhoneNumber">
120-
export default {
121-
mounted() {
122-
this.el.addEventListener("input", e => {
123-
let match = this.el.value.replace(/\D/g, "").match(/^(\d{3})(\d{3})(\d{4})$/)
124-
if(match) {
125-
this.el.value = `${match[1]}-${match[2]}-${match[3]}`
126-
}
127-
})
128-
}
129-
}
130-
</script>
131-
"""
132-
end
158+
```heex
159+
<%!-- in some nested LiveView or component --%>
160+
<.portal id="my-element" target="body">
161+
<%!-- any content here will be teleported into the body tag --%>
162+
</.portal>
133163
```
134164

135-
Important: LiveView now supports the `phx-hook` attribute to start with a dot (`.PhoneNumber` above) for namespacing. Any hook name starting with a dot is prefixed at compile time with the module name of the component. If you named your hooks with a leading dot in the past, you'll need to adjust this for your hooks to work properly on LiveView 1.1.
136-
137-
Colocated hooks are extracted to a `phoenix-colocated` folder inside your `_build/$MIX_ENV` directory (`Mix.Project.build_path()`). See the quick update section at the top of the changelog on how to adjust your `esbuild` configuration to handle this. With everything configured, you can import your colocated hooks inside of your `app.js` like this:
138-
139-
```diff
140-
...
141-
import {LiveSocket} from "phoenix_live_view"
142-
+ import {hooks as colocatedHooks} from "phoenix-colocated/my_app"
143-
import topbar from "../vendor/topbar"
144-
...
145-
const liveSocket = new LiveSocket("/live", Socket, {
146-
longPollFallbackMs: 2500,
147-
+ params: {_csrf_token, csrfToken},
148-
+ hooks: {...colocatedHooks}
149-
})
150-
```
165+
Any element can be teleported, even LiveComponents and nested LiveViews, and any `phx-*` events from inside a portal will still be handled by the correct LiveView. This is similar to [`<Teleport>` in Vue.js](https://vuejs.org/guide/built-ins/teleport) or [`createPortal` in React](https://react.dev/reference/react-dom/createPortal).
151166

152-
The `phoenix-colocated` folder has subfolders for each application that uses colocated hooks, therefore you'll need to adjust the `my_app` part of the import depending on the name of your project (defined in your `mix.exs`). You can read more about colocated hooks in the module documentation of `Phoenix.LiveView.ColocatedHook` and `Phoenix.LiveView.ColocatedJS`.
167+
As a demo, we created [an example for implementing tooltips using `Phoenix.Component.portal`](https://gist.github.com/SteffenDE/f599405c7c2eddbb14723ed4f3b7213f) as a single-file Elixir script. When saved as `portal.exs`, you can execute it as `elixir portal.exs` and visit `http://localhost:5001` in your browser.
153168

154169
## JS.ignore_attributes
155170

0 commit comments

Comments
 (0)