> =
[block | static]
|> :erlang.term_to_binary()
@@ -985,6 +1124,12 @@ defmodule Phoenix.LiveView.Engine do
fingerprint
end
+ @doc false
+ defmacro __raise__(special_form, arity) do
+ message = "cannot invoke special form #{special_form}/#{arity} inside HEEx templates"
+ reraise ArgumentError.exception(message), Macro.Env.stacktrace(__CALLER__)
+ end
+
@doc false
defmacro to_safe(ast) do
to_safe(ast, false)
@@ -1010,8 +1155,7 @@ defmodule Phoenix.LiveView.Engine do
# Calls to attributes escape is always safe
defp to_safe(
- {{:., _, [{:__aliases__, _, [:Phoenix, :HTML, :Tag]}, :attributes_escape]}, _, [_]} =
- safe,
+ {{:., _, [{:__aliases__, _, [:Phoenix, :HTML]}, :attributes_escape]}, _, [_]} = safe,
line,
_extra_clauses?
) do
@@ -1050,7 +1194,13 @@ defmodule Phoenix.LiveView.Engine do
end
@doc false
- def changed_assign?(changed, name), do: changed_assign(changed, name) != false
+ def changed_assign?(changed, name) do
+ case changed do
+ %{^name => _} -> true
+ %{} -> false
+ nil -> true
+ end
+ end
defp changed_assign(changed, name) do
case changed do
@@ -1061,14 +1211,14 @@ defmodule Phoenix.LiveView.Engine do
end
@doc false
- def nested_changed_assign?(assigns, changed, head, tail),
- do: nested_changed_assign(assigns, changed, head, tail) != false
+ def nested_changed_assign?(tail, head, assigns, changed),
+ do: nested_changed_assign(tail, head, assigns, changed) != false
- defp nested_changed_assign(assigns, changed, head, tail) do
+ defp nested_changed_assign(tail, head, assigns, changed) do
case changed do
%{^head => changed} ->
case assigns do
- %{^head => assigns} -> recur_changed_assign(assigns, changed, tail)
+ %{^head => assigns} -> recur_changed_assign(tail, assigns, changed)
%{} -> true
end
@@ -1080,19 +1230,23 @@ defmodule Phoenix.LiveView.Engine do
end
end
- defp recur_changed_assign(assigns, changed, [{:struct, head} | tail]) do
- recur_changed_assign(assigns, changed, head, tail)
+ defp recur_changed_assign([{:struct, head} | tail], assigns, changed) do
+ recur_changed_assign(tail, head, assigns, changed)
+ end
+
+ defp recur_changed_assign([{:access, head}], %Form{} = form1, %Form{} = form2) do
+ Form.input_changed?(form1, form2, head)
end
- defp recur_changed_assign(assigns, changed, [{:access, head} | tail]) do
+ defp recur_changed_assign([{:access, head} | tail], assigns, changed) do
if match?(%_{}, assigns) or match?(%_{}, changed) do
true
else
- recur_changed_assign(assigns, changed, head, tail)
+ recur_changed_assign(tail, head, assigns, changed)
end
end
- defp recur_changed_assign(assigns, changed, head, []) do
+ defp recur_changed_assign([], head, assigns, changed) do
case {assigns, changed} do
{%{^head => value}, %{^head => value}} -> false
{_, %{^head => value}} when is_map(value) -> value
@@ -1100,80 +1254,48 @@ defmodule Phoenix.LiveView.Engine do
end
end
- defp recur_changed_assign(assigns, changed, head, tail) do
+ defp recur_changed_assign(tail, head, assigns, changed) do
case {assigns, changed} do
{%{^head => assigns_value}, %{^head => changed_value}} ->
- recur_changed_assign(assigns_value, changed_value, tail)
+ recur_changed_assign(tail, assigns_value, changed_value)
{_, _} ->
true
end
end
- @doc false
- def fetch_assign!(assigns, key) do
- case assigns do
- %{^key => val} ->
- val
-
- %{} when key == :inner_block ->
- raise ArgumentError, """
- assign @#{key} not available in template.
-
- This means a component requires a do-block or HTML children to
- be given as argument but none were given. For example, instead of:
-
- <.component />
-
- You must do:
-
- <.component>
- more content
-
-
- Available assigns: #{inspect(Enum.map(assigns, &elem(&1, 0)))}
- """
-
- %{} ->
- raise ArgumentError, """
- assign @#{key} not available in template.
-
- Please make sure all proper assigns have been set. If you are
- calling a component, make sure you are passing all required
- assigns as arguments.
-
- Available assigns: #{inspect(Enum.map(assigns, &elem(&1, 0)))}
- """
- end
- end
-
- # For case/if/unless, we are not leaking the variable given as argument,
- # such as `if var = ... do`. This does not follow Elixir semantics, but
- # yields better optimizations.
+ # For case/if/unless in particular, we are not leaking the
+ # variables defined in arguments, such as `if var = ... do`.
+ # This does not follow Elixir semantics, but yields better
+ # optimizations.
defp classify_taint(:case, [_, _]), do: :live
defp classify_taint(:if, [_, _]), do: :live
defp classify_taint(:unless, [_, _]), do: :live
defp classify_taint(:cond, [_]), do: :live
defp classify_taint(:try, [_]), do: :live
defp classify_taint(:receive, [_]), do: :live
+
+ # with/for are specially handled during analyze
defp classify_taint(:with, _), do: :live
+ defp classify_taint(:for, _), do: :live
+
+ # Constructs from Phoenix and TagEngine
+ defp classify_taint(:inner_block, [_, [do: _]]), do: :live
+ defp classify_taint(:render_layout, [_, _, _, [do: _]]), do: :live
# TODO: Remove me when live_component/2/3 are removed
- defp classify_taint(:live_component, [_, [do: _]]), do: :render
- defp classify_taint(:live_component, [_, _, [do: _]]), do: :render
- defp classify_taint(:slot, [_, [do: _]]), do: :render
- defp classify_taint(:render_layout, [_, _, _, [do: _]]), do: :render
-
- defp classify_taint(:alias, [_]), do: :always
- defp classify_taint(:import, [_]), do: :always
- defp classify_taint(:require, [_]), do: :always
- defp classify_taint(:alias, [_, _]), do: :always
- defp classify_taint(:import, [_, _]), do: :always
- defp classify_taint(:require, [_, _]), do: :always
+ defp classify_taint(:live_component, [_, [do: _]]), do: :live
+ defp classify_taint(:live_component, [_, _, [do: _]]), do: :live
+
+ # Special forms are forbidden and raise.
+ defp classify_taint(:alias, [_]), do: :special_form
+ defp classify_taint(:import, [_]), do: :special_form
+ defp classify_taint(:require, [_]), do: :special_form
+ defp classify_taint(:alias, [_, _]), do: :special_form
+ defp classify_taint(:import, [_, _]), do: :special_form
+ defp classify_taint(:require, [_, _]), do: :special_form
defp classify_taint(:&, [_]), do: :never
- defp classify_taint(:for, _), do: :never
defp classify_taint(:fn, _), do: :never
-
defp classify_taint(_, _), do: :none
end
diff --git a/lib/phoenix_live_view/helpers.ex b/lib/phoenix_live_view/helpers.ex
index 6941c900f4..13dfec667a 100644
--- a/lib/phoenix_live_view/helpers.ex
+++ b/lib/phoenix_live_view/helpers.ex
@@ -1,12 +1,8 @@
defmodule Phoenix.LiveView.Helpers do
- @moduledoc """
- A collection of helpers to be imported into your views.
- """
-
- # TODO: Convert all functions with the `live_` prefix to function components?
+ @moduledoc false
+ import Phoenix.Component
- alias Phoenix.LiveView
- alias Phoenix.LiveView.{Component, Socket, Static}
+ alias Phoenix.LiveView.{Component, Socket}
@doc """
Provides `~L` sigil with HTML safe Live EEx syntax inside source files.
@@ -29,214 +25,12 @@ defmodule Phoenix.LiveView.Helpers do
EEx.compile_string(expr, options)
end
- @doc ~S'''
- Provides `~H` sigil with HTML-safe and HTML-aware syntax inside source files.
-
- > Note: `HEEx` requires Elixir >= `1.12.0` in order to provide accurate
- > file:line:column information in error messages. Earlier Elixir versions will
- > work but will show inaccurate error messages.
-
- `HEEx` is a HTML-aware and component-friendly extension of `EEx` that provides:
-
- * Built-in handling of HTML attributes
- * An HTML-like notation for injecting function components
- * Compile-time validation of the structure of the template
- * The ability to minimize the amount of data sent over the wire
-
- ## Example
-
- ~H"""
-
- """
-
- ## Syntax
-
- `HEEx` is built on top of Embedded Elixir (`EEx`), a templating syntax that uses
- `<%= ... %>` for interpolating results. In this section, we are going to cover the
- basic constructs in `HEEx` templates as well as its syntax extensions.
-
- ### Interpolation
-
- Both `HEEx` and `EEx` templates use `<%= ... %>` for interpolating code inside the body
- of HTML tags:
-
- Hello, <%= @name %>
-
- Similarly, conditionals and other block Elixir constructs are supported:
-
- <%= if @show_greeting? do %>
- Hello, <%= @name %>
- <% end %>
-
- Note we don't include the equal sign `=` in the closing `<% end %>` tag
- (because the closing tag does not output anything).
-
- There is one important difference between `HEEx` and Elixir's builtin `EEx`.
- `HEEx` uses a specific annotation for interpolating HTML tags and attributes.
- Let's check it out.
-
- ### HEEx extension: Defining attributes
-
- Since `HEEx` must parse and validate the HTML structure, code interpolation using
- `<%= ... %>` and `<% ... %>` are restricted to the body (inner content) of the
- HTML/component nodes and it cannot be applied within tags.
-
- For instance, the following syntax is invalid:
-
-
- ...
-
-
- Instead do:
-
-
- ...
-
-
- You can put any Elixir expression between `{ ... }`. For example, if you want
- to set classes, where some are static and others are dynamic, you can using
- string interpolation:
-
-
- ...
-
-
- For multiple dynamic attributes, you can use the same notation but without
- assigning the expression to any specific attribute.
-
-
- ...
-
-
- The expression inside `{...}` must be either a keyword list or a map containing
- the key-value pairs representing the dynamic attributes.
-
- ### HEEx extension: Defining function components
-
- Function components are stateless components implemented as pure functions
- with the help of the `Phoenix.Component` module. They can be either local
- (same module) or remote (external module).
-
- `HEEx` allows invoking these function components directly in the template
- using an HTML-like notation. For example, a remote function:
-
-
-
- A local function can be invoked with a leading dot:
-
- <.city name="Kraków"/>
-
- where the component could be defined as follows:
-
- defmodule MyApp.Weather do
- use Phoenix.Component
-
- def city(assigns) do
- ~H"""
- The chosen city is: <%= @name %>.
- """
- end
-
- def country(assigns) do
- ~H"""
- The chosen country is: <%= @name %>.
- """
- end
- end
-
- It is typically best to group related functions into a single module, as
- opposed to having many modules with a single `render/1` function. Function
- components support other important features, such as slots. You can learn
- more about components in `Phoenix.Component`.
- '''
- defmacro sigil_H({:<<>>, meta, [expr]}, []) do
- options = [
- engine: Phoenix.LiveView.HTMLEngine,
- file: __CALLER__.file,
- line: __CALLER__.line + 1,
- module: __CALLER__.module,
- indentation: meta[:indentation] || 0
- ]
-
- EEx.compile_string(expr, options)
- end
-
- @doc ~S'''
- Filters the assigns as a list of keywords for use in dynamic tag attributes.
-
- Useful for transforming caller assigns into dynamic attributes while
- stripping reserved keys from the result.
-
- ## Examples
-
- Imagine the following `my_link` component which allows a caller
- to pass a `new_window` assign, along with any other attributes they
- would like to add to the element, such as class, data attributes, etc:
-
- <.my_link href="/" id={@id} new_window={true} class="my-class">Home
-
- We could support the dynamic attributes with the following component:
-
- def my_link(assigns) do
- target = if assigns[:new_window], do: "_blank", else: false
- extra = assigns_to_attributes(assigns, [:new_window])
-
- assigns =
- assigns
- |> Phoenix.LiveView.assign(:target, target)
- |> Phoenix.LiveView.assign(:extra, extra)
-
- ~H"""
-
- <%= render_slot(@inner_block) %>
-
- """
- end
-
- The optional second argument to `assigns_to_attributes` takes a list of keys to exclude
- which will typically be the keys reserved by the component itself which either
- do not belong in the markup, or are already handled explicitly by the component.
- '''
- def assigns_to_attributes(assigns, exclude \\ []) do
- excluded_keys = [:__changed__, :__slot__, :inner_block] ++ exclude
- for {key, val} <- assigns, key not in excluded_keys, into: [], do: {key, val}
- end
-
- @doc false
+ @doc deprecated: "Use link/1 instead"
def live_patch(opts) when is_list(opts) do
live_link("patch", Keyword.fetch!(opts, :do), Keyword.delete(opts, :do))
end
- @doc """
- Generates a link that will patch the current LiveView.
-
- When navigating to the current LiveView,
- `c:Phoenix.LiveView.handle_params/3` is
- immediately invoked to handle the change of params and URL state.
- Then the new state is pushed to the client, without reloading the
- whole page while also maintaining the current scroll position.
- For live redirects to another LiveView, use `live_redirect/2`.
-
- ## Options
-
- * `:to` - the required path to link to.
- * `:replace` - the flag to replace the current history or push a new state.
- Defaults `false`.
-
- All other options are forwarded to the anchor tag.
-
- ## Examples
-
- <%= live_patch "home", to: Routes.page_path(@socket, :index) %>
- <%= live_patch "next", to: Routes.live_path(@socket, MyLive, @page + 1) %>
- <%= live_patch to: Routes.live_path(@socket, MyLive, dir: :asc), replace: false do %>
- Sort By Price
- <% end %>
-
- """
+ @doc deprecated: "Use <.link> instead"
def live_patch(text, opts)
def live_patch(%Socket{}, _) do
@@ -256,41 +50,12 @@ defmodule Phoenix.LiveView.Helpers do
live_link("patch", text, opts)
end
- @doc false
+ @doc deprecated: "Use <.link> instead"
def live_redirect(opts) when is_list(opts) do
live_link("redirect", Keyword.fetch!(opts, :do), Keyword.delete(opts, :do))
end
- @doc """
- Generates a link that will redirect to a new LiveView of the same live session.
-
- The current LiveView will be shut down and a new one will be mounted
- in its place, without reloading the whole page. This can
- also be used to remount the same LiveView, in case you want to start
- fresh. If you want to navigate to the same LiveView without remounting
- it, use `live_patch/2` instead.
-
- *Note*: The live redirects are only supported between two LiveViews defined
- under the same live session. See `Phoenix.LiveView.Router.live_session/3` for
- more details.
-
- ## Options
-
- * `:to` - the required path to link to.
- * `:replace` - the flag to replace the current history or push a new state.
- Defaults `false`.
-
- All other options are forwarded to the anchor tag.
-
- ## Examples
-
- <%= live_redirect "home", to: Routes.page_path(@socket, :index) %>
- <%= live_redirect "next", to: Routes.live_path(@socket, MyLive, @page + 1) %>
- <%= live_redirect to: Routes.live_path(@socket, MyLive, dir: :asc), replace: false do %>
- Sort By Price
- <% end %>
-
- """
+ @doc deprecated: "Use <.link> instead"
def live_redirect(text, opts)
def live_redirect(%Socket{}, _) do
@@ -321,134 +86,11 @@ defmodule Phoenix.LiveView.Helpers do
opts
|> Keyword.update(:data, data, &Keyword.merge(&1, data))
|> Keyword.put(:href, uri)
+ |> Keyword.delete(:to)
- Phoenix.HTML.Tag.content_tag(:a, Keyword.delete(opts, :to), do: block_or_text)
- end
-
- @doc """
- Renders a LiveView within an originating plug request or
- within a parent LiveView.
-
- ## Options
-
- * `:session` - the map of extra session data to be serialized
- and sent to the client. Note that all session data currently in
- the connection is automatically available in LiveViews. You
- can use this option to provide extra data. Also note that the keys
- in the session are strings keys, as a reminder that data has
- to be serialized first.
- * `:container` - an optional tuple for the HTML tag and DOM
- attributes to be used for the LiveView container. For example:
- `{:li, style: "color: blue;"}`. By default it uses the module
- definition container. See the "Containers" section below for more
- information.
- * `:id` - both the DOM ID and the ID to uniquely identify a LiveView.
- An `:id` is automatically generated when rendering root LiveViews
- but it is a required option when rendering a child LiveView.
- * `:router` - an optional router that enables this LiveView to
- perform live navigation. Only a single LiveView in a page may
- have the `:router` set. LiveViews defined at the router with
- the `live` macro automatically have the `:router` option set.
-
- ## Examples
-
- # within eex template
- <%= live_render(@conn, MyApp.ThermostatLive) %>
-
- # within leex template
- <%= live_render(@socket, MyApp.ThermostatLive, id: "thermostat") %>
-
- ## Containers
-
- When a `LiveView` is rendered, its contents are wrapped in a container.
- By default, said container is a `div` tag with a handful of `LiveView`
- specific attributes.
-
- The container can be customized in different ways:
-
- * You can change the default `container` on `use Phoenix.LiveView`:
-
- use Phoenix.LiveView, container: {:tr, id: "foo-bar"}
-
- * You can override the container tag and pass extra attributes when
- calling `live_render` (as well as on your `live` call in your router):
-
- live_render socket, MyLiveView, container: {:tr, class: "highlight"}
-
- """
- def live_render(conn_or_socket, view, opts \\ [])
-
- def live_render(%Plug.Conn{} = conn, view, opts) do
- case Static.render(conn, view, opts) do
- {:ok, content, _assigns} ->
- content
-
- {:stop, _} ->
- raise RuntimeError, "cannot redirect from a child LiveView"
- end
- end
-
- def live_render(%Socket{} = parent, view, opts) do
- Static.nested_render(parent, view, opts)
- end
-
- @doc """
- A function component for rendering `Phoenix.LiveComponent`
- within a parent LiveView.
-
- While `LiveView`s can be nested, each LiveView starts its
- own process. A `LiveComponent` provides similar functionality
- to `LiveView`, except they run in the same process as the
- `LiveView`, with its own encapsulated state. That's why they
- are called stateful components.
-
- See `Phoenix.LiveComponent` for more information.
+ assigns = %{opts: opts, content: block_or_text}
- ## Examples
-
- `.live_component` requires the component `:module` and its
- `:id` to be given:
-
- <.live_component module={MyApp.WeatherComponent} id="thermostat" city="Kraków" />
-
- The `:id` is used to identify this `LiveComponent` throughout the
- LiveView lifecycle. Note the `:id` won't necessarily be used as the
- DOM ID. That's up to the component.
- """
- def live_component(assigns) when is_map(assigns) do
- id = assigns[:id]
-
- {module, assigns} =
- assigns
- |> Map.delete(:__changed__)
- |> Map.pop(:module)
-
- if module == nil or not is_atom(module) do
- raise ArgumentError,
- ".live_component expects module={...} to be given and to be an atom, " <>
- "got: #{inspect(module)}"
- end
-
- if id == nil do
- raise ArgumentError, ".live_component expects id={...} to be given, got: nil"
- end
-
- case module.__live__() do
- %{kind: :component} ->
- %Component{id: id, assigns: assigns, component: module}
-
- %{kind: kind} ->
- raise ArgumentError, "expected #{inspect(module)} to be a component, but it is a #{kind}"
- end
- end
-
- def live_component(component) when is_atom(component) do
- IO.warn(
- "<%= live_component Component %> is deprecated, " <>
- "please use <.live_component module={Component} id=\"hello\" /> inside HEEx templates instead"
- )
-
- Phoenix.LiveView.Helpers.__live_component__(component.__live__(), %{}, nil)
+ ~H|<%= @content %> |
end
@doc """
@@ -464,15 +106,19 @@ defmodule Phoenix.LiveView.Helpers do
2. Then instead of:
- <%= live_component MyModule, id: "hello" do %>
- ...
- <% end %>
+ ```
+ <%= live_component MyModule, id: "hello" do %>
+ ...
+ <% end %>
+ ```
You should do:
- <.live_component module={MyModule} id="hello">
- ...
-
+ ```
+ <.live_component module={MyModule} id="hello">
+ ...
+
+ ```
3. If your component is using `render_block/2`, replace
it by `render_slot/2`
@@ -549,45 +195,13 @@ defmodule Phoenix.LiveView.Helpers do
raise "expected #{inspect(module)} to be a component, but it is a #{kind}"
end
- @doc """
- Renders a component defined by the given function.
-
- This function is rarely invoked directly by users. Instead, it is used by `~H`
- to render `Phoenix.Component`s. For example, the following:
-
-
-
- It the same as:
-
- <%= component(&MyApp.Weather.city/1, name: "Kraków") %>
-
- """
- def component(func, assigns \\ [])
- when (is_function(func, 1) and is_list(assigns)) or is_map(assigns) do
- assigns =
- case assigns do
- %{__changed__: _} -> assigns
- _ -> assigns |> Map.new() |> Map.put_new(:__changed__, nil)
- end
-
- case func.(assigns) do
- %Phoenix.LiveView.Rendered{} = rendered ->
- rendered
-
- %Phoenix.LiveView.Component{} = component ->
- component
-
- other ->
- raise RuntimeError, """
- expected #{inspect(func)} to return a %Phoenix.LiveView.Rendered{} struct
-
- Ensure your render function uses ~H to define its template.
-
- Got:
-
- #{inspect(other)}
-
- """
+ defp rewrite_do!(do_block, key, caller) do
+ if Macro.Env.has_var?(caller, {:assigns, nil}) do
+ # TODO: make __inner_block__ private once this is removed.
+ Phoenix.LiveView.TagEngine.__inner_block__(do_block, key)
+ else
+ raise ArgumentError,
+ "cannot use live_component because the assigns var is unbound/unset"
end
end
@@ -613,300 +227,16 @@ defmodule Phoenix.LiveView.Helpers do
def __render_block__([%{inner_block: fun}]), do: fun
def __render_block__(fun), do: fun
- @doc ~S'''
- Renders a slot entry with the given optional `argument`.
-
- <%= render_slot(@inner_block, @form) %>
-
- If multiple slot entries are defined for the same slot,
- `render_slot/2` will automatically render all entries,
- merging their contents. In case you want to use the entries'
- attributes, you need to iterate over the list to access each
- slot individually.
-
- For example, imagine a table component:
-
- <.table rows={@users}>
- <:col let={user} label="Name">
- <%= user.name %>
-
-
- <:col let={user} label="Address">
- <%= user.address %>
-
-
-
- At the top level, we pass the rows as an assign and we define
- a `:col` slot for each column we want in the table. Each
- column also has a `label`, which we are going to use in the
- table header.
-
- Inside the component, you can render the table with headers,
- rows, and columns:
-
- def table(assigns) do
- ~H"""
-
-
- <%= for col <- @col do %>
- <%= col.label %>
- <% end >
-
- <%= for row <- @rows do %>
-
- <%= for col <- @col do %>
- <%= render_slot(col, row) %>
- <% end %>
-
- <% end %>
-
- """
- end
-
- '''
- defmacro render_slot(slot, argument \\ nil) do
- quote do
- unquote(__MODULE__).__render_slot__(
- var!(changed, Phoenix.LiveView.Engine),
- unquote(slot),
- unquote(argument)
- )
- end
- end
-
- @doc false
- def __render_slot__(_, [], _), do: ""
-
- def __render_slot__(changed, [entry], argument) do
- call_inner_block!(entry, changed, argument)
- end
-
- def __render_slot__(changed, entries, argument) when is_list(entries) do
- assigns = %{}
-
- ~H"""
- <%= for entry <- entries do %><%= call_inner_block!(entry, changed, argument) %><% end %>
- """
- end
-
- def __render_slot__(changed, entry, argument) when is_map(entry) do
- entry.inner_block.(changed, argument)
- end
-
- defp call_inner_block!(entry, changed, 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.(changed, argument)
- end
-
- @doc """
- Defines a slot's inner block.
-
- This macro is mostly used by HTML engines that provides
- a `slot` implementation and rarely called directly.
-
- If you're using HEEx templates, you should use its higher
- level `<:slot>` notation instead. See `Phoenix.Component`
- for more information.
- """
- defmacro slot(name, do: do_block) do
- rewrite_do!(do_block, name, __CALLER__)
- end
-
- defp rewrite_do!(do_block, key, caller) do
- if Macro.Env.has_var?(caller, {:assigns, nil}) do
- rewrite_do(do_block, key)
- else
- raise ArgumentError,
- "cannot use component/live_component because the assigns var is unbound/unset"
- end
- end
-
- defp rewrite_do([{:->, meta, _} | _] = do_block, key) do
- inner_fun = {:fn, meta, do_block}
-
- quote do
- fn parent_changed, arg ->
- var!(assigns) =
- unquote(__MODULE__).__assigns__(var!(assigns), unquote(key), parent_changed)
-
- _ = var!(assigns)
- unquote(inner_fun).(arg)
- end
- end
- end
-
- defp rewrite_do(do_block, key) do
- quote do
- fn parent_changed, arg ->
- var!(assigns) =
- unquote(__MODULE__).__assigns__(var!(assigns), unquote(key), parent_changed)
-
- _ = var!(assigns)
- unquote(do_block)
- end
- end
- end
-
- @doc false
- def __assigns__(assigns, key, parent_changed) do
- # If the component is in its initial render (parent_changed == nil)
- # or the slot/block key in the parent_changed is true, then we render
- # the function with the assigns as is.
- #
- # Otherwise, we will set changed to an empty list, which is the same
- # as marking everything as not changed. This is correct because
- # parent_changed will always be marked as changed whenever any of the
- # assigns it references inside is changed. It will also be marked as
- # changed if it has any variable (such as the ones coming from let).
- if is_nil(parent_changed) or parent_changed[key] == true do
- assigns
- else
- Map.put(assigns, :__changed__, %{})
- end
- end
-
- @doc """
- Returns the flash message from the LiveView flash assign.
-
- ## Examples
-
- <%= live_flash(@flash, :info) %>
- <%= live_flash(@flash, :error) %>
- """
- def live_flash(%_struct{} = other, _key) do
- raise ArgumentError, "live_flash/2 expects a @flash assign, got: #{inspect(other)}"
+ @doc deprecated: "Use <.live_img_preview /> instead"
+ def live_img_preview(entry, opts) do
+ live_img_preview(Enum.into(opts, %{entry: entry}))
end
- def live_flash(%{} = flash, key), do: Map.get(flash, to_string(key))
-
- @doc """
- Returns the entry errors for an upload.
-
- The following errors may be returned:
-
- * `:too_many_files` - The number of selected files exceeds the `:max_entries` constraint
-
- ## Examples
-
- def error_to_string(:too_many_files), do: "You have selected too many files"
-
- <%= for err <- upload_errors(@uploads.avatar) do %>
-
- <%= error_to_string(err) %>
-
- <% end %>
- """
- def upload_errors(%Phoenix.LiveView.UploadConfig{} = conf) do
- for {ref, error} <- conf.errors, ref == conf.ref, do: error
- end
-
- @doc """
- Returns the entry errors for an upload.
-
- The following errors may be returned:
-
- * `:too_large` - The entry exceeds the `:max_file_size` constraint
- * `:not_accepted` - The entry does not match the `:accept` MIME types
-
- ## Examples
-
- def error_to_string(:too_large), do: "Too large"
- def error_to_string(:not_accepted), do: "You have selected an unacceptable file type"
-
- <%= for entry <- @uploads.avatar.entries do %>
- <%= for err <- upload_errors(@uploads.avatar, entry) do %>
-
- <%= error_to_string(err) %>
-
- <% end %>
- <% end %>
- """
- def upload_errors(
- %Phoenix.LiveView.UploadConfig{} = conf,
- %Phoenix.LiveView.UploadEntry{} = entry
- ) do
- for {ref, error} <- conf.errors, ref == entry.ref, do: error
- end
-
- @doc """
- Generates an image preview on the client for a selected file.
-
- ## Examples
-
- <%= for entry <- @uploads.avatar.entries do %>
- <%= live_img_preview entry, width: 75 %>
- <% end %>
- """
- def live_img_preview(%Phoenix.LiveView.UploadEntry{ref: ref} = entry, opts \\ []) do
- attrs =
- Keyword.merge(opts,
- id: "phx-preview-#{ref}",
- data_phx_upload_ref: entry.upload_ref,
- data_phx_entry_ref: ref,
- data_phx_hook: "Phoenix.LiveImgPreview",
- data_phx_update: "ignore"
- )
-
- assigns = LiveView.assign(%{__changed__: nil}, attrs: attrs)
-
- ~H" "
- end
-
- @doc """
- Builds a file input tag for a LiveView upload.
-
- Options may be passed through to the tag builder for custom attributes.
-
- ## Drag and Drop
-
- Drag and drop is supported by annotating the droppable container with a `phx-drop-target`
- attribute pointing to the DOM ID of the file input. By default, the file input ID is the
- upload `ref`, so the following markup is all that is required for drag and drop support:
-
-
- ...
- <%= live_file_input @uploads.avatar %>
-
-
- ## Examples
-
- <%= live_file_input @uploads.avatar %>
- """
- def live_file_input(%Phoenix.LiveView.UploadConfig{} = conf, opts \\ []) do
- if opts[:id], do: raise(ArgumentError, "the :id cannot be overridden on a live_file_input")
-
- opts =
- if conf.max_entries > 1 do
- Keyword.put(opts, :multiple, true)
- else
- opts
- end
-
- preflighted_entries = for entry <- conf.entries, entry.preflighted?, do: entry
- done_entries = for entry <- conf.entries, entry.done?, do: entry
- valid? = Enum.any?(conf.entries) && Enum.empty?(conf.errors)
-
- Phoenix.HTML.Tag.content_tag(
- :input,
- "",
- Keyword.merge(opts,
- type: "file",
- id: conf.ref,
- name: conf.name,
- accept: if(conf.accept != :any, do: conf.accept),
- phx_hook: "Phoenix.LiveFileUpload",
- data_phx_update: "ignore",
- data_phx_upload_ref: conf.ref,
- data_phx_active_refs: Enum.map_join(conf.entries, ",", & &1.ref),
- data_phx_done_refs: Enum.map_join(done_entries, ",", & &1.ref),
- data_phx_preflighted_refs: Enum.map_join(preflighted_entries, ",", & &1.ref),
- data_phx_auto_upload: valid? && conf.auto_upload?
- )
- )
+ @doc deprecated: "Use <.live_file_input /> instead"
+ def live_file_input(%Phoenix.LiveView.UploadConfig{} = conf, opts) when is_list(opts) do
+ require Phoenix.LiveViewTest
+ assigns = Enum.into(opts, %{upload: conf})
+ {:safe, Phoenix.LiveViewTest.render_component(&live_file_input/1, assigns)}
end
@doc """
@@ -914,143 +244,22 @@ defmodule Phoenix.LiveView.Helpers do
## Examples
- <%= live_title_tag assigns[:page_title] || "Welcome", prefix: "MyApp – " %>
+ <%= live_title_tag assigns[:page_title] || "Welcome", prefix: "MyApp – " %>
- <%= live_title_tag assigns[:page_title] || "Welcome", suffix: " – MyApp" %>
+ <%= live_title_tag assigns[:page_title] || "Welcome", suffix: " – MyApp" %>
"""
+ @doc deprecated: "Use <.live_title> instead"
def live_title_tag(title, opts \\ []) do
- title_tag(title, opts[:prefix], opts[:suffix], opts)
- end
-
- defp title_tag(title, nil = _prefix, "" <> suffix, _opts) do
- Phoenix.HTML.Tag.content_tag(:title, title <> suffix, data: [suffix: suffix])
- end
-
- defp title_tag(title, "" <> prefix, nil = _suffix, _opts) do
- Phoenix.HTML.Tag.content_tag(:title, prefix <> title, data: [prefix: prefix])
- end
-
- defp title_tag(title, "" <> pre, "" <> post, _opts) do
- Phoenix.HTML.Tag.content_tag(:title, pre <> title <> post, data: [prefix: pre, suffix: post])
- end
-
- defp title_tag(title, _prefix = nil, _postfix = nil, []) do
- Phoenix.HTML.Tag.content_tag(:title, title)
- end
-
- defp title_tag(_title, _prefix = nil, _suffix = nil, opts) do
- raise ArgumentError,
- "live_title_tag/2 expects a :prefix and/or :suffix option, got: #{inspect(opts)}"
- end
-
- @doc """
- Renders a form function component.
-
- This function is built on top of `Phoenix.HTML.Form.form_for/4`. For
- more information about options and how to build inputs, see
- `Phoenix.HTML.Form`.
-
- ## Options
-
- The `:for` assign is the form's source data and the optional `:action`
- assign can be provided for the form's action. Additionally accepts
- the same options as `Phoenix.HTML.Form.form_for/4` as optional assigns:
-
- * `:as` - the server side parameter in which all params for this
- form will be collected (i.e. `as: :user_params` would mean all fields
- for this form will be accessed as `conn.params.user_params` server
- side). Automatically inflected when a changeset is given.
-
- * `:method` - the HTTP method. If the method is not "get" nor "post",
- an input tag with name `_method` is generated along-side the form tag.
- Defaults to "post".
-
- * `:multipart` - when true, sets enctype to "multipart/form-data".
- Required when uploading files
-
- * `:csrf_token` - for "post" requests, the form tag will automatically
- include an input tag with name `_csrf_token`. When set to false, this
- is disabled
-
- * `:errors` - use this to manually pass a keyword list of errors to the form
- (for example from `conn.assigns[:errors]`). This option is only used when a
- connection is used as the form source and it will make the errors available
- under `f.errors`
-
- * `:id` - the ID of the form attribute. If an ID is given, all form inputs
- will also be prefixed by the given ID
-
- All further assigns will be passed to the form tag.
-
- ## Examples
-
- <.form let={f} for={@changeset}>
- <%= text_input f, :name %>
-
-
- <.form let={user_form} for={@changeset} as="user" multipart {@extra}>
- <%= text_input user_form, :name %>
-
- """
- def form(assigns) do
- # Extract options and then to the same call as form_for
- action = assigns[:action] || "#"
- form_for = assigns[:for] || raise ArgumentError, "missing :for assign to form"
- form_options = assigns_to_attributes(assigns, [:action, :for])
-
- # Since FormData may add options, read the actual options from form
- %{options: opts} =
- form = %Phoenix.HTML.Form{
- Phoenix.HTML.FormData.to_form(form_for, form_options)
- | action: action
- }
-
- # And then process method, csrf_token, and multipart as in form_tag
- {method, opts} = Keyword.pop(opts, :method, "post")
- {method, hidden_method} = form_method(method)
-
- {csrf_token, opts} =
- Keyword.pop_lazy(opts, :csrf_token, fn ->
- if method == "post", do: Phoenix.HTML.Tag.csrf_token_value(action)
- end)
-
- opts =
- case Keyword.pop(opts, :multipart, false) do
- {false, opts} -> opts
- {true, opts} -> Keyword.put(opts, :enctype, "multipart/form-data")
- end
-
- # Finally we can render the form
- assigns =
- LiveView.assign(assigns,
- form: form,
- csrf_token: csrf_token,
- hidden_method: hidden_method,
- attrs: [action: action, method: method] ++ opts
- )
+ assigns = %{title: title, prefix: opts[:prefix], suffix: opts[:suffix]}
~H"""
-
+ <%= @title %>
"""
end
- defp form_method(method) when method in ~w(get post), do: {method, nil}
- defp form_method(method) when is_binary(method), do: {"post", method}
-
defp is_assign?(assign_name, expression) do
match?({:@, _, [{^assign_name, _, _}]}, expression) or
match?({^assign_name, _, _}, expression) or
- match?(
- {{:., _, [Phoenix.LiveView.Engine, :fetch_assign!]}, _, [{:assigns, _, _}, ^assign_name]},
- expression
- )
+ match?({{:., _, [{:assigns, _, nil}, ^assign_name]}, _, []}, expression)
end
end
diff --git a/lib/phoenix_live_view/html_algebra.ex b/lib/phoenix_live_view/html_algebra.ex
new file mode 100644
index 0000000000..82e42bfca4
--- /dev/null
+++ b/lib/phoenix_live_view/html_algebra.ex
@@ -0,0 +1,566 @@
+defmodule Phoenix.LiveView.HTMLAlgebra do
+ @moduledoc false
+
+ import Inspect.Algebra, except: [format: 2]
+
+ # TODO: Remove it after versions before Elixir 1.13 are no longer supported.
+ @compile {:no_warn_undefined, Code}
+
+ @languages ~w(style script)
+
+ # The formatter has two modes:
+ #
+ # * :normal
+ # * :preserve - for preserving text in , " <> rest, line, column, buffer, acc, state) do
acc = [
- {:tag_close, "script", %{line: line, column: column}}
- | text_to_acc(buffer, acc, line, column)
+ {:close, :tag, "script", %{line: line, column: column, inner_location: {line, column}}}
+ | text_to_acc(buffer, acc, line, column, [])
]
handle_text(rest, line, column + 9, [], acc, state)
@@ -134,29 +202,69 @@ defmodule Phoenix.LiveView.HTMLTokenizer do
end
defp handle_script(<<>>, line, column, buffer, acc, _state) do
- ok(text_to_acc(buffer, acc, line, column), :script)
+ ok(text_to_acc(buffer, acc, line, column, []), :script)
+ end
+
+ ## handle_style
+
+ defp handle_style("" <> rest, line, column, buffer, acc, state) do
+ acc = [
+ {:close, :tag, "style", %{line: line, column: column, inner_location: {line, column}}}
+ | text_to_acc(buffer, acc, line, column, [])
+ ]
+
+ handle_text(rest, line, column + 9, [], acc, state)
+ end
+
+ defp handle_style("\r\n" <> rest, line, _column, buffer, acc, state) do
+ handle_style(rest, line + 1, state.column_offset, ["\r\n" | buffer], acc, state)
+ end
+
+ defp handle_style("\n" <> rest, line, _column, buffer, acc, state) do
+ handle_style(rest, line + 1, state.column_offset, ["\n" | buffer], acc, state)
+ end
+
+ defp handle_style(<>, line, column, buffer, acc, state) do
+ handle_style(rest, line, column + 1, [char_or_bin(c) | buffer], acc, state)
+ end
+
+ defp handle_style(<<>>, line, column, buffer, acc, _state) do
+ ok(text_to_acc(buffer, acc, line, column, []), :style)
end
## handle_comment
- defp handle_comment("\r\n" <> rest, line, _column, buffer, acc, state) do
- handle_comment(rest, line + 1, state.column_offset, ["\r\n" | buffer], acc, state)
+ defp handle_comment(rest, line, column, buffer, acc, state) do
+ case handle_comment(rest, line, column, buffer, state) do
+ {:text, rest, line, column, buffer} ->
+ state = update_in(state.context, &[:comment_end | &1])
+ handle_text(rest, line, column, buffer, acc, state)
+
+ {:ok, line_end, column_end, buffer} ->
+ acc = text_to_acc(buffer, acc, line_end, column_end, state.context)
+ # We do column - 4 to point to the opening " <> rest, line, column, buffer, acc, state) do
- handle_text(rest, line, column + 3, ["-->" | buffer], acc, state)
+ defp handle_comment("-->" <> rest, line, column, buffer, _state) do
+ {:text, rest, line, column + 3, ["-->" | buffer]}
end
- defp handle_comment(<>, line, column, buffer, acc, state) do
- handle_comment(rest, line, column + 1, [char_or_bin(c) | buffer], acc, state)
+ defp handle_comment(<>, line, column, buffer, state) do
+ handle_comment(rest, line, column + 1, [char_or_bin(c) | buffer], state)
end
- defp handle_comment(<<>>, line, column, buffer, acc, _state) do
- ok(text_to_acc(buffer, acc, line, column), {:comment, line, column})
+ defp handle_comment(<<>>, line, column, buffer, _state) do
+ {:ok, line, column, buffer}
end
## handle_tag_open
@@ -164,12 +272,24 @@ defmodule Phoenix.LiveView.HTMLTokenizer do
defp handle_tag_open(text, line, column, acc, state) do
case handle_tag_name(text, column, []) do
{:ok, name, new_column, rest} ->
- acc = if strip_tag?(name), do: strip_text_token_partially(acc), else: acc
- acc = [{:tag_open, name, [], %{line: line, column: column - 1}} | acc]
- handle_maybe_tag_open_end(rest, line, new_column, acc, state)
+ meta = %{line: line, column: column - 1, inner_location: nil, tag_name: name}
- {:error, message} ->
- raise ParseError, file: state.file, line: line, column: column, description: message
+ case state.tag_handler.classify_type(name) do
+ {:error, message} ->
+ raise_syntax_error!(message, meta, state)
+
+ {type, name} ->
+ acc = [{type, name, [], meta} | acc]
+ handle_maybe_tag_open_end(rest, line, new_column, acc, state)
+ end
+
+ :error ->
+ message =
+ "expected tag name after <. If you meant to use < as part of a text, use < instead"
+
+ meta = %{line: line, column: column}
+
+ raise_syntax_error!(message, meta, state)
end
end
@@ -178,16 +298,31 @@ defmodule Phoenix.LiveView.HTMLTokenizer do
defp handle_tag_close(text, line, column, acc, state) do
case handle_tag_name(text, column, []) do
{:ok, name, new_column, ">" <> rest} ->
- acc = [{:tag_close, name, %{line: line, column: column - 2}} | acc]
- rest = if strip_tag?(name), do: String.trim_leading(rest), else: rest
- handle_text(rest, line, new_column + 1, [], acc, state)
+ meta = %{
+ line: line,
+ column: column - 2,
+ inner_location: {line, column - 2},
+ tag_name: name
+ }
+
+ case state.tag_handler.classify_type(name) do
+ {:error, message} ->
+ raise_syntax_error!(message, meta, state)
+
+ {type, name} ->
+ acc = [{:close, type, name, meta} | acc]
+ handle_text(rest, line, new_column + 1, [], acc, state)
+ end
{:ok, _, new_column, _} ->
message = "expected closing `>`"
- raise ParseError, file: state.file, line: line, column: new_column, description: message
+ meta = %{line: line, column: new_column}
+ raise_syntax_error!(message, meta, state)
- {:error, message} ->
- raise ParseError, file: state.file, line: line, column: column, description: message
+ :error ->
+ message = "expected tag name after "
+ meta = %{line: line, column: column}
+ raise_syntax_error!(message, meta, state)
end
end
@@ -207,7 +342,7 @@ defmodule Phoenix.LiveView.HTMLTokenizer do
end
defp done_tag_name(_text, _column, []) do
- {:error, "expected tag name"}
+ :error
end
defp done_tag_name(text, column, buffer) do
@@ -230,15 +365,18 @@ defmodule Phoenix.LiveView.HTMLTokenizer do
end
defp handle_maybe_tag_open_end("/>" <> rest, line, column, acc, state) do
- acc = reverse_attrs(acc)
+ acc = reverse_attrs(acc, line, column + 2)
handle_text(rest, line, column + 2, [], put_self_close(acc), state)
end
defp handle_maybe_tag_open_end(">" <> rest, line, column, acc, state) do
- case reverse_attrs(acc) do
- [{:tag_open, "script", _, _} | _] = acc ->
+ case reverse_attrs(acc, line, column + 1) do
+ [{:tag, "script", _, _} | _] = acc ->
handle_script(rest, line, column + 1, [], acc, state)
+ [{:tag, "style", _, _} | _] = acc ->
+ handle_style(rest, line, column + 1, [], acc, state)
+
acc ->
handle_text(rest, line, column + 1, [], acc, state)
end
@@ -286,11 +424,12 @@ defmodule Phoenix.LiveView.HTMLTokenizer do
defp handle_attribute(text, line, column, acc, state) do
case handle_attr_name(text, column, []) do
{:ok, name, new_column, rest} ->
- acc = put_attr(acc, name)
+ acc = put_attr(acc, name, %{line: line, column: column})
handle_maybe_attr_value(rest, line, new_column, acc, state)
{:error, message, column} ->
- raise ParseError, file: state.file, line: line, column: column, description: message
+ meta = %{line: line, column: column}
+ raise_syntax_error!(message, meta, state)
end
end
@@ -299,11 +438,14 @@ defmodule Phoenix.LiveView.HTMLTokenizer do
defp handle_root_attribute(text, line, column, acc, state) do
case handle_interpolation(text, line, column, [], state) do
{:ok, value, new_line, new_column, rest, state} ->
- acc = put_attr(acc, :root, {:expr, value, %{line: line, column: column}})
+ meta = %{line: line, column: column}
+ acc = put_attr(acc, :root, meta, {:expr, value, meta})
handle_maybe_tag_open_end(rest, new_line, new_column, acc, state)
- {:error, message, line, column} ->
- raise ParseError, file: state.file, line: line, column: column, description: message
+ {:error, message} ->
+ # We do column - 1 to point to the opening {
+ meta = %{line: line, column: column - 1}
+ raise_syntax_error!(message, meta, state)
end
end
@@ -383,7 +525,8 @@ defmodule Phoenix.LiveView.HTMLTokenizer do
"invalid attribute value after `=`. Expected either a value between quotes " <>
"(such as \"value\" or \'value\') or an Elixir expression between curly brackets (such as `{expr}`)"
- raise ParseError, file: state.file, line: line, column: column, description: message
+ meta = %{line: line, column: column}
+ raise_syntax_error!(message, meta, state)
end
## handle_attr_value_quote
@@ -427,7 +570,8 @@ defmodule Phoenix.LiveView.HTMLTokenizer do
Where @some_attributes must be a keyword list or a map.
"""
- raise ParseError, file: state.file, line: line, column: column, description: message
+ meta = %{line: line, column: column}
+ raise_syntax_error!(message, meta, state)
end
## handle_attr_value_as_expr
@@ -438,8 +582,10 @@ defmodule Phoenix.LiveView.HTMLTokenizer do
acc = put_attr_value(acc, {:expr, value, %{line: line, column: column}})
handle_maybe_tag_open_end(rest, new_line, new_column, acc, state)
- {:error, message, line, column} ->
- raise ParseError, file: state.file, line: line, column: column, description: message
+ {:error, message} ->
+ # We do column - 1 to point to the opening {
+ meta = %{line: line, column: column - 1}
+ raise_syntax_error!(message, meta, state)
end
end
@@ -480,8 +626,8 @@ defmodule Phoenix.LiveView.HTMLTokenizer do
handle_interpolation(rest, line, column + 1, [char_or_bin(c) | buffer], state)
end
- defp handle_interpolation(<<>>, line, column, _buffer, _state) do
- {:error, "expected closing `}` for expression", line, column}
+ defp handle_interpolation(<<>>, _line, _column, _buffer, _state) do
+ {:error, "expected closing `}` for expression"}
end
## helpers
@@ -496,30 +642,49 @@ defmodule Phoenix.LiveView.HTMLTokenizer do
IO.iodata_to_binary(Enum.reverse(buffer))
end
- defp text_to_acc([], acc, _line, _column),
+ defp text_to_acc(buffer, acc, line, column, context)
+
+ defp text_to_acc([], acc, _line, _column, _context),
do: acc
- defp text_to_acc(buffer, acc, line, column),
- do: [{:text, buffer_to_string(buffer), %{line_end: line, column_end: column}} | acc]
+ defp text_to_acc(buffer, acc, line, column, context) do
+ meta = %{line_end: line, column_end: column}
- defp put_attr([{:tag_open, name, attrs, meta} | acc], attr, value \\ nil) do
- attrs = [{attr, value} | attrs]
- [{:tag_open, name, attrs, meta} | acc]
+ meta =
+ if context = get_context(context) do
+ Map.put(meta, :context, trim_context(context))
+ else
+ meta
+ end
+
+ [{:text, buffer_to_string(buffer), meta} | acc]
+ end
+
+ defp trim_context([:comment_start, :comment_end | [_ | _] = rest]), do: trim_context(rest)
+ defp trim_context(rest), do: rest
+
+ defp get_context([]), do: nil
+ defp get_context(context), do: Enum.reverse(context)
+
+ defp put_attr([{type, name, attrs, meta} | acc], attr, attr_meta, value \\ nil) do
+ attrs = [{attr, value, attr_meta} | attrs]
+ [{type, name, attrs, meta} | acc]
end
- defp put_attr_value([{:tag_open, name, [{attr, _value} | attrs], meta} | acc], value) do
- attrs = [{attr, value} | attrs]
- [{:tag_open, name, attrs, meta} | acc]
+ defp put_attr_value([{type, name, [{attr, _value, attr_meta} | attrs], meta} | acc], value) do
+ attrs = [{attr, value, attr_meta} | attrs]
+ [{type, name, attrs, meta} | acc]
end
- defp reverse_attrs([{:tag_open, name, attrs, meta} | acc]) do
+ defp reverse_attrs([{type, name, attrs, meta} | acc], line, column) do
attrs = Enum.reverse(attrs)
- [{:tag_open, name, attrs, meta} | acc]
+ meta = %{meta | inner_location: {line, column}}
+ [{type, name, attrs, meta} | acc]
end
- defp put_self_close([{:tag_open, name, attrs, meta} | acc]) do
+ defp put_self_close([{type, name, attrs, meta} | acc]) do
meta = Map.put(meta, :self_close, true)
- [{:tag_open, name, attrs, meta} | acc]
+ [{type, name, attrs, meta} | acc]
end
defp push_brace(state, pos) do
@@ -530,10 +695,6 @@ defmodule Phoenix.LiveView.HTMLTokenizer do
{pos, %{state | braces: braces}}
end
- # Strip space before slots
- defp strip_tag?(":" <> _), do: true
- defp strip_tag?(_), do: false
-
defp strip_text_token_fully(tokens) do
with [{:text, text, _} | rest] <- tokens,
"" <- String.trim_leading(text) do
@@ -543,14 +704,11 @@ defmodule Phoenix.LiveView.HTMLTokenizer do
end
end
- defp strip_text_token_partially(tokens) do
- with [{:text, text, meta} | rest] <- tokens do
- case String.trim_leading(text) do
- "" -> strip_text_token_partially(rest)
- text -> [{:text, text, meta} | rest]
- end
- else
- _ -> tokens
- end
+ defp raise_syntax_error!(message, meta, state) do
+ raise ParseError,
+ file: state.file,
+ line: meta.line,
+ column: meta.column,
+ description: message <> ParseError.code_snippet(state.source, meta, state.indentation)
end
end
diff --git a/lib/phoenix_live_view/upload.ex b/lib/phoenix_live_view/upload.ex
index fd472b80d8..06707a63e5 100644
--- a/lib/phoenix_live_view/upload.ex
+++ b/lib/phoenix_live_view/upload.ex
@@ -9,7 +9,8 @@ defmodule Phoenix.LiveView.Upload do
@doc """
Allows an upload.
"""
- def allow_upload(%Socket{} = socket, name, opts) when is_atom(name) and is_list(opts) do
+ def allow_upload(%Socket{} = socket, name, opts)
+ when (is_atom(name) or is_binary(name)) and is_list(opts) do
case uploaded_entries(socket, name) do
{[], []} ->
:ok
@@ -37,7 +38,7 @@ defmodule Phoenix.LiveView.Upload do
@doc """
Disallows a previously allowed upload.
"""
- def disallow_upload(%Socket{} = socket, name) when is_atom(name) do
+ def disallow_upload(%Socket{} = socket, name) when is_atom(name) or is_binary(name) do
case uploaded_entries(socket, name) do
{[], []} ->
uploads = socket.assigns[:uploads] || %{}
@@ -70,11 +71,16 @@ defmodule Phoenix.LiveView.Upload do
"""
def cancel_upload(socket, name, entry_ref) do
upload_config = Map.fetch!(socket.assigns[:uploads] || %{}, name)
- %UploadEntry{} = entry = UploadConfig.get_entry_by_ref(upload_config, entry_ref)
- upload_config
- |> UploadConfig.cancel_entry(entry)
- |> update_uploads(socket)
+ case UploadConfig.get_entry_by_ref(upload_config, entry_ref) do
+ %UploadEntry{} = entry ->
+ upload_config
+ |> UploadConfig.cancel_entry(entry)
+ |> update_uploads(socket)
+
+ _ ->
+ raise ArgumentError, "no entry in upload \"#{inspect(name)}\" with ref \"#{entry_ref}\""
+ end
end
@doc """
@@ -279,16 +285,43 @@ defmodule Phoenix.LiveView.Upload do
|> Enum.map(fn entry ->
meta = Map.fetch!(conf.entry_refs_to_metas, entry.ref)
- cond do
- is_function(func, 1) -> func.(meta)
- is_function(func, 2) -> func.(meta, entry)
+ result =
+ cond do
+ is_function(func, 1) -> func.(meta)
+ is_function(func, 2) -> func.(meta, entry)
+ end
+
+ case result do
+ {:ok, return} ->
+ {entry.ref, return}
+
+ {:postpone, return} ->
+ {:postpone, return}
+
+ return ->
+ IO.warn("""
+ consuming uploads requires a return signature matching:
+
+ {:ok, value} | {:postpone, value}
+
+ got:
+
+ #{inspect(return)}
+ """)
+
+ {entry.ref, return}
end
end)
- entry_refs = for entry <- entries, do: entry.ref
- Phoenix.LiveView.Channel.drop_upload_entries(conf, entry_refs)
+ consumed_refs =
+ Enum.flat_map(results, fn
+ {:postpone, _result} -> []
+ {ref, _result} -> [ref]
+ end)
+
+ Phoenix.LiveView.Channel.drop_upload_entries(conf, consumed_refs)
- results
+ Enum.map(results, fn {_ref, result} -> result end)
else
entries
|> Enum.map(fn entry -> {entry, UploadConfig.entry_pid(conf, entry)} end)
diff --git a/lib/phoenix_live_view/upload_channel.ex b/lib/phoenix_live_view/upload_channel.ex
index f02571adf4..dace0c458c 100644
--- a/lib/phoenix_live_view/upload_channel.ex
+++ b/lib/phoenix_live_view/upload_channel.ex
@@ -15,12 +15,37 @@ defmodule Phoenix.LiveView.UploadChannel do
case GenServer.call(pid, :consume_start, @timeout) do
{:ok, file_meta} ->
try do
- cond do
- is_function(func, 1) -> func.(file_meta)
- is_function(func, 2) -> func.(file_meta, entry)
+ result =
+ cond do
+ is_function(func, 1) -> func.(file_meta)
+ is_function(func, 2) -> func.(file_meta, entry)
+ end
+
+ case result do
+ {:ok, return} ->
+ GenServer.call(pid, :consume_done, @timeout)
+ return
+
+ {:postpone, return} ->
+ return
+
+ return ->
+ IO.warn """
+ consuming uploads requires a return signature matching:
+
+ {:ok, value} | {:postpone, value}
+
+ got:
+
+ #{inspect(return)}
+ """
+ GenServer.call(pid, :consume_done, @timeout)
+ return
end
- after
- GenServer.call(pid, :consume_done, @timeout)
+ rescue
+ exception ->
+ GenServer.call(pid, :consume_done, @timeout)
+ reraise(exception, __STACKTRACE__)
end
{:error, :in_progress} ->
diff --git a/lib/phoenix_live_view/upload_config.ex b/lib/phoenix_live_view/upload_config.ex
index f7aaa5267f..d219a5063f 100644
--- a/lib/phoenix_live_view/upload_config.ex
+++ b/lib/phoenix_live_view/upload_config.ex
@@ -15,6 +15,7 @@ defmodule Phoenix.LiveView.UploadEntry do
done?: false,
cancelled?: false,
client_name: nil,
+ client_relative_path: nil,
client_size: nil,
client_type: nil,
client_last_modified: nil
@@ -29,6 +30,7 @@ defmodule Phoenix.LiveView.UploadEntry do
done?: boolean(),
cancelled?: boolean(),
client_name: String.t() | nil,
+ client_relative_path: String.t() | nil,
client_size: integer() | nil,
client_type: String.t() | nil,
client_last_modified: integer() | nil
@@ -62,20 +64,18 @@ defmodule Phoenix.LiveView.UploadConfig do
@too_many_files :too_many_files
- if Version.match?(System.version(), ">= 1.8.0") do
- @derive {Inspect,
- only: [
- :name,
- :ref,
- :entries,
- :max_entries,
- :max_file_size,
- :accept,
- :errors,
- :auto_upload?,
- :progress_event
- ]}
- end
+ @derive {Inspect,
+ only: [
+ :name,
+ :ref,
+ :entries,
+ :max_entries,
+ :max_file_size,
+ :accept,
+ :errors,
+ :auto_upload?,
+ :progress_event
+ ]}
defstruct name: nil,
cid: :unregistered,
@@ -128,7 +128,7 @@ defmodule Phoenix.LiveView.UploadConfig do
# we require a random_ref in order to ensure unique calls to `allow_upload`
# invalidate old uploads on the client and expire old tokens for the same
# upload name
- def build(name, random_ref, [_ | _] = opts) when is_atom(name) do
+ def build(name, random_ref, [_ | _] = opts) when is_atom(name) or is_binary(name) do
{html_accept, acceptable_types, acceptable_exts} =
case Keyword.fetch(opts, :accept) do
{:ok, [_ | _] = accept} ->
@@ -243,9 +243,7 @@ defmodule Phoenix.LiveView.UploadConfig do
raise ArgumentError, """
invalid :chunk_timeout value provided to allow_upload.
- Only a positive integer in milliseconds is supported (Defaults to #{
- @default_chunk_timeout
- } ms). Got:
+ Only a positive integer in milliseconds is supported (Defaults to #{@default_chunk_timeout} ms). Got:
#{inspect(other)}
"""
@@ -521,6 +519,7 @@ defmodule Phoenix.LiveView.UploadConfig do
upload_ref: conf.ref,
upload_config: conf.name,
client_name: Map.fetch!(client_entry, "name"),
+ client_relative_path: Map.get(client_entry, "relative_path"),
client_size: Map.fetch!(client_entry, "size"),
client_type: Map.fetch!(client_entry, "type"),
client_last_modified: Map.get(client_entry, "last_modified")
@@ -634,7 +633,6 @@ defmodule Phoenix.LiveView.UploadConfig do
case entry_pid(conf, entry) do
channel_pid when is_pid(channel_pid) ->
Phoenix.LiveView.UploadChannel.cancel(channel_pid)
-
update_entry(conf, entry.ref, fn entry -> %UploadEntry{entry | cancelled?: true} end)
_ ->
diff --git a/lib/phoenix_live_view/utils.ex b/lib/phoenix_live_view/utils.ex
index 3563f166b9..bfbdc529ed 100644
--- a/lib/phoenix_live_view/utils.ex
+++ b/lib/phoenix_live_view/utils.ex
@@ -10,6 +10,26 @@ defmodule Phoenix.LiveView.Utils do
@max_flash_age :timer.seconds(60)
+ @valid_uri_schemes [
+ "http:",
+ "https:",
+ "ftp:",
+ "ftps:",
+ "mailto:",
+ "news:",
+ "irc:",
+ "gopher:",
+ "nntp:",
+ "feed:",
+ "telnet:",
+ "mms:",
+ "rtsp:",
+ "svn:",
+ "tel:",
+ "fax:",
+ "xmpp:"
+ ]
+
@doc """
Assigns a value if it changed.
"""
@@ -64,6 +84,7 @@ defmodule Phoenix.LiveView.Utils do
@doc """
Checks if the given assign changed.
"""
+ def changed?(%Socket{} = socket, assign), do: changed?(socket.assigns, assign)
def changed?(%{__changed__: nil}, _assign), do: true
def changed?(%{__changed__: changed}, assign), do: Map.has_key?(changed, assign)
@@ -125,6 +146,32 @@ defmodule Phoenix.LiveView.Utils do
|> drop_private([:connect_info, :connect_params, :assign_new])
end
+ @doc """
+ Validate and normalizes the layout.
+ """
+ def normalize_layout(false, _warn_ctx), do: false
+
+ def normalize_layout({mod, layout}, _warn_ctx) when is_atom(mod) and is_atom(layout) do
+ {mod, Atom.to_string(layout)}
+ end
+
+ def normalize_layout({mod, layout}, warn_ctx) when is_atom(mod) and is_binary(layout) do
+ root_template = Path.rootname(layout)
+
+ IO.warn(
+ "passing a string as a layout template in #{warn_ctx} is deprecated, please pass " <>
+ "{#{inspect(mod)}, :#{root_template}} instead of {#{inspect(mod)}, \"#{root_template}.html\"}"
+ )
+
+ {mod, root_template}
+ end
+
+ def normalize_layout(other, _warn_ctx) do
+ raise ArgumentError,
+ ":layout expects a tuple of the form {MyLayoutView, :my_template} or false, " <>
+ "got: #{inspect(other)}"
+ end
+
@doc """
Renders the view with socket into a rendered struct.
"""
@@ -141,8 +188,8 @@ defmodule Phoenix.LiveView.Utils do
assigns = put_in(assigns[:inner_content], inner_content)
assigns = put_in(assigns.__changed__[:inner_content], true)
- layout_template
- |> layout_mod.render(assigns)
+ layout_mod
+ |> Phoenix.Template.render(to_string(layout_template), "html", assigns)
|> check_rendered!(layout_mod)
false ->
@@ -280,20 +327,20 @@ defmodule Phoenix.LiveView.Utils do
attempted to live patch while mounting.
a LiveView cannot be mounted while issuing a live patch to the client. \
- Use push_redirect/2 or redirect/2 instead if you wish to mount and redirect.
+ Use push_navigate/2 or redirect/2 instead if you wish to mount and redirect.
"""
end
@doc """
Calls the `c:Phoenix.LiveView.mount/3` callback, otherwise returns the socket as is.
"""
- def maybe_call_live_view_mount!(%Socket{} = socket, view, params, session) do
+ def maybe_call_live_view_mount!(%Socket{} = socket, view, params, session, uri \\ nil) do
%{any?: any?, exported?: exported?} = Lifecycle.stage_info(socket, view, :mount, 3)
if any? do
:telemetry.span(
[:phoenix, :live_view, :mount],
- %{socket: socket, params: params, session: session},
+ %{socket: socket, params: params, session: session, uri: uri},
fn ->
socket =
case Lifecycle.mount(params, session, socket) do
@@ -305,7 +352,7 @@ defmodule Phoenix.LiveView.Utils do
end
|> handle_mount_result!({:mount, 3, view})
- {socket, %{socket: socket, params: params, session: session}}
+ {socket, %{socket: socket, params: params, session: session, uri: uri}}
end
)
else
@@ -347,7 +394,7 @@ defmodule Phoenix.LiveView.Utils do
"""
end
- defp validate_mount_redirect!({:live, {_, _}, _}), do: raise_bad_mount_and_live_patch!()
+ defp validate_mount_redirect!({:live, :patch, _}), do: raise_bad_mount_and_live_patch!()
defp validate_mount_redirect!(_), do: :ok
@doc """
@@ -404,7 +451,8 @@ defmodule Phoenix.LiveView.Utils do
end
if socket.redirected do
- raise "cannot redirect socket on update/2"
+ raise "cannot redirect socket on update. Redirect before `update/2` is called" <>
+ " or use `send/2` and redirect in the `handle_info/2` response"
end
socket
@@ -455,18 +503,8 @@ defmodule Phoenix.LiveView.Utils do
"""
end
- defp do_mount_opt(socket, :layout, {mod, template}) when is_atom(mod) and is_binary(template) do
- %Socket{socket | private: Map.put(socket.private, :phoenix_live_layout, {mod, template})}
- end
-
- defp do_mount_opt(socket, :layout, false) do
- %Socket{socket | private: Map.put(socket.private, :phoenix_live_layout, false)}
- end
-
- defp do_mount_opt(_socket, :layout, bad_layout) do
- raise ArgumentError,
- "the :layout mount option expects a tuple of the form {MyLayoutView, \"my_template.html\"}, " <>
- "got: #{inspect(bad_layout)}"
+ defp do_mount_opt(socket, :layout, layout) do
+ put_in(socket.private[:live_layout], normalize_layout(layout, "mount options"))
end
defp do_mount_opt(socket, :temporary_assigns, temp_assigns) do
@@ -494,12 +532,43 @@ defmodule Phoenix.LiveView.Utils do
defp layout(socket, view) do
case socket.private do
- %{phoenix_live_layout: layout} -> layout
- %{} -> view.__live__()[:layout] || false
+ %{live_layout: layout} -> layout
+ %{} -> view.__live__()[:layout]
end
end
defp flash_salt(endpoint_mod) when is_atom(endpoint_mod) do
"flash:" <> salt!(endpoint_mod)
end
+
+ def valid_destination!(%URI{} = uri, context) do
+ valid_destination!(URI.to_string(uri), context)
+ end
+
+ def valid_destination!({:safe, to}, context) do
+ {:safe, valid_string_destination!(IO.iodata_to_binary(to), context)}
+ end
+
+ def valid_destination!({other, to}, _context) when is_atom(other) do
+ [Atom.to_string(other), ?:, to]
+ end
+
+ def valid_destination!(to, context) do
+ valid_string_destination!(IO.iodata_to_binary(to), context)
+ end
+
+ for scheme <- @valid_uri_schemes do
+ def valid_string_destination!(unquote(scheme) <> _ = string, _context), do: string
+ end
+
+ def valid_string_destination!(to, context) do
+ if not match?("/" <> _, to) and String.contains?(to, ":") do
+ raise ArgumentError, """
+ unsupported scheme given to #{context}. In case you want to link to an
+ unknown or unsafe scheme, such as javascript, use a tuple: {:javascript, rest}
+ """
+ else
+ to
+ end
+ end
end
diff --git a/mix.exs b/mix.exs
index b551980e63..d69cdb26eb 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,16 +1,16 @@
defmodule Phoenix.LiveView.MixProject do
use Mix.Project
- @version "0.17.0-dev"
+ @version "0.18.16"
def project do
[
app: :phoenix_live_view,
version: @version,
- elixir: "~> 1.7",
+ elixir: "~> 1.12",
start_permanent: Mix.env() == :prod,
elixirc_paths: elixirc_paths(Mix.env()),
- compilers: compilers(Mix.env()),
+ test_options: [docs: true],
package: package(),
xref: [exclude: [Floki]],
deps: deps(),
@@ -24,46 +24,90 @@ defmodule Phoenix.LiveView.MixProject do
]
end
- defp compilers(:test), do: [:phoenix] ++ Mix.compilers()
- defp compilers(_), do: Mix.compilers()
-
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
def application do
[
- extra_applications: [:logger],
- mod: {Phoenix.LiveView.Application, []}
+ mod: {Phoenix.LiveView.Application, []},
+ extra_applications: [:logger]
]
end
defp deps do
[
- {:phoenix, "~> 1.5.9 or ~> 1.6.0"},
- {:phoenix_html, "~> 3.0"},
+ {:phoenix, "~> 1.6.15 or ~> 1.7.0"},
+ {:phoenix_view, "~> 2.0", optional: true},
+ {:phoenix_template, "~> 1.0"},
+ {:phoenix_html, "~> 3.3"},
{:esbuild, "~> 0.2", only: :dev},
{:telemetry, "~> 0.4.2 or ~> 1.0"},
{:jason, "~> 1.0", optional: true},
- {:ex_doc, "~> 0.22", only: :docs},
{:floki, "~> 0.30.0", only: :test},
+ {:ex_doc, "~> 0.29", only: :docs},
+ {:makeup_eex, ">= 0.1.1", only: :docs},
{:html_entities, ">= 0.0.0", only: :test},
+ {:phoenix_live_reload, "~> 1.4.1", only: :test}
]
end
defp docs do
[
- main: "Phoenix.LiveView",
+ main: "Phoenix.Component",
source_ref: "v#{@version}",
source_url: "https://github.com/phoenixframework/phoenix_live_view",
extra_section: "GUIDES",
extras: extras(),
groups_for_extras: groups_for_extras(),
- groups_for_modules: groups_for_modules()
+ groups_for_modules: groups_for_modules(),
+ groups_for_functions: [
+ Components: &(&1[:type] == :component),
+ Macros: &(&1[:type] == :macro)
+ ],
+ skip_undefined_reference_warnings_on: ["CHANGELOG.md"],
+ before_closing_body_tag: &before_closing_body_tag/1
]
end
+ defp before_closing_body_tag(:html) do
+ """
+
+
+ """
+ end
+
+ defp before_closing_body_tag(_), do: ""
+
defp extras do
[
+ "CHANGELOG.md",
"guides/introduction/installation.md",
"guides/client/bindings.md",
"guides/client/form-bindings.md",
@@ -92,18 +136,19 @@ defmodule Phoenix.LiveView.MixProject do
defp groups_for_modules do
# Ungrouped Modules:
#
+ # Phoenix.Component
+ # Phoenix.LiveComponent
# Phoenix.LiveView
# Phoenix.LiveView.Controller
- # Phoenix.LiveView.Helpers
+ # Phoenix.LiveView.JS
# Phoenix.LiveView.Router
- # Phoenix.LiveView.Socket
# Phoenix.LiveViewTest
[
- "Components": [
- Phoenix.Component,
- Phoenix.LiveComponent,
- Phoenix.LiveComponent.CID
+ Configuration: [
+ Phoenix.LiveView.HTMLFormatter,
+ Phoenix.LiveView.Logger,
+ Phoenix.LiveView.Socket
],
"Testing structures": [
Phoenix.LiveViewTest.Element,
@@ -115,7 +160,9 @@ defmodule Phoenix.LiveView.MixProject do
Phoenix.LiveView.UploadEntry
],
"Plugin API": [
+ Phoenix.LiveComponent.CID,
Phoenix.LiveView.Engine,
+ Phoenix.LiveView.TagEngine,
Phoenix.LiveView.HTMLEngine,
Phoenix.LiveView.Component,
Phoenix.LiveView.Rendered,
@@ -128,10 +175,13 @@ defmodule Phoenix.LiveView.MixProject do
[
maintainers: ["Chris McCord", "José Valim", "Gary Rennie", "Alex Garibay", "Scott Newcomer"],
licenses: ["MIT"],
- links: %{github: "https://github.com/phoenixframework/phoenix_live_view"},
+ links: %{
+ Changelog: "https://hexdocs.pm/phoenix_live_view/changelog.html",
+ GitHub: "https://github.com/phoenixframework/phoenix_live_view"
+ },
files:
~w(assets/js lib priv) ++
- ~w(CHANGELOG.md LICENSE.md mix.exs package.json README.md)
+ ~w(CHANGELOG.md LICENSE.md mix.exs package.json README.md .formatter.exs)
]
end
diff --git a/mix.lock b/mix.lock
index 53482c7910..d83d82d77a 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,20 +1,26 @@
%{
- "castore": {:hex, :castore, "0.1.11", "c0665858e0e1c3e8c27178e73dffea699a5b28eb72239a3b2642d208e8594914", [:mix], [], "hexpm", "91b009ba61973b532b84f7c09ce441cba7aa15cb8b006cf06c6f4bba18220081"},
- "earmark_parser": {:hex, :earmark_parser, "1.4.13", "0c98163e7d04a15feb62000e1a891489feb29f3d10cb57d4f845c405852bbef8", [:mix], [], "hexpm", "d602c26af3a0af43d2f2645613f65841657ad6efc9f0e361c3b6c06b578214ba"},
- "esbuild": {:hex, :esbuild, "0.2.0", "51e6ec981584324179dbd707a37277a334cffceddca1be57fbe7dc0161921657", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "2aa1bf4f08b60a2b5a874f11b3d778d0d8e734714060fd71ef52e21ef2aebe16"},
- "ex_doc": {:hex, :ex_doc, "0.25.1", "4b736fa38dc76488a937e5ef2944f5474f3eff921de771b25371345a8dc810bc", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "3200b0a69ddb2028365281fbef3753ea9e728683863d8cdaa96580925c891f67"},
- "floki": {:hex, :floki, "0.30.0", "22ebbe681a5d3777cdd830ca091b1b806d33c3449c26312eadca7f7be685c0c8", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "a9e128a4ca9bb71f11affa315b6768a9ad326d5996ff1e92acf1d7a01a10076a"},
+ "castore": {:hex, :castore, "0.1.18", "deb5b9ab02400561b6f5708f3e7660fc35ca2d51bfc6a940d2f513f89c2975fc", [:mix], [], "hexpm", "61bbaf6452b782ef80b33cdb45701afbcf0a918a45ebe7e73f1130d661e66a06"},
+ "earmark_parser": {:hex, :earmark_parser, "1.4.30", "0b938aa5b9bafd455056440cdaa2a79197ca5e693830b4a982beada840513c5f", [:mix], [], "hexpm", "3b5385c2d36b0473d0b206927b841343d25adb14f95f0110062506b300cd5a1b"},
+ "esbuild": {:hex, :esbuild, "0.5.0", "d5bb08ff049d7880ee3609ed5c4b864bd2f46445ea40b16b4acead724fb4c4a3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "f183a0b332d963c4cfaf585477695ea59eef9a6f2204fdd0efa00e099694ffe5"},
+ "ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"},
+ "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
+ "floki": {:hex, :floki, "0.30.1", "75d35526d3a1459920b6e87fdbc2e0b8a3670f965dd0903708d2b267e0904c55", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "e9c03524447d1c4cbfccd672d739b8c18453eee377846b119d4fd71b1a176bb8"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
- "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
- "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
- "makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"},
+ "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
+ "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
+ "makeup_eex": {:hex, :makeup_eex, "0.1.1", "89352d5da318d97ae27bbcc87201f274504d2b71ede58ca366af6a5fbed9508d", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.16", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_html, "~> 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d111a0994eaaab09ef1a4b3b313ef806513bb4652152c26c0d7ca2be8402a964"},
+ "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
- "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
- "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
- "phoenix": {:hex, :phoenix, "1.5.9", "a6368d36cfd59d917b37c44386e01315bc89f7609a10a45a22f47c007edf2597", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7e4bce20a67c012f1fbb0af90e5da49fa7bf0d34e3a067795703b74aef75427d"},
- "phoenix_html": {:hex, :phoenix_html, "3.0.0", "b50048ea6be552af0f19218f703b1cc6c182f569fb28f8a849056e452a2bfed5", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "373d054f29812c5ba98096762cacfc320c5e484bfb40501511e39f662c2cd1c7"},
- "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
- "plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"},
- "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
- "telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"},
+ "makeup_html": {:hex, :makeup_html, "0.1.0", "b0228fda985e311d8f0d25bed58f8280826633a38d7448cabdd723e116165bcf", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "0ca44e7dcb8d933e010740324470dd8ec947243b51304bd34b8165ef3281edc2"},
+ "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
+ "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
+ "phoenix": {:hex, :phoenix, "1.6.15", "0a1d96bbc10747fd83525370d691953cdb6f3ccbac61aa01b4acb012474b047d", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d70ab9fbf6b394755ea88b644d34d79d8b146e490973151f248cacd122d20672"},
+ "phoenix_html": {:hex, :phoenix_html, "3.3.0", "bf451c71ebdaac8d2f40d3b703435e819ccfbb9ff243140ca3bd10c155f134cc", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "272c5c1533499f0132309936c619186480bafcc2246588f99a69ce85095556ef"},
+ "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"},
+ "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
+ "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"},
+ "phoenix_view": {:hex, :phoenix_view, "2.0.1", "a653e3d9d944aace0a064e4a13ad473ffa68f7bc4ca42dbf83cc1d464f1fb295", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "6c358e2cefc5f341c728914b867c556bbfd239fed9e881bac257d70cb2b8a6f6"},
+ "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"},
+ "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"},
+ "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
}
diff --git a/package.json b/package.json
index 21d905deb4..b6d89d7b53 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "phoenix_live_view",
- "version": "0.16.3",
+ "version": "0.18.16",
"description": "The Phoenix LiveView JavaScript client.",
"license": "MIT",
"module": "./priv/static/phoenix_live_view.esm.js",
diff --git a/priv/static/phoenix_live_view.cjs.js b/priv/static/phoenix_live_view.cjs.js
index 621f8e4bb7..f77356c869 100644
--- a/priv/static/phoenix_live_view.cjs.js
+++ b/priv/static/phoenix_live_view.cjs.js
@@ -14,7 +14,8 @@ __export(exports, {
// js/phoenix_live_view/constants.js
var CONSECUTIVE_RELOADS = "consecutive-reloads";
var MAX_RELOADS = 10;
-var RELOAD_JITTER = [1e3, 3e3];
+var RELOAD_JITTER_MIN = 5e3;
+var RELOAD_JITTER_MAX = 1e4;
var FAILSAFE_JITTER = 3e4;
var PHX_EVENT_CLASSES = [
"phx-click-loading",
@@ -30,6 +31,7 @@ var PHX_LIVE_LINK = "data-phx-link";
var PHX_TRACK_STATIC = "track-static";
var PHX_LINK_STATE = "data-phx-link-state";
var PHX_REF = "data-phx-ref";
+var PHX_REF_SRC = "data-phx-ref-src";
var PHX_TRACK_UPLOADS = "track-uploads";
var PHX_UPLOAD_REF = "data-phx-upload-ref";
var PHX_PREFLIGHTED_REFS = "data-phx-preflighted-refs";
@@ -38,10 +40,10 @@ var PHX_DROP_TARGET = "drop-target";
var PHX_ACTIVE_ENTRY_REFS = "data-phx-active-refs";
var PHX_LIVE_FILE_UPDATED = "phx:live-file:updated";
var PHX_SKIP = "data-phx-skip";
-var PHX_REMOVE = "data-phx-remove";
+var PHX_PRUNE = "data-phx-prune";
var PHX_PAGE_LOADING = "page-loading";
var PHX_CONNECTED_CLASS = "phx-connected";
-var PHX_DISCONNECTED_CLASS = "phx-disconnected";
+var PHX_DISCONNECTED_CLASS = "phx-loading";
var PHX_NO_FEEDBACK_CLASS = "phx-no-feedback";
var PHX_ERROR_CLASS = "phx-error";
var PHX_PARENT_ID = "data-phx-parent-id";
@@ -50,11 +52,12 @@ var PHX_ROOT_ID = "data-phx-root-id";
var PHX_TRIGGER_ACTION = "trigger-action";
var PHX_FEEDBACK_FOR = "feedback-for";
var PHX_HAS_FOCUSED = "phx-has-focused";
-var FOCUSABLE_INPUTS = ["text", "textarea", "number", "email", "password", "search", "tel", "url", "date", "time"];
+var FOCUSABLE_INPUTS = ["text", "textarea", "number", "email", "password", "search", "tel", "url", "date", "time", "datetime-local", "color", "range"];
var CHECKABLE_INPUTS = ["checkbox", "radio"];
var PHX_HAS_SUBMITTED = "phx-has-submitted";
var PHX_SESSION = "data-phx-session";
var PHX_VIEW_SELECTOR = `[${PHX_SESSION}]`;
+var PHX_STICKY = "data-phx-sticky";
var PHX_STATIC = "data-phx-static";
var PHX_READONLY = "data-phx-readonly";
var PHX_DISABLED = "data-phx-disabled";
@@ -64,6 +67,7 @@ var PHX_HOOK = "hook";
var PHX_DEBOUNCE = "debounce";
var PHX_THROTTLE = "throttle";
var PHX_UPDATE = "update";
+var PHX_STREAM = "stream";
var PHX_KEY = "key";
var PHX_PRIVATE = "phxPrivate";
var PHX_AUTO_RECOVER = "auto-recover";
@@ -71,6 +75,7 @@ var PHX_LV_DEBUG = "phx:live-socket:debug";
var PHX_LV_PROFILE = "phx:live-socket:profiling";
var PHX_LV_LATENCY_SIM = "phx:live-socket:latency-sim";
var PHX_PROGRESS = "progress";
+var PHX_MOUNTED = "mounted";
var LOADER_TIMEOUT = 1;
var BEFORE_UNLOAD_LOADER_TIMEOUT = 200;
var BINDING_PREFIX = "phx-";
@@ -88,6 +93,8 @@ var COMPONENTS = "c";
var EVENTS = "e";
var REPLY = "r";
var TITLE = "t";
+var TEMPLATES = "p";
+var STREAM = "stream";
// js/phoenix_live_view/entry_uploader.js
var EntryUploader = class {
@@ -139,7 +146,10 @@ var EntryUploader = class {
// js/phoenix_live_view/utils.js
var logError = (msg, obj) => console.error && console.error(msg, obj);
-var isCid = (cid) => typeof cid === "number";
+var isCid = (cid) => {
+ let type = typeof cid;
+ return type === "number" || type === "string" && /^(0|[1-9]\d*)$/.test(cid);
+};
function detectDuplicateIds() {
let ids = new Set();
let elems = document.querySelectorAll("*[id]");
@@ -164,7 +174,7 @@ var clone = (obj) => {
};
var closestPhxBinding = (el, binding, borderEl) => {
do {
- if (el.matches(`[${binding}]`)) {
+ if (el.matches(`[${binding}]`) && !el.disabled) {
return el;
}
el = el.parentElement || el.parentNode;
@@ -294,8 +304,35 @@ var DOM = {
isPhxDestroyed(node) {
return node.id && DOM.private(node, "destroyed") ? true : false;
},
+ wantsNewTab(e) {
+ let wantsNewTab = e.ctrlKey || e.shiftKey || e.metaKey || e.button && e.button === 1;
+ return wantsNewTab || e.target.getAttribute("target") === "_blank";
+ },
+ isUnloadableFormSubmit(e) {
+ return !e.defaultPrevented && !this.wantsNewTab(e);
+ },
+ isNewPageHref(href, currentLocation) {
+ let url;
+ try {
+ url = new URL(href);
+ } catch (e) {
+ try {
+ url = new URL(href, currentLocation);
+ } catch (e2) {
+ return true;
+ }
+ }
+ if (url.host === currentLocation.host && url.protocol === currentLocation.protocol) {
+ if (url.pathname === currentLocation.pathname && url.search === currentLocation.search) {
+ return url.hash === "" && !url.href.endsWith("#");
+ }
+ }
+ return true;
+ },
markPhxChildDestroyed(el) {
- el.setAttribute(PHX_SESSION, "");
+ if (this.isPhxChild(el)) {
+ el.setAttribute(PHX_SESSION, "");
+ }
this.putPrivate(el, "destroyed", true);
},
findPhxChildrenInFragment(html, parentId) {
@@ -309,16 +346,20 @@ var DOM = {
isPhxUpdate(el, phxUpdate, updateTypes) {
return el.getAttribute && updateTypes.indexOf(el.getAttribute(phxUpdate)) >= 0;
},
+ findPhxSticky(el) {
+ return this.all(el, `[${PHX_STICKY}]`);
+ },
findPhxChildren(el, parentId) {
return this.all(el, `${PHX_VIEW_SELECTOR}[${PHX_PARENT_ID}="${parentId}"]`);
},
findParentCIDs(node, cids) {
let initial = new Set(cids);
- return cids.reduce((acc, cid) => {
+ let parentCids = cids.reduce((acc, cid) => {
let selector = `[${PHX_COMPONENT}="${cid}"] [${PHX_COMPONENT}]`;
this.filterWithinSameLiveView(this.all(node, selector), node).map((el) => parseInt(el.getAttribute(PHX_COMPONENT))).forEach((childCID) => acc.delete(childCID));
return acc;
}, initial);
+ return parentCids.size === 0 ? new Set(cids) : parentCids;
},
filterWithinSameLiveView(nodes, parent) {
if (parent.querySelector(PHX_VIEW_SELECTOR)) {
@@ -349,17 +390,29 @@ var DOM = {
}
el[PHX_PRIVATE][key] = value;
},
+ updatePrivate(el, key, defaultVal, updateFunc) {
+ let existing = this.private(el, key);
+ if (existing === void 0) {
+ this.putPrivate(el, key, updateFunc(defaultVal));
+ } else {
+ this.putPrivate(el, key, updateFunc(existing));
+ }
+ },
copyPrivates(target, source) {
if (source[PHX_PRIVATE]) {
- target[PHX_PRIVATE] = clone(source[PHX_PRIVATE]);
+ target[PHX_PRIVATE] = source[PHX_PRIVATE];
}
},
putTitle(str) {
let titleEl = document.querySelector("title");
- let { prefix, suffix } = titleEl.dataset;
- document.title = `${prefix || ""}${str}${suffix || ""}`;
+ if (titleEl) {
+ let { prefix, suffix } = titleEl.dataset;
+ document.title = `${prefix || ""}${str}${suffix || ""}`;
+ } else {
+ document.title = str;
+ }
},
- debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, callback) {
+ debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, asyncFilter, callback) {
let debounce = el.getAttribute(phxDebounce);
let throttle = el.getAttribute(phxThrottle);
if (debounce === "") {
@@ -396,10 +449,18 @@ var DOM = {
} else {
callback();
this.putPrivate(el, THROTTLED, true);
- setTimeout(() => this.triggerCycle(el, DEBOUNCE_TRIGGER), timeout);
+ setTimeout(() => {
+ if (asyncFilter()) {
+ this.triggerCycle(el, DEBOUNCE_TRIGGER);
+ }
+ }, timeout);
}
} else {
- setTimeout(() => this.triggerCycle(el, DEBOUNCE_TRIGGER, currentCycle), timeout);
+ setTimeout(() => {
+ if (asyncFilter()) {
+ this.triggerCycle(el, DEBOUNCE_TRIGGER, currentCycle);
+ }
+ }, timeout);
}
let form = el.form;
if (form && this.once(form, "bind-debounce")) {
@@ -442,14 +503,26 @@ var DOM = {
},
discardError(container, el, phxFeedbackFor) {
let field = el.getAttribute && el.getAttribute(phxFeedbackFor);
- let input = field && container.querySelector(`[id="${field}"], [name="${field}"]`);
+ let input = field && container.querySelector(`[id="${field}"], [name="${field}"], [name="${field}[]"]`);
if (!input) {
return;
}
- if (!(this.private(input, PHX_HAS_FOCUSED) || this.private(input.form, PHX_HAS_SUBMITTED))) {
+ if (!(this.private(input, PHX_HAS_FOCUSED) || this.private(input, PHX_HAS_SUBMITTED))) {
el.classList.add(PHX_NO_FEEDBACK_CLASS);
}
},
+ resetForm(form, phxFeedbackFor) {
+ Array.from(form.elements).forEach((input) => {
+ let query = `[${phxFeedbackFor}="${input.id}"],
+ [${phxFeedbackFor}="${input.name}"],
+ [${phxFeedbackFor}="${input.name.replace(/\[\]$/, "")}"]`;
+ this.deletePrivate(input, PHX_HAS_FOCUSED);
+ this.deletePrivate(input, PHX_HAS_SUBMITTED);
+ this.all(document, query, (feedbackEl) => {
+ feedbackEl.classList.add(PHX_NO_FEEDBACK_CLASS);
+ });
+ });
+ },
showError(inputEl, phxFeedbackFor) {
if (inputEl.id || inputEl.name) {
this.all(inputEl.form, `[${phxFeedbackFor}="${inputEl.id}"], [${phxFeedbackFor}="${inputEl.name}"]`, (el) => {
@@ -460,8 +533,16 @@ var DOM = {
isPhxChild(node) {
return node.getAttribute && node.getAttribute(PHX_PARENT_ID);
},
- dispatchEvent(target, eventString, detail = {}) {
- let event = new CustomEvent(eventString, { bubbles: true, cancelable: true, detail });
+ isPhxSticky(node) {
+ return node.getAttribute && node.getAttribute(PHX_STICKY) !== null;
+ },
+ firstPhxChild(el) {
+ return this.isPhxChild(el) ? el : this.all(el, `[${PHX_PARENT_ID}]`)[0];
+ },
+ dispatchEvent(target, name, opts = {}) {
+ let bubbles = opts.bubbles === void 0 ? true : !!opts.bubbles;
+ let eventOpts = { bubbles, cancelable: true, detail: opts.detail || {} };
+ let event = name === "click" ? new MouseEvent("click", eventOpts) : new CustomEvent(name, eventOpts);
target.dispatchEvent(event);
},
cloneNode(node, html) {
@@ -499,7 +580,7 @@ var DOM = {
},
mergeFocusedInput(target, source) {
if (!(target instanceof HTMLSelectElement)) {
- DOM.mergeAttrs(target, source, { except: ["value"] });
+ DOM.mergeAttrs(target, source, { exclude: ["value"] });
}
if (source.readOnly) {
target.setAttribute("readonly", true);
@@ -533,14 +614,6 @@ var DOM = {
el.checked = el.getAttribute("checked") !== null;
}
},
- syncPropsToAttrs(el) {
- if (el instanceof HTMLSelectElement) {
- let selectedItem = el.options.item(el.selectedIndex);
- if (selectedItem && selectedItem.getAttribute("selected") === null) {
- selectedItem.setAttribute("selected", "");
- }
- }
- },
isTextualInput(el) {
return FOCUSABLE_INPUTS.indexOf(el.type) >= 0;
},
@@ -552,6 +625,7 @@ var DOM = {
if (ref === null) {
return true;
}
+ let refSrc = fromEl.getAttribute(PHX_REF_SRC);
if (DOM.isFormInput(fromEl) || fromEl.getAttribute(disableWith) !== null) {
if (DOM.isUploadInput(fromEl)) {
DOM.mergeAttrs(fromEl, toEl, { isIgnored: true });
@@ -563,6 +637,7 @@ var DOM = {
fromEl.classList.contains(className) && toEl.classList.add(className);
});
toEl.setAttribute(PHX_REF, ref);
+ toEl.setAttribute(PHX_REF_SRC, refSrc);
return true;
}
},
@@ -586,7 +661,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
}
},
replaceRootContainer(container, tagName, attrs) {
- let retainedAttrs = new Set(["id", PHX_SESSION, PHX_STATIC, PHX_MAIN]);
+ let retainedAttrs = new Set(["id", PHX_SESSION, PHX_STATIC, PHX_MAIN, PHX_ROOT_ID]);
if (container.tagName.toLowerCase() === tagName.toLowerCase()) {
Array.from(container.attributes).filter((attr) => !retainedAttrs.has(attr.name.toLowerCase())).forEach((attr) => container.removeAttribute(attr.name));
Object.keys(attrs).filter((name) => !retainedAttrs.has(name.toLowerCase())).forEach((attr) => container.setAttribute(attr, attrs[attr]));
@@ -599,6 +674,39 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
container.replaceWith(newContainer);
return newContainer;
}
+ },
+ getSticky(el, name, defaultVal) {
+ let op = (DOM.private(el, "sticky") || []).find(([existingName]) => name === existingName);
+ if (op) {
+ let [_name, _op, stashedResult] = op;
+ return stashedResult;
+ } else {
+ return typeof defaultVal === "function" ? defaultVal() : defaultVal;
+ }
+ },
+ deleteSticky(el, name) {
+ this.updatePrivate(el, "sticky", [], (ops) => {
+ return ops.filter(([existingName, _]) => existingName !== name);
+ });
+ },
+ putSticky(el, name, op) {
+ let stashedResult = op(el);
+ this.updatePrivate(el, "sticky", [], (ops) => {
+ let existingIndex = ops.findIndex(([existingName]) => name === existingName);
+ if (existingIndex >= 0) {
+ ops[existingIndex] = [name, op, stashedResult];
+ } else {
+ ops.push([name, op, stashedResult]);
+ }
+ return ops;
+ });
+ },
+ applyStickyOperations(el) {
+ let ops = DOM.private(el, "sticky");
+ if (!ops) {
+ return;
+ }
+ ops.forEach(([name, op, _stashed]) => this.putSticky(el, name, op));
}
};
var dom_default = DOM;
@@ -660,6 +768,7 @@ var UploadEntry = class {
return this._isDone;
}
error(reason = "failed") {
+ this.fileEl.removeEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated);
this.view.pushFileProgress(this.fileEl, this.ref, { error: reason });
LiveUploader.clearFiles(this.fileEl);
}
@@ -679,6 +788,7 @@ var UploadEntry = class {
return {
last_modified: this.file.lastModified,
name: this.file.name,
+ relative_path: this.file.webkitRelativePath,
size: this.file.size,
type: this.file.type,
ref: this.ref
@@ -733,7 +843,9 @@ var LiveUploader = class {
let uploadRef = inputEl.getAttribute(PHX_UPLOAD_REF);
fileData[uploadRef] = fileData[uploadRef] || [];
entry.ref = this.genFileRef(file);
+ entry.last_modified = file.lastModified;
entry.name = file.name || entry.ref;
+ entry.relative_path = file.webkitRelativePath;
entry.type = file.type;
entry.size = file.size;
fileData[uploadRef].push(entry);
@@ -748,12 +860,15 @@ var LiveUploader = class {
static untrackFile(inputEl, file) {
dom_default.putPrivate(inputEl, "files", dom_default.private(inputEl, "files").filter((f) => !Object.is(f, file)));
}
- static trackFiles(inputEl, files) {
+ static trackFiles(inputEl, files, dataTransfer) {
if (inputEl.getAttribute("multiple") !== null) {
let newFiles = files.filter((file) => !this.activeFiles(inputEl).find((f) => Object.is(f, file)));
dom_default.putPrivate(inputEl, "files", this.activeFiles(inputEl).concat(newFiles));
inputEl.value = null;
} else {
+ if (dataTransfer && dataTransfer.files.length > 0) {
+ inputEl.files = dataTransfer.files;
+ }
dom_default.putPrivate(inputEl, "files", files);
}
}
@@ -804,6 +919,62 @@ var LiveUploader = class {
}
};
+// js/phoenix_live_view/aria.js
+var ARIA = {
+ focusMain() {
+ let target = document.querySelector("main h1, main, h1");
+ if (target) {
+ let origTabIndex = target.tabIndex;
+ target.tabIndex = -1;
+ target.focus();
+ target.tabIndex = origTabIndex;
+ }
+ },
+ anyOf(instance, classes) {
+ return classes.find((name) => instance instanceof name);
+ },
+ isFocusable(el, interactiveOnly) {
+ return el instanceof HTMLAnchorElement && el.rel !== "ignore" || el instanceof HTMLAreaElement && el.href !== void 0 || !el.disabled && this.anyOf(el, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, HTMLButtonElement]) || el instanceof HTMLIFrameElement || (el.tabIndex > 0 || !interactiveOnly && el.tabIndex === 0 && el.getAttribute("tabindex") !== null && el.getAttribute("aria-hidden") !== "true");
+ },
+ attemptFocus(el, interactiveOnly) {
+ if (this.isFocusable(el, interactiveOnly)) {
+ try {
+ el.focus();
+ } catch (e) {
+ }
+ }
+ return !!document.activeElement && document.activeElement.isSameNode(el);
+ },
+ focusFirstInteractive(el) {
+ let child = el.firstElementChild;
+ while (child) {
+ if (this.attemptFocus(child, true) || this.focusFirstInteractive(child, true)) {
+ return true;
+ }
+ child = child.nextElementSibling;
+ }
+ },
+ focusFirst(el) {
+ let child = el.firstElementChild;
+ while (child) {
+ if (this.attemptFocus(child) || this.focusFirst(child)) {
+ return true;
+ }
+ child = child.nextElementSibling;
+ }
+ },
+ focusLast(el) {
+ let child = el.lastElementChild;
+ while (child) {
+ if (this.attemptFocus(child) || this.focusLast(child)) {
+ return true;
+ }
+ child = child.previousElementSibling;
+ }
+ }
+};
+var aria_default = ARIA;
+
// js/phoenix_live_view/hooks.js
var Hooks = {
LiveFileUpload: {
@@ -842,6 +1013,18 @@ var Hooks = {
destroyed() {
URL.revokeObjectURL(this.url);
}
+ },
+ FocusWrap: {
+ mounted() {
+ this.focusStart = this.el.firstElementChild;
+ this.focusEnd = this.el.lastElementChild;
+ this.focusStart.addEventListener("focus", () => aria_default.focusLast(this.el));
+ this.focusEnd.addEventListener("focus", () => aria_default.focusFirst(this.el));
+ this.el.addEventListener("phx:show-end", () => this.el.focus());
+ if (window.getComputedStyle(this.el).display !== "none") {
+ aria_default.focusFirst(this.el);
+ }
+ }
}
};
var hooks_default = Hooks;
@@ -1114,6 +1297,8 @@ function morphdomFactory(morphAttrs2) {
} else {
toNode = toElement(toNode);
}
+ } else if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE$1) {
+ toNode = toNode.firstElementChild;
}
var getNodeKey = options.getNodeKey || defaultGetNodeKey;
var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;
@@ -1123,6 +1308,10 @@ function morphdomFactory(morphAttrs2) {
var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;
var onNodeDiscarded = options.onNodeDiscarded || noop;
var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;
+ var skipFromChildren = options.skipFromChildren || noop;
+ var addChild = options.addChild || function(parent, child) {
+ return parent.appendChild(child);
+ };
var childrenOnly = options.childrenOnly === true;
var fromNodesLookup = Object.create(null);
var keyedRemovalList = [];
@@ -1223,6 +1412,7 @@ function morphdomFactory(morphAttrs2) {
}
}
function morphChildren(fromEl, toEl) {
+ var skipFrom = skipFromChildren(fromEl);
var curToNodeChild = toEl.firstChild;
var curFromNodeChild = fromEl.firstChild;
var curToNodeKey;
@@ -1234,7 +1424,7 @@ function morphdomFactory(morphAttrs2) {
while (curToNodeChild) {
toNextSibling = curToNodeChild.nextSibling;
curToNodeKey = getNodeKey(curToNodeChild);
- while (curFromNodeChild) {
+ while (!skipFrom && curFromNodeChild) {
fromNextSibling = curFromNodeChild.nextSibling;
if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {
curToNodeChild = toNextSibling;
@@ -1291,7 +1481,9 @@ function morphdomFactory(morphAttrs2) {
curFromNodeChild = fromNextSibling;
}
if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {
- fromEl.appendChild(matchingFromEl);
+ if (!skipFrom) {
+ addChild(fromEl, matchingFromEl);
+ }
morphEl(matchingFromEl, curToNodeChild);
} else {
var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);
@@ -1302,7 +1494,7 @@ function morphdomFactory(morphAttrs2) {
if (curToNodeChild.actualize) {
curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);
}
- fromEl.appendChild(curToNodeChild);
+ addChild(fromEl, curToNodeChild);
handleNodeAdded(curToNodeChild);
}
}
@@ -1380,15 +1572,19 @@ var DOMPatch = class {
}
});
}
- constructor(view, container, id, html, targetCID) {
+ constructor(view, container, id, html, streams, targetCID) {
this.view = view;
this.liveSocket = view.liveSocket;
this.container = container;
this.id = id;
this.rootID = view.root.id;
this.html = html;
+ this.streams = streams;
+ this.streamInserts = {};
this.targetCID = targetCID;
this.cidPatch = isCid(this.targetCID);
+ this.pendingRemoves = [];
+ this.phxRemove = this.liveSocket.binding("remove");
this.callbacks = {
beforeadded: [],
beforeupdated: [],
@@ -1396,7 +1592,8 @@ var DOMPatch = class {
afteradded: [],
afterupdated: [],
afterdiscarded: [],
- afterphxChildAdded: []
+ afterphxChildAdded: [],
+ aftertransitionsDiscarded: []
};
}
before(kind, callback) {
@@ -1412,8 +1609,10 @@ var DOMPatch = class {
this.callbacks[`after${kind}`].forEach((callback) => callback(...args));
}
markPrunableContentForRemoval() {
- dom_default.all(this.container, "[phx-update=append] > *, [phx-update=prepend] > *", (el) => {
- el.setAttribute(PHX_REMOVE, "");
+ let phxUpdate = this.liveSocket.binding(PHX_UPDATE);
+ dom_default.all(this.container, `[${phxUpdate}=${PHX_STREAM}]`, (el) => el.innerHTML = "");
+ dom_default.all(this.container, `[${phxUpdate}=append] > *, [${phxUpdate}=prepend] > *`, (el) => {
+ el.setAttribute(PHX_PRUNE, "");
});
}
perform() {
@@ -1438,11 +1637,41 @@ var DOMPatch = class {
this.trackBefore("added", container);
this.trackBefore("updated", container, container);
liveSocket.time("morphdom", () => {
+ this.streams.forEach(([inserts, deleteIds]) => {
+ this.streamInserts = Object.assign(this.streamInserts, inserts);
+ deleteIds.forEach((id) => {
+ let child = container.querySelector(`[id="${id}"]`);
+ if (child) {
+ if (!this.maybePendingRemove(child)) {
+ child.remove();
+ this.onNodeDiscarded(child);
+ }
+ }
+ });
+ });
morphdom_esm_default(targetContainer, diffHTML, {
childrenOnly: targetContainer.getAttribute(PHX_COMPONENT) === null,
getNodeKey: (node) => {
return dom_default.isPhxDestroyed(node) ? null : node.id;
},
+ skipFromChildren: (from) => {
+ return from.getAttribute(phxUpdate) === PHX_STREAM;
+ },
+ addChild: (parent, child) => {
+ let streamAt = child.id ? this.streamInserts[child.id] : void 0;
+ if (streamAt === void 0) {
+ return parent.appendChild(child);
+ }
+ dom_default.putPrivate(child, PHX_STREAM, true);
+ if (streamAt === 0) {
+ parent.insertAdjacentElement("afterbegin", child);
+ } else if (streamAt === -1) {
+ parent.appendChild(child);
+ } else if (streamAt > 0) {
+ let sibling = Array.from(parent.children)[streamAt];
+ parent.insertBefore(child, sibling);
+ }
+ },
onBeforeNodeAdded: (el) => {
this.trackBefore("added", el);
return el;
@@ -1457,22 +1686,23 @@ var DOMPatch = class {
externalFormTriggered = el;
}
dom_default.discardError(targetContainer, el, phxFeedbackFor);
- if (dom_default.isPhxChild(el) && view.ownsElement(el)) {
+ if (dom_default.isPhxChild(el) && view.ownsElement(el) || dom_default.isPhxSticky(el) && view.ownsElement(el.parentNode)) {
this.trackAfter("phxChildAdded", el);
}
added.push(el);
},
- onNodeDiscarded: (el) => {
- if (dom_default.isPhxChild(el)) {
- liveSocket.destroyViewByEl(el);
- }
- this.trackAfter("discarded", el);
- },
+ onNodeDiscarded: (el) => this.onNodeDiscarded(el),
onBeforeNodeDiscarded: (el) => {
- if (el.getAttribute && el.getAttribute(PHX_REMOVE) !== null) {
+ if (el.getAttribute && el.getAttribute(PHX_PRUNE) !== null) {
return true;
}
- if (el.parentNode !== null && dom_default.isPhxUpdate(el.parentNode, phxUpdate, ["append", "prepend"]) && el.id) {
+ if (dom_default.private(el, PHX_STREAM)) {
+ return false;
+ }
+ if (el.parentElement !== null && dom_default.isPhxUpdate(el.parentElement, phxUpdate, ["append", "prepend"]) && el.id) {
+ return false;
+ }
+ if (this.maybePendingRemove(el)) {
return false;
}
if (this.skipCIDSibling(el)) {
@@ -1485,16 +1715,21 @@ var DOMPatch = class {
externalFormTriggered = el;
}
updates.push(el);
+ this.maybeReOrderStream(el);
},
onBeforeElUpdated: (fromEl, toEl) => {
dom_default.cleanChildNodes(toEl, phxUpdate);
if (this.skipCIDSibling(toEl)) {
return false;
}
- if (dom_default.isIgnored(fromEl, phxUpdate)) {
+ if (dom_default.isPhxSticky(fromEl)) {
+ return false;
+ }
+ if (dom_default.isIgnored(fromEl, phxUpdate) || fromEl.form && fromEl.form.isSameNode(externalFormTriggered)) {
this.trackBefore("updated", fromEl, toEl);
dom_default.mergeAttrs(fromEl, toEl, { isIgnored: true });
updates.push(fromEl);
+ dom_default.applyStickyOperations(fromEl);
return false;
}
if (fromEl.type === "number" && (fromEl.validity && fromEl.validity.badInput)) {
@@ -1505,6 +1740,7 @@ var DOMPatch = class {
this.trackBefore("updated", fromEl, toEl);
updates.push(fromEl);
}
+ dom_default.applyStickyOperations(fromEl);
return false;
}
if (dom_default.isPhxChild(toEl)) {
@@ -1514,23 +1750,25 @@ var DOMPatch = class {
fromEl.setAttribute(PHX_SESSION, prevSession);
}
fromEl.setAttribute(PHX_ROOT_ID, this.rootID);
+ dom_default.applyStickyOperations(fromEl);
return false;
}
dom_default.copyPrivates(toEl, fromEl);
dom_default.discardError(targetContainer, toEl, phxFeedbackFor);
- dom_default.syncPropsToAttrs(toEl);
let isFocusedFormEl = focused && fromEl.isSameNode(focused) && dom_default.isFormInput(fromEl);
- if (isFocusedFormEl && !this.forceFocusedSelectUpdate(fromEl, toEl)) {
+ if (isFocusedFormEl && fromEl.type !== "hidden") {
this.trackBefore("updated", fromEl, toEl);
dom_default.mergeFocusedInput(fromEl, toEl);
dom_default.syncAttrsToProps(fromEl);
updates.push(fromEl);
+ dom_default.applyStickyOperations(fromEl);
return false;
} else {
if (dom_default.isPhxUpdate(toEl, phxUpdate, ["append", "prepend"])) {
appendPrependUpdates.push(new DOMPostMorphRestorer(fromEl, toEl, toEl.getAttribute(phxUpdate)));
}
dom_default.syncAttrsToProps(toEl);
+ dom_default.applyStickyOperations(toEl);
this.trackBefore("updated", fromEl, toEl);
return true;
}
@@ -1549,15 +1787,65 @@ var DOMPatch = class {
dom_default.dispatchEvent(document, "phx:update");
added.forEach((el) => this.trackAfter("added", el));
updates.forEach((el) => this.trackAfter("updated", el));
+ this.transitionPendingRemoves();
if (externalFormTriggered) {
- liveSocket.disconnect();
+ liveSocket.unload();
externalFormTriggered.submit();
}
return true;
}
- forceFocusedSelectUpdate(fromEl, toEl) {
- let isSelect = ["select", "select-one", "select-multiple"].find((t) => t === fromEl.type);
- return fromEl.multiple === true || isSelect && fromEl.innerHTML != toEl.innerHTML;
+ onNodeDiscarded(el) {
+ if (dom_default.isPhxChild(el) || dom_default.isPhxSticky(el)) {
+ this.liveSocket.destroyViewByEl(el);
+ }
+ this.trackAfter("discarded", el);
+ }
+ maybePendingRemove(node) {
+ if (node.getAttribute && node.getAttribute(this.phxRemove) !== null) {
+ this.pendingRemoves.push(node);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ maybeReOrderStream(el) {
+ let streamAt = el.id ? this.streamInserts[el.id] : void 0;
+ if (streamAt === void 0) {
+ return;
+ }
+ dom_default.putPrivate(el, PHX_STREAM, true);
+ if (streamAt === 0) {
+ el.parentElement.insertBefore(el, el.parentElement.firstElementChild);
+ } else if (streamAt > 0) {
+ let children = Array.from(el.parentElement.children);
+ let oldIndex = children.indexOf(el);
+ if (streamAt >= children.length - 1) {
+ el.parentElement.appendChild(el);
+ } else {
+ let sibling = children[streamAt];
+ if (oldIndex > streamAt) {
+ el.parentElement.insertBefore(el, sibling);
+ } else {
+ el.parentElement.insertBefore(el, sibling.nextElementSibling);
+ }
+ }
+ }
+ }
+ transitionPendingRemoves() {
+ let { pendingRemoves, liveSocket } = this;
+ if (pendingRemoves.length > 0) {
+ liveSocket.transitionRemoves(pendingRemoves);
+ liveSocket.requestDOMUpdate(() => {
+ pendingRemoves.forEach((el) => {
+ let child = dom_default.firstPhxChild(el);
+ if (child) {
+ liveSocket.destroyViewByEl(child);
+ }
+ el.remove();
+ });
+ this.trackAfter("transitionsDiscarded", pendingRemoves);
+ });
+ }
}
isCIDPatch() {
return this.cidPatch;
@@ -1599,6 +1887,9 @@ var DOMPatch = class {
return diffContainer.outerHTML;
}
}
+ indexOf(parent, child) {
+ return Array.from(parent.children).indexOf(child);
+ }
};
// js/phoenix_live_view/rendered.js
@@ -1619,13 +1910,14 @@ var Rendered = class {
return this.viewId;
}
toString(onlyCids) {
- return this.recursiveToString(this.rendered, this.rendered[COMPONENTS], onlyCids);
+ let [str, streams] = this.recursiveToString(this.rendered, this.rendered[COMPONENTS], onlyCids);
+ return [str, streams];
}
recursiveToString(rendered, components = rendered[COMPONENTS], onlyCids) {
onlyCids = onlyCids ? new Set(onlyCids) : null;
- let output = { buffer: "", components, onlyCids };
- this.toOutputBuffer(rendered, output);
- return output.buffer;
+ let output = { buffer: "", components, onlyCids, streams: new Set() };
+ this.toOutputBuffer(rendered, null, output);
+ return [output.buffer, output.streams];
}
componentCIDs(diff) {
return Object.keys(diff[COMPONENTS] || {}).map((i) => parseInt(i));
@@ -1650,8 +1942,8 @@ var Rendered = class {
for (let cid in newc) {
newc[cid] = this.cachedFindComponent(cid, newc[cid], oldc, newc, cache);
}
- for (var key in newc) {
- oldc[key] = newc[key];
+ for (let cid in newc) {
+ oldc[cid] = newc[cid];
}
diff[COMPONENTS] = newc;
}
@@ -1690,7 +1982,8 @@ var Rendered = class {
for (let key in source) {
let val = source[key];
let targetVal = target[key];
- if (isObject(val) && val[STATIC] === void 0 && isObject(targetVal)) {
+ let isObjVal = isObject(val);
+ if (isObjVal && val[STATIC] === void 0 && isObject(targetVal)) {
this.doMutableMerge(targetVal, val);
} else {
target[key] = val;
@@ -1709,7 +2002,8 @@ var Rendered = class {
return merged;
}
componentToString(cid) {
- return this.recursiveCIDToString(this.rendered[COMPONENTS], cid);
+ let [str, streams] = this.recursiveCIDToString(this.rendered[COMPONENTS], cid);
+ return [str, streams];
}
pruneCIDs(cids) {
cids.forEach((cid) => delete this.rendered[COMPONENTS][cid]);
@@ -1720,33 +2014,50 @@ var Rendered = class {
isNewFingerprint(diff = {}) {
return !!diff[STATIC];
}
- toOutputBuffer(rendered, output) {
+ templateStatic(part, templates) {
+ if (typeof part === "number") {
+ return templates[part];
+ } else {
+ return part;
+ }
+ }
+ toOutputBuffer(rendered, templates, output) {
if (rendered[DYNAMICS]) {
- return this.comprehensionToBuffer(rendered, output);
+ return this.comprehensionToBuffer(rendered, templates, output);
}
let { [STATIC]: statics } = rendered;
+ statics = this.templateStatic(statics, templates);
output.buffer += statics[0];
for (let i = 1; i < statics.length; i++) {
- this.dynamicToBuffer(rendered[i - 1], output);
+ this.dynamicToBuffer(rendered[i - 1], templates, output);
output.buffer += statics[i];
}
}
- comprehensionToBuffer(rendered, output) {
- let { [DYNAMICS]: dynamics, [STATIC]: statics } = rendered;
+ comprehensionToBuffer(rendered, templates, output) {
+ let { [DYNAMICS]: dynamics, [STATIC]: statics, [STREAM]: stream } = rendered;
+ let [_inserts, deleteIds] = stream || [{}, []];
+ statics = this.templateStatic(statics, templates);
+ let compTemplates = templates || rendered[TEMPLATES];
for (let d = 0; d < dynamics.length; d++) {
let dynamic = dynamics[d];
output.buffer += statics[0];
for (let i = 1; i < statics.length; i++) {
- this.dynamicToBuffer(dynamic[i - 1], output);
+ this.dynamicToBuffer(dynamic[i - 1], compTemplates, output);
output.buffer += statics[i];
}
}
+ if (stream !== void 0 && (rendered[DYNAMICS].length > 0 || deleteIds.length > 0)) {
+ rendered[DYNAMICS] = [];
+ output.streams.add(stream);
+ }
}
- dynamicToBuffer(rendered, output) {
+ dynamicToBuffer(rendered, templates, output) {
if (typeof rendered === "number") {
- output.buffer += this.recursiveCIDToString(output.components, rendered, output.onlyCids);
+ let [str, streams] = this.recursiveCIDToString(output.components, rendered, output.onlyCids);
+ output.buffer += str;
+ output.streams = new Set([...output.streams, ...streams]);
} else if (isObject(rendered)) {
- this.toOutputBuffer(rendered, output);
+ this.toOutputBuffer(rendered, templates, output);
} else {
output.buffer += rendered;
}
@@ -1754,7 +2065,8 @@ var Rendered = class {
recursiveCIDToString(components, cid, onlyCids) {
let component = components[cid] || logError(`no component for CID ${cid}`, components);
let template = document.createElement("template");
- template.innerHTML = this.recursiveToString(component, components, onlyCids);
+ let [html, streams] = this.recursiveToString(component, components, onlyCids);
+ template.innerHTML = html;
let container = template.content;
let skip = onlyCids && !onlyCids.has(cid);
let [hasChildNodes, hasChildComponents] = Array.from(container.childNodes).reduce(([hasNodes, hasComponents], child, i) => {
@@ -1789,12 +2101,12 @@ within:
}, [false, false]);
if (!hasChildNodes && !hasChildComponents) {
logError("expected at least one HTML element tag inside a component, but the component is empty:\n", template.innerHTML.trim());
- return this.createSpan("", cid).outerHTML;
+ return [this.createSpan("", cid).outerHTML, streams];
} else if (!hasChildNodes && hasChildComponents) {
logError("expected at least one HTML element tag directly inside a component, but only subcomponents were found. A component must render at least one HTML tag directly inside itself.", template.innerHTML.trim());
- return template.innerHTML;
+ return [template.innerHTML, streams];
} else {
- return template.innerHTML;
+ return [template.innerHTML, streams];
}
}
createSpan(text, cid) {
@@ -1816,7 +2128,7 @@ var ViewHook = class {
}
constructor(view, el, callbacks) {
this.__view = view;
- this.__liveSocket = view.liveSocket;
+ this.liveSocket = view.liveSocket;
this.__callbacks = callbacks;
this.__listeners = new Set();
this.__isDisconnected = false;
@@ -1860,13 +2172,13 @@ var ViewHook = class {
}
handleEvent(event, callback) {
let callbackRef = (customEvent, bypass) => bypass ? event : callback(customEvent.detail);
- window.addEventListener(`phx:hook:${event}`, callbackRef);
+ window.addEventListener(`phx:${event}`, callbackRef);
this.__listeners.add(callbackRef);
return callbackRef;
}
removeHandleEvent(callbackRef) {
let event = callbackRef(null, true);
- window.removeEventListener(`phx:hook:${event}`, callbackRef);
+ window.removeEventListener(`phx:${event}`, callbackRef);
this.__listeners.delete(callbackRef);
}
upload(name, files) {
@@ -1880,8 +2192,211 @@ var ViewHook = class {
}
};
+// js/phoenix_live_view/js.js
+var focusStack = null;
+var JS = {
+ exec(eventType, phxEvent, view, sourceEl, defaults) {
+ let [defaultKind, defaultArgs] = defaults || [null, {}];
+ let commands = phxEvent.charAt(0) === "[" ? JSON.parse(phxEvent) : [[defaultKind, defaultArgs]];
+ commands.forEach(([kind, args]) => {
+ if (kind === defaultKind && defaultArgs.data) {
+ args.data = Object.assign(args.data || {}, defaultArgs.data);
+ }
+ this.filterToEls(sourceEl, args).forEach((el) => {
+ this[`exec_${kind}`](eventType, phxEvent, view, sourceEl, el, args);
+ });
+ });
+ },
+ isVisible(el) {
+ return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length > 0);
+ },
+ exec_dispatch(eventType, phxEvent, view, sourceEl, el, { to, event, detail, bubbles }) {
+ detail = detail || {};
+ detail.dispatcher = sourceEl;
+ dom_default.dispatchEvent(el, event, { detail, bubbles });
+ },
+ exec_push(eventType, phxEvent, view, sourceEl, el, args) {
+ if (!view.isConnected()) {
+ return;
+ }
+ let { event, data, target, page_loading, loading, value, dispatcher } = args;
+ let pushOpts = { loading, value, target, page_loading: !!page_loading };
+ let targetSrc = eventType === "change" && dispatcher ? dispatcher : sourceEl;
+ let phxTarget = target || targetSrc.getAttribute(view.binding("target")) || targetSrc;
+ view.withinTargets(phxTarget, (targetView, targetCtx) => {
+ if (eventType === "change") {
+ let { newCid, _target, callback } = args;
+ _target = _target || (dom_default.isFormInput(sourceEl) ? sourceEl.name : void 0);
+ if (_target) {
+ pushOpts._target = _target;
+ }
+ targetView.pushInput(sourceEl, targetCtx, newCid, event || phxEvent, pushOpts, callback);
+ } else if (eventType === "submit") {
+ targetView.submitForm(sourceEl, targetCtx, event || phxEvent, pushOpts);
+ } else {
+ targetView.pushEvent(eventType, sourceEl, targetCtx, event || phxEvent, data, pushOpts);
+ }
+ });
+ },
+ exec_navigate(eventType, phxEvent, view, sourceEl, el, { href, replace }) {
+ view.liveSocket.historyRedirect(href, replace ? "replace" : "push");
+ },
+ exec_patch(eventType, phxEvent, view, sourceEl, el, { href, replace }) {
+ view.liveSocket.pushHistoryPatch(href, replace ? "replace" : "push", sourceEl);
+ },
+ exec_focus(eventType, phxEvent, view, sourceEl, el) {
+ window.requestAnimationFrame(() => aria_default.attemptFocus(el));
+ },
+ exec_focus_first(eventType, phxEvent, view, sourceEl, el) {
+ window.requestAnimationFrame(() => aria_default.focusFirstInteractive(el) || aria_default.focusFirst(el));
+ },
+ exec_push_focus(eventType, phxEvent, view, sourceEl, el) {
+ window.requestAnimationFrame(() => focusStack = el || sourceEl);
+ },
+ exec_pop_focus(eventType, phxEvent, view, sourceEl, el) {
+ window.requestAnimationFrame(() => {
+ if (focusStack) {
+ focusStack.focus();
+ }
+ focusStack = null;
+ });
+ },
+ exec_add_class(eventType, phxEvent, view, sourceEl, el, { names, transition, time }) {
+ this.addOrRemoveClasses(el, names, [], transition, time, view);
+ },
+ exec_remove_class(eventType, phxEvent, view, sourceEl, el, { names, transition, time }) {
+ this.addOrRemoveClasses(el, [], names, transition, time, view);
+ },
+ exec_transition(eventType, phxEvent, view, sourceEl, el, { time, transition }) {
+ this.addOrRemoveClasses(el, [], [], transition, time, view);
+ },
+ exec_toggle(eventType, phxEvent, view, sourceEl, el, { display, ins, outs, time }) {
+ this.toggle(eventType, view, el, display, ins, outs, time);
+ },
+ exec_show(eventType, phxEvent, view, sourceEl, el, { display, transition, time }) {
+ this.show(eventType, view, el, display, transition, time);
+ },
+ exec_hide(eventType, phxEvent, view, sourceEl, el, { display, transition, time }) {
+ this.hide(eventType, view, el, display, transition, time);
+ },
+ exec_set_attr(eventType, phxEvent, view, sourceEl, el, { attr: [attr, val] }) {
+ this.setOrRemoveAttrs(el, [[attr, val]], []);
+ },
+ exec_remove_attr(eventType, phxEvent, view, sourceEl, el, { attr }) {
+ this.setOrRemoveAttrs(el, [], [attr]);
+ },
+ show(eventType, view, el, display, transition, time) {
+ if (!this.isVisible(el)) {
+ this.toggle(eventType, view, el, display, transition, null, time);
+ }
+ },
+ hide(eventType, view, el, display, transition, time) {
+ if (this.isVisible(el)) {
+ this.toggle(eventType, view, el, display, null, transition, time);
+ }
+ },
+ toggle(eventType, view, el, display, ins, outs, time) {
+ let [inClasses, inStartClasses, inEndClasses] = ins || [[], [], []];
+ let [outClasses, outStartClasses, outEndClasses] = outs || [[], [], []];
+ if (inClasses.length > 0 || outClasses.length > 0) {
+ if (this.isVisible(el)) {
+ let onStart = () => {
+ this.addOrRemoveClasses(el, outStartClasses, inClasses.concat(inStartClasses).concat(inEndClasses));
+ window.requestAnimationFrame(() => {
+ this.addOrRemoveClasses(el, outClasses, []);
+ window.requestAnimationFrame(() => this.addOrRemoveClasses(el, outEndClasses, outStartClasses));
+ });
+ };
+ el.dispatchEvent(new Event("phx:hide-start"));
+ view.transition(time, onStart, () => {
+ this.addOrRemoveClasses(el, [], outClasses.concat(outEndClasses));
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = "none");
+ el.dispatchEvent(new Event("phx:hide-end"));
+ });
+ } else {
+ if (eventType === "remove") {
+ return;
+ }
+ let onStart = () => {
+ this.addOrRemoveClasses(el, inStartClasses, outClasses.concat(outStartClasses).concat(outEndClasses));
+ let stickyDisplay = display || this.defaultDisplay(el);
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = stickyDisplay);
+ window.requestAnimationFrame(() => {
+ this.addOrRemoveClasses(el, inClasses, []);
+ window.requestAnimationFrame(() => this.addOrRemoveClasses(el, inEndClasses, inStartClasses));
+ });
+ };
+ el.dispatchEvent(new Event("phx:show-start"));
+ view.transition(time, onStart, () => {
+ this.addOrRemoveClasses(el, [], inClasses.concat(inEndClasses));
+ el.dispatchEvent(new Event("phx:show-end"));
+ });
+ }
+ } else {
+ if (this.isVisible(el)) {
+ window.requestAnimationFrame(() => {
+ el.dispatchEvent(new Event("phx:hide-start"));
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = "none");
+ el.dispatchEvent(new Event("phx:hide-end"));
+ });
+ } else {
+ window.requestAnimationFrame(() => {
+ el.dispatchEvent(new Event("phx:show-start"));
+ let stickyDisplay = display || this.defaultDisplay(el);
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = stickyDisplay);
+ el.dispatchEvent(new Event("phx:show-end"));
+ });
+ }
+ }
+ },
+ addOrRemoveClasses(el, adds, removes, transition, time, view) {
+ let [transition_run, transition_start, transition_end] = transition || [[], [], []];
+ if (transition_run.length > 0) {
+ let onStart = () => this.addOrRemoveClasses(el, transition_start.concat(transition_run), []);
+ let onDone = () => this.addOrRemoveClasses(el, adds.concat(transition_end), removes.concat(transition_run).concat(transition_start));
+ return view.transition(time, onStart, onDone);
+ }
+ window.requestAnimationFrame(() => {
+ let [prevAdds, prevRemoves] = dom_default.getSticky(el, "classes", [[], []]);
+ let keepAdds = adds.filter((name) => prevAdds.indexOf(name) < 0 && !el.classList.contains(name));
+ let keepRemoves = removes.filter((name) => prevRemoves.indexOf(name) < 0 && el.classList.contains(name));
+ let newAdds = prevAdds.filter((name) => removes.indexOf(name) < 0).concat(keepAdds);
+ let newRemoves = prevRemoves.filter((name) => adds.indexOf(name) < 0).concat(keepRemoves);
+ dom_default.putSticky(el, "classes", (currentEl) => {
+ currentEl.classList.remove(...newRemoves);
+ currentEl.classList.add(...newAdds);
+ return [newAdds, newRemoves];
+ });
+ });
+ },
+ setOrRemoveAttrs(el, sets, removes) {
+ let [prevSets, prevRemoves] = dom_default.getSticky(el, "attrs", [[], []]);
+ let alteredAttrs = sets.map(([attr, _val]) => attr).concat(removes);
+ let newSets = prevSets.filter(([attr, _val]) => !alteredAttrs.includes(attr)).concat(sets);
+ let newRemoves = prevRemoves.filter((attr) => !alteredAttrs.includes(attr)).concat(removes);
+ dom_default.putSticky(el, "attrs", (currentEl) => {
+ newRemoves.forEach((attr) => currentEl.removeAttribute(attr));
+ newSets.forEach(([attr, val]) => currentEl.setAttribute(attr, val));
+ return [newSets, newRemoves];
+ });
+ },
+ hasAllClasses(el, classes) {
+ return classes.every((name) => el.classList.contains(name));
+ },
+ isToggledOut(el, outClasses) {
+ return !this.isVisible(el) || this.hasAllClasses(el, outClasses);
+ },
+ filterToEls(sourceEl, { to }) {
+ return to ? dom_default.all(document, to) : [sourceEl];
+ },
+ defaultDisplay(el) {
+ return { tr: "table-row", td: "table-cell" }[el.tagName.toLowerCase()] || "block";
+ }
+};
+var js_default = JS;
+
// js/phoenix_live_view/view.js
-var serializeForm = (form, meta = {}) => {
+var serializeForm = (form, meta, onlyNames = []) => {
let formData = new FormData(form);
let toRemove = [];
formData.forEach((val, key, _index) => {
@@ -1892,7 +2407,9 @@ var serializeForm = (form, meta = {}) => {
toRemove.forEach((key) => formData.delete(key));
let params = new URLSearchParams();
for (let [key, val] of formData.entries()) {
- params.append(key, val);
+ if (onlyNames.length === 0 || onlyNames.indexOf(key) >= 0) {
+ params.append(key, val);
+ }
}
for (let metaKey in meta) {
params.append(metaKey, meta[metaKey]);
@@ -1900,7 +2417,8 @@ var serializeForm = (form, meta = {}) => {
return params.toString();
};
var View = class {
- constructor(el, liveSocket, parentView, flash) {
+ constructor(el, liveSocket, parentView, flash, liveReferer) {
+ this.isDead = false;
this.liveSocket = liveSocket;
this.flash = flash;
this.parent = parentView;
@@ -1917,7 +2435,8 @@ var View = class {
this.joinCount = this.parent ? this.parent.joinCount - 1 : 0;
this.joinPending = true;
this.destroyed = false;
- this.joinCallback = function() {
+ this.joinCallback = function(onDone) {
+ onDone && onDone();
};
this.stopCallback = function() {
};
@@ -1931,14 +2450,12 @@ var View = class {
return {
redirect: this.redirect ? this.href : void 0,
url: this.redirect ? void 0 : this.href || void 0,
- params: this.connectParams(),
+ params: this.connectParams(liveReferer),
session: this.getSession(),
static: this.getStatic(),
flash: this.flash
};
});
- this.showLoader(this.liveSocket.loaderTimeout);
- this.bindChannel();
}
setHref(href) {
this.href = href;
@@ -1948,15 +2465,16 @@ var View = class {
this.href = href;
}
isMain() {
- return this.liveSocket.main === this;
+ return this.el.hasAttribute(PHX_MAIN);
}
- connectParams() {
+ connectParams(liveReferer) {
let params = this.liveSocket.params(this.el);
let manifest = dom_default.all(document, `[${this.binding(PHX_TRACK_STATIC)}]`).map((node) => node.src || node.href).filter((url) => typeof url === "string");
if (manifest.length > 0) {
params["_track_static"] = manifest;
}
params["_mounts"] = this.joinCount;
+ params["_live_referer"] = liveReferer;
return params;
}
isConnected() {
@@ -1992,9 +2510,6 @@ var View = class {
this.el.classList.remove(PHX_CONNECTED_CLASS, PHX_DISCONNECTED_CLASS, PHX_ERROR_CLASS);
this.el.classList.add(...classes);
}
- isLoading() {
- return this.el.classList.contains(PHX_DISCONNECTED_CLASS);
- }
showLoader(timeout) {
clearTimeout(this.loaderTimer);
if (timeout) {
@@ -2006,9 +2521,13 @@ var View = class {
this.setContainerClasses(PHX_DISCONNECTED_CLASS);
}
}
+ execAll(binding) {
+ dom_default.all(this.el, `[${binding}]`, (el) => this.liveSocket.execJS(el, el.getAttribute(binding)));
+ }
hideLoader() {
clearTimeout(this.loaderTimer);
this.setContainerClasses(PHX_CONNECTED_CLASS);
+ this.execAll(this.binding("connected"));
}
triggerReconnected() {
for (let id in this.viewHooks) {
@@ -2018,16 +2537,20 @@ var View = class {
log(kind, msgCallback) {
this.liveSocket.log(this, kind, msgCallback);
}
+ transition(time, onStart, onDone = function() {
+ }) {
+ this.liveSocket.transition(time, onStart, onDone);
+ }
withinTargets(phxTarget, callback) {
- if (phxTarget instanceof HTMLElement) {
+ if (phxTarget instanceof HTMLElement || phxTarget instanceof SVGElement) {
return this.liveSocket.owner(phxTarget, (view) => callback(view, phxTarget));
}
- if (/^(0|[1-9]\d*)$/.test(phxTarget)) {
+ if (isCid(phxTarget)) {
let targets = dom_default.findComponentNodeList(this.el, phxTarget);
if (targets.length === 0) {
logError(`no component found matching phx-target of ${phxTarget}`);
} else {
- callback(this, targets[0]);
+ callback(this, parseInt(phxTarget));
}
} else {
let targets = Array.from(document.querySelectorAll(phxTarget));
@@ -2040,11 +2563,10 @@ var View = class {
applyDiff(type, rawDiff, callback) {
this.log(type, () => ["", clone(rawDiff)]);
let { diff, reply, events, title } = Rendered.extract(rawDiff);
+ callback({ diff, reply, events });
if (title) {
- dom_default.putTitle(title);
+ window.requestAnimationFrame(() => dom_default.putTitle(title));
}
- callback({ diff, reply, events });
- return reply;
}
onJoin(resp) {
let { rendered, container } = resp;
@@ -2058,7 +2580,7 @@ var View = class {
browser_default.dropLocal(this.liveSocket.localStorage, window.location.pathname, CONSECUTIVE_RELOADS);
this.applyDiff("mount", rendered, ({ diff, events }) => {
this.rendered = new Rendered(this.id, diff);
- let html = this.renderContainer(null, "join");
+ let [html, streams] = this.renderContainer(null, "join");
this.dropPendingRefs();
let forms = this.formsForRecovery(html);
this.joinCount++;
@@ -2066,21 +2588,24 @@ var View = class {
forms.forEach(([form, newForm, newCid], i) => {
this.pushFormRecovery(form, newCid, (resp2) => {
if (i === forms.length - 1) {
- this.onJoinComplete(resp2, html, events);
+ this.onJoinComplete(resp2, html, streams, events);
}
});
});
} else {
- this.onJoinComplete(resp, html, events);
+ this.onJoinComplete(resp, html, streams, events);
}
});
}
dropPendingRefs() {
- dom_default.all(this.el, `[${PHX_REF}]`, (el) => el.removeAttribute(PHX_REF));
+ dom_default.all(document, `[${PHX_REF_SRC}="${this.id}"][${PHX_REF}]`, (el) => {
+ el.removeAttribute(PHX_REF);
+ el.removeAttribute(PHX_REF_SRC);
+ });
}
- onJoinComplete({ live_patch }, html, events) {
+ onJoinComplete({ live_patch }, html, streams, events) {
if (this.joinCount > 1 || this.parent && !this.parent.isJoinPending()) {
- return this.applyJoinPatch(live_patch, html, events);
+ return this.applyJoinPatch(live_patch, html, streams, events);
}
let newChildren = dom_default.findPhxChildrenInFragment(html, this.id).filter((toEl) => {
let fromEl = toEl.id && this.el.querySelector(`[id="${toEl.id}"]`);
@@ -2092,39 +2617,35 @@ var View = class {
});
if (newChildren.length === 0) {
if (this.parent) {
- this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, events)]);
+ this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, streams, events)]);
this.parent.ackJoin(this);
} else {
this.onAllChildJoinsComplete();
- this.applyJoinPatch(live_patch, html, events);
+ this.applyJoinPatch(live_patch, html, streams, events);
}
} else {
- this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, events)]);
+ this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, streams, events)]);
}
}
attachTrueDocEl() {
this.el = dom_default.byId(this.id);
this.el.setAttribute(PHX_ROOT_ID, this.root.id);
}
- dispatchEvents(events) {
- events.forEach(([event, payload]) => {
- window.dispatchEvent(new CustomEvent(`phx:hook:${event}`, { detail: payload }));
+ execNewMounted() {
+ dom_default.all(this.el, `[${this.binding(PHX_HOOK)}], [data-phx-${PHX_HOOK}]`, (hookEl) => {
+ this.maybeAddNewHook(hookEl);
});
+ dom_default.all(this.el, `[${this.binding(PHX_MOUNTED)}]`, (el) => this.maybeMounted(el));
}
- applyJoinPatch(live_patch, html, events) {
+ applyJoinPatch(live_patch, html, streams, events) {
this.attachTrueDocEl();
- let patch = new DOMPatch(this, this.el, this.id, html, null);
+ let patch = new DOMPatch(this, this.el, this.id, html, streams, null);
patch.markPrunableContentForRemoval();
this.performPatch(patch, false);
this.joinNewChildren();
- dom_default.all(this.el, `[${this.binding(PHX_HOOK)}], [data-phx-${PHX_HOOK}]`, (hookEl) => {
- let hook = this.addHook(hookEl);
- if (hook) {
- hook.__mounted();
- }
- });
+ this.execNewMounted();
this.joinPending = false;
- this.dispatchEvents(events);
+ this.liveSocket.dispatchEvents(events);
this.applyPendingUpdates();
if (live_patch) {
let { kind, to } = live_patch;
@@ -2145,18 +2666,38 @@ var View = class {
return hook;
}
}
+ maybeMounted(el) {
+ let phxMounted = el.getAttribute(this.binding(PHX_MOUNTED));
+ let hasBeenInvoked = phxMounted && dom_default.private(el, "mounted");
+ if (phxMounted && !hasBeenInvoked) {
+ this.liveSocket.execJS(el, phxMounted);
+ dom_default.putPrivate(el, "mounted", true);
+ }
+ }
+ maybeAddNewHook(el, force) {
+ let newHook = this.addHook(el);
+ if (newHook) {
+ newHook.__mounted();
+ }
+ }
performPatch(patch, pruneCids) {
- let destroyedCIDs = [];
+ let removedEls = [];
let phxChildrenAdded = false;
let updatedHookIds = new Set();
patch.after("added", (el) => {
this.liveSocket.triggerDOM("onNodeAdded", [el]);
- let newHook = this.addHook(el);
- if (newHook) {
- newHook.__mounted();
+ this.maybeAddNewHook(el);
+ if (el.getAttribute) {
+ this.maybeMounted(el);
+ }
+ });
+ patch.after("phxChildAdded", (el) => {
+ if (dom_default.isPhxSticky(el)) {
+ this.liveSocket.joinRootViews();
+ } else {
+ phxChildrenAdded = true;
}
});
- patch.after("phxChildAdded", (_el) => phxChildrenAdded = true);
patch.before("updated", (fromEl, toEl) => {
let hook = this.triggerBeforeUpdateHook(fromEl, toEl);
if (hook) {
@@ -2169,18 +2710,34 @@ var View = class {
}
});
patch.after("discarded", (el) => {
- let cid = this.componentID(el);
- if (isCid(cid) && destroyedCIDs.indexOf(cid) === -1) {
- destroyedCIDs.push(cid);
+ if (el.nodeType === Node.ELEMENT_NODE) {
+ removedEls.push(el);
}
- let hook = this.getHook(el);
- hook && this.destroyHook(hook);
});
+ patch.after("transitionsDiscarded", (els) => this.afterElementsRemoved(els, pruneCids));
patch.perform();
+ this.afterElementsRemoved(removedEls, pruneCids);
+ return phxChildrenAdded;
+ }
+ afterElementsRemoved(elements, pruneCids) {
+ let destroyedCIDs = [];
+ elements.forEach((parent) => {
+ let components = dom_default.all(parent, `[${PHX_COMPONENT}]`);
+ let hooks = dom_default.all(parent, `[${this.binding(PHX_HOOK)}]`);
+ components.concat(parent).forEach((el) => {
+ let cid = this.componentID(el);
+ if (isCid(cid) && destroyedCIDs.indexOf(cid) === -1) {
+ destroyedCIDs.push(cid);
+ }
+ });
+ hooks.concat(parent).forEach((hookEl) => {
+ let hook = this.getHook(hookEl);
+ hook && this.destroyHook(hook);
+ });
+ });
if (pruneCids) {
this.maybePushComponentsDestroyed(destroyedCIDs);
}
- return phxChildrenAdded;
}
joinNewChildren() {
dom_default.findPhxChildren(this.el, this.id).forEach((el) => this.joinChild(el));
@@ -2228,16 +2785,17 @@ var View = class {
}
}
onAllChildJoinsComplete() {
- this.joinCallback();
- this.pendingJoinOps.forEach(([view, op]) => {
- if (!view.isDestroyed()) {
- op();
- }
+ this.joinCallback(() => {
+ this.pendingJoinOps.forEach(([view, op]) => {
+ if (!view.isDestroyed()) {
+ op();
+ }
+ });
+ this.pendingJoinOps = [];
});
- this.pendingJoinOps = [];
}
update(diff, events) {
- if (this.isJoinPending() || this.liveSocket.hasPendingLink()) {
+ if (this.isJoinPending() || this.liveSocket.hasPendingLink() && this.root.isMain()) {
return this.pendingDiffs.push({ diff, events });
}
this.rendered.mergeDiff(diff);
@@ -2253,12 +2811,12 @@ var View = class {
});
} else if (!isEmpty(diff)) {
this.liveSocket.time("full patch complete", () => {
- let html = this.renderContainer(diff, "update");
- let patch = new DOMPatch(this, this.el, this.id, html, null);
+ let [html, streams] = this.renderContainer(diff, "update");
+ let patch = new DOMPatch(this, this.el, this.id, html, streams, null);
phxChildrenAdded = this.performPatch(patch, true);
});
}
- this.dispatchEvents(events);
+ this.liveSocket.dispatchEvents(events);
if (phxChildrenAdded) {
this.joinNewChildren();
}
@@ -2267,15 +2825,15 @@ var View = class {
return this.liveSocket.time(`toString diff (${kind})`, () => {
let tag = this.el.tagName;
let cids = diff ? this.rendered.componentCIDs(diff).concat(this.pruningCIDs) : null;
- let html = this.rendered.toString(cids);
- return `<${tag}>${html}${tag}>`;
+ let [html, streams] = this.rendered.toString(cids);
+ return [`<${tag}>${html}${tag}>`, streams];
});
}
componentPatch(diff, cid) {
if (isEmpty(diff))
return false;
- let html = this.rendered.componentToString(cid);
- let patch = new DOMPatch(this, this.el, this.id, html, cid);
+ let [html, streams] = this.rendered.componentToString(cid);
+ let patch = new DOMPatch(this, this.el, this.id, html, streams, cid);
let childrenAdded = this.performPatch(patch, true);
return childrenAdded;
}
@@ -2310,19 +2868,28 @@ var View = class {
applyPendingUpdates() {
this.pendingDiffs.forEach(({ diff, events }) => this.update(diff, events));
this.pendingDiffs = [];
+ this.eachChild((child) => child.applyPendingUpdates());
+ }
+ eachChild(callback) {
+ let children = this.root.children[this.id] || {};
+ for (let id in children) {
+ callback(this.getChildById(id));
+ }
}
onChannel(event, cb) {
this.liveSocket.onChannel(this.channel, event, (resp) => {
if (this.isJoinPending()) {
this.root.pendingJoinOps.push([this, () => cb(resp)]);
} else {
- cb(resp);
+ this.liveSocket.requestDOMUpdate(() => cb(resp));
}
});
}
bindChannel() {
this.liveSocket.onChannel(this.channel, "diff", (rawDiff) => {
- this.applyDiff("update", rawDiff, ({ diff, events }) => this.update(diff, events));
+ this.liveSocket.requestDOMUpdate(() => {
+ this.applyDiff("update", rawDiff, ({ diff, events }) => this.update(diff, events));
+ });
});
this.onChannel("redirect", ({ to, flash }) => this.onRedirect({ to, flash }));
this.onChannel("live_patch", (redir) => this.onLivePatch(redir));
@@ -2331,9 +2898,7 @@ var View = class {
this.channel.onClose((reason) => this.onClose(reason));
}
destroyAllChildren() {
- for (let id in this.root.children[this.id]) {
- this.getChildById(id).destroy();
- }
+ this.eachChild((child) => child.destroy());
}
onLiveRedirect(redir) {
let { to, kind, flash } = redir;
@@ -2354,17 +2919,33 @@ var View = class {
isDestroyed() {
return this.destroyed;
}
+ joinDead() {
+ this.isDead = true;
+ }
join(callback) {
- if (!this.parent) {
+ this.showLoader(this.liveSocket.loaderTimeout);
+ this.bindChannel();
+ if (this.isMain()) {
this.stopCallback = this.liveSocket.withPageLoading({ to: this.href, kind: "initial" });
}
- this.joinCallback = () => callback && callback(this.joinCount);
+ this.joinCallback = (onDone) => {
+ onDone = onDone || function() {
+ };
+ callback ? callback(this.joinCount, onDone) : onDone();
+ };
this.liveSocket.wrapPush(this, { timeout: false }, () => {
- return this.channel.join().receive("ok", (data) => !this.isDestroyed() && this.onJoin(data)).receive("error", (resp) => !this.isDestroyed() && this.onJoinError(resp)).receive("timeout", () => !this.isDestroyed() && this.onJoinError({ reason: "timeout" }));
+ return this.channel.join().receive("ok", (data) => {
+ if (!this.isDestroyed()) {
+ this.liveSocket.requestDOMUpdate(() => this.onJoin(data));
+ }
+ }).receive("error", (resp) => !this.isDestroyed() && this.onJoinError(resp)).receive("timeout", () => !this.isDestroyed() && this.onJoinError({ reason: "timeout" }));
});
}
onJoinError(resp) {
- if (resp.reason === "unauthorized" || resp.reason === "stale") {
+ if (resp.reason === "reload") {
+ this.log("error", () => [`failed mount with ${resp.status}. Falling back to page request`, resp]);
+ return this.onRedirect({ to: this.href });
+ } else if (resp.reason === "unauthorized" || resp.reason === "stale") {
this.log("error", () => ["unauthorized live_redirect. Falling back to page request", resp]);
return this.onRedirect({ to: this.href });
}
@@ -2379,13 +2960,15 @@ var View = class {
return this.onLiveRedirect(resp.live_redirect);
}
this.log("error", () => ["unable to join", resp]);
- return this.liveSocket.reloadWithJitter(this);
+ if (this.liveSocket.isConnected()) {
+ this.liveSocket.reloadWithJitter(this);
+ }
}
onClose(reason) {
if (this.isDestroyed()) {
return;
}
- if (this.isJoinPending() && document.visibilityState !== "hidden" || this.liveSocket.hasPendingLink() && reason !== "leave") {
+ if (this.liveSocket.hasPendingLink() && reason !== "leave") {
return this.liveSocket.reloadWithJitter(this);
}
this.destroyAllChildren();
@@ -2399,27 +2982,30 @@ var View = class {
}
onError(reason) {
this.onClose(reason);
- this.log("error", () => ["view crashed", reason]);
+ if (this.liveSocket.isConnected()) {
+ this.log("error", () => ["view crashed", reason]);
+ }
if (!this.liveSocket.isUnloaded()) {
this.displayError();
}
}
displayError() {
if (this.isMain()) {
- dom_default.dispatchEvent(window, "phx:page-loading-start", { to: this.href, kind: "error" });
+ dom_default.dispatchEvent(window, "phx:page-loading-start", { detail: { to: this.href, kind: "error" } });
}
this.showLoader();
this.setContainerClasses(PHX_DISCONNECTED_CLASS, PHX_ERROR_CLASS);
+ this.execAll(this.binding("disconnected"));
}
pushWithReply(refGenerator, event, payload, onReply = function() {
}) {
if (!this.isConnected()) {
return;
}
- let [ref, [el]] = refGenerator ? refGenerator() : [null, []];
+ let [ref, [el], opts] = refGenerator ? refGenerator() : [null, [], {}];
let onLoadingDone = function() {
};
- if (el && el.getAttribute(this.binding(PHX_PAGE_LOADING)) !== null) {
+ if (opts.page_loading || el && el.getAttribute(this.binding(PHX_PAGE_LOADING)) !== null) {
onLoadingDone = this.liveSocket.withPageLoading({ kind: "element", target: el });
}
if (typeof payload.cid !== "number") {
@@ -2427,33 +3013,43 @@ var View = class {
}
return this.liveSocket.wrapPush(this, { timeout: true }, () => {
return this.channel.push(event, payload, PUSH_TIMEOUT).receive("ok", (resp) => {
- let hookReply = null;
- if (ref !== null) {
- this.undoRefs(ref);
- }
+ let finish = (hookReply) => {
+ if (resp.redirect) {
+ this.onRedirect(resp.redirect);
+ }
+ if (resp.live_patch) {
+ this.onLivePatch(resp.live_patch);
+ }
+ if (resp.live_redirect) {
+ this.onLiveRedirect(resp.live_redirect);
+ }
+ if (ref !== null) {
+ this.undoRefs(ref);
+ }
+ onLoadingDone();
+ onReply(resp, hookReply);
+ };
if (resp.diff) {
- hookReply = this.applyDiff("update", resp.diff, ({ diff, events }) => {
- this.update(diff, events);
+ this.liveSocket.requestDOMUpdate(() => {
+ this.applyDiff("update", resp.diff, ({ diff, reply, events }) => {
+ this.update(diff, events);
+ finish(reply);
+ });
});
+ } else {
+ finish(null);
}
- if (resp.redirect) {
- this.onRedirect(resp.redirect);
- }
- if (resp.live_patch) {
- this.onLivePatch(resp.live_patch);
- }
- if (resp.live_redirect) {
- this.onLiveRedirect(resp.live_redirect);
- }
- onLoadingDone();
- onReply(resp, hookReply);
});
});
}
undoRefs(ref) {
- dom_default.all(this.el, `[${PHX_REF}="${ref}"]`, (el) => {
+ if (!this.isConnected()) {
+ return;
+ }
+ dom_default.all(document, `[${PHX_REF_SRC}="${this.id}"][${PHX_REF}="${ref}"]`, (el) => {
let disabledVal = el.getAttribute(PHX_DISABLED);
el.removeAttribute(PHX_REF);
+ el.removeAttribute(PHX_REF_SRC);
if (el.getAttribute(PHX_READONLY) !== null) {
el.readOnly = false;
el.removeAttribute(PHX_READONLY);
@@ -2479,35 +3075,50 @@ var View = class {
}
});
}
- putRef(elements, event) {
+ putRef(elements, event, opts = {}) {
let newRef = this.ref++;
let disableWith = this.binding(PHX_DISABLE_WITH);
+ if (opts.loading) {
+ elements = elements.concat(dom_default.all(document, opts.loading));
+ }
elements.forEach((el) => {
el.classList.add(`phx-${event}-loading`);
el.setAttribute(PHX_REF, newRef);
+ el.setAttribute(PHX_REF_SRC, this.el.id);
let disableText = el.getAttribute(disableWith);
if (disableText !== null) {
if (!el.getAttribute(PHX_DISABLE_WITH_RESTORE)) {
el.setAttribute(PHX_DISABLE_WITH_RESTORE, el.innerText);
}
- el.innerText = disableText;
+ if (disableText !== "") {
+ el.innerText = disableText;
+ }
+ el.setAttribute("disabled", "");
}
});
- return [newRef, elements];
+ return [newRef, elements, opts];
}
componentID(el) {
let cid = el.getAttribute && el.getAttribute(PHX_COMPONENT);
return cid ? parseInt(cid) : null;
}
- targetComponentID(target, targetCtx) {
- if (target.getAttribute(this.binding("target"))) {
+ targetComponentID(target, targetCtx, opts = {}) {
+ if (isCid(targetCtx)) {
+ return targetCtx;
+ }
+ let cidOrSelector = target.getAttribute(this.binding("target"));
+ if (isCid(cidOrSelector)) {
+ return parseInt(cidOrSelector);
+ } else if (targetCtx && (cidOrSelector !== null || opts.target)) {
return this.closestComponentID(targetCtx);
} else {
return null;
}
}
closestComponentID(targetCtx) {
- if (targetCtx) {
+ if (isCid(targetCtx)) {
+ return targetCtx;
+ } else if (targetCtx) {
return maybe(targetCtx.closest(`[${PHX_COMPONENT}]`), (el) => this.ownsElement(el) && this.componentID(el));
} else {
return null;
@@ -2518,8 +3129,8 @@ var View = class {
this.log("hook", () => ["unable to push hook event. LiveView not connected", event, payload]);
return false;
}
- let [ref, els] = this.putRef([], "hook");
- this.pushWithReply(() => [ref, els], "event", {
+ let [ref, els, opts] = this.putRef([], "hook");
+ this.pushWithReply(() => [ref, els, opts], "event", {
type: "hook",
event,
value: payload,
@@ -2527,36 +3138,42 @@ var View = class {
}, (resp, reply) => onReply(reply, ref));
return ref;
}
- extractMeta(el, meta) {
+ extractMeta(el, meta, value) {
let prefix = this.binding("value-");
for (let i = 0; i < el.attributes.length; i++) {
+ if (!meta) {
+ meta = {};
+ }
let name = el.attributes[i].name;
if (name.startsWith(prefix)) {
meta[name.replace(prefix, "")] = el.getAttribute(name);
}
}
if (el.value !== void 0) {
+ if (!meta) {
+ meta = {};
+ }
meta.value = el.value;
if (el.tagName === "INPUT" && CHECKABLE_INPUTS.indexOf(el.type) >= 0 && !el.checked) {
delete meta.value;
}
}
+ if (value) {
+ if (!meta) {
+ meta = {};
+ }
+ for (let key in value) {
+ meta[key] = value[key];
+ }
+ }
return meta;
}
- pushEvent(type, el, targetCtx, phxEvent, meta) {
- this.pushWithReply(() => this.putRef([el], type), "event", {
+ pushEvent(type, el, targetCtx, phxEvent, meta, opts = {}) {
+ this.pushWithReply(() => this.putRef([el], type, opts), "event", {
type,
event: phxEvent,
- value: this.extractMeta(el, meta),
- cid: this.targetComponentID(el, targetCtx)
- });
- }
- pushKey(keyElement, targetCtx, kind, phxEvent, meta) {
- this.pushWithReply(() => this.putRef([keyElement], kind), "event", {
- type: kind,
- event: phxEvent,
- value: this.extractMeta(keyElement, meta),
- cid: this.targetComponentID(keyElement, targetCtx)
+ value: this.extractMeta(el, meta, opts.value),
+ cid: this.targetComponentID(el, targetCtx, opts)
});
}
pushFileProgress(fileEl, entryRef, progress, onReply = function() {
@@ -2571,11 +3188,16 @@ var View = class {
}, onReply);
});
}
- pushInput(inputEl, targetCtx, forceCid, phxEvent, eventTarget, callback) {
+ pushInput(inputEl, targetCtx, forceCid, phxEvent, opts, callback) {
let uploads;
let cid = isCid(forceCid) ? forceCid : this.targetComponentID(inputEl.form, targetCtx);
- let refGenerator = () => this.putRef([inputEl, inputEl.form], "change");
- let formData = serializeForm(inputEl.form, { _target: eventTarget.name });
+ let refGenerator = () => this.putRef([inputEl, inputEl.form], "change", opts);
+ let formData;
+ if (inputEl.getAttribute(this.binding("change"))) {
+ formData = serializeForm(inputEl.form, { _target: opts._target }, [inputEl.name]);
+ } else {
+ formData = serializeForm(inputEl.form, { _target: opts._target });
+ }
if (dom_default.isUploadInput(inputEl) && inputEl.files && inputEl.files.length > 0) {
LiveUploader.trackFiles(inputEl, Array.from(inputEl.files));
}
@@ -2605,19 +3227,19 @@ var View = class {
triggerAwaitingSubmit(formEl) {
let awaitingSubmit = this.getScheduledSubmit(formEl);
if (awaitingSubmit) {
- let [_el, _ref, callback] = awaitingSubmit;
+ let [_el, _ref, _opts, callback] = awaitingSubmit;
this.cancelSubmit(formEl);
callback();
}
}
getScheduledSubmit(formEl) {
- return this.formSubmits.find(([el, _callback]) => el.isSameNode(formEl));
+ return this.formSubmits.find(([el, _ref, _opts, _callback]) => el.isSameNode(formEl));
}
- scheduleSubmit(formEl, ref, callback) {
+ scheduleSubmit(formEl, ref, opts, callback) {
if (this.getScheduledSubmit(formEl)) {
return true;
}
- this.formSubmits.push([formEl, ref, callback]);
+ this.formSubmits.push([formEl, ref, opts, callback]);
}
cancelSubmit(formEl) {
this.formSubmits = this.formSubmits.filter(([el, ref, _callback]) => {
@@ -2629,7 +3251,7 @@ var View = class {
}
});
}
- pushFormSubmit(formEl, targetCtx, phxEvent, onReply) {
+ disableForm(formEl, opts = {}) {
let filterIgnored = (el) => {
let userIgnored = closestPhxBinding(el, `${this.binding(PHX_UPDATE)}=ignore`, el.form);
return !(userIgnored || closestPhxBinding(el, "data-phx-update=ignore", el.form));
@@ -2639,33 +3261,35 @@ var View = class {
};
let filterButton = (el) => el.tagName == "BUTTON";
let filterInput = (el) => ["INPUT", "TEXTAREA", "SELECT"].includes(el.tagName);
- let refGenerator = () => {
- let formElements = Array.from(formEl.elements);
- let disables = formElements.filter(filterDisables);
- let buttons = formElements.filter(filterButton).filter(filterIgnored);
- let inputs = formElements.filter(filterInput).filter(filterIgnored);
- buttons.forEach((button) => {
- button.setAttribute(PHX_DISABLED, button.disabled);
- button.disabled = true;
- });
- inputs.forEach((input) => {
- input.setAttribute(PHX_READONLY, input.readOnly);
- input.readOnly = true;
- if (input.files) {
- input.setAttribute(PHX_DISABLED, input.disabled);
- input.disabled = true;
- }
- });
- formEl.setAttribute(this.binding(PHX_PAGE_LOADING), "");
- return this.putRef([formEl].concat(disables).concat(buttons).concat(inputs), "submit");
- };
+ let formElements = Array.from(formEl.elements);
+ let disables = formElements.filter(filterDisables);
+ let buttons = formElements.filter(filterButton).filter(filterIgnored);
+ let inputs = formElements.filter(filterInput).filter(filterIgnored);
+ buttons.forEach((button) => {
+ button.setAttribute(PHX_DISABLED, button.disabled);
+ button.disabled = true;
+ });
+ inputs.forEach((input) => {
+ input.setAttribute(PHX_READONLY, input.readOnly);
+ input.readOnly = true;
+ if (input.files) {
+ input.setAttribute(PHX_DISABLED, input.disabled);
+ input.disabled = true;
+ }
+ });
+ formEl.setAttribute(this.binding(PHX_PAGE_LOADING), "");
+ return this.putRef([formEl].concat(disables).concat(buttons).concat(inputs), "submit", opts);
+ }
+ pushFormSubmit(formEl, targetCtx, phxEvent, opts, onReply) {
+ let refGenerator = () => this.disableForm(formEl, opts);
let cid = this.targetComponentID(formEl, targetCtx);
if (LiveUploader.hasUploadsInProgress(formEl)) {
let [ref, _els] = refGenerator();
- return this.scheduleSubmit(formEl, ref, () => this.pushFormSubmit(formEl, targetCtx, phxEvent, onReply));
+ let push = () => this.pushFormSubmit(formEl, targetCtx, phxEvent, opts, onReply);
+ return this.scheduleSubmit(formEl, ref, opts, push);
} else if (LiveUploader.inputsAwaitingPreflight(formEl).length > 0) {
let [ref, els] = refGenerator();
- let proxyRefGen = () => [ref, els];
+ let proxyRefGen = () => [ref, els, opts];
this.uploadFiles(formEl, targetCtx, ref, cid, (_uploads) => {
let formData = serializeForm(formEl, {});
this.pushWithReply(proxyRefGen, "event", {
@@ -2676,7 +3300,7 @@ var View = class {
}, onReply);
});
} else {
- let formData = serializeForm(formEl);
+ let formData = serializeForm(formEl, {});
this.pushWithReply(refGenerator, "event", {
type: "form",
event: phxEvent,
@@ -2730,30 +3354,40 @@ var View = class {
} else if (inputs.length > 1) {
logError(`duplicate live file inputs found matching the name "${name}"`);
} else {
- dom_default.dispatchEvent(inputs[0], PHX_TRACK_UPLOADS, { files: filesOrBlobs });
+ dom_default.dispatchEvent(inputs[0], PHX_TRACK_UPLOADS, { detail: { files: filesOrBlobs } });
}
}
pushFormRecovery(form, newCid, callback) {
this.liveSocket.withinOwners(form, (view, targetCtx) => {
- let input = form.elements[0];
+ let input = Array.from(form.elements).find((el) => {
+ return dom_default.isFormInput(el) && el.type !== "hidden" && !el.hasAttribute(this.binding("change"));
+ });
let phxEvent = form.getAttribute(this.binding(PHX_AUTO_RECOVER)) || form.getAttribute(this.binding("change"));
- view.pushInput(input, targetCtx, newCid, phxEvent, input, callback);
+ js_default.exec("change", phxEvent, view, input, ["push", { _target: input.name, newCid, callback }]);
});
}
pushLinkPatch(href, targetEl, callback) {
let linkRef = this.liveSocket.setPendingLink(href);
let refGen = targetEl ? () => this.putRef([targetEl], "click") : null;
- this.pushWithReply(refGen, "live_patch", { url: href }, (resp) => {
- if (resp.link_redirect) {
- this.liveSocket.replaceMain(href, null, callback, linkRef);
- } else {
- if (this.liveSocket.commitPendingLink(linkRef)) {
- this.href = href;
+ let fallback = () => this.liveSocket.redirect(window.location.href);
+ let push = this.pushWithReply(refGen, "live_patch", { url: href }, (resp) => {
+ this.liveSocket.requestDOMUpdate(() => {
+ if (resp.link_redirect) {
+ this.liveSocket.replaceMain(href, null, callback, linkRef);
+ } else {
+ if (this.liveSocket.commitPendingLink(linkRef)) {
+ this.href = href;
+ }
+ this.applyPendingUpdates();
+ callback && callback(linkRef);
}
- this.applyPendingUpdates();
- callback && callback(linkRef);
- }
- }).receive("timeout", () => this.liveSocket.redirect(window.location.href));
+ });
+ });
+ if (push) {
+ push.receive("timeout", fallback);
+ } else {
+ fallback();
+ }
}
formsForRecovery(html) {
if (this.joinCount === 0) {
@@ -2765,7 +3399,7 @@ var View = class {
return dom_default.all(this.el, `form[${phxChange}]`).filter((form) => form.id && this.ownsElement(form)).filter((form) => form.elements.length > 0).filter((form) => form.getAttribute(this.binding(PHX_AUTO_RECOVER)) !== "ignore").map((form) => {
let newForm = template.content.querySelector(`form[id="${form.id}"][${phxChange}="${form.getAttribute(phxChange)}"]`);
if (newForm) {
- return [form, newForm, this.componentID(newForm)];
+ return [form, newForm, this.targetComponentID(newForm)];
} else {
return [form, null, null];
}
@@ -2791,12 +3425,17 @@ var View = class {
}
}
ownsElement(el) {
- return el.getAttribute(PHX_PARENT_ID) === this.id || maybe(el.closest(PHX_VIEW_SELECTOR), (node) => node.id) === this.id;
+ let parentViewEl = el.closest(PHX_VIEW_SELECTOR);
+ return el.getAttribute(PHX_PARENT_ID) === this.id || parentViewEl && parentViewEl.id === this.id || !parentViewEl && this.isDead;
}
- submitForm(form, targetCtx, phxEvent) {
+ submitForm(form, targetCtx, phxEvent, opts = {}) {
dom_default.putPrivate(form, PHX_HAS_SUBMITTED, true);
+ let phxFeedback = this.liveSocket.binding(PHX_FEEDBACK_FOR);
+ let inputs = Array.from(form.elements);
+ inputs.forEach((input) => dom_default.putPrivate(input, PHX_HAS_SUBMITTED, true));
this.liveSocket.blurActiveElement(this);
- this.pushFormSubmit(form, targetCtx, phxEvent, () => {
+ this.pushFormSubmit(form, targetCtx, phxEvent, opts, () => {
+ inputs.forEach((input) => dom_default.showError(input, phxFeedback));
this.liveSocket.restorePreviouslyActiveFocus();
});
}
@@ -2814,7 +3453,7 @@ var LiveSocket = class {
a phoenix Socket must be provided as the second argument to the LiveSocket constructor. For example:
import {Socket} from "phoenix"
- import LiveSocket from "phoenix_live_view"
+ import {LiveSocket} from "phoenix_live_view"
let liveSocket = new LiveSocket("/live", Socket, {...})
`);
}
@@ -2829,6 +3468,8 @@ var LiveSocket = class {
this.prevActive = null;
this.silenced = false;
this.main = null;
+ this.outgoingMainEl = null;
+ this.clickStartedAtTarget = null;
this.linkRef = 1;
this.roots = {};
this.href = window.location.href;
@@ -2837,10 +3478,16 @@ var LiveSocket = class {
this.hooks = opts.hooks || {};
this.uploaders = opts.uploaders || {};
this.loaderTimeout = opts.loaderTimeout || LOADER_TIMEOUT;
+ this.reloadWithJitterTimer = null;
+ this.maxReloads = opts.maxReloads || MAX_RELOADS;
+ this.reloadJitterMin = opts.reloadJitterMin || RELOAD_JITTER_MIN;
+ this.reloadJitterMax = opts.reloadJitterMax || RELOAD_JITTER_MAX;
+ this.failsafeJitter = opts.failsafeJitter || FAILSAFE_JITTER;
this.localStorage = opts.localStorage || window.localStorage;
this.sessionStorage = opts.sessionStorage || window.sessionStorage;
this.boundTopLevelEvents = false;
this.domCallbacks = Object.assign({ onNodeAdded: closure(), onBeforeElUpdated: closure() }, opts.dom || {});
+ this.transitions = new TransitionSet();
window.addEventListener("pagehide", (_e) => {
this.unloaded = true;
});
@@ -2856,6 +3503,9 @@ var LiveSocket = class {
isDebugEnabled() {
return this.sessionStorage.getItem(PHX_LV_DEBUG) === "true";
}
+ isDebugDisabled() {
+ return this.sessionStorage.getItem(PHX_LV_DEBUG) === "false";
+ }
enableDebug() {
this.sessionStorage.setItem(PHX_LV_DEBUG, "true");
}
@@ -2863,7 +3513,7 @@ var LiveSocket = class {
this.sessionStorage.setItem(PHX_LV_PROFILE, "true");
}
disableDebug() {
- this.sessionStorage.removeItem(PHX_LV_DEBUG);
+ this.sessionStorage.setItem(PHX_LV_DEBUG, "false");
}
disableProfiling() {
this.sessionStorage.removeItem(PHX_LV_PROFILE);
@@ -2884,11 +3534,19 @@ var LiveSocket = class {
return this.socket;
}
connect() {
+ if (window.location.hostname === "localhost" && !this.isDebugDisabled()) {
+ this.enableDebug();
+ }
let doConnect = () => {
if (this.joinRootViews()) {
this.bindTopLevelEvents();
this.socket.connect();
+ } else if (this.main) {
+ this.socket.connect();
+ } else {
+ this.bindTopLevelEvents({ dead: true });
}
+ this.joinDeadView();
};
if (["complete", "loaded", "interactive"].indexOf(document.readyState) >= 0) {
doConnect();
@@ -2897,8 +3555,28 @@ var LiveSocket = class {
}
}
disconnect(callback) {
+ clearTimeout(this.reloadWithJitterTimer);
this.socket.disconnect(callback);
}
+ replaceTransport(transport) {
+ clearTimeout(this.reloadWithJitterTimer);
+ this.socket.replaceTransport(transport);
+ this.connect();
+ }
+ execJS(el, encodedJS, eventType = null) {
+ this.owner(el, (view) => js_default.exec(eventType, encodedJS, view, el));
+ }
+ unload() {
+ if (this.unloaded) {
+ return;
+ }
+ if (this.main && this.isConnected()) {
+ this.log(this.main, "socket", () => ["disconnect for page nav"]);
+ }
+ this.unloaded = true;
+ this.destroyAllViews();
+ this.disconnect();
+ }
triggerDOM(kind, args) {
this.domCallbacks[kind](...args);
}
@@ -2920,13 +3598,19 @@ var LiveSocket = class {
debug(view, kind, msg, obj);
}
}
+ requestDOMUpdate(callback) {
+ this.transitions.after(callback);
+ }
+ transition(time, onStart, onDone = function() {
+ }) {
+ this.transitions.addTransition(time, onStart, onDone);
+ }
onChannel(channel, event, cb) {
channel.on(event, (data) => {
let latency = this.getLatencySim();
if (!latency) {
cb(data);
} else {
- console.log(`simulating ${latency}ms of latency from server to client`);
setTimeout(() => cb(data), latency);
}
});
@@ -2935,7 +3619,7 @@ var LiveSocket = class {
let latency = this.getLatencySim();
let oldJoinCount = view.joinCount;
if (!latency) {
- if (opts.timeout) {
+ if (this.isConnected() && opts.timeout) {
return push().receive("timeout", () => {
if (view.joinCount === oldJoinCount && !view.isDestroyed()) {
this.reloadWithJitter(view, () => {
@@ -2947,7 +3631,6 @@ var LiveSocket = class {
return push();
}
}
- console.log(`simulating ${latency}ms of latency from client to server`);
let fakePush = {
receives: [],
receive(kind, cb) {
@@ -2963,17 +3646,24 @@ var LiveSocket = class {
return fakePush;
}
reloadWithJitter(view, log) {
- view.destroy();
+ clearTimeout(this.reloadWithJitterTimer);
this.disconnect();
- let [minMs, maxMs] = RELOAD_JITTER;
+ let minMs = this.reloadJitterMin;
+ let maxMs = this.reloadJitterMax;
let afterMs = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs;
let tries = browser_default.updateLocal(this.localStorage, window.location.pathname, CONSECUTIVE_RELOADS, 0, (count) => count + 1);
- log ? log() : this.log(view, "join", () => [`encountered ${tries} consecutive reloads`]);
- if (tries > MAX_RELOADS) {
- this.log(view, "join", () => [`exceeded ${MAX_RELOADS} consecutive reloads. Entering failsafe mode`]);
- afterMs = FAILSAFE_JITTER;
+ if (tries > this.maxReloads) {
+ afterMs = this.failsafeJitter;
}
- setTimeout(() => {
+ this.reloadWithJitterTimer = setTimeout(() => {
+ if (view.isDestroyed() || view.isConnected()) {
+ return;
+ }
+ view.destroy();
+ log ? log() : this.log(view, "join", () => [`encountered ${tries} consecutive reloads`]);
+ if (tries > this.maxReloads) {
+ this.log(view, "join", () => [`exceeded ${this.maxReloads} consecutive reloads. Entering failsafe mode`]);
+ }
if (this.hasPendingLink()) {
window.location = this.pendingLink;
} else {
@@ -2999,6 +3689,18 @@ var LiveSocket = class {
channel(topic, params) {
return this.socket.channel(topic, params);
}
+ joinDeadView() {
+ let body = document.body;
+ if (body && !this.isPhxView(body) && !this.isPhxView(document.firstElementChild)) {
+ let view = this.newRootView(body);
+ view.setHref(this.getHref());
+ view.joinDead();
+ if (!this.main) {
+ this.main = view;
+ }
+ window.requestAnimationFrame(() => view.execNewMounted());
+ }
+ }
joinRootViews() {
let rootsFound = false;
dom_default.all(document, `${PHX_VIEW_SELECTOR}:not([${PHX_PARENT_ID}])`, (rootEl) => {
@@ -3006,7 +3708,7 @@ var LiveSocket = class {
let view = this.newRootView(rootEl);
view.setHref(this.getHref());
view.join();
- if (rootEl.getAttribute(PHX_MAIN)) {
+ if (rootEl.hasAttribute(PHX_MAIN)) {
this.main = view;
}
}
@@ -3015,46 +3717,55 @@ var LiveSocket = class {
return rootsFound;
}
redirect(to, flash) {
- this.disconnect();
+ this.unload();
browser_default.redirect(to, flash);
}
replaceMain(href, flash, callback = null, linkRef = this.setPendingLink(href)) {
- let oldMainEl = this.main.el;
- let newMainEl = dom_default.cloneNode(oldMainEl, "");
+ let liveReferer = this.currentLocation.href;
+ this.outgoingMainEl = this.outgoingMainEl || this.main.el;
+ let newMainEl = dom_default.cloneNode(this.outgoingMainEl, "");
this.main.showLoader(this.loaderTimeout);
this.main.destroy();
- this.main = this.newRootView(newMainEl, flash);
+ this.main = this.newRootView(newMainEl, flash, liveReferer);
this.main.setRedirect(href);
- this.main.join((joinCount) => {
+ this.transitionRemoves();
+ this.main.join((joinCount, onDone) => {
if (joinCount === 1 && this.commitPendingLink(linkRef)) {
- oldMainEl.replaceWith(newMainEl);
- callback && callback();
+ this.requestDOMUpdate(() => {
+ dom_default.findPhxSticky(document).forEach((el) => newMainEl.appendChild(el));
+ this.outgoingMainEl.replaceWith(newMainEl);
+ this.outgoingMainEl = null;
+ callback && requestAnimationFrame(callback);
+ onDone();
+ });
+ }
+ });
+ }
+ transitionRemoves(elements) {
+ let removeAttr = this.binding("remove");
+ elements = elements || dom_default.all(document, `[${removeAttr}]`);
+ elements.forEach((el) => {
+ if (document.body.contains(el)) {
+ this.execJS(el, el.getAttribute(removeAttr), "remove");
}
});
}
isPhxView(el) {
return el.getAttribute && el.getAttribute(PHX_SESSION) !== null;
}
- newRootView(el, flash) {
- let view = new View(el, this, null, flash);
+ newRootView(el, flash, liveReferer) {
+ let view = new View(el, this, null, flash, liveReferer);
this.roots[view.id] = view;
return view;
}
owner(childEl, callback) {
- let view = maybe(childEl.closest(PHX_VIEW_SELECTOR), (el) => this.getViewByEl(el));
+ let view = maybe(childEl.closest(PHX_VIEW_SELECTOR), (el) => this.getViewByEl(el)) || this.main;
if (view) {
callback(view);
}
}
withinOwners(childEl, callback) {
- this.owner(childEl, (view) => {
- let phxTarget = childEl.getAttribute(this.binding("target"));
- if (phxTarget === null) {
- callback(view, childEl);
- } else {
- view.withinTargets(phxTarget, callback);
- }
- });
+ this.owner(childEl, (view) => callback(view, childEl));
}
getViewByEl(el) {
let rootId = el.getAttribute(PHX_ROOT_ID);
@@ -3068,10 +3779,14 @@ var LiveSocket = class {
this.roots[id].destroy();
delete this.roots[id];
}
+ this.main = null;
}
destroyViewByEl(el) {
let root = this.getRootById(el.getAttribute(PHX_ROOT_ID));
- if (root) {
+ if (root && root.id === el.id) {
+ root.destroy();
+ delete this.roots[root.id];
+ } else if (root) {
root.destroyDescendent(el.id);
}
}
@@ -3113,11 +3828,19 @@ var LiveSocket = class {
this.prevActive.blur();
}
}
- bindTopLevelEvents() {
+ bindTopLevelEvents({ dead } = {}) {
if (this.boundTopLevelEvents) {
return;
}
this.boundTopLevelEvents = true;
+ this.socket.onClose((event) => {
+ if (event && event.code === 1001) {
+ return this.unload();
+ }
+ if (event && event.code === 1e3 && this.main) {
+ return this.reloadWithJitter(this.main);
+ }
+ });
document.body.addEventListener("click", function() {
});
window.addEventListener("pageshow", (e) => {
@@ -3127,25 +3850,32 @@ var LiveSocket = class {
window.location.reload();
}
}, true);
- this.bindNav();
+ if (!dead) {
+ this.bindNav();
+ }
this.bindClicks();
- this.bindForms();
- this.bind({ keyup: "keyup", keydown: "keydown" }, (e, type, view, target, targetCtx, phxEvent, _phxTarget) => {
- let matchKey = target.getAttribute(this.binding(PHX_KEY));
+ if (!dead) {
+ this.bindForms();
+ }
+ this.bind({ keyup: "keyup", keydown: "keydown" }, (e, type, view, targetEl, phxEvent, eventTarget) => {
+ let matchKey = targetEl.getAttribute(this.binding(PHX_KEY));
let pressedKey = e.key && e.key.toLowerCase();
if (matchKey && matchKey.toLowerCase() !== pressedKey) {
return;
}
- view.pushKey(target, targetCtx, type, phxEvent, { key: e.key, ...this.eventMeta(type, e, target) });
+ let data = { key: e.key, ...this.eventMeta(type, e, targetEl) };
+ js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
});
- this.bind({ blur: "focusout", focus: "focusin" }, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {
- if (!phxTarget) {
- view.pushEvent(type, targetEl, targetCtx, phxEvent, this.eventMeta(type, e, targetEl));
+ this.bind({ blur: "focusout", focus: "focusin" }, (e, type, view, targetEl, phxEvent, eventTarget) => {
+ if (!eventTarget) {
+ let data = { key: e.key, ...this.eventMeta(type, e, targetEl) };
+ js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
}
});
this.bind({ blur: "blur", focus: "focus" }, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {
- if (phxTarget && !phxTarget !== "window") {
- view.pushEvent(type, targetEl, targetCtx, phxEvent, this.eventMeta(type, e, targetEl));
+ if (phxTarget === "window") {
+ let data = this.eventMeta(type, e, targetEl);
+ js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
}
});
window.addEventListener("dragover", (e) => e.preventDefault());
@@ -3159,7 +3889,7 @@ var LiveSocket = class {
if (!dropTarget || dropTarget.disabled || files.length === 0 || !(dropTarget.files instanceof FileList)) {
return;
}
- LiveUploader.trackFiles(dropTarget, files);
+ LiveUploader.trackFiles(dropTarget, files, e.dataTransfer);
dropTarget.dispatchEvent(new Event("input", { bubbles: true }));
});
this.on(PHX_TRACK_UPLOADS, (e) => {
@@ -3204,17 +3934,17 @@ var LiveSocket = class {
let windowBinding = this.binding(`window-${event}`);
let targetPhxEvent = e.target.getAttribute && e.target.getAttribute(binding);
if (targetPhxEvent) {
- this.debounce(e.target, e, () => {
- this.withinOwners(e.target, (view, targetCtx) => {
- callback(e, event, view, e.target, targetCtx, targetPhxEvent, null);
+ this.debounce(e.target, e, browserEventName, () => {
+ this.withinOwners(e.target, (view) => {
+ callback(e, event, view, e.target, targetPhxEvent, null);
});
});
} else {
dom_default.all(document, `[${windowBinding}]`, (el) => {
let phxEvent = el.getAttribute(windowBinding);
- this.debounce(el, e, () => {
- this.withinOwners(el, (view, targetCtx) => {
- callback(e, event, view, el, targetCtx, phxEvent, "window");
+ this.debounce(el, e, browserEventName, () => {
+ this.withinOwners(el, (view) => {
+ callback(e, event, view, el, phxEvent, "window");
});
});
});
@@ -3223,35 +3953,53 @@ var LiveSocket = class {
}
}
bindClicks() {
+ window.addEventListener("click", (e) => this.clickStartedAtTarget = e.target);
this.bindClick("click", "click", false);
this.bindClick("mousedown", "capture-click", true);
}
bindClick(eventName, bindingName, capture) {
let click = this.binding(bindingName);
window.addEventListener(eventName, (e) => {
- if (!this.isConnected()) {
- return;
- }
let target = null;
if (capture) {
target = e.target.matches(`[${click}]`) ? e.target : e.target.querySelector(`[${click}]`);
} else {
- target = closestPhxBinding(e.target, click);
+ let clickStartedAtTarget = this.clickStartedAtTarget || e.target;
+ target = closestPhxBinding(clickStartedAtTarget, click);
+ this.dispatchClickAway(e, clickStartedAtTarget);
+ this.clickStartedAtTarget = null;
}
let phxEvent = target && target.getAttribute(click);
if (!phxEvent) {
+ let href = e.target instanceof HTMLAnchorElement ? e.target.getAttribute("href") : null;
+ if (!capture && href !== null && !dom_default.wantsNewTab(e) && dom_default.isNewPageHref(href, window.location)) {
+ this.unload();
+ }
return;
}
if (target.getAttribute("href") === "#") {
e.preventDefault();
}
- this.debounce(target, e, () => {
- this.withinOwners(target, (view, targetCtx) => {
- view.pushEvent("click", target, targetCtx, phxEvent, this.eventMeta("click", e, target));
+ this.debounce(target, e, "click", () => {
+ this.withinOwners(target, (view) => {
+ js_default.exec("click", phxEvent, view, target, ["push", { data: this.eventMeta("click", e, target) }]);
});
});
}, capture);
}
+ dispatchClickAway(e, clickStartedAt) {
+ let phxClickAway = this.binding("click-away");
+ dom_default.all(document, `[${phxClickAway}]`, (el) => {
+ if (!(el.isSameNode(clickStartedAt) || el.contains(clickStartedAt))) {
+ this.withinOwners(e.target, (view) => {
+ let phxEvent = el.getAttribute(phxClickAway);
+ if (js_default.isVisible(el)) {
+ js_default.exec("click", phxEvent, view, el, ["push", { data: this.eventMeta("click", e, e.target) }]);
+ }
+ });
+ }
+ });
+ }
bindNav() {
if (!browser_default.canPushState()) {
return;
@@ -3272,49 +4020,71 @@ var LiveSocket = class {
}
let { type, id, root, scroll } = event.state || {};
let href = window.location.href;
- if (this.main.isConnected() && (type === "patch" && id === this.main.id)) {
- this.main.pushLinkPatch(href, null);
- } else {
- this.replaceMain(href, null, () => {
- if (root) {
- this.replaceRootHistory();
- }
- if (typeof scroll === "number") {
- setTimeout(() => {
- window.scrollTo(0, scroll);
- }, 0);
- }
- });
- }
+ this.requestDOMUpdate(() => {
+ if (this.main.isConnected() && (type === "patch" && id === this.main.id)) {
+ this.main.pushLinkPatch(href, null, () => {
+ this.maybeScroll(scroll);
+ });
+ } else {
+ this.replaceMain(href, null, () => {
+ if (root) {
+ this.replaceRootHistory();
+ }
+ this.maybeScroll(scroll);
+ });
+ }
+ });
}, false);
window.addEventListener("click", (e) => {
let target = closestPhxBinding(e.target, PHX_LIVE_LINK);
let type = target && target.getAttribute(PHX_LIVE_LINK);
- let wantsNewTab = e.metaKey || e.ctrlKey || e.button === 1;
- if (!type || !this.isConnected() || !this.main || wantsNewTab) {
+ if (!type || !this.isConnected() || !this.main || dom_default.wantsNewTab(e)) {
return;
}
let href = target.href;
let linkState = target.getAttribute(PHX_LINK_STATE);
e.preventDefault();
+ e.stopImmediatePropagation();
if (this.pendingLink === href) {
return;
}
- if (type === "patch") {
- this.pushHistoryPatch(href, linkState, target);
- } else if (type === "redirect") {
- this.historyRedirect(href, linkState);
- } else {
- throw new Error(`expected ${PHX_LIVE_LINK} to be "patch" or "redirect", got: ${type}`);
- }
+ this.requestDOMUpdate(() => {
+ if (type === "patch") {
+ this.pushHistoryPatch(href, linkState, target);
+ } else if (type === "redirect") {
+ this.historyRedirect(href, linkState);
+ } else {
+ throw new Error(`expected ${PHX_LIVE_LINK} to be "patch" or "redirect", got: ${type}`);
+ }
+ let phxClick = target.getAttribute(this.binding("click"));
+ if (phxClick) {
+ this.requestDOMUpdate(() => this.execJS(target, phxClick, "click"));
+ }
+ });
}, false);
}
+ maybeScroll(scroll) {
+ if (typeof scroll === "number") {
+ requestAnimationFrame(() => {
+ window.scrollTo(0, scroll);
+ });
+ }
+ }
+ dispatchEvent(event, payload = {}) {
+ dom_default.dispatchEvent(window, `phx:${event}`, { detail: payload });
+ }
+ dispatchEvents(events) {
+ events.forEach(([event, payload]) => this.dispatchEvent(event, payload));
+ }
withPageLoading(info, callback) {
- dom_default.dispatchEvent(window, "phx:page-loading-start", info);
- let done = () => dom_default.dispatchEvent(window, "phx:page-loading-stop", info);
+ dom_default.dispatchEvent(window, "phx:page-loading-start", { detail: info });
+ let done = () => dom_default.dispatchEvent(window, "phx:page-loading-stop", { detail: info });
return callback ? callback(done) : done;
}
pushHistoryPatch(href, linkState, targetEl) {
+ if (!this.isConnected()) {
+ return browser_default.redirect(href);
+ }
this.withPageLoading({ to: href, kind: "patch" }, (done) => {
this.main.pushLinkPatch(href, targetEl, (linkRef) => {
this.historyPatch(href, linkState, linkRef);
@@ -3330,6 +4100,13 @@ var LiveSocket = class {
this.registerNewLocation(window.location);
}
historyRedirect(href, linkState, flash) {
+ if (!this.isConnected()) {
+ return browser_default.redirect(href, flash);
+ }
+ if (/^\/$|^\/[^\/]+.*$/.test(href)) {
+ let { protocol, host } = window.location;
+ href = `${protocol}//${host}${href}`;
+ }
let scroll = window.scrollY;
this.withPageLoading({ to: href, kind: "redirect" }, (done) => {
this.replaceMain(href, flash, () => {
@@ -3353,25 +4130,52 @@ var LiveSocket = class {
}
bindForms() {
let iterations = 0;
+ let externalFormSubmitted = false;
+ this.on("submit", (e) => {
+ let phxSubmit = e.target.getAttribute(this.binding("submit"));
+ let phxChange = e.target.getAttribute(this.binding("change"));
+ if (!externalFormSubmitted && phxChange && !phxSubmit) {
+ externalFormSubmitted = true;
+ e.preventDefault();
+ this.withinOwners(e.target, (view) => {
+ view.disableForm(e.target);
+ window.requestAnimationFrame(() => {
+ if (dom_default.isUnloadableFormSubmit(e)) {
+ this.unload();
+ }
+ e.target.submit();
+ });
+ });
+ }
+ }, true);
this.on("submit", (e) => {
let phxEvent = e.target.getAttribute(this.binding("submit"));
if (!phxEvent) {
+ if (dom_default.isUnloadableFormSubmit(e)) {
+ this.unload();
+ }
return;
}
e.preventDefault();
e.target.disabled = true;
- this.withinOwners(e.target, (view, targetCtx) => view.submitForm(e.target, targetCtx, phxEvent));
+ this.withinOwners(e.target, (view) => {
+ js_default.exec("submit", phxEvent, view, e.target, ["push", {}]);
+ });
}, false);
for (let type of ["change", "input"]) {
this.on(type, (e) => {
+ let phxChange = this.binding("change");
let input = e.target;
- let phxEvent = input.form && input.form.getAttribute(this.binding("change"));
+ let inputEvent = input.getAttribute(phxChange);
+ let formEvent = input.form && input.form.getAttribute(phxChange);
+ let phxEvent = inputEvent || formEvent;
if (!phxEvent) {
return;
}
if (input.type === "number" && input.validity && input.validity.badInput) {
return;
}
+ let dispatcher = inputEvent ? input : input.form;
let currentIterations = iterations;
iterations++;
let { at, type: lastType } = dom_default.private(input, "prev-iteration") || {};
@@ -3379,24 +4183,40 @@ var LiveSocket = class {
return;
}
dom_default.putPrivate(input, "prev-iteration", { at: currentIterations, type });
- this.debounce(input, e, () => {
- this.withinOwners(input.form, (view, targetCtx) => {
+ this.debounce(input, e, type, () => {
+ this.withinOwners(dispatcher, (view) => {
dom_default.putPrivate(input, PHX_HAS_FOCUSED, true);
if (!dom_default.isTextualInput(input)) {
this.setActiveElement(input);
}
- view.pushInput(input, targetCtx, null, phxEvent, e.target);
+ js_default.exec("change", phxEvent, view, input, ["push", { _target: e.target.name, dispatcher }]);
});
});
}, false);
}
+ this.on("reset", (e) => {
+ let form = e.target;
+ dom_default.resetForm(form, this.binding(PHX_FEEDBACK_FOR));
+ let input = Array.from(form.elements).find((el) => el.type === "reset");
+ window.requestAnimationFrame(() => {
+ input.dispatchEvent(new Event("input", { bubbles: true, cancelable: false }));
+ });
+ });
}
- debounce(el, event, callback) {
+ debounce(el, event, eventType, callback) {
+ if (eventType === "blur" || eventType === "focusout") {
+ return callback();
+ }
let phxDebounce = this.binding(PHX_DEBOUNCE);
let phxThrottle = this.binding(PHX_THROTTLE);
let defaultDebounce = this.defaults.debounce.toString();
let defaultThrottle = this.defaults.throttle.toString();
- dom_default.debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, callback);
+ this.withinOwners(el, (view) => {
+ let asyncFilter = () => !view.isDestroyed() && document.body.contains(el);
+ dom_default.debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, asyncFilter, () => {
+ callback();
+ });
+ });
}
silenceEvents(callback) {
this.silenced = true;
@@ -3411,4 +4231,49 @@ var LiveSocket = class {
});
}
};
+var TransitionSet = class {
+ constructor() {
+ this.transitions = new Set();
+ this.pendingOps = [];
+ }
+ reset() {
+ this.transitions.forEach((timer) => {
+ clearTimeout(timer);
+ this.transitions.delete(timer);
+ });
+ this.flushPendingOps();
+ }
+ after(callback) {
+ if (this.size() === 0) {
+ callback();
+ } else {
+ this.pushPendingOp(callback);
+ }
+ }
+ addTransition(time, onStart, onDone) {
+ onStart();
+ let timer = setTimeout(() => {
+ this.transitions.delete(timer);
+ onDone();
+ this.flushPendingOps();
+ }, time);
+ this.transitions.add(timer);
+ }
+ pushPendingOp(op) {
+ this.pendingOps.push(op);
+ }
+ size() {
+ return this.transitions.size;
+ }
+ flushPendingOps() {
+ if (this.size() > 0) {
+ return;
+ }
+ let op = this.pendingOps.shift();
+ if (op) {
+ op();
+ this.flushPendingOps();
+ }
+ }
+};
//# sourceMappingURL=phoenix_live_view.cjs.js.map
diff --git a/priv/static/phoenix_live_view.cjs.js.map b/priv/static/phoenix_live_view.cjs.js.map
index 5ec8d2278a..5a970106cb 100644
--- a/priv/static/phoenix_live_view.cjs.js.map
+++ b/priv/static/phoenix_live_view.cjs.js.map
@@ -1,7 +1,7 @@
{
"version": 3,
- "sources": ["../../assets/js/phoenix_live_view/index.js", "../../assets/js/phoenix_live_view/constants.js", "../../assets/js/phoenix_live_view/entry_uploader.js", "../../assets/js/phoenix_live_view/utils.js", "../../assets/js/phoenix_live_view/browser.js", "../../assets/js/phoenix_live_view/dom.js", "../../assets/js/phoenix_live_view/upload_entry.js", "../../assets/js/phoenix_live_view/live_uploader.js", "../../assets/js/phoenix_live_view/hooks.js", "../../assets/js/phoenix_live_view/dom_post_morph_restorer.js", "../../assets/node_modules/morphdom/dist/morphdom-esm.js", "../../assets/js/phoenix_live_view/dom_patch.js", "../../assets/js/phoenix_live_view/rendered.js", "../../assets/js/phoenix_live_view/view_hook.js", "../../assets/js/phoenix_live_view/view.js", "../../assets/js/phoenix_live_view/live_socket.js"],
- "sourcesContent": ["/*\n================================================================================\nPhoenix LiveView JavaScript Client\n================================================================================\n\nSee the hexdocs at `https://hexdocs.pm/phoenix_live_view` for documentation.\n\n*/\n\nimport LiveSocket from \"./live_socket\"\nexport {\n LiveSocket\n}\n", "\nexport const CONSECUTIVE_RELOADS = \"consecutive-reloads\"\nexport const MAX_RELOADS = 10\nexport const RELOAD_JITTER = [1000, 3000]\nexport const FAILSAFE_JITTER = 30000\nexport const PHX_EVENT_CLASSES = [\n \"phx-click-loading\", \"phx-change-loading\", \"phx-submit-loading\",\n \"phx-keydown-loading\", \"phx-keyup-loading\", \"phx-blur-loading\", \"phx-focus-loading\"\n]\nexport const PHX_COMPONENT = \"data-phx-component\"\nexport const PHX_LIVE_LINK = \"data-phx-link\"\nexport const PHX_TRACK_STATIC = \"track-static\"\nexport const PHX_LINK_STATE = \"data-phx-link-state\"\nexport const PHX_REF = \"data-phx-ref\"\nexport const PHX_TRACK_UPLOADS = \"track-uploads\"\nexport const PHX_UPLOAD_REF = \"data-phx-upload-ref\"\nexport const PHX_PREFLIGHTED_REFS = \"data-phx-preflighted-refs\"\nexport const PHX_DONE_REFS = \"data-phx-done-refs\"\nexport const PHX_DROP_TARGET = \"drop-target\"\nexport const PHX_ACTIVE_ENTRY_REFS = \"data-phx-active-refs\"\nexport const PHX_LIVE_FILE_UPDATED = \"phx:live-file:updated\"\nexport const PHX_SKIP = \"data-phx-skip\"\nexport const PHX_REMOVE = \"data-phx-remove\"\nexport const PHX_PAGE_LOADING = \"page-loading\"\nexport const PHX_CONNECTED_CLASS = \"phx-connected\"\nexport const PHX_DISCONNECTED_CLASS = \"phx-disconnected\"\nexport const PHX_NO_FEEDBACK_CLASS = \"phx-no-feedback\"\nexport const PHX_ERROR_CLASS = \"phx-error\"\nexport const PHX_PARENT_ID = \"data-phx-parent-id\"\nexport const PHX_MAIN = \"data-phx-main\"\nexport const PHX_ROOT_ID = \"data-phx-root-id\"\nexport const PHX_TRIGGER_ACTION = \"trigger-action\"\nexport const PHX_FEEDBACK_FOR = \"feedback-for\"\nexport const PHX_HAS_FOCUSED = \"phx-has-focused\"\nexport const FOCUSABLE_INPUTS = [\"text\", \"textarea\", \"number\", \"email\", \"password\", \"search\", \"tel\", \"url\", \"date\", \"time\"]\nexport const CHECKABLE_INPUTS = [\"checkbox\", \"radio\"]\nexport const PHX_HAS_SUBMITTED = \"phx-has-submitted\"\nexport const PHX_SESSION = \"data-phx-session\"\nexport const PHX_VIEW_SELECTOR = `[${PHX_SESSION}]`\nexport const PHX_STATIC = \"data-phx-static\"\nexport const PHX_READONLY = \"data-phx-readonly\"\nexport const PHX_DISABLED = \"data-phx-disabled\"\nexport const PHX_DISABLE_WITH = \"disable-with\"\nexport const PHX_DISABLE_WITH_RESTORE = \"data-phx-disable-with-restore\"\nexport const PHX_HOOK = \"hook\"\nexport const PHX_DEBOUNCE = \"debounce\"\nexport const PHX_THROTTLE = \"throttle\"\nexport const PHX_UPDATE = \"update\"\nexport const PHX_KEY = \"key\"\nexport const PHX_PRIVATE = \"phxPrivate\"\nexport const PHX_AUTO_RECOVER = \"auto-recover\"\nexport const PHX_LV_DEBUG = \"phx:live-socket:debug\"\nexport const PHX_LV_PROFILE = \"phx:live-socket:profiling\"\nexport const PHX_LV_LATENCY_SIM = \"phx:live-socket:latency-sim\"\nexport const PHX_PROGRESS = \"progress\"\nexport const LOADER_TIMEOUT = 1\nexport const BEFORE_UNLOAD_LOADER_TIMEOUT = 200\nexport const BINDING_PREFIX = \"phx-\"\nexport const PUSH_TIMEOUT = 30000\nexport const LINK_HEADER = \"x-requested-with\"\nexport const RESPONSE_URL_HEADER = \"x-response-url\"\nexport const DEBOUNCE_TRIGGER = \"debounce-trigger\"\nexport const THROTTLED = \"throttled\"\nexport const DEBOUNCE_PREV_KEY = \"debounce-prev-key\"\nexport const DEFAULTS = {\n debounce: 300,\n throttle: 300\n}\n\n// Rendered\nexport const DYNAMICS = \"d\"\nexport const STATIC = \"s\"\nexport const COMPONENTS = \"c\"\nexport const EVENTS = \"e\"\nexport const REPLY = \"r\"\nexport const TITLE = \"t\"\n", "import {\n logError\n} from \"./utils\"\n\nexport default class EntryUploader {\n constructor(entry, chunkSize, liveSocket){\n this.liveSocket = liveSocket\n this.entry = entry\n this.offset = 0\n this.chunkSize = chunkSize\n this.chunkTimer = null\n this.uploadChannel = liveSocket.channel(`lvu:${entry.ref}`, {token: entry.metadata()})\n }\n\n error(reason){\n clearTimeout(this.chunkTimer)\n this.uploadChannel.leave()\n this.entry.error(reason)\n }\n\n upload(){\n this.uploadChannel.onError(reason => this.error(reason))\n this.uploadChannel.join()\n .receive(\"ok\", _data => this.readNextChunk())\n .receive(\"error\", reason => this.error(reason))\n }\n\n isDone(){ return this.offset >= this.entry.file.size }\n\n readNextChunk(){\n let reader = new window.FileReader()\n let blob = this.entry.file.slice(this.offset, this.chunkSize + this.offset)\n reader.onload = (e) => {\n if(e.target.error === null){\n this.offset += e.target.result.byteLength\n this.pushChunk(e.target.result)\n } else {\n return logError(\"Read error: \" + e.target.error)\n }\n }\n reader.readAsArrayBuffer(blob)\n }\n\n pushChunk(chunk){\n if(!this.uploadChannel.isJoined()){ return }\n this.uploadChannel.push(\"chunk\", chunk)\n .receive(\"ok\", () => {\n this.entry.progress((this.offset / this.entry.file.size) * 100)\n if(!this.isDone()){\n this.chunkTimer = setTimeout(() => this.readNextChunk(), this.liveSocket.getLatencySim() || 0)\n }\n })\n }\n}\n", "import {\n PHX_VIEW_SELECTOR\n} from \"./constants\"\n\nimport EntryUploader from \"./entry_uploader\"\n\nexport let logError = (msg, obj) => console.error && console.error(msg, obj)\n\nexport let isCid = (cid) => typeof(cid) === \"number\"\n\nexport function detectDuplicateIds(){\n let ids = new Set()\n let elems = document.querySelectorAll(\"*[id]\")\n for(let i = 0, len = elems.length; i < len; i++){\n if(ids.has(elems[i].id)){\n console.error(`Multiple IDs detected: ${elems[i].id}. Ensure unique element ids.`)\n } else {\n ids.add(elems[i].id)\n }\n }\n}\n\nexport let debug = (view, kind, msg, obj) => {\n if(view.liveSocket.isDebugEnabled()){\n console.log(`${view.id} ${kind}: ${msg} - `, obj)\n }\n}\n\n// wraps value in closure or returns closure\nexport let closure = (val) => typeof val === \"function\" ? val : function (){ return val }\n\nexport let clone = (obj) => { return JSON.parse(JSON.stringify(obj)) }\n\nexport let closestPhxBinding = (el, binding, borderEl) => {\n do {\n if(el.matches(`[${binding}]`)){ return el }\n el = el.parentElement || el.parentNode\n } while(el !== null && el.nodeType === 1 && !((borderEl && borderEl.isSameNode(el)) || el.matches(PHX_VIEW_SELECTOR)))\n return null\n}\n\nexport let isObject = (obj) => {\n return obj !== null && typeof obj === \"object\" && !(obj instanceof Array)\n}\n\nexport let isEqualObj = (obj1, obj2) => JSON.stringify(obj1) === JSON.stringify(obj2)\n\nexport let isEmpty = (obj) => {\n for(let x in obj){ return false }\n return true\n}\n\nexport let maybe = (el, callback) => el && callback(el)\n\nexport let channelUploader = function (entries, onError, resp, liveSocket){\n entries.forEach(entry => {\n let entryUploader = new EntryUploader(entry, resp.config.chunk_size, liveSocket)\n entryUploader.upload()\n })\n}\n", "let Browser = {\n canPushState(){ return (typeof (history.pushState) !== \"undefined\") },\n\n dropLocal(localStorage, namespace, subkey){\n return localStorage.removeItem(this.localKey(namespace, subkey))\n },\n\n updateLocal(localStorage, namespace, subkey, initial, func){\n let current = this.getLocal(localStorage, namespace, subkey)\n let key = this.localKey(namespace, subkey)\n let newVal = current === null ? initial : func(current)\n localStorage.setItem(key, JSON.stringify(newVal))\n return newVal\n },\n\n getLocal(localStorage, namespace, subkey){\n return JSON.parse(localStorage.getItem(this.localKey(namespace, subkey)))\n },\n\n updateCurrentState(callback){\n if(!this.canPushState()){ return }\n history.replaceState(callback(history.state || {}), \"\", window.location.href)\n },\n\n pushState(kind, meta, to){\n if(this.canPushState()){\n if(to !== window.location.href){\n if(meta.type == \"redirect\" && meta.scroll){\n // If we're redirecting store the current scrollY for the current history state.\n let currentState = history.state || {}\n currentState.scroll = meta.scroll\n history.replaceState(currentState, \"\", window.location.href)\n }\n\n delete meta.scroll // Only store the scroll in the redirect case.\n history[kind + \"State\"](meta, \"\", to || null) // IE will coerce undefined to string\n let hashEl = this.getHashTargetEl(window.location.hash)\n\n if(hashEl){\n hashEl.scrollIntoView()\n } else if(meta.type === \"redirect\"){\n window.scroll(0, 0)\n }\n }\n } else {\n this.redirect(to)\n }\n },\n\n setCookie(name, value){\n document.cookie = `${name}=${value}`\n },\n\n getCookie(name){\n return document.cookie.replace(new RegExp(`(?:(?:^|.*;\\s*)${name}\\s*\\=\\s*([^;]*).*$)|^.*$`), \"$1\")\n },\n\n redirect(toURL, flash){\n if(flash){ Browser.setCookie(\"__phoenix_flash__\", flash + \"; max-age=60000; path=/\") }\n window.location = toURL\n },\n\n localKey(namespace, subkey){ return `${namespace}-${subkey}` },\n\n getHashTargetEl(maybeHash){\n let hash = maybeHash.toString().substring(1)\n if(hash === \"\"){ return }\n return document.getElementById(hash) || document.querySelector(`a[name=\"${hash}\"]`)\n }\n}\n\nexport default Browser\n", "import {\n CHECKABLE_INPUTS,\n DEBOUNCE_PREV_KEY,\n DEBOUNCE_TRIGGER,\n FOCUSABLE_INPUTS,\n PHX_COMPONENT,\n PHX_EVENT_CLASSES,\n PHX_HAS_FOCUSED,\n PHX_HAS_SUBMITTED,\n PHX_MAIN,\n PHX_NO_FEEDBACK_CLASS,\n PHX_PARENT_ID,\n PHX_PRIVATE,\n PHX_REF,\n PHX_SESSION,\n PHX_STATIC,\n PHX_UPLOAD_REF,\n PHX_VIEW_SELECTOR,\n THROTTLED\n} from \"./constants\"\n\nimport {\n clone,\n logError\n} from \"./utils\"\n\nlet DOM = {\n byId(id){ return document.getElementById(id) || logError(`no id found for ${id}`) },\n\n removeClass(el, className){\n el.classList.remove(className)\n if(el.classList.length === 0){ el.removeAttribute(\"class\") }\n },\n\n all(node, query, callback){\n if(!node){ return [] }\n let array = Array.from(node.querySelectorAll(query))\n return callback ? array.forEach(callback) : array\n },\n\n childNodeLength(html){\n let template = document.createElement(\"template\")\n template.innerHTML = html\n return template.content.childElementCount\n },\n\n isUploadInput(el){ return el.type === \"file\" && el.getAttribute(PHX_UPLOAD_REF) !== null },\n\n findUploadInputs(node){ return this.all(node, `input[type=\"file\"][${PHX_UPLOAD_REF}]`) },\n\n findComponentNodeList(node, cid){\n return this.filterWithinSameLiveView(this.all(node, `[${PHX_COMPONENT}=\"${cid}\"]`), node)\n },\n\n isPhxDestroyed(node){\n return node.id && DOM.private(node, \"destroyed\") ? true : false\n },\n\n markPhxChildDestroyed(el){\n el.setAttribute(PHX_SESSION, \"\")\n this.putPrivate(el, \"destroyed\", true)\n },\n\n findPhxChildrenInFragment(html, parentId){\n let template = document.createElement(\"template\")\n template.innerHTML = html\n return this.findPhxChildren(template.content, parentId)\n },\n\n isIgnored(el, phxUpdate){\n return (el.getAttribute(phxUpdate) || el.getAttribute(\"data-phx-update\")) === \"ignore\"\n },\n\n isPhxUpdate(el, phxUpdate, updateTypes){\n return el.getAttribute && updateTypes.indexOf(el.getAttribute(phxUpdate)) >= 0\n },\n\n findPhxChildren(el, parentId){\n return this.all(el, `${PHX_VIEW_SELECTOR}[${PHX_PARENT_ID}=\"${parentId}\"]`)\n },\n\n findParentCIDs(node, cids){\n let initial = new Set(cids)\n return cids.reduce((acc, cid) => {\n let selector = `[${PHX_COMPONENT}=\"${cid}\"] [${PHX_COMPONENT}]`\n\n this.filterWithinSameLiveView(this.all(node, selector), node)\n .map(el => parseInt(el.getAttribute(PHX_COMPONENT)))\n .forEach(childCID => acc.delete(childCID))\n\n return acc\n }, initial)\n },\n\n filterWithinSameLiveView(nodes, parent){\n if(parent.querySelector(PHX_VIEW_SELECTOR)){\n return nodes.filter(el => this.withinSameLiveView(el, parent))\n } else {\n return nodes\n }\n },\n\n withinSameLiveView(node, parent){\n while(node = node.parentNode){\n if(node.isSameNode(parent)){ return true }\n if(node.getAttribute(PHX_SESSION) !== null){ return false }\n }\n },\n\n private(el, key){ return el[PHX_PRIVATE] && el[PHX_PRIVATE][key] },\n\n deletePrivate(el, key){ el[PHX_PRIVATE] && delete (el[PHX_PRIVATE][key]) },\n\n putPrivate(el, key, value){\n if(!el[PHX_PRIVATE]){ el[PHX_PRIVATE] = {} }\n el[PHX_PRIVATE][key] = value\n },\n\n copyPrivates(target, source){\n if(source[PHX_PRIVATE]){\n target[PHX_PRIVATE] = clone(source[PHX_PRIVATE])\n }\n },\n\n putTitle(str){\n let titleEl = document.querySelector(\"title\")\n let {prefix, suffix} = titleEl.dataset\n document.title = `${prefix || \"\"}${str}${suffix || \"\"}`\n },\n\n debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, callback){\n let debounce = el.getAttribute(phxDebounce)\n let throttle = el.getAttribute(phxThrottle)\n if(debounce === \"\"){ debounce = defaultDebounce }\n if(throttle === \"\"){ throttle = defaultThrottle }\n let value = debounce || throttle\n switch(value){\n case null: return callback()\n\n case \"blur\":\n if(this.once(el, \"debounce-blur\")){\n el.addEventListener(\"blur\", () => callback())\n }\n return\n\n default:\n let timeout = parseInt(value)\n let trigger = () => throttle ? this.deletePrivate(el, THROTTLED) : callback()\n let currentCycle = this.incCycle(el, DEBOUNCE_TRIGGER, trigger)\n if(isNaN(timeout)){ return logError(`invalid throttle/debounce value: ${value}`) }\n if(throttle){\n let newKeyDown = false\n if(event.type === \"keydown\"){\n let prevKey = this.private(el, DEBOUNCE_PREV_KEY)\n this.putPrivate(el, DEBOUNCE_PREV_KEY, event.key)\n newKeyDown = prevKey !== event.key\n }\n\n if(!newKeyDown && this.private(el, THROTTLED)){\n return false\n } else {\n callback()\n this.putPrivate(el, THROTTLED, true)\n setTimeout(() => this.triggerCycle(el, DEBOUNCE_TRIGGER), timeout)\n }\n } else {\n setTimeout(() => this.triggerCycle(el, DEBOUNCE_TRIGGER, currentCycle), timeout)\n }\n\n\n let form = el.form\n if(form && this.once(form, \"bind-debounce\")){\n form.addEventListener(\"submit\", () => {\n Array.from((new FormData(form)).entries(), ([name]) => {\n let input = form.querySelector(`[name=\"${name}\"]`)\n this.incCycle(input, DEBOUNCE_TRIGGER)\n this.deletePrivate(input, THROTTLED)\n })\n })\n }\n if(this.once(el, \"bind-debounce\")){\n el.addEventListener(\"blur\", () => this.triggerCycle(el, DEBOUNCE_TRIGGER))\n }\n }\n },\n\n triggerCycle(el, key, currentCycle){\n let [cycle, trigger] = this.private(el, key)\n if(!currentCycle){ currentCycle = cycle }\n if(currentCycle === cycle){\n this.incCycle(el, key)\n trigger()\n }\n },\n\n once(el, key){\n if(this.private(el, key) === true){ return false }\n this.putPrivate(el, key, true)\n return true\n },\n\n incCycle(el, key, trigger = function (){ }){\n let [currentCycle] = this.private(el, key) || [0, trigger]\n currentCycle++\n this.putPrivate(el, key, [currentCycle, trigger])\n return currentCycle\n },\n\n discardError(container, el, phxFeedbackFor){\n let field = el.getAttribute && el.getAttribute(phxFeedbackFor)\n // TODO: Remove id lookup after we update Phoenix to use input_name instead of input_id\n let input = field && container.querySelector(`[id=\"${field}\"], [name=\"${field}\"]`)\n if(!input){ return }\n\n if(!(this.private(input, PHX_HAS_FOCUSED) || this.private(input.form, PHX_HAS_SUBMITTED))){\n el.classList.add(PHX_NO_FEEDBACK_CLASS)\n }\n },\n\n showError(inputEl, phxFeedbackFor){\n if(inputEl.id || inputEl.name){\n this.all(inputEl.form, `[${phxFeedbackFor}=\"${inputEl.id}\"], [${phxFeedbackFor}=\"${inputEl.name}\"]`, (el) => {\n this.removeClass(el, PHX_NO_FEEDBACK_CLASS)\n })\n }\n },\n\n isPhxChild(node){\n return node.getAttribute && node.getAttribute(PHX_PARENT_ID)\n },\n\n dispatchEvent(target, eventString, detail = {}){\n let event = new CustomEvent(eventString, {bubbles: true, cancelable: true, detail: detail})\n target.dispatchEvent(event)\n },\n\n cloneNode(node, html){\n if(typeof (html) === \"undefined\"){\n return node.cloneNode(true)\n } else {\n let cloned = node.cloneNode(false)\n cloned.innerHTML = html\n return cloned\n }\n },\n\n mergeAttrs(target, source, opts = {}){\n let exclude = opts.exclude || []\n let isIgnored = opts.isIgnored\n let sourceAttrs = source.attributes\n for(let i = sourceAttrs.length - 1; i >= 0; i--){\n let name = sourceAttrs[i].name\n if(exclude.indexOf(name) < 0){ target.setAttribute(name, source.getAttribute(name)) }\n }\n\n let targetAttrs = target.attributes\n for(let i = targetAttrs.length - 1; i >= 0; i--){\n let name = targetAttrs[i].name\n if(isIgnored){\n if(name.startsWith(\"data-\") && !source.hasAttribute(name)){ target.removeAttribute(name) }\n } else {\n if(!source.hasAttribute(name)){ target.removeAttribute(name) }\n }\n }\n },\n\n mergeFocusedInput(target, source){\n // skip selects because FF will reset highlighted index for any setAttribute\n if(!(target instanceof HTMLSelectElement)){ DOM.mergeAttrs(target, source, {except: [\"value\"]}) }\n if(source.readOnly){\n target.setAttribute(\"readonly\", true)\n } else {\n target.removeAttribute(\"readonly\")\n }\n },\n\n hasSelectionRange(el){\n return el.setSelectionRange && (el.type === \"text\" || el.type === \"textarea\")\n },\n\n restoreFocus(focused, selectionStart, selectionEnd){\n if(!DOM.isTextualInput(focused)){ return }\n let wasFocused = focused.matches(\":focus\")\n if(focused.readOnly){ focused.blur() }\n if(!wasFocused){ focused.focus() }\n if(this.hasSelectionRange(focused)){\n focused.setSelectionRange(selectionStart, selectionEnd)\n }\n },\n\n isFormInput(el){ return /^(?:input|select|textarea)$/i.test(el.tagName) && el.type !== \"button\" },\n\n syncAttrsToProps(el){\n if(el instanceof HTMLInputElement && CHECKABLE_INPUTS.indexOf(el.type.toLocaleLowerCase()) >= 0){\n el.checked = el.getAttribute(\"checked\") !== null\n }\n },\n\n syncPropsToAttrs(el){\n if(el instanceof HTMLSelectElement){\n let selectedItem = el.options.item(el.selectedIndex)\n if(selectedItem && selectedItem.getAttribute(\"selected\") === null){\n selectedItem.setAttribute(\"selected\", \"\")\n }\n }\n },\n\n isTextualInput(el){ return FOCUSABLE_INPUTS.indexOf(el.type) >= 0 },\n\n isNowTriggerFormExternal(el, phxTriggerExternal){\n return el.getAttribute && el.getAttribute(phxTriggerExternal) !== null\n },\n\n syncPendingRef(fromEl, toEl, disableWith){\n let ref = fromEl.getAttribute(PHX_REF)\n if(ref === null){ return true }\n\n if(DOM.isFormInput(fromEl) || fromEl.getAttribute(disableWith) !== null){\n if(DOM.isUploadInput(fromEl)){ DOM.mergeAttrs(fromEl, toEl, {isIgnored: true}) }\n DOM.putPrivate(fromEl, PHX_REF, toEl)\n return false\n } else {\n PHX_EVENT_CLASSES.forEach(className => {\n fromEl.classList.contains(className) && toEl.classList.add(className)\n })\n toEl.setAttribute(PHX_REF, ref)\n return true\n }\n },\n\n cleanChildNodes(container, phxUpdate){\n if(DOM.isPhxUpdate(container, phxUpdate, [\"append\", \"prepend\"])){\n let toRemove = []\n container.childNodes.forEach(childNode => {\n if(!childNode.id){\n // Skip warning if it's an empty text node (e.g. a new-line)\n let isEmptyTextNode = childNode.nodeType === Node.TEXT_NODE && childNode.nodeValue.trim() === \"\"\n if(!isEmptyTextNode){\n logError(\"only HTML element tags with an id are allowed inside containers with phx-update.\\n\\n\" +\n `removing illegal node: \"${(childNode.outerHTML || childNode.nodeValue).trim()}\"\\n\\n`)\n }\n toRemove.push(childNode)\n }\n })\n toRemove.forEach(childNode => childNode.remove())\n }\n },\n\n replaceRootContainer(container, tagName, attrs){\n let retainedAttrs = new Set([\"id\", PHX_SESSION, PHX_STATIC, PHX_MAIN])\n if(container.tagName.toLowerCase() === tagName.toLowerCase()){\n Array.from(container.attributes)\n .filter(attr => !retainedAttrs.has(attr.name.toLowerCase()))\n .forEach(attr => container.removeAttribute(attr.name))\n\n Object.keys(attrs)\n .filter(name => !retainedAttrs.has(name.toLowerCase()))\n .forEach(attr => container.setAttribute(attr, attrs[attr]))\n\n return container\n\n } else {\n let newContainer = document.createElement(tagName)\n Object.keys(attrs).forEach(attr => newContainer.setAttribute(attr, attrs[attr]))\n retainedAttrs.forEach(attr => newContainer.setAttribute(attr, container.getAttribute(attr)))\n newContainer.innerHTML = container.innerHTML\n container.replaceWith(newContainer)\n return newContainer\n }\n }\n}\n\nexport default DOM\n", "import {\n PHX_ACTIVE_ENTRY_REFS,\n PHX_LIVE_FILE_UPDATED,\n PHX_PREFLIGHTED_REFS\n} from \"./constants\"\n\nimport {\n channelUploader,\n logError\n} from \"./utils\"\n\nimport LiveUploader from \"./live_uploader\"\n\nexport default class UploadEntry {\n static isActive(fileEl, file){\n let isNew = file._phxRef === undefined\n let activeRefs = fileEl.getAttribute(PHX_ACTIVE_ENTRY_REFS).split(\",\")\n let isActive = activeRefs.indexOf(LiveUploader.genFileRef(file)) >= 0\n return file.size > 0 && (isNew || isActive)\n }\n\n static isPreflighted(fileEl, file){\n let preflightedRefs = fileEl.getAttribute(PHX_PREFLIGHTED_REFS).split(\",\")\n let isPreflighted = preflightedRefs.indexOf(LiveUploader.genFileRef(file)) >= 0\n return isPreflighted && this.isActive(fileEl, file)\n }\n\n constructor(fileEl, file, view){\n this.ref = LiveUploader.genFileRef(file)\n this.fileEl = fileEl\n this.file = file\n this.view = view\n this.meta = null\n this._isCancelled = false\n this._isDone = false\n this._progress = 0\n this._lastProgressSent = -1\n this._onDone = function (){ }\n this._onElUpdated = this.onElUpdated.bind(this)\n this.fileEl.addEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated)\n }\n\n metadata(){ return this.meta }\n\n progress(progress){\n this._progress = Math.floor(progress)\n if(this._progress > this._lastProgressSent){\n if(this._progress >= 100){\n this._progress = 100\n this._lastProgressSent = 100\n this._isDone = true\n this.view.pushFileProgress(this.fileEl, this.ref, 100, () => {\n LiveUploader.untrackFile(this.fileEl, this.file)\n this._onDone()\n })\n } else {\n this._lastProgressSent = this._progress\n this.view.pushFileProgress(this.fileEl, this.ref, this._progress)\n }\n }\n }\n\n cancel(){\n this._isCancelled = true\n this._isDone = true\n this._onDone()\n }\n\n isDone(){ return this._isDone }\n\n error(reason = \"failed\"){\n this.view.pushFileProgress(this.fileEl, this.ref, {error: reason})\n LiveUploader.clearFiles(this.fileEl)\n }\n\n //private\n\n onDone(callback){\n this._onDone = () => {\n this.fileEl.removeEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated)\n callback()\n }\n }\n\n onElUpdated(){\n let activeRefs = this.fileEl.getAttribute(PHX_ACTIVE_ENTRY_REFS).split(\",\")\n if(activeRefs.indexOf(this.ref) === -1){ this.cancel() }\n }\n\n toPreflightPayload(){\n return {\n last_modified: this.file.lastModified,\n name: this.file.name,\n size: this.file.size,\n type: this.file.type,\n ref: this.ref\n }\n }\n\n uploader(uploaders){\n if(this.meta.uploader){\n let callback = uploaders[this.meta.uploader] || logError(`no uploader configured for ${this.meta.uploader}`)\n return {name: this.meta.uploader, callback: callback}\n } else {\n return {name: \"channel\", callback: channelUploader}\n }\n }\n\n zipPostFlight(resp){\n this.meta = resp.entries[this.ref]\n if(!this.meta){ logError(`no preflight upload response returned with ref ${this.ref}`, {input: this.fileEl, response: resp}) }\n }\n}\n", "import {\n PHX_DONE_REFS,\n PHX_PREFLIGHTED_REFS,\n PHX_UPLOAD_REF\n} from \"./constants\"\n\nimport {\n} from \"./utils\"\n\nimport DOM from \"./dom\"\nimport UploadEntry from \"./upload_entry\"\n\nlet liveUploaderFileRef = 0\n\nexport default class LiveUploader {\n static genFileRef(file){\n let ref = file._phxRef\n if(ref !== undefined){\n return ref\n } else {\n file._phxRef = (liveUploaderFileRef++).toString()\n return file._phxRef\n }\n }\n\n static getEntryDataURL(inputEl, ref, callback){\n let file = this.activeFiles(inputEl).find(file => this.genFileRef(file) === ref)\n callback(URL.createObjectURL(file))\n }\n\n static hasUploadsInProgress(formEl){\n let active = 0\n DOM.findUploadInputs(formEl).forEach(input => {\n if(input.getAttribute(PHX_PREFLIGHTED_REFS) !== input.getAttribute(PHX_DONE_REFS)){\n active++\n }\n })\n return active > 0\n }\n\n static serializeUploads(inputEl){\n let files = this.activeFiles(inputEl)\n let fileData = {}\n files.forEach(file => {\n let entry = {path: inputEl.name}\n let uploadRef = inputEl.getAttribute(PHX_UPLOAD_REF)\n fileData[uploadRef] = fileData[uploadRef] || []\n entry.ref = this.genFileRef(file)\n entry.name = file.name || entry.ref\n entry.type = file.type\n entry.size = file.size\n fileData[uploadRef].push(entry)\n })\n return fileData\n }\n\n static clearFiles(inputEl){\n inputEl.value = null\n inputEl.removeAttribute(PHX_UPLOAD_REF)\n DOM.putPrivate(inputEl, \"files\", [])\n }\n\n static untrackFile(inputEl, file){\n DOM.putPrivate(inputEl, \"files\", DOM.private(inputEl, \"files\").filter(f => !Object.is(f, file)))\n }\n\n static trackFiles(inputEl, files){\n if(inputEl.getAttribute(\"multiple\") !== null){\n let newFiles = files.filter(file => !this.activeFiles(inputEl).find(f => Object.is(f, file)))\n DOM.putPrivate(inputEl, \"files\", this.activeFiles(inputEl).concat(newFiles))\n inputEl.value = null\n } else {\n DOM.putPrivate(inputEl, \"files\", files)\n }\n }\n\n static activeFileInputs(formEl){\n let fileInputs = DOM.findUploadInputs(formEl)\n return Array.from(fileInputs).filter(el => el.files && this.activeFiles(el).length > 0)\n }\n\n static activeFiles(input){\n return (DOM.private(input, \"files\") || []).filter(f => UploadEntry.isActive(input, f))\n }\n\n static inputsAwaitingPreflight(formEl){\n let fileInputs = DOM.findUploadInputs(formEl)\n return Array.from(fileInputs).filter(input => this.filesAwaitingPreflight(input).length > 0)\n }\n\n static filesAwaitingPreflight(input){\n return this.activeFiles(input).filter(f => !UploadEntry.isPreflighted(input, f))\n }\n\n constructor(inputEl, view, onComplete){\n this.view = view\n this.onComplete = onComplete\n this._entries =\n Array.from(LiveUploader.filesAwaitingPreflight(inputEl) || [])\n .map(file => new UploadEntry(inputEl, file, view))\n\n this.numEntriesInProgress = this._entries.length\n }\n\n entries(){ return this._entries }\n\n initAdapterUpload(resp, onError, liveSocket){\n this._entries =\n this._entries.map(entry => {\n entry.zipPostFlight(resp)\n entry.onDone(() => {\n this.numEntriesInProgress--\n if(this.numEntriesInProgress === 0){ this.onComplete() }\n })\n return entry\n })\n\n let groupedEntries = this._entries.reduce((acc, entry) => {\n let {name, callback} = entry.uploader(liveSocket.uploaders)\n acc[name] = acc[name] || {callback: callback, entries: []}\n acc[name].entries.push(entry)\n return acc\n }, {})\n\n for(let name in groupedEntries){\n let {callback, entries} = groupedEntries[name]\n callback(entries, onError, resp, liveSocket)\n }\n }\n}\n", "import {\n PHX_ACTIVE_ENTRY_REFS,\n PHX_LIVE_FILE_UPDATED,\n PHX_PREFLIGHTED_REFS,\n PHX_UPLOAD_REF\n} from \"./constants\"\n\nimport LiveUploader from \"./live_uploader\"\n\nlet Hooks = {\n LiveFileUpload: {\n activeRefs(){ return this.el.getAttribute(PHX_ACTIVE_ENTRY_REFS) },\n\n preflightedRefs(){ return this.el.getAttribute(PHX_PREFLIGHTED_REFS) },\n\n mounted(){ this.preflightedWas = this.preflightedRefs() },\n\n updated(){\n let newPreflights = this.preflightedRefs()\n if(this.preflightedWas !== newPreflights){\n this.preflightedWas = newPreflights\n if(newPreflights === \"\"){\n this.__view.cancelSubmit(this.el.form)\n }\n }\n\n if(this.activeRefs() === \"\"){ this.el.value = null }\n this.el.dispatchEvent(new CustomEvent(PHX_LIVE_FILE_UPDATED))\n }\n },\n\n LiveImgPreview: {\n mounted(){\n this.ref = this.el.getAttribute(\"data-phx-entry-ref\")\n this.inputEl = document.getElementById(this.el.getAttribute(PHX_UPLOAD_REF))\n LiveUploader.getEntryDataURL(this.inputEl, this.ref, url => {\n this.url = url\n this.el.src = url\n })\n },\n destroyed(){\n URL.revokeObjectURL(this.url)\n }\n }\n}\n\nexport default Hooks\n", "import {\n maybe\n} from \"./utils\"\n\nimport DOM from \"./dom\"\n\nexport default class DOMPostMorphRestorer {\n constructor(containerBefore, containerAfter, updateType){\n let idsBefore = new Set()\n let idsAfter = new Set([...containerAfter.children].map(child => child.id))\n\n let elementsToModify = []\n\n Array.from(containerBefore.children).forEach(child => {\n if(child.id){ // all of our children should be elements with ids\n idsBefore.add(child.id)\n if(idsAfter.has(child.id)){\n let previousElementId = child.previousElementSibling && child.previousElementSibling.id\n elementsToModify.push({elementId: child.id, previousElementId: previousElementId})\n }\n }\n })\n\n this.containerId = containerAfter.id\n this.updateType = updateType\n this.elementsToModify = elementsToModify\n this.elementIdsToAdd = [...idsAfter].filter(id => !idsBefore.has(id))\n }\n\n // We do the following to optimize append/prepend operations:\n // 1) Track ids of modified elements & of new elements\n // 2) All the modified elements are put back in the correct position in the DOM tree\n // by storing the id of their previous sibling\n // 3) New elements are going to be put in the right place by morphdom during append.\n // For prepend, we move them to the first position in the container\n perform(){\n let container = DOM.byId(this.containerId)\n this.elementsToModify.forEach(elementToModify => {\n if(elementToModify.previousElementId){\n maybe(document.getElementById(elementToModify.previousElementId), previousElem => {\n maybe(document.getElementById(elementToModify.elementId), elem => {\n let isInRightPlace = elem.previousElementSibling && elem.previousElementSibling.id == previousElem.id\n if(!isInRightPlace){\n previousElem.insertAdjacentElement(\"afterend\", elem)\n }\n })\n })\n } else {\n // This is the first element in the container\n maybe(document.getElementById(elementToModify.elementId), elem => {\n let isInRightPlace = elem.previousElementSibling == null\n if(!isInRightPlace){\n container.insertAdjacentElement(\"afterbegin\", elem)\n }\n })\n }\n })\n\n if(this.updateType == \"prepend\"){\n this.elementIdsToAdd.reverse().forEach(elemId => {\n maybe(document.getElementById(elemId), elem => container.insertAdjacentElement(\"afterbegin\", elem))\n })\n }\n }\n}\n", "var DOCUMENT_FRAGMENT_NODE = 11;\n\nfunction morphAttrs(fromNode, toNode) {\n var toNodeAttrs = toNode.attributes;\n var attr;\n var attrName;\n var attrNamespaceURI;\n var attrValue;\n var fromValue;\n\n // document-fragments dont have attributes so lets not do anything\n if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE || fromNode.nodeType === DOCUMENT_FRAGMENT_NODE) {\n return;\n }\n\n // update attributes on original DOM element\n for (var i = toNodeAttrs.length - 1; i >= 0; i--) {\n attr = toNodeAttrs[i];\n attrName = attr.name;\n attrNamespaceURI = attr.namespaceURI;\n attrValue = attr.value;\n\n if (attrNamespaceURI) {\n attrName = attr.localName || attrName;\n fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName);\n\n if (fromValue !== attrValue) {\n if (attr.prefix === 'xmlns'){\n attrName = attr.name; // It's not allowed to set an attribute with the XMLNS namespace without specifying the `xmlns` prefix\n }\n fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue);\n }\n } else {\n fromValue = fromNode.getAttribute(attrName);\n\n if (fromValue !== attrValue) {\n fromNode.setAttribute(attrName, attrValue);\n }\n }\n }\n\n // Remove any extra attributes found on the original DOM element that\n // weren't found on the target element.\n var fromNodeAttrs = fromNode.attributes;\n\n for (var d = fromNodeAttrs.length - 1; d >= 0; d--) {\n attr = fromNodeAttrs[d];\n attrName = attr.name;\n attrNamespaceURI = attr.namespaceURI;\n\n if (attrNamespaceURI) {\n attrName = attr.localName || attrName;\n\n if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) {\n fromNode.removeAttributeNS(attrNamespaceURI, attrName);\n }\n } else {\n if (!toNode.hasAttribute(attrName)) {\n fromNode.removeAttribute(attrName);\n }\n }\n }\n}\n\nvar range; // Create a range object for efficently rendering strings to elements.\nvar NS_XHTML = 'http://www.w3.org/1999/xhtml';\n\nvar doc = typeof document === 'undefined' ? undefined : document;\nvar HAS_TEMPLATE_SUPPORT = !!doc && 'content' in doc.createElement('template');\nvar HAS_RANGE_SUPPORT = !!doc && doc.createRange && 'createContextualFragment' in doc.createRange();\n\nfunction createFragmentFromTemplate(str) {\n var template = doc.createElement('template');\n template.innerHTML = str;\n return template.content.childNodes[0];\n}\n\nfunction createFragmentFromRange(str) {\n if (!range) {\n range = doc.createRange();\n range.selectNode(doc.body);\n }\n\n var fragment = range.createContextualFragment(str);\n return fragment.childNodes[0];\n}\n\nfunction createFragmentFromWrap(str) {\n var fragment = doc.createElement('body');\n fragment.innerHTML = str;\n return fragment.childNodes[0];\n}\n\n/**\n * This is about the same\n * var html = new DOMParser().parseFromString(str, 'text/html');\n * return html.body.firstChild;\n *\n * @method toElement\n * @param {String} str\n */\nfunction toElement(str) {\n str = str.trim();\n if (HAS_TEMPLATE_SUPPORT) {\n // avoid restrictions on content for things like `Hi ` which\n // createContextualFragment doesn't support\n // support not available in IE\n return createFragmentFromTemplate(str);\n } else if (HAS_RANGE_SUPPORT) {\n return createFragmentFromRange(str);\n }\n\n return createFragmentFromWrap(str);\n}\n\n/**\n * Returns true if two node's names are the same.\n *\n * NOTE: We don't bother checking `namespaceURI` because you will never find two HTML elements with the same\n * nodeName and different namespace URIs.\n *\n * @param {Element} a\n * @param {Element} b The target element\n * @return {boolean}\n */\nfunction compareNodeNames(fromEl, toEl) {\n var fromNodeName = fromEl.nodeName;\n var toNodeName = toEl.nodeName;\n var fromCodeStart, toCodeStart;\n\n if (fromNodeName === toNodeName) {\n return true;\n }\n\n fromCodeStart = fromNodeName.charCodeAt(0);\n toCodeStart = toNodeName.charCodeAt(0);\n\n // If the target element is a virtual DOM node or SVG node then we may\n // need to normalize the tag name before comparing. Normal HTML elements that are\n // in the \"http://www.w3.org/1999/xhtml\"\n // are converted to upper case\n if (fromCodeStart <= 90 && toCodeStart >= 97) { // from is upper and to is lower\n return fromNodeName === toNodeName.toUpperCase();\n } else if (toCodeStart <= 90 && fromCodeStart >= 97) { // to is upper and from is lower\n return toNodeName === fromNodeName.toUpperCase();\n } else {\n return false;\n }\n}\n\n/**\n * Create an element, optionally with a known namespace URI.\n *\n * @param {string} name the element name, e.g. 'div' or 'svg'\n * @param {string} [namespaceURI] the element's namespace URI, i.e. the value of\n * its `xmlns` attribute or its inferred namespace.\n *\n * @return {Element}\n */\nfunction createElementNS(name, namespaceURI) {\n return !namespaceURI || namespaceURI === NS_XHTML ?\n doc.createElement(name) :\n doc.createElementNS(namespaceURI, name);\n}\n\n/**\n * Copies the children of one DOM element to another DOM element\n */\nfunction moveChildren(fromEl, toEl) {\n var curChild = fromEl.firstChild;\n while (curChild) {\n var nextChild = curChild.nextSibling;\n toEl.appendChild(curChild);\n curChild = nextChild;\n }\n return toEl;\n}\n\nfunction syncBooleanAttrProp(fromEl, toEl, name) {\n if (fromEl[name] !== toEl[name]) {\n fromEl[name] = toEl[name];\n if (fromEl[name]) {\n fromEl.setAttribute(name, '');\n } else {\n fromEl.removeAttribute(name);\n }\n }\n}\n\nvar specialElHandlers = {\n OPTION: function(fromEl, toEl) {\n var parentNode = fromEl.parentNode;\n if (parentNode) {\n var parentName = parentNode.nodeName.toUpperCase();\n if (parentName === 'OPTGROUP') {\n parentNode = parentNode.parentNode;\n parentName = parentNode && parentNode.nodeName.toUpperCase();\n }\n if (parentName === 'SELECT' && !parentNode.hasAttribute('multiple')) {\n if (fromEl.hasAttribute('selected') && !toEl.selected) {\n // Workaround for MS Edge bug where the 'selected' attribute can only be\n // removed if set to a non-empty value:\n // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12087679/\n fromEl.setAttribute('selected', 'selected');\n fromEl.removeAttribute('selected');\n }\n // We have to reset select element's selectedIndex to -1, otherwise setting\n // fromEl.selected using the syncBooleanAttrProp below has no effect.\n // The correct selectedIndex will be set in the SELECT special handler below.\n parentNode.selectedIndex = -1;\n }\n }\n syncBooleanAttrProp(fromEl, toEl, 'selected');\n },\n /**\n * The \"value\" attribute is special for the element since it sets\n * the initial value. Changing the \"value\" attribute without changing the\n * \"value\" property will have no effect since it is only used to the set the\n * initial value. Similar for the \"checked\" attribute, and \"disabled\".\n */\n INPUT: function(fromEl, toEl) {\n syncBooleanAttrProp(fromEl, toEl, 'checked');\n syncBooleanAttrProp(fromEl, toEl, 'disabled');\n\n if (fromEl.value !== toEl.value) {\n fromEl.value = toEl.value;\n }\n\n if (!toEl.hasAttribute('value')) {\n fromEl.removeAttribute('value');\n }\n },\n\n TEXTAREA: function(fromEl, toEl) {\n var newValue = toEl.value;\n if (fromEl.value !== newValue) {\n fromEl.value = newValue;\n }\n\n var firstChild = fromEl.firstChild;\n if (firstChild) {\n // Needed for IE. Apparently IE sets the placeholder as the\n // node value and vise versa. This ignores an empty update.\n var oldValue = firstChild.nodeValue;\n\n if (oldValue == newValue || (!newValue && oldValue == fromEl.placeholder)) {\n return;\n }\n\n firstChild.nodeValue = newValue;\n }\n },\n SELECT: function(fromEl, toEl) {\n if (!toEl.hasAttribute('multiple')) {\n var selectedIndex = -1;\n var i = 0;\n // We have to loop through children of fromEl, not toEl since nodes can be moved\n // from toEl to fromEl directly when morphing.\n // At the time this special handler is invoked, all children have already been morphed\n // and appended to / removed from fromEl, so using fromEl here is safe and correct.\n var curChild = fromEl.firstChild;\n var optgroup;\n var nodeName;\n while(curChild) {\n nodeName = curChild.nodeName && curChild.nodeName.toUpperCase();\n if (nodeName === 'OPTGROUP') {\n optgroup = curChild;\n curChild = optgroup.firstChild;\n } else {\n if (nodeName === 'OPTION') {\n if (curChild.hasAttribute('selected')) {\n selectedIndex = i;\n break;\n }\n i++;\n }\n curChild = curChild.nextSibling;\n if (!curChild && optgroup) {\n curChild = optgroup.nextSibling;\n optgroup = null;\n }\n }\n }\n\n fromEl.selectedIndex = selectedIndex;\n }\n }\n};\n\nvar ELEMENT_NODE = 1;\nvar DOCUMENT_FRAGMENT_NODE$1 = 11;\nvar TEXT_NODE = 3;\nvar COMMENT_NODE = 8;\n\nfunction noop() {}\n\nfunction defaultGetNodeKey(node) {\n if (node) {\n return (node.getAttribute && node.getAttribute('id')) || node.id;\n }\n}\n\nfunction morphdomFactory(morphAttrs) {\n\n return function morphdom(fromNode, toNode, options) {\n if (!options) {\n options = {};\n }\n\n if (typeof toNode === 'string') {\n if (fromNode.nodeName === '#document' || fromNode.nodeName === 'HTML' || fromNode.nodeName === 'BODY') {\n var toNodeHtml = toNode;\n toNode = doc.createElement('html');\n toNode.innerHTML = toNodeHtml;\n } else {\n toNode = toElement(toNode);\n }\n }\n\n var getNodeKey = options.getNodeKey || defaultGetNodeKey;\n var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;\n var onNodeAdded = options.onNodeAdded || noop;\n var onBeforeElUpdated = options.onBeforeElUpdated || noop;\n var onElUpdated = options.onElUpdated || noop;\n var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;\n var onNodeDiscarded = options.onNodeDiscarded || noop;\n var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;\n var childrenOnly = options.childrenOnly === true;\n\n // This object is used as a lookup to quickly find all keyed elements in the original DOM tree.\n var fromNodesLookup = Object.create(null);\n var keyedRemovalList = [];\n\n function addKeyedRemoval(key) {\n keyedRemovalList.push(key);\n }\n\n function walkDiscardedChildNodes(node, skipKeyedNodes) {\n if (node.nodeType === ELEMENT_NODE) {\n var curChild = node.firstChild;\n while (curChild) {\n\n var key = undefined;\n\n if (skipKeyedNodes && (key = getNodeKey(curChild))) {\n // If we are skipping keyed nodes then we add the key\n // to a list so that it can be handled at the very end.\n addKeyedRemoval(key);\n } else {\n // Only report the node as discarded if it is not keyed. We do this because\n // at the end we loop through all keyed elements that were unmatched\n // and then discard them in one final pass.\n onNodeDiscarded(curChild);\n if (curChild.firstChild) {\n walkDiscardedChildNodes(curChild, skipKeyedNodes);\n }\n }\n\n curChild = curChild.nextSibling;\n }\n }\n }\n\n /**\n * Removes a DOM node out of the original DOM\n *\n * @param {Node} node The node to remove\n * @param {Node} parentNode The nodes parent\n * @param {Boolean} skipKeyedNodes If true then elements with keys will be skipped and not discarded.\n * @return {undefined}\n */\n function removeNode(node, parentNode, skipKeyedNodes) {\n if (onBeforeNodeDiscarded(node) === false) {\n return;\n }\n\n if (parentNode) {\n parentNode.removeChild(node);\n }\n\n onNodeDiscarded(node);\n walkDiscardedChildNodes(node, skipKeyedNodes);\n }\n\n // // TreeWalker implementation is no faster, but keeping this around in case this changes in the future\n // function indexTree(root) {\n // var treeWalker = document.createTreeWalker(\n // root,\n // NodeFilter.SHOW_ELEMENT);\n //\n // var el;\n // while((el = treeWalker.nextNode())) {\n // var key = getNodeKey(el);\n // if (key) {\n // fromNodesLookup[key] = el;\n // }\n // }\n // }\n\n // // NodeIterator implementation is no faster, but keeping this around in case this changes in the future\n //\n // function indexTree(node) {\n // var nodeIterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT);\n // var el;\n // while((el = nodeIterator.nextNode())) {\n // var key = getNodeKey(el);\n // if (key) {\n // fromNodesLookup[key] = el;\n // }\n // }\n // }\n\n function indexTree(node) {\n if (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE$1) {\n var curChild = node.firstChild;\n while (curChild) {\n var key = getNodeKey(curChild);\n if (key) {\n fromNodesLookup[key] = curChild;\n }\n\n // Walk recursively\n indexTree(curChild);\n\n curChild = curChild.nextSibling;\n }\n }\n }\n\n indexTree(fromNode);\n\n function handleNodeAdded(el) {\n onNodeAdded(el);\n\n var curChild = el.firstChild;\n while (curChild) {\n var nextSibling = curChild.nextSibling;\n\n var key = getNodeKey(curChild);\n if (key) {\n var unmatchedFromEl = fromNodesLookup[key];\n // if we find a duplicate #id node in cache, replace `el` with cache value\n // and morph it to the child node.\n if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) {\n curChild.parentNode.replaceChild(unmatchedFromEl, curChild);\n morphEl(unmatchedFromEl, curChild);\n } else {\n handleNodeAdded(curChild);\n }\n } else {\n // recursively call for curChild and it's children to see if we find something in\n // fromNodesLookup\n handleNodeAdded(curChild);\n }\n\n curChild = nextSibling;\n }\n }\n\n function cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey) {\n // We have processed all of the \"to nodes\". If curFromNodeChild is\n // non-null then we still have some from nodes left over that need\n // to be removed\n while (curFromNodeChild) {\n var fromNextSibling = curFromNodeChild.nextSibling;\n if ((curFromNodeKey = getNodeKey(curFromNodeChild))) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n curFromNodeChild = fromNextSibling;\n }\n }\n\n function morphEl(fromEl, toEl, childrenOnly) {\n var toElKey = getNodeKey(toEl);\n\n if (toElKey) {\n // If an element with an ID is being morphed then it will be in the final\n // DOM so clear it out of the saved elements collection\n delete fromNodesLookup[toElKey];\n }\n\n if (!childrenOnly) {\n // optional\n if (onBeforeElUpdated(fromEl, toEl) === false) {\n return;\n }\n\n // update attributes on original DOM element first\n morphAttrs(fromEl, toEl);\n // optional\n onElUpdated(fromEl);\n\n if (onBeforeElChildrenUpdated(fromEl, toEl) === false) {\n return;\n }\n }\n\n if (fromEl.nodeName !== 'TEXTAREA') {\n morphChildren(fromEl, toEl);\n } else {\n specialElHandlers.TEXTAREA(fromEl, toEl);\n }\n }\n\n function morphChildren(fromEl, toEl) {\n var curToNodeChild = toEl.firstChild;\n var curFromNodeChild = fromEl.firstChild;\n var curToNodeKey;\n var curFromNodeKey;\n\n var fromNextSibling;\n var toNextSibling;\n var matchingFromEl;\n\n // walk the children\n outer: while (curToNodeChild) {\n toNextSibling = curToNodeChild.nextSibling;\n curToNodeKey = getNodeKey(curToNodeChild);\n\n // walk the fromNode children all the way through\n while (curFromNodeChild) {\n fromNextSibling = curFromNodeChild.nextSibling;\n\n if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n continue outer;\n }\n\n curFromNodeKey = getNodeKey(curFromNodeChild);\n\n var curFromNodeType = curFromNodeChild.nodeType;\n\n // this means if the curFromNodeChild doesnt have a match with the curToNodeChild\n var isCompatible = undefined;\n\n if (curFromNodeType === curToNodeChild.nodeType) {\n if (curFromNodeType === ELEMENT_NODE) {\n // Both nodes being compared are Element nodes\n\n if (curToNodeKey) {\n // The target node has a key so we want to match it up with the correct element\n // in the original DOM tree\n if (curToNodeKey !== curFromNodeKey) {\n // The current element in the original DOM tree does not have a matching key so\n // let's check our lookup to see if there is a matching element in the original\n // DOM tree\n if ((matchingFromEl = fromNodesLookup[curToNodeKey])) {\n if (fromNextSibling === matchingFromEl) {\n // Special case for single element removals. To avoid removing the original\n // DOM node out of the tree (since that can break CSS transitions, etc.),\n // we will instead discard the current node and wait until the next\n // iteration to properly match up the keyed target element with its matching\n // element in the original tree\n isCompatible = false;\n } else {\n // We found a matching keyed element somewhere in the original DOM tree.\n // Let's move the original DOM node into the current position and morph\n // it.\n\n // NOTE: We use insertBefore instead of replaceChild because we want to go through\n // the `removeNode()` function for the node that is being discarded so that\n // all lifecycle hooks are correctly invoked\n fromEl.insertBefore(matchingFromEl, curFromNodeChild);\n\n // fromNextSibling = curFromNodeChild.nextSibling;\n\n if (curFromNodeKey) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n\n curFromNodeChild = matchingFromEl;\n }\n } else {\n // The nodes are not compatible since the \"to\" node has a key and there\n // is no matching keyed node in the source tree\n isCompatible = false;\n }\n }\n } else if (curFromNodeKey) {\n // The original has a key\n isCompatible = false;\n }\n\n isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild);\n if (isCompatible) {\n // We found compatible DOM elements so transform\n // the current \"from\" node to match the current\n // target DOM node.\n // MORPH\n morphEl(curFromNodeChild, curToNodeChild);\n }\n\n } else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) {\n // Both nodes being compared are Text or Comment nodes\n isCompatible = true;\n // Simply update nodeValue on the original node to\n // change the text value\n if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) {\n curFromNodeChild.nodeValue = curToNodeChild.nodeValue;\n }\n\n }\n }\n\n if (isCompatible) {\n // Advance both the \"to\" child and the \"from\" child since we found a match\n // Nothing else to do as we already recursively called morphChildren above\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n continue outer;\n }\n\n // No compatible match so remove the old node from the DOM and continue trying to find a\n // match in the original DOM. However, we only do this if the from node is not keyed\n // since it is possible that a keyed node might match up with a node somewhere else in the\n // target tree and we don't want to discard it just yet since it still might find a\n // home in the final DOM tree. After everything is done we will remove any keyed nodes\n // that didn't find a home\n if (curFromNodeKey) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n\n curFromNodeChild = fromNextSibling;\n } // END: while(curFromNodeChild) {}\n\n // If we got this far then we did not find a candidate match for\n // our \"to node\" and we exhausted all of the children \"from\"\n // nodes. Therefore, we will just append the current \"to\" node\n // to the end\n if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {\n fromEl.appendChild(matchingFromEl);\n // MORPH\n morphEl(matchingFromEl, curToNodeChild);\n } else {\n var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);\n if (onBeforeNodeAddedResult !== false) {\n if (onBeforeNodeAddedResult) {\n curToNodeChild = onBeforeNodeAddedResult;\n }\n\n if (curToNodeChild.actualize) {\n curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);\n }\n fromEl.appendChild(curToNodeChild);\n handleNodeAdded(curToNodeChild);\n }\n }\n\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n }\n\n cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey);\n\n var specialElHandler = specialElHandlers[fromEl.nodeName];\n if (specialElHandler) {\n specialElHandler(fromEl, toEl);\n }\n } // END: morphChildren(...)\n\n var morphedNode = fromNode;\n var morphedNodeType = morphedNode.nodeType;\n var toNodeType = toNode.nodeType;\n\n if (!childrenOnly) {\n // Handle the case where we are given two DOM nodes that are not\n // compatible (e.g. -->
or --> TEXT)\n if (morphedNodeType === ELEMENT_NODE) {\n if (toNodeType === ELEMENT_NODE) {\n if (!compareNodeNames(fromNode, toNode)) {\n onNodeDiscarded(fromNode);\n morphedNode = moveChildren(fromNode, createElementNS(toNode.nodeName, toNode.namespaceURI));\n }\n } else {\n // Going from an element node to a text node\n morphedNode = toNode;\n }\n } else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node\n if (toNodeType === morphedNodeType) {\n if (morphedNode.nodeValue !== toNode.nodeValue) {\n morphedNode.nodeValue = toNode.nodeValue;\n }\n\n return morphedNode;\n } else {\n // Text node to something else\n morphedNode = toNode;\n }\n }\n }\n\n if (morphedNode === toNode) {\n // The \"to node\" was not compatible with the \"from node\" so we had to\n // toss out the \"from node\" and use the \"to node\"\n onNodeDiscarded(fromNode);\n } else {\n if (toNode.isSameNode && toNode.isSameNode(morphedNode)) {\n return;\n }\n\n morphEl(morphedNode, toNode, childrenOnly);\n\n // We now need to loop over any keyed nodes that might need to be\n // removed. We only do the removal if we know that the keyed node\n // never found a match. When a keyed node is matched up we remove\n // it out of fromNodesLookup and we use fromNodesLookup to determine\n // if a keyed node has been matched up or not\n if (keyedRemovalList) {\n for (var i=0, len=keyedRemovalList.length; i
{\n if(activeElement && activeElement.isSameNode(fromEl) && DOM.isFormInput(fromEl)){\n DOM.mergeFocusedInput(fromEl, toEl)\n return false\n }\n }\n })\n }\n\n constructor(view, container, id, html, targetCID){\n this.view = view\n this.liveSocket = view.liveSocket\n this.container = container\n this.id = id\n this.rootID = view.root.id\n this.html = html\n this.targetCID = targetCID\n this.cidPatch = isCid(this.targetCID)\n this.callbacks = {\n beforeadded: [], beforeupdated: [], beforephxChildAdded: [],\n afteradded: [], afterupdated: [], afterdiscarded: [], afterphxChildAdded: []\n }\n }\n\n before(kind, callback){ this.callbacks[`before${kind}`].push(callback) }\n after(kind, callback){ this.callbacks[`after${kind}`].push(callback) }\n\n trackBefore(kind, ...args){\n this.callbacks[`before${kind}`].forEach(callback => callback(...args))\n }\n\n trackAfter(kind, ...args){\n this.callbacks[`after${kind}`].forEach(callback => callback(...args))\n }\n\n markPrunableContentForRemoval(){\n DOM.all(this.container, \"[phx-update=append] > *, [phx-update=prepend] > *\", el => {\n el.setAttribute(PHX_REMOVE, \"\")\n })\n }\n\n perform(){\n let {view, liveSocket, container, html} = this\n let targetContainer = this.isCIDPatch() ? this.targetCIDContainer(html) : container\n if(this.isCIDPatch() && !targetContainer){ return }\n\n let focused = liveSocket.getActiveElement()\n let {selectionStart, selectionEnd} = focused && DOM.hasSelectionRange(focused) ? focused : {}\n let phxUpdate = liveSocket.binding(PHX_UPDATE)\n let phxFeedbackFor = liveSocket.binding(PHX_FEEDBACK_FOR)\n let disableWith = liveSocket.binding(PHX_DISABLE_WITH)\n let phxTriggerExternal = liveSocket.binding(PHX_TRIGGER_ACTION)\n let added = []\n let updates = []\n let appendPrependUpdates = []\n let externalFormTriggered = null\n\n let diffHTML = liveSocket.time(\"premorph container prep\", () => {\n return this.buildDiffHTML(container, html, phxUpdate, targetContainer)\n })\n\n this.trackBefore(\"added\", container)\n this.trackBefore(\"updated\", container, container)\n\n liveSocket.time(\"morphdom\", () => {\n morphdom(targetContainer, diffHTML, {\n childrenOnly: targetContainer.getAttribute(PHX_COMPONENT) === null,\n getNodeKey: (node) => {\n return DOM.isPhxDestroyed(node) ? null : node.id\n },\n onBeforeNodeAdded: (el) => {\n this.trackBefore(\"added\", el)\n return el\n },\n onNodeAdded: (el) => {\n // hack to fix Safari handling of img srcset and video tags\n if(el instanceof HTMLImageElement && el.srcset){\n el.srcset = el.srcset\n } else if(el instanceof HTMLVideoElement && el.autoplay){\n el.play()\n }\n if(DOM.isNowTriggerFormExternal(el, phxTriggerExternal)){\n externalFormTriggered = el\n }\n //input handling\n DOM.discardError(targetContainer, el, phxFeedbackFor)\n // nested view handling\n if(DOM.isPhxChild(el) && view.ownsElement(el)){\n this.trackAfter(\"phxChildAdded\", el)\n }\n added.push(el)\n },\n onNodeDiscarded: (el) => {\n // nested view handling\n if(DOM.isPhxChild(el)){ liveSocket.destroyViewByEl(el) }\n this.trackAfter(\"discarded\", el)\n },\n onBeforeNodeDiscarded: (el) => {\n if(el.getAttribute && el.getAttribute(PHX_REMOVE) !== null){ return true }\n if(el.parentNode !== null && DOM.isPhxUpdate(el.parentNode, phxUpdate, [\"append\", \"prepend\"]) && el.id){ return false }\n if(this.skipCIDSibling(el)){ return false }\n return true\n },\n onElUpdated: (el) => {\n if(DOM.isNowTriggerFormExternal(el, phxTriggerExternal)){\n externalFormTriggered = el\n }\n updates.push(el)\n },\n onBeforeElUpdated: (fromEl, toEl) => {\n DOM.cleanChildNodes(toEl, phxUpdate)\n if(this.skipCIDSibling(toEl)){ return false }\n if(DOM.isIgnored(fromEl, phxUpdate)){\n this.trackBefore(\"updated\", fromEl, toEl)\n DOM.mergeAttrs(fromEl, toEl, {isIgnored: true})\n updates.push(fromEl)\n return false\n }\n if(fromEl.type === \"number\" && (fromEl.validity && fromEl.validity.badInput)){ return false }\n if(!DOM.syncPendingRef(fromEl, toEl, disableWith)){\n if(DOM.isUploadInput(fromEl)){\n this.trackBefore(\"updated\", fromEl, toEl)\n updates.push(fromEl)\n }\n return false\n }\n\n // nested view handling\n if(DOM.isPhxChild(toEl)){\n let prevSession = fromEl.getAttribute(PHX_SESSION)\n DOM.mergeAttrs(fromEl, toEl, {exclude: [PHX_STATIC]})\n if(prevSession !== \"\"){ fromEl.setAttribute(PHX_SESSION, prevSession) }\n fromEl.setAttribute(PHX_ROOT_ID, this.rootID)\n return false\n }\n\n // input handling\n DOM.copyPrivates(toEl, fromEl)\n DOM.discardError(targetContainer, toEl, phxFeedbackFor)\n DOM.syncPropsToAttrs(toEl)\n\n let isFocusedFormEl = focused && fromEl.isSameNode(focused) && DOM.isFormInput(fromEl)\n if(isFocusedFormEl && !this.forceFocusedSelectUpdate(fromEl, toEl)){\n this.trackBefore(\"updated\", fromEl, toEl)\n DOM.mergeFocusedInput(fromEl, toEl)\n DOM.syncAttrsToProps(fromEl)\n updates.push(fromEl)\n return false\n } else {\n if(DOM.isPhxUpdate(toEl, phxUpdate, [\"append\", \"prepend\"])){\n appendPrependUpdates.push(new DOMPostMorphRestorer(fromEl, toEl, toEl.getAttribute(phxUpdate)))\n }\n DOM.syncAttrsToProps(toEl)\n this.trackBefore(\"updated\", fromEl, toEl)\n return true\n }\n }\n })\n })\n\n if(liveSocket.isDebugEnabled()){ detectDuplicateIds() }\n\n if(appendPrependUpdates.length > 0){\n liveSocket.time(\"post-morph append/prepend restoration\", () => {\n appendPrependUpdates.forEach(update => update.perform())\n })\n }\n\n liveSocket.silenceEvents(() => DOM.restoreFocus(focused, selectionStart, selectionEnd))\n DOM.dispatchEvent(document, \"phx:update\")\n added.forEach(el => this.trackAfter(\"added\", el))\n updates.forEach(el => this.trackAfter(\"updated\", el))\n\n if(externalFormTriggered){\n liveSocket.disconnect()\n externalFormTriggered.submit()\n }\n return true\n }\n\n forceFocusedSelectUpdate(fromEl, toEl){\n let isSelect = [\"select\", \"select-one\", \"select-multiple\"].find((t) => t === fromEl.type)\n return fromEl.multiple === true || (isSelect && fromEl.innerHTML != toEl.innerHTML)\n }\n\n isCIDPatch(){ return this.cidPatch }\n\n skipCIDSibling(el){\n return el.nodeType === Node.ELEMENT_NODE && el.getAttribute(PHX_SKIP) !== null\n }\n\n targetCIDContainer(html){\n if(!this.isCIDPatch()){ return }\n let [first, ...rest] = DOM.findComponentNodeList(this.container, this.targetCID)\n if(rest.length === 0 && DOM.childNodeLength(html) === 1){\n return first\n } else {\n return first && first.parentNode\n }\n }\n\n // builds HTML for morphdom patch\n // - for full patches of LiveView or a component with a single\n // root node, simply returns the HTML\n // - for patches of a component with multiple root nodes, the\n // parent node becomes the target container and non-component\n // siblings are marked as skip.\n buildDiffHTML(container, html, phxUpdate, targetContainer){\n let isCIDPatch = this.isCIDPatch()\n let isCIDWithSingleRoot = isCIDPatch && targetContainer.getAttribute(PHX_COMPONENT) === this.targetCID.toString()\n if(!isCIDPatch || isCIDWithSingleRoot){\n return html\n } else {\n // component patch with multiple CID roots\n let diffContainer = null\n let template = document.createElement(\"template\")\n diffContainer = DOM.cloneNode(targetContainer)\n let [firstComponent, ...rest] = DOM.findComponentNodeList(diffContainer, this.targetCID)\n template.innerHTML = html\n rest.forEach(el => el.remove())\n Array.from(diffContainer.childNodes).forEach(child => {\n // we can only skip trackable nodes with an ID\n if(child.id && child.nodeType === Node.ELEMENT_NODE && child.getAttribute(PHX_COMPONENT) !== this.targetCID.toString()){\n child.setAttribute(PHX_SKIP, \"\")\n child.innerHTML = \"\"\n }\n })\n Array.from(template.content.childNodes).forEach(el => diffContainer.insertBefore(el, firstComponent))\n firstComponent.remove()\n return diffContainer.outerHTML\n }\n }\n}\n", "import {\n COMPONENTS,\n DYNAMICS,\n EVENTS,\n PHX_COMPONENT,\n PHX_SKIP,\n REPLY,\n STATIC,\n TITLE\n} from \"./constants\"\n\nimport {\n isObject,\n logError,\n isCid,\n} from \"./utils\"\n\nexport default class Rendered {\n static extract(diff){\n let {[REPLY]: reply, [EVENTS]: events, [TITLE]: title} = diff\n delete diff[REPLY]\n delete diff[EVENTS]\n delete diff[TITLE]\n return {diff, title, reply: reply || null, events: events || []}\n }\n\n constructor(viewId, rendered){\n this.viewId = viewId\n this.rendered = {}\n this.mergeDiff(rendered)\n }\n\n parentViewId(){ return this.viewId }\n\n toString(onlyCids){\n return this.recursiveToString(this.rendered, this.rendered[COMPONENTS], onlyCids)\n }\n\n recursiveToString(rendered, components = rendered[COMPONENTS], onlyCids){\n onlyCids = onlyCids ? new Set(onlyCids) : null\n let output = {buffer: \"\", components: components, onlyCids: onlyCids}\n this.toOutputBuffer(rendered, output)\n return output.buffer\n }\n\n componentCIDs(diff){ return Object.keys(diff[COMPONENTS] || {}).map(i => parseInt(i)) }\n\n isComponentOnlyDiff(diff){\n if(!diff[COMPONENTS]){ return false }\n return Object.keys(diff).length === 1\n }\n\n getComponent(diff, cid){ return diff[COMPONENTS][cid] }\n\n mergeDiff(diff){\n let newc = diff[COMPONENTS]\n let cache = {}\n delete diff[COMPONENTS]\n this.rendered = this.mutableMerge(this.rendered, diff)\n this.rendered[COMPONENTS] = this.rendered[COMPONENTS] || {}\n\n if(newc){\n let oldc = this.rendered[COMPONENTS]\n\n for(let cid in newc){\n newc[cid] = this.cachedFindComponent(cid, newc[cid], oldc, newc, cache)\n }\n\n for(var key in newc){ oldc[key] = newc[key] }\n diff[COMPONENTS] = newc\n }\n }\n\n cachedFindComponent(cid, cdiff, oldc, newc, cache){\n if(cache[cid]){\n return cache[cid]\n } else {\n let ndiff, stat, scid = cdiff[STATIC]\n\n if(isCid(scid)){\n let tdiff\n\n if(scid > 0){\n tdiff = this.cachedFindComponent(scid, newc[scid], oldc, newc, cache)\n } else {\n tdiff = oldc[-scid]\n }\n\n stat = tdiff[STATIC]\n ndiff = this.cloneMerge(tdiff, cdiff)\n ndiff[STATIC] = stat\n } else {\n ndiff = cdiff[STATIC] !== undefined ? cdiff : this.cloneMerge(oldc[cid] || {}, cdiff)\n }\n\n cache[cid] = ndiff\n return ndiff\n }\n }\n\n mutableMerge(target, source){\n if(source[STATIC] !== undefined){\n return source\n } else {\n this.doMutableMerge(target, source)\n return target\n }\n }\n\n doMutableMerge(target, source){\n for(let key in source){\n let val = source[key]\n let targetVal = target[key]\n if(isObject(val) && val[STATIC] === undefined && isObject(targetVal)){\n this.doMutableMerge(targetVal, val)\n } else {\n target[key] = val\n }\n }\n }\n\n cloneMerge(target, source){\n let merged = {...target, ...source}\n for(let key in merged){\n let val = source[key]\n let targetVal = target[key]\n if(isObject(val) && val[STATIC] === undefined && isObject(targetVal)){\n merged[key] = this.cloneMerge(targetVal, val)\n }\n }\n return merged\n }\n\n componentToString(cid){ return this.recursiveCIDToString(this.rendered[COMPONENTS], cid) }\n\n pruneCIDs(cids){\n cids.forEach(cid => delete this.rendered[COMPONENTS][cid])\n }\n\n // private\n\n get(){ return this.rendered }\n\n isNewFingerprint(diff = {}){ return !!diff[STATIC] }\n\n toOutputBuffer(rendered, output){\n if(rendered[DYNAMICS]){ return this.comprehensionToBuffer(rendered, output) }\n let {[STATIC]: statics} = rendered\n\n output.buffer += statics[0]\n for(let i = 1; i < statics.length; i++){\n this.dynamicToBuffer(rendered[i - 1], output)\n output.buffer += statics[i]\n }\n }\n\n comprehensionToBuffer(rendered, output){\n let {[DYNAMICS]: dynamics, [STATIC]: statics} = rendered\n\n for(let d = 0; d < dynamics.length; d++){\n let dynamic = dynamics[d]\n output.buffer += statics[0]\n for(let i = 1; i < statics.length; i++){\n this.dynamicToBuffer(dynamic[i - 1], output)\n output.buffer += statics[i]\n }\n }\n }\n\n dynamicToBuffer(rendered, output){\n if(typeof (rendered) === \"number\"){\n output.buffer += this.recursiveCIDToString(output.components, rendered, output.onlyCids)\n } else if(isObject(rendered)){\n this.toOutputBuffer(rendered, output)\n } else {\n output.buffer += rendered\n }\n }\n\n recursiveCIDToString(components, cid, onlyCids){\n let component = components[cid] || logError(`no component for CID ${cid}`, components)\n let template = document.createElement(\"template\")\n template.innerHTML = this.recursiveToString(component, components, onlyCids)\n let container = template.content\n let skip = onlyCids && !onlyCids.has(cid)\n\n let [hasChildNodes, hasChildComponents] =\n Array.from(container.childNodes).reduce(([hasNodes, hasComponents], child, i) => {\n if(child.nodeType === Node.ELEMENT_NODE){\n if(child.getAttribute(PHX_COMPONENT)){\n return [hasNodes, true]\n }\n child.setAttribute(PHX_COMPONENT, cid)\n if(!child.id){ child.id = `${this.parentViewId()}-${cid}-${i}` }\n if(skip){\n child.setAttribute(PHX_SKIP, \"\")\n child.innerHTML = \"\"\n }\n return [true, hasComponents]\n } else {\n if(child.nodeValue.trim() !== \"\"){\n logError(\"only HTML element tags are allowed at the root of components.\\n\\n\" +\n `got: \"${child.nodeValue.trim()}\"\\n\\n` +\n \"within:\\n\", template.innerHTML.trim())\n child.replaceWith(this.createSpan(child.nodeValue, cid))\n return [true, hasComponents]\n } else {\n child.remove()\n return [hasNodes, hasComponents]\n }\n }\n }, [false, false])\n\n if(!hasChildNodes && !hasChildComponents){\n logError(\"expected at least one HTML element tag inside a component, but the component is empty:\\n\",\n template.innerHTML.trim())\n return this.createSpan(\"\", cid).outerHTML\n } else if(!hasChildNodes && hasChildComponents){\n logError(\"expected at least one HTML element tag directly inside a component, but only subcomponents were found. A component must render at least one HTML tag directly inside itself.\",\n template.innerHTML.trim())\n return template.innerHTML\n } else {\n return template.innerHTML\n }\n }\n\n createSpan(text, cid){\n let span = document.createElement(\"span\")\n span.innerText = text\n span.setAttribute(PHX_COMPONENT, cid)\n return span\n }\n}\n", "let viewHookID = 1\nexport default class ViewHook {\n static makeID(){ return viewHookID++ }\n static elementID(el){ return el.phxHookId }\n\n constructor(view, el, callbacks){\n this.__view = view\n this.__liveSocket = view.liveSocket\n this.__callbacks = callbacks\n this.__listeners = new Set()\n this.__isDisconnected = false\n this.el = el\n this.el.phxHookId = this.constructor.makeID()\n for(let key in this.__callbacks){ this[key] = this.__callbacks[key] }\n }\n\n __mounted(){ this.mounted && this.mounted() }\n __updated(){ this.updated && this.updated() }\n __beforeUpdate(){ this.beforeUpdate && this.beforeUpdate() }\n __destroyed(){ this.destroyed && this.destroyed() }\n __reconnected(){\n if(this.__isDisconnected){\n this.__isDisconnected = false\n this.reconnected && this.reconnected()\n }\n }\n __disconnected(){\n this.__isDisconnected = true\n this.disconnected && this.disconnected()\n }\n\n pushEvent(event, payload = {}, onReply = function (){ }){\n return this.__view.pushHookEvent(null, event, payload, onReply)\n }\n\n pushEventTo(phxTarget, event, payload = {}, onReply = function (){ }){\n return this.__view.withinTargets(phxTarget, (view, targetCtx) => {\n return view.pushHookEvent(targetCtx, event, payload, onReply)\n })\n }\n\n handleEvent(event, callback){\n let callbackRef = (customEvent, bypass) => bypass ? event : callback(customEvent.detail)\n window.addEventListener(`phx:hook:${event}`, callbackRef)\n this.__listeners.add(callbackRef)\n return callbackRef\n }\n\n removeHandleEvent(callbackRef){\n let event = callbackRef(null, true)\n window.removeEventListener(`phx:hook:${event}`, callbackRef)\n this.__listeners.delete(callbackRef)\n }\n\n upload(name, files){\n return this.__view.dispatchUploads(name, files)\n }\n\n uploadTo(phxTarget, name, files){\n return this.__view.withinTargets(phxTarget, view => view.dispatchUploads(name, files))\n }\n\n __cleanup__(){\n this.__listeners.forEach(callbackRef => this.removeHandleEvent(callbackRef))\n }\n}\n", "import {\n BEFORE_UNLOAD_LOADER_TIMEOUT,\n CHECKABLE_INPUTS,\n CONSECUTIVE_RELOADS,\n PHX_AUTO_RECOVER,\n PHX_COMPONENT,\n PHX_CONNECTED_CLASS,\n PHX_DISABLE_WITH,\n PHX_DISABLE_WITH_RESTORE,\n PHX_DISABLED,\n PHX_DISCONNECTED_CLASS,\n PHX_EVENT_CLASSES,\n PHX_ERROR_CLASS,\n PHX_FEEDBACK_FOR,\n PHX_HAS_SUBMITTED,\n PHX_HOOK,\n PHX_PAGE_LOADING,\n PHX_PARENT_ID,\n PHX_PROGRESS,\n PHX_READONLY,\n PHX_REF,\n PHX_ROOT_ID,\n PHX_SESSION,\n PHX_STATIC,\n PHX_TRACK_STATIC,\n PHX_TRACK_UPLOADS,\n PHX_UPDATE,\n PHX_UPLOAD_REF,\n PHX_VIEW_SELECTOR,\n PUSH_TIMEOUT,\n} from \"./constants\"\n\nimport {\n clone,\n closestPhxBinding,\n isEmpty,\n isEqualObj,\n logError,\n maybe,\n isCid,\n} from \"./utils\"\n\nimport Browser from \"./browser\"\nimport DOM from \"./dom\"\nimport DOMPatch from \"./dom_patch\"\nimport LiveUploader from \"./live_uploader\"\nimport Rendered from \"./rendered\"\nimport ViewHook from \"./view_hook\"\n\nlet serializeForm = (form, meta = {}) => {\n let formData = new FormData(form)\n let toRemove = []\n\n formData.forEach((val, key, _index) => {\n if(val instanceof File){ toRemove.push(key) }\n })\n\n // Cleanup after building fileData\n toRemove.forEach(key => formData.delete(key))\n\n let params = new URLSearchParams()\n for(let [key, val] of formData.entries()){ params.append(key, val) }\n for(let metaKey in meta){ params.append(metaKey, meta[metaKey]) }\n\n return params.toString()\n}\n\nexport default class View {\n constructor(el, liveSocket, parentView, flash){\n this.liveSocket = liveSocket\n this.flash = flash\n this.parent = parentView\n this.root = parentView ? parentView.root : this\n this.el = el\n this.id = this.el.id\n this.ref = 0\n this.childJoins = 0\n this.loaderTimer = null\n this.pendingDiffs = []\n this.pruningCIDs = []\n this.redirect = false\n this.href = null\n this.joinCount = this.parent ? this.parent.joinCount - 1 : 0\n this.joinPending = true\n this.destroyed = false\n this.joinCallback = function (){ }\n this.stopCallback = function (){ }\n this.pendingJoinOps = this.parent ? null : []\n this.viewHooks = {}\n this.uploaders = {}\n this.formSubmits = []\n this.children = this.parent ? null : {}\n this.root.children[this.id] = {}\n this.channel = this.liveSocket.channel(`lv:${this.id}`, () => {\n return {\n redirect: this.redirect ? this.href : undefined,\n url: this.redirect ? undefined : this.href || undefined,\n params: this.connectParams(),\n session: this.getSession(),\n static: this.getStatic(),\n flash: this.flash\n }\n })\n this.showLoader(this.liveSocket.loaderTimeout)\n this.bindChannel()\n }\n\n setHref(href){ this.href = href }\n\n setRedirect(href){\n this.redirect = true\n this.href = href\n }\n\n isMain(){ return this.liveSocket.main === this }\n\n connectParams(){\n let params = this.liveSocket.params(this.el)\n let manifest =\n DOM.all(document, `[${this.binding(PHX_TRACK_STATIC)}]`)\n .map(node => node.src || node.href).filter(url => typeof (url) === \"string\")\n\n if(manifest.length > 0){ params[\"_track_static\"] = manifest }\n params[\"_mounts\"] = this.joinCount\n\n return params\n }\n\n isConnected(){ return this.channel.canPush() }\n\n getSession(){ return this.el.getAttribute(PHX_SESSION) }\n\n getStatic(){\n let val = this.el.getAttribute(PHX_STATIC)\n return val === \"\" ? null : val\n }\n\n destroy(callback = function (){ }){\n this.destroyAllChildren()\n this.destroyed = true\n delete this.root.children[this.id]\n if(this.parent){ delete this.root.children[this.parent.id][this.id] }\n clearTimeout(this.loaderTimer)\n let onFinished = () => {\n callback()\n for(let id in this.viewHooks){\n this.destroyHook(this.viewHooks[id])\n }\n }\n\n DOM.markPhxChildDestroyed(this.el)\n\n this.log(\"destroyed\", () => [\"the child has been removed from the parent\"])\n this.channel.leave()\n .receive(\"ok\", onFinished)\n .receive(\"error\", onFinished)\n .receive(\"timeout\", onFinished)\n }\n\n setContainerClasses(...classes){\n this.el.classList.remove(\n PHX_CONNECTED_CLASS,\n PHX_DISCONNECTED_CLASS,\n PHX_ERROR_CLASS\n )\n this.el.classList.add(...classes)\n }\n\n isLoading(){ return this.el.classList.contains(PHX_DISCONNECTED_CLASS) }\n\n showLoader(timeout){\n clearTimeout(this.loaderTimer)\n if(timeout){\n this.loaderTimer = setTimeout(() => this.showLoader(), timeout)\n } else {\n for(let id in this.viewHooks){ this.viewHooks[id].__disconnected() }\n this.setContainerClasses(PHX_DISCONNECTED_CLASS)\n }\n }\n\n hideLoader(){\n clearTimeout(this.loaderTimer)\n this.setContainerClasses(PHX_CONNECTED_CLASS)\n }\n\n triggerReconnected(){\n for(let id in this.viewHooks){ this.viewHooks[id].__reconnected() }\n }\n\n log(kind, msgCallback){\n this.liveSocket.log(this, kind, msgCallback)\n }\n\n withinTargets(phxTarget, callback){\n if(phxTarget instanceof HTMLElement){\n return this.liveSocket.owner(phxTarget, view => callback(view, phxTarget))\n }\n\n if(/^(0|[1-9]\\d*)$/.test(phxTarget)){\n let targets = DOM.findComponentNodeList(this.el, phxTarget)\n if(targets.length === 0){\n logError(`no component found matching phx-target of ${phxTarget}`)\n } else {\n callback(this, targets[0])\n }\n } else {\n let targets = Array.from(document.querySelectorAll(phxTarget))\n if(targets.length === 0){ logError(`nothing found matching the phx-target selector \"${phxTarget}\"`) }\n targets.forEach(target => this.liveSocket.owner(target, view => callback(view, target)))\n }\n }\n\n applyDiff(type, rawDiff, callback){\n this.log(type, () => [\"\", clone(rawDiff)])\n let {diff, reply, events, title} = Rendered.extract(rawDiff)\n if(title){ DOM.putTitle(title) }\n\n callback({diff, reply, events})\n return reply\n }\n\n onJoin(resp){\n let {rendered, container} = resp\n if(container){\n let [tag, attrs] = container\n this.el = DOM.replaceRootContainer(this.el, tag, attrs)\n }\n this.childJoins = 0\n this.joinPending = true\n this.flash = null\n\n Browser.dropLocal(this.liveSocket.localStorage, window.location.pathname, CONSECUTIVE_RELOADS)\n this.applyDiff(\"mount\", rendered, ({diff, events}) => {\n this.rendered = new Rendered(this.id, diff)\n let html = this.renderContainer(null, \"join\")\n this.dropPendingRefs()\n let forms = this.formsForRecovery(html)\n this.joinCount++\n\n if(forms.length > 0){\n forms.forEach(([form, newForm, newCid], i) => {\n this.pushFormRecovery(form, newCid, resp => {\n if(i === forms.length - 1){\n this.onJoinComplete(resp, html, events)\n }\n })\n })\n } else {\n this.onJoinComplete(resp, html, events)\n }\n })\n }\n\n dropPendingRefs(){ DOM.all(this.el, `[${PHX_REF}]`, el => el.removeAttribute(PHX_REF)) }\n\n onJoinComplete({live_patch}, html, events){\n // In order to provide a better experience, we want to join\n // all LiveViews first and only then apply their patches.\n if(this.joinCount > 1 || (this.parent && !this.parent.isJoinPending())){\n return this.applyJoinPatch(live_patch, html, events)\n }\n\n // One downside of this approach is that we need to find phxChildren\n // in the html fragment, instead of directly on the DOM. The fragment\n // also does not include PHX_STATIC, so we need to copy it over from\n // the DOM.\n let newChildren = DOM.findPhxChildrenInFragment(html, this.id).filter(toEl => {\n let fromEl = toEl.id && this.el.querySelector(`[id=\"${toEl.id}\"]`)\n let phxStatic = fromEl && fromEl.getAttribute(PHX_STATIC)\n if(phxStatic){ toEl.setAttribute(PHX_STATIC, phxStatic) }\n return this.joinChild(toEl)\n })\n\n if(newChildren.length === 0){\n if(this.parent){\n this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, events)])\n this.parent.ackJoin(this)\n } else {\n this.onAllChildJoinsComplete()\n this.applyJoinPatch(live_patch, html, events)\n }\n } else {\n this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, events)])\n }\n }\n\n attachTrueDocEl(){\n this.el = DOM.byId(this.id)\n this.el.setAttribute(PHX_ROOT_ID, this.root.id)\n }\n\n dispatchEvents(events){\n events.forEach(([event, payload]) => {\n window.dispatchEvent(new CustomEvent(`phx:hook:${event}`, {detail: payload}))\n })\n }\n\n applyJoinPatch(live_patch, html, events){\n this.attachTrueDocEl()\n let patch = new DOMPatch(this, this.el, this.id, html, null)\n patch.markPrunableContentForRemoval()\n this.performPatch(patch, false)\n this.joinNewChildren()\n DOM.all(this.el, `[${this.binding(PHX_HOOK)}], [data-phx-${PHX_HOOK}]`, hookEl => {\n let hook = this.addHook(hookEl)\n if(hook){ hook.__mounted() }\n })\n\n this.joinPending = false\n this.dispatchEvents(events)\n this.applyPendingUpdates()\n\n if(live_patch){\n let {kind, to} = live_patch\n this.liveSocket.historyPatch(to, kind)\n }\n this.hideLoader()\n if(this.joinCount > 1){ this.triggerReconnected() }\n this.stopCallback()\n }\n\n triggerBeforeUpdateHook(fromEl, toEl){\n this.liveSocket.triggerDOM(\"onBeforeElUpdated\", [fromEl, toEl])\n let hook = this.getHook(fromEl)\n let isIgnored = hook && DOM.isIgnored(fromEl, this.binding(PHX_UPDATE))\n if(hook && !fromEl.isEqualNode(toEl) && !(isIgnored && isEqualObj(fromEl.dataset, toEl.dataset))){\n hook.__beforeUpdate()\n return hook\n }\n }\n\n performPatch(patch, pruneCids){\n let destroyedCIDs = []\n let phxChildrenAdded = false\n let updatedHookIds = new Set()\n\n patch.after(\"added\", el => {\n this.liveSocket.triggerDOM(\"onNodeAdded\", [el])\n\n let newHook = this.addHook(el)\n if(newHook){ newHook.__mounted() }\n })\n\n patch.after(\"phxChildAdded\", _el => phxChildrenAdded = true)\n\n patch.before(\"updated\", (fromEl, toEl) => {\n let hook = this.triggerBeforeUpdateHook(fromEl, toEl)\n if(hook){ updatedHookIds.add(fromEl.id) }\n })\n\n patch.after(\"updated\", el => {\n if(updatedHookIds.has(el.id)){ this.getHook(el).__updated() }\n })\n\n patch.after(\"discarded\", (el) => {\n let cid = this.componentID(el)\n if(isCid(cid) && destroyedCIDs.indexOf(cid) === -1){ destroyedCIDs.push(cid) }\n let hook = this.getHook(el)\n hook && this.destroyHook(hook)\n })\n\n patch.perform()\n\n // We should not pruneCids on joins. Otherwise, in case of\n // rejoins, we may notify cids that no longer belong to the\n // current LiveView to be removed.\n if(pruneCids){\n this.maybePushComponentsDestroyed(destroyedCIDs)\n }\n\n return phxChildrenAdded\n }\n\n joinNewChildren(){\n DOM.findPhxChildren(this.el, this.id).forEach(el => this.joinChild(el))\n }\n\n getChildById(id){ return this.root.children[this.id][id] }\n\n getDescendentByEl(el){\n if(el.id === this.id){\n return this\n } else {\n return this.children[el.getAttribute(PHX_PARENT_ID)][el.id]\n }\n }\n\n destroyDescendent(id){\n for(let parentId in this.root.children){\n for(let childId in this.root.children[parentId]){\n if(childId === id){ return this.root.children[parentId][childId].destroy() }\n }\n }\n }\n\n joinChild(el){\n let child = this.getChildById(el.id)\n if(!child){\n let view = new View(el, this.liveSocket, this)\n this.root.children[this.id][view.id] = view\n view.join()\n this.childJoins++\n return true\n }\n }\n\n isJoinPending(){ return this.joinPending }\n\n ackJoin(_child){\n this.childJoins--\n\n if(this.childJoins === 0){\n if(this.parent){\n this.parent.ackJoin(this)\n } else {\n this.onAllChildJoinsComplete()\n }\n }\n }\n\n onAllChildJoinsComplete(){\n this.joinCallback()\n this.pendingJoinOps.forEach(([view, op]) => {\n if(!view.isDestroyed()){ op() }\n })\n this.pendingJoinOps = []\n }\n\n update(diff, events){\n if(this.isJoinPending() || this.liveSocket.hasPendingLink()){\n return this.pendingDiffs.push({diff, events})\n }\n\n this.rendered.mergeDiff(diff)\n let phxChildrenAdded = false\n\n // When the diff only contains component diffs, then walk components\n // and patch only the parent component containers found in the diff.\n // Otherwise, patch entire LV container.\n if(this.rendered.isComponentOnlyDiff(diff)){\n this.liveSocket.time(\"component patch complete\", () => {\n let parentCids = DOM.findParentCIDs(this.el, this.rendered.componentCIDs(diff))\n parentCids.forEach(parentCID => {\n if(this.componentPatch(this.rendered.getComponent(diff, parentCID), parentCID)){ phxChildrenAdded = true }\n })\n })\n } else if(!isEmpty(diff)){\n this.liveSocket.time(\"full patch complete\", () => {\n let html = this.renderContainer(diff, \"update\")\n let patch = new DOMPatch(this, this.el, this.id, html, null)\n phxChildrenAdded = this.performPatch(patch, true)\n })\n }\n\n this.dispatchEvents(events)\n if(phxChildrenAdded){ this.joinNewChildren() }\n }\n\n renderContainer(diff, kind){\n return this.liveSocket.time(`toString diff (${kind})`, () => {\n let tag = this.el.tagName\n // Don't skip any component in the diff nor any marked as pruned\n // (as they may have been added back)\n let cids = diff ? this.rendered.componentCIDs(diff).concat(this.pruningCIDs) : null\n let html = this.rendered.toString(cids)\n return `<${tag}>${html}${tag}>`\n })\n }\n\n componentPatch(diff, cid){\n if(isEmpty(diff)) return false\n let html = this.rendered.componentToString(cid)\n let patch = new DOMPatch(this, this.el, this.id, html, cid)\n let childrenAdded = this.performPatch(patch, true)\n return childrenAdded\n }\n\n getHook(el){ return this.viewHooks[ViewHook.elementID(el)] }\n\n addHook(el){\n if(ViewHook.elementID(el) || !el.getAttribute){ return }\n let hookName = el.getAttribute(`data-phx-${PHX_HOOK}`) || el.getAttribute(this.binding(PHX_HOOK))\n if(hookName && !this.ownsElement(el)){ return }\n let callbacks = this.liveSocket.getHookCallbacks(hookName)\n\n if(callbacks){\n if(!el.id){ logError(`no DOM ID for hook \"${hookName}\". Hooks require a unique ID on each element.`, el) }\n let hook = new ViewHook(this, el, callbacks)\n this.viewHooks[ViewHook.elementID(hook.el)] = hook\n return hook\n } else if(hookName !== null){\n logError(`unknown hook found for \"${hookName}\"`, el)\n }\n }\n\n destroyHook(hook){\n hook.__destroyed()\n hook.__cleanup__()\n delete this.viewHooks[ViewHook.elementID(hook.el)]\n }\n\n applyPendingUpdates(){\n this.pendingDiffs.forEach(({diff, events}) => this.update(diff, events))\n this.pendingDiffs = []\n }\n\n onChannel(event, cb){\n this.liveSocket.onChannel(this.channel, event, resp => {\n if(this.isJoinPending()){\n this.root.pendingJoinOps.push([this, () => cb(resp)])\n } else {\n cb(resp)\n }\n })\n }\n\n bindChannel(){\n // The diff event should be handled by the regular update operations.\n // All other operations are queued to be applied only after join.\n this.liveSocket.onChannel(this.channel, \"diff\", (rawDiff) => {\n this.applyDiff(\"update\", rawDiff, ({diff, events}) => this.update(diff, events))\n })\n this.onChannel(\"redirect\", ({to, flash}) => this.onRedirect({to, flash}))\n this.onChannel(\"live_patch\", (redir) => this.onLivePatch(redir))\n this.onChannel(\"live_redirect\", (redir) => this.onLiveRedirect(redir))\n this.channel.onError(reason => this.onError(reason))\n this.channel.onClose(reason => this.onClose(reason))\n }\n\n destroyAllChildren(){\n for(let id in this.root.children[this.id]){\n this.getChildById(id).destroy()\n }\n }\n\n onLiveRedirect(redir){\n let {to, kind, flash} = redir\n let url = this.expandURL(to)\n this.liveSocket.historyRedirect(url, kind, flash)\n }\n\n onLivePatch(redir){\n let {to, kind} = redir\n this.href = this.expandURL(to)\n this.liveSocket.historyPatch(to, kind)\n }\n\n expandURL(to){\n return to.startsWith(\"/\") ? `${window.location.protocol}//${window.location.host}${to}` : to\n }\n\n onRedirect({to, flash}){ this.liveSocket.redirect(to, flash) }\n\n isDestroyed(){ return this.destroyed }\n\n join(callback){\n if(!this.parent){\n this.stopCallback = this.liveSocket.withPageLoading({to: this.href, kind: \"initial\"})\n }\n this.joinCallback = () => callback && callback(this.joinCount)\n this.liveSocket.wrapPush(this, {timeout: false}, () => {\n return this.channel.join()\n .receive(\"ok\", data => !this.isDestroyed() && this.onJoin(data))\n .receive(\"error\", resp => !this.isDestroyed() && this.onJoinError(resp))\n .receive(\"timeout\", () => !this.isDestroyed() && this.onJoinError({reason: \"timeout\"}))\n })\n }\n\n onJoinError(resp){\n if(resp.reason === \"unauthorized\" || resp.reason === \"stale\"){\n this.log(\"error\", () => [\"unauthorized live_redirect. Falling back to page request\", resp])\n return this.onRedirect({to: this.href})\n }\n if(resp.redirect || resp.live_redirect){\n this.joinPending = false\n this.channel.leave()\n }\n if(resp.redirect){ return this.onRedirect(resp.redirect) }\n if(resp.live_redirect){ return this.onLiveRedirect(resp.live_redirect) }\n this.log(\"error\", () => [\"unable to join\", resp])\n return this.liveSocket.reloadWithJitter(this)\n }\n\n onClose(reason){\n if(this.isDestroyed()){ return }\n if((this.isJoinPending() && document.visibilityState !== \"hidden\") ||\n (this.liveSocket.hasPendingLink() && reason !== \"leave\")){\n\n return this.liveSocket.reloadWithJitter(this)\n }\n this.destroyAllChildren()\n this.liveSocket.dropActiveElement(this)\n // document.activeElement can be null in Internet Explorer 11\n if(document.activeElement){ document.activeElement.blur() }\n if(this.liveSocket.isUnloaded()){\n this.showLoader(BEFORE_UNLOAD_LOADER_TIMEOUT)\n }\n }\n\n onError(reason){\n this.onClose(reason)\n this.log(\"error\", () => [\"view crashed\", reason])\n if(!this.liveSocket.isUnloaded()){ this.displayError() }\n }\n\n displayError(){\n if(this.isMain()){ DOM.dispatchEvent(window, \"phx:page-loading-start\", {to: this.href, kind: \"error\"}) }\n this.showLoader()\n this.setContainerClasses(PHX_DISCONNECTED_CLASS, PHX_ERROR_CLASS)\n }\n\n pushWithReply(refGenerator, event, payload, onReply = function (){ }){\n if(!this.isConnected()){ return }\n\n let [ref, [el]] = refGenerator ? refGenerator() : [null, []]\n let onLoadingDone = function (){ }\n if(el && (el.getAttribute(this.binding(PHX_PAGE_LOADING)) !== null)){\n onLoadingDone = this.liveSocket.withPageLoading({kind: \"element\", target: el})\n }\n\n if(typeof (payload.cid) !== \"number\"){ delete payload.cid }\n return (\n this.liveSocket.wrapPush(this, {timeout: true}, () => {\n return this.channel.push(event, payload, PUSH_TIMEOUT).receive(\"ok\", resp => {\n let hookReply = null\n if(ref !== null){ this.undoRefs(ref) }\n if(resp.diff){\n hookReply = this.applyDiff(\"update\", resp.diff, ({diff, events}) => {\n this.update(diff, events)\n })\n }\n if(resp.redirect){ this.onRedirect(resp.redirect) }\n if(resp.live_patch){ this.onLivePatch(resp.live_patch) }\n if(resp.live_redirect){ this.onLiveRedirect(resp.live_redirect) }\n onLoadingDone()\n onReply(resp, hookReply)\n })\n })\n )\n }\n\n undoRefs(ref){\n DOM.all(this.el, `[${PHX_REF}=\"${ref}\"]`, el => {\n let disabledVal = el.getAttribute(PHX_DISABLED)\n // remove refs\n el.removeAttribute(PHX_REF)\n // restore inputs\n if(el.getAttribute(PHX_READONLY) !== null){\n el.readOnly = false\n el.removeAttribute(PHX_READONLY)\n }\n if(disabledVal !== null){\n el.disabled = disabledVal === \"true\" ? true : false\n el.removeAttribute(PHX_DISABLED)\n }\n // remove classes\n PHX_EVENT_CLASSES.forEach(className => DOM.removeClass(el, className))\n // restore disables\n let disableRestore = el.getAttribute(PHX_DISABLE_WITH_RESTORE)\n if(disableRestore !== null){\n el.innerText = disableRestore\n el.removeAttribute(PHX_DISABLE_WITH_RESTORE)\n }\n let toEl = DOM.private(el, PHX_REF)\n if(toEl){\n let hook = this.triggerBeforeUpdateHook(el, toEl)\n DOMPatch.patchEl(el, toEl, this.liveSocket.getActiveElement())\n if(hook){ hook.__updated() }\n DOM.deletePrivate(el, PHX_REF)\n }\n })\n }\n\n putRef(elements, event){\n let newRef = this.ref++\n let disableWith = this.binding(PHX_DISABLE_WITH)\n\n elements.forEach(el => {\n el.classList.add(`phx-${event}-loading`)\n el.setAttribute(PHX_REF, newRef)\n let disableText = el.getAttribute(disableWith)\n if(disableText !== null){\n if(!el.getAttribute(PHX_DISABLE_WITH_RESTORE)){\n el.setAttribute(PHX_DISABLE_WITH_RESTORE, el.innerText)\n }\n el.innerText = disableText\n }\n })\n return [newRef, elements]\n }\n\n componentID(el){\n let cid = el.getAttribute && el.getAttribute(PHX_COMPONENT)\n return cid ? parseInt(cid) : null\n }\n\n targetComponentID(target, targetCtx){\n if(target.getAttribute(this.binding(\"target\"))){\n return this.closestComponentID(targetCtx)\n } else {\n return null\n }\n }\n\n closestComponentID(targetCtx){\n if(targetCtx){\n return maybe(targetCtx.closest(`[${PHX_COMPONENT}]`), el => this.ownsElement(el) && this.componentID(el))\n } else {\n return null\n }\n }\n\n pushHookEvent(targetCtx, event, payload, onReply){\n if(!this.isConnected()){\n this.log(\"hook\", () => [\"unable to push hook event. LiveView not connected\", event, payload])\n return false\n }\n let [ref, els] = this.putRef([], \"hook\")\n this.pushWithReply(() => [ref, els], \"event\", {\n type: \"hook\",\n event: event,\n value: payload,\n cid: this.closestComponentID(targetCtx)\n }, (resp, reply) => onReply(reply, ref))\n\n return ref\n }\n\n extractMeta(el, meta){\n let prefix = this.binding(\"value-\")\n for(let i = 0; i < el.attributes.length; i++){\n let name = el.attributes[i].name\n if(name.startsWith(prefix)){ meta[name.replace(prefix, \"\")] = el.getAttribute(name) }\n }\n if(el.value !== undefined){\n meta.value = el.value\n\n if(el.tagName === \"INPUT\" && CHECKABLE_INPUTS.indexOf(el.type) >= 0 && !el.checked){\n delete meta.value\n }\n }\n return meta\n }\n\n pushEvent(type, el, targetCtx, phxEvent, meta){\n this.pushWithReply(() => this.putRef([el], type), \"event\", {\n type: type,\n event: phxEvent,\n value: this.extractMeta(el, meta),\n cid: this.targetComponentID(el, targetCtx)\n })\n }\n\n pushKey(keyElement, targetCtx, kind, phxEvent, meta){\n this.pushWithReply(() => this.putRef([keyElement], kind), \"event\", {\n type: kind,\n event: phxEvent,\n value: this.extractMeta(keyElement, meta),\n cid: this.targetComponentID(keyElement, targetCtx)\n })\n }\n\n pushFileProgress(fileEl, entryRef, progress, onReply = function (){ }){\n this.liveSocket.withinOwners(fileEl.form, (view, targetCtx) => {\n view.pushWithReply(null, \"progress\", {\n event: fileEl.getAttribute(view.binding(PHX_PROGRESS)),\n ref: fileEl.getAttribute(PHX_UPLOAD_REF),\n entry_ref: entryRef,\n progress: progress,\n cid: view.targetComponentID(fileEl.form, targetCtx)\n }, onReply)\n })\n }\n\n pushInput(inputEl, targetCtx, forceCid, phxEvent, eventTarget, callback){\n let uploads\n let cid = isCid(forceCid) ? forceCid : this.targetComponentID(inputEl.form, targetCtx)\n let refGenerator = () => this.putRef([inputEl, inputEl.form], \"change\")\n let formData = serializeForm(inputEl.form, {_target: eventTarget.name})\n if(DOM.isUploadInput(inputEl) && inputEl.files && inputEl.files.length > 0){\n LiveUploader.trackFiles(inputEl, Array.from(inputEl.files))\n }\n uploads = LiveUploader.serializeUploads(inputEl)\n let event = {\n type: \"form\",\n event: phxEvent,\n value: formData,\n uploads: uploads,\n cid: cid\n }\n this.pushWithReply(refGenerator, \"event\", event, resp => {\n DOM.showError(inputEl, this.liveSocket.binding(PHX_FEEDBACK_FOR))\n if(DOM.isUploadInput(inputEl) && inputEl.getAttribute(\"data-phx-auto-upload\") !== null){\n if(LiveUploader.filesAwaitingPreflight(inputEl).length > 0){\n let [ref, _els] = refGenerator()\n this.uploadFiles(inputEl.form, targetCtx, ref, cid, (_uploads) => {\n callback && callback(resp)\n this.triggerAwaitingSubmit(inputEl.form)\n })\n }\n } else {\n callback && callback(resp)\n }\n })\n }\n\n triggerAwaitingSubmit(formEl){\n let awaitingSubmit = this.getScheduledSubmit(formEl)\n if(awaitingSubmit){\n let [_el, _ref, callback] = awaitingSubmit\n this.cancelSubmit(formEl)\n callback()\n }\n }\n\n getScheduledSubmit(formEl){\n return this.formSubmits.find(([el, _callback]) => el.isSameNode(formEl))\n }\n\n scheduleSubmit(formEl, ref, callback){\n if(this.getScheduledSubmit(formEl)){ return true }\n this.formSubmits.push([formEl, ref, callback])\n }\n\n cancelSubmit(formEl){\n this.formSubmits = this.formSubmits.filter(([el, ref, _callback]) => {\n if(el.isSameNode(formEl)){\n this.undoRefs(ref)\n return false\n } else {\n return true\n }\n })\n }\n\n pushFormSubmit(formEl, targetCtx, phxEvent, onReply){\n let filterIgnored = el => {\n let userIgnored = closestPhxBinding(el, `${this.binding(PHX_UPDATE)}=ignore`, el.form)\n return !(userIgnored || closestPhxBinding(el, \"data-phx-update=ignore\", el.form))\n }\n let filterDisables = el => {\n return el.hasAttribute(this.binding(PHX_DISABLE_WITH))\n }\n let filterButton = el => el.tagName == \"BUTTON\"\n\n let filterInput = el => [\"INPUT\", \"TEXTAREA\", \"SELECT\"].includes(el.tagName)\n\n let refGenerator = () => {\n let formElements = Array.from(formEl.elements)\n let disables = formElements.filter(filterDisables)\n let buttons = formElements.filter(filterButton).filter(filterIgnored)\n let inputs = formElements.filter(filterInput).filter(filterIgnored)\n\n buttons.forEach(button => {\n button.setAttribute(PHX_DISABLED, button.disabled)\n button.disabled = true\n })\n inputs.forEach(input => {\n input.setAttribute(PHX_READONLY, input.readOnly)\n input.readOnly = true\n if(input.files){\n input.setAttribute(PHX_DISABLED, input.disabled)\n input.disabled = true\n }\n })\n formEl.setAttribute(this.binding(PHX_PAGE_LOADING), \"\")\n return this.putRef([formEl].concat(disables).concat(buttons).concat(inputs), \"submit\")\n }\n\n let cid = this.targetComponentID(formEl, targetCtx)\n if(LiveUploader.hasUploadsInProgress(formEl)){\n let [ref, _els] = refGenerator()\n return this.scheduleSubmit(formEl, ref, () => this.pushFormSubmit(formEl, targetCtx, phxEvent, onReply))\n } else if(LiveUploader.inputsAwaitingPreflight(formEl).length > 0){\n let [ref, els] = refGenerator()\n let proxyRefGen = () => [ref, els]\n this.uploadFiles(formEl, targetCtx, ref, cid, (_uploads) => {\n let formData = serializeForm(formEl, {})\n this.pushWithReply(proxyRefGen, \"event\", {\n type: \"form\",\n event: phxEvent,\n value: formData,\n cid: cid\n }, onReply)\n })\n } else {\n let formData = serializeForm(formEl)\n this.pushWithReply(refGenerator, \"event\", {\n type: \"form\",\n event: phxEvent,\n value: formData,\n cid: cid\n }, onReply)\n }\n }\n\n uploadFiles(formEl, targetCtx, ref, cid, onComplete){\n let joinCountAtUpload = this.joinCount\n let inputEls = LiveUploader.activeFileInputs(formEl)\n let numFileInputsInProgress = inputEls.length\n\n // get each file input\n inputEls.forEach(inputEl => {\n let uploader = new LiveUploader(inputEl, this, () => {\n numFileInputsInProgress--\n if(numFileInputsInProgress === 0){ onComplete() }\n });\n\n this.uploaders[inputEl] = uploader\n let entries = uploader.entries().map(entry => entry.toPreflightPayload())\n\n let payload = {\n ref: inputEl.getAttribute(PHX_UPLOAD_REF),\n entries: entries,\n cid: this.targetComponentID(inputEl.form, targetCtx)\n }\n\n this.log(\"upload\", () => [\"sending preflight request\", payload])\n\n this.pushWithReply(null, \"allow_upload\", payload, resp => {\n this.log(\"upload\", () => [\"got preflight response\", resp])\n if(resp.error){\n this.undoRefs(ref)\n let [entry_ref, reason] = resp.error\n this.log(\"upload\", () => [`error for entry ${entry_ref}`, reason])\n } else {\n let onError = (callback) => {\n this.channel.onError(() => {\n if(this.joinCount === joinCountAtUpload){ callback() }\n })\n }\n uploader.initAdapterUpload(resp, onError, this.liveSocket)\n }\n })\n })\n }\n\n dispatchUploads(name, filesOrBlobs){\n let inputs = DOM.findUploadInputs(this.el).filter(el => el.name === name)\n if(inputs.length === 0){ logError(`no live file inputs found matching the name \"${name}\"`) }\n else if(inputs.length > 1){ logError(`duplicate live file inputs found matching the name \"${name}\"`) }\n else { DOM.dispatchEvent(inputs[0], PHX_TRACK_UPLOADS, {files: filesOrBlobs}) }\n }\n\n pushFormRecovery(form, newCid, callback){\n this.liveSocket.withinOwners(form, (view, targetCtx) => {\n let input = form.elements[0]\n let phxEvent = form.getAttribute(this.binding(PHX_AUTO_RECOVER)) || form.getAttribute(this.binding(\"change\"))\n view.pushInput(input, targetCtx, newCid, phxEvent, input, callback)\n })\n }\n\n pushLinkPatch(href, targetEl, callback){\n let linkRef = this.liveSocket.setPendingLink(href)\n let refGen = targetEl ? () => this.putRef([targetEl], \"click\") : null\n\n this.pushWithReply(refGen, \"live_patch\", {url: href}, resp => {\n if(resp.link_redirect){\n this.liveSocket.replaceMain(href, null, callback, linkRef)\n } else {\n if(this.liveSocket.commitPendingLink(linkRef)){\n this.href = href\n }\n this.applyPendingUpdates()\n callback && callback(linkRef)\n }\n }).receive(\"timeout\", () => this.liveSocket.redirect(window.location.href))\n }\n\n formsForRecovery(html){\n if(this.joinCount === 0){ return [] }\n\n let phxChange = this.binding(\"change\")\n let template = document.createElement(\"template\")\n template.innerHTML = html\n\n return (\n DOM.all(this.el, `form[${phxChange}]`)\n .filter(form => form.id && this.ownsElement(form))\n .filter(form => form.elements.length > 0)\n .filter(form => form.getAttribute(this.binding(PHX_AUTO_RECOVER)) !== \"ignore\")\n .map(form => {\n let newForm = template.content.querySelector(`form[id=\"${form.id}\"][${phxChange}=\"${form.getAttribute(phxChange)}\"]`)\n if(newForm){\n return [form, newForm, this.componentID(newForm)]\n } else {\n return [form, null, null]\n }\n })\n .filter(([form, newForm, newCid]) => newForm)\n )\n }\n\n maybePushComponentsDestroyed(destroyedCIDs){\n let willDestroyCIDs = destroyedCIDs.filter(cid => {\n return DOM.findComponentNodeList(this.el, cid).length === 0\n })\n if(willDestroyCIDs.length > 0){\n this.pruningCIDs.push(...willDestroyCIDs)\n\n this.pushWithReply(null, \"cids_will_destroy\", {cids: willDestroyCIDs}, () => {\n // The cids are either back on the page or they will be fully removed,\n // so we can remove them from the pruningCIDs.\n this.pruningCIDs = this.pruningCIDs.filter(cid => willDestroyCIDs.indexOf(cid) !== -1)\n\n // See if any of the cids we wanted to destroy were added back,\n // if they were added back, we don't actually destroy them.\n let completelyDestroyCIDs = willDestroyCIDs.filter(cid => {\n return DOM.findComponentNodeList(this.el, cid).length === 0\n })\n\n if(completelyDestroyCIDs.length > 0){\n this.pushWithReply(null, \"cids_destroyed\", {cids: completelyDestroyCIDs}, (resp) => {\n this.rendered.pruneCIDs(resp.cids)\n })\n }\n })\n }\n }\n\n ownsElement(el){\n return el.getAttribute(PHX_PARENT_ID) === this.id ||\n maybe(el.closest(PHX_VIEW_SELECTOR), node => node.id) === this.id\n }\n\n submitForm(form, targetCtx, phxEvent){\n DOM.putPrivate(form, PHX_HAS_SUBMITTED, true)\n this.liveSocket.blurActiveElement(this)\n this.pushFormSubmit(form, targetCtx, phxEvent, () => {\n this.liveSocket.restorePreviouslyActiveFocus()\n })\n }\n\n binding(kind){ return this.liveSocket.binding(kind) }\n}\n", "/** Initializes the LiveSocket\n *\n *\n * @param {string} endPoint - The string WebSocket endpoint, ie, `\"wss://example.com/live\"`,\n * `\"/live\"` (inherited host & protocol)\n * @param {Phoenix.Socket} socket - the required Phoenix Socket class imported from \"phoenix\". For example:\n *\n * import {Socket} from \"phoenix\"\n * import {LiveSocket} from \"phoenix_live_view\"\n * let liveSocket = new LiveSocket(\"/live\", Socket, {...})\n *\n * @param {Object} [opts] - Optional configuration. Outside of keys listed below, all\n * configuration is passed directly to the Phoenix Socket constructor.\n * @param {Object} [opts.defaults] - The optional defaults to use for various bindings,\n * such as `phx-debounce`. Supports the following keys:\n *\n * - debounce - the millisecond phx-debounce time. Defaults 300\n * - throttle - the millisecond phx-throttle time. Defaults 300\n *\n * @param {Function} [opts.params] - The optional function for passing connect params.\n * The function receives the element associated with a given LiveView. For example:\n *\n * (el) => {view: el.getAttribute(\"data-my-view-name\", token: window.myToken}\n *\n * @param {string} [opts.bindingPrefix] - The optional prefix to use for all phx DOM annotations.\n * Defaults to \"phx-\".\n * @param {Object} [opts.hooks] - The optional object for referencing LiveView hook callbacks.\n * @param {Object} [opts.uploaders] - The optional object for referencing LiveView uploader callbacks.\n * @param {integer} [opts.loaderTimeout] - The optional delay in milliseconds to wait before apply\n * loading states.\n * @param {Function} [opts.viewLogger] - The optional function to log debug information. For example:\n *\n * (view, kind, msg, obj) => console.log(`${view.id} ${kind}: ${msg} - `, obj)\n *\n * @param {Object} [opts.metadata] - The optional object mapping event names to functions for\n * populating event metadata. For example:\n *\n * metadata: {\n * click: (e, el) => {\n * return {\n * ctrlKey: e.ctrlKey,\n * metaKey: e.metaKey,\n * detail: e.detail || 1,\n * }\n * },\n * keydown: (e, el) => {\n * return {\n * key: e.key,\n * ctrlKey: e.ctrlKey,\n * metaKey: e.metaKey,\n * shiftKey: e.shiftKey\n * }\n * }\n * }\n * @param {Object} [opts.sessionStorage] - An optional Storage compatible object\n * Useful when LiveView won't have access to `sessionStorage`. For example, This could\n * happen if a site loads a cross-domain LiveView in an iframe. Example usage:\n *\n * class InMemoryStorage {\n * constructor() { this.storage = {} }\n * getItem(keyName) { return this.storage[keyName] }\n * removeItem(keyName) { delete this.storage[keyName] }\n * setItem(keyName, keyValue) { this.storage[keyName] = keyValue }\n * }\n *\n * @param {Object} [opts.localStorage] - An optional Storage compatible object\n * Useful for when LiveView won't have access to `localStorage`.\n * See `opts.sessionStorage` for examples.\n*/\n\nimport {\n BINDING_PREFIX,\n CONSECUTIVE_RELOADS,\n DEFAULTS,\n FAILSAFE_JITTER,\n LOADER_TIMEOUT,\n MAX_RELOADS,\n PHX_DEBOUNCE,\n PHX_DROP_TARGET,\n PHX_HAS_FOCUSED,\n PHX_KEY,\n PHX_LINK_STATE,\n PHX_LIVE_LINK,\n PHX_LV_DEBUG,\n PHX_LV_LATENCY_SIM,\n PHX_LV_PROFILE,\n PHX_MAIN,\n PHX_PARENT_ID,\n PHX_VIEW_SELECTOR,\n PHX_ROOT_ID,\n PHX_THROTTLE,\n PHX_TRACK_UPLOADS,\n RELOAD_JITTER\n\n} from \"./constants\"\n\nimport {\n clone,\n closestPhxBinding,\n closure,\n debug,\n maybe\n} from \"./utils\"\n\nimport Browser from \"./browser\"\nimport DOM from \"./dom\"\nimport Hooks from \"./hooks\"\nimport LiveUploader from \"./live_uploader\"\nimport View from \"./view\"\nimport {PHX_SESSION} from \"./constants\"\n\nexport default class LiveSocket {\n constructor(url, phxSocket, opts = {}){\n this.unloaded = false\n if(!phxSocket || phxSocket.constructor.name === \"Object\"){\n throw new Error(`\n a phoenix Socket must be provided as the second argument to the LiveSocket constructor. For example:\n\n import {Socket} from \"phoenix\"\n import LiveSocket from \"phoenix_live_view\"\n let liveSocket = new LiveSocket(\"/live\", Socket, {...})\n `)\n }\n this.socket = new phxSocket(url, opts)\n this.bindingPrefix = opts.bindingPrefix || BINDING_PREFIX\n this.opts = opts\n this.params = closure(opts.params || {})\n this.viewLogger = opts.viewLogger\n this.metadataCallbacks = opts.metadata || {}\n this.defaults = Object.assign(clone(DEFAULTS), opts.defaults || {})\n this.activeElement = null\n this.prevActive = null\n this.silenced = false\n this.main = null\n this.linkRef = 1\n this.roots = {}\n this.href = window.location.href\n this.pendingLink = null\n this.currentLocation = clone(window.location)\n this.hooks = opts.hooks || {}\n this.uploaders = opts.uploaders || {}\n this.loaderTimeout = opts.loaderTimeout || LOADER_TIMEOUT\n this.localStorage = opts.localStorage || window.localStorage\n this.sessionStorage = opts.sessionStorage || window.sessionStorage\n this.boundTopLevelEvents = false\n this.domCallbacks = Object.assign({onNodeAdded: closure(), onBeforeElUpdated: closure()}, opts.dom || {})\n window.addEventListener(\"pagehide\", _e => {\n this.unloaded = true\n })\n this.socket.onOpen(() => {\n if(this.isUnloaded()){\n // reload page if being restored from back/forward cache and browser does not emit \"pageshow\"\n window.location.reload()\n }\n })\n }\n\n // public\n\n isProfileEnabled(){ return this.sessionStorage.getItem(PHX_LV_PROFILE) === \"true\" }\n\n isDebugEnabled(){ return this.sessionStorage.getItem(PHX_LV_DEBUG) === \"true\" }\n\n enableDebug(){ this.sessionStorage.setItem(PHX_LV_DEBUG, \"true\") }\n\n enableProfiling(){ this.sessionStorage.setItem(PHX_LV_PROFILE, \"true\") }\n\n disableDebug(){ this.sessionStorage.removeItem(PHX_LV_DEBUG) }\n\n disableProfiling(){ this.sessionStorage.removeItem(PHX_LV_PROFILE) }\n\n enableLatencySim(upperBoundMs){\n this.enableDebug()\n console.log(\"latency simulator enabled for the duration of this browser session. Call disableLatencySim() to disable\")\n this.sessionStorage.setItem(PHX_LV_LATENCY_SIM, upperBoundMs)\n }\n\n disableLatencySim(){ this.sessionStorage.removeItem(PHX_LV_LATENCY_SIM) }\n\n getLatencySim(){\n let str = this.sessionStorage.getItem(PHX_LV_LATENCY_SIM)\n return str ? parseInt(str) : null\n }\n\n getSocket(){ return this.socket }\n\n connect(){\n let doConnect = () => {\n if(this.joinRootViews()){\n this.bindTopLevelEvents()\n this.socket.connect()\n }\n }\n if([\"complete\", \"loaded\", \"interactive\"].indexOf(document.readyState) >= 0){\n doConnect()\n } else {\n document.addEventListener(\"DOMContentLoaded\", () => doConnect())\n }\n }\n\n disconnect(callback){ this.socket.disconnect(callback) }\n\n // private\n\n triggerDOM(kind, args){ this.domCallbacks[kind](...args) }\n\n time(name, func){\n if(!this.isProfileEnabled() || !console.time){ return func() }\n console.time(name)\n let result = func()\n console.timeEnd(name)\n return result\n }\n\n log(view, kind, msgCallback){\n if(this.viewLogger){\n let [msg, obj] = msgCallback()\n this.viewLogger(view, kind, msg, obj)\n } else if(this.isDebugEnabled()){\n let [msg, obj] = msgCallback()\n debug(view, kind, msg, obj)\n }\n }\n\n onChannel(channel, event, cb){\n channel.on(event, data => {\n let latency = this.getLatencySim()\n if(!latency){\n cb(data)\n } else {\n console.log(`simulating ${latency}ms of latency from server to client`)\n setTimeout(() => cb(data), latency)\n }\n })\n }\n\n wrapPush(view, opts, push){\n let latency = this.getLatencySim()\n let oldJoinCount = view.joinCount\n if(!latency){\n if(opts.timeout){\n return push().receive(\"timeout\", () => {\n if(view.joinCount === oldJoinCount && !view.isDestroyed()){\n this.reloadWithJitter(view, () => {\n this.log(view, \"timeout\", () => [\"received timeout while communicating with server. Falling back to hard refresh for recovery\"])\n })\n }\n })\n } else {\n return push()\n }\n }\n\n console.log(`simulating ${latency}ms of latency from client to server`)\n let fakePush = {\n receives: [],\n receive(kind, cb){ this.receives.push([kind, cb]) }\n }\n setTimeout(() => {\n if(view.isDestroyed()){ return }\n fakePush.receives.reduce((acc, [kind, cb]) => acc.receive(kind, cb), push())\n }, latency)\n return fakePush\n }\n\n reloadWithJitter(view, log){\n view.destroy()\n this.disconnect()\n let [minMs, maxMs] = RELOAD_JITTER\n let afterMs = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs\n let tries = Browser.updateLocal(this.localStorage, window.location.pathname, CONSECUTIVE_RELOADS, 0, count => count + 1)\n log ? log() : this.log(view, \"join\", () => [`encountered ${tries} consecutive reloads`])\n if(tries > MAX_RELOADS){\n this.log(view, \"join\", () => [`exceeded ${MAX_RELOADS} consecutive reloads. Entering failsafe mode`])\n afterMs = FAILSAFE_JITTER\n }\n setTimeout(() => {\n if(this.hasPendingLink()){\n window.location = this.pendingLink\n } else {\n window.location.reload()\n }\n }, afterMs)\n }\n\n getHookCallbacks(name){\n return name && name.startsWith(\"Phoenix.\") ? Hooks[name.split(\".\")[1]] : this.hooks[name]\n }\n\n isUnloaded(){ return this.unloaded }\n\n isConnected(){ return this.socket.isConnected() }\n\n getBindingPrefix(){ return this.bindingPrefix }\n\n binding(kind){ return `${this.getBindingPrefix()}${kind}` }\n\n channel(topic, params){ return this.socket.channel(topic, params) }\n\n joinRootViews(){\n let rootsFound = false\n DOM.all(document, `${PHX_VIEW_SELECTOR}:not([${PHX_PARENT_ID}])`, rootEl => {\n if(!this.getRootById(rootEl.id)){\n let view = this.newRootView(rootEl)\n view.setHref(this.getHref())\n view.join()\n if(rootEl.getAttribute(PHX_MAIN)){ this.main = view }\n }\n rootsFound = true\n })\n return rootsFound\n }\n\n redirect(to, flash){\n this.disconnect()\n Browser.redirect(to, flash)\n }\n\n replaceMain(href, flash, callback = null, linkRef = this.setPendingLink(href)){\n let oldMainEl = this.main.el\n let newMainEl = DOM.cloneNode(oldMainEl, \"\")\n this.main.showLoader(this.loaderTimeout)\n this.main.destroy()\n\n this.main = this.newRootView(newMainEl, flash)\n this.main.setRedirect(href)\n this.main.join(joinCount => {\n if(joinCount === 1 && this.commitPendingLink(linkRef)){\n oldMainEl.replaceWith(newMainEl)\n callback && callback()\n }\n })\n }\n\n isPhxView(el){ return el.getAttribute && el.getAttribute(PHX_SESSION) !== null }\n\n newRootView(el, flash){\n let view = new View(el, this, null, flash)\n this.roots[view.id] = view\n return view\n }\n\n owner(childEl, callback){\n let view = maybe(childEl.closest(PHX_VIEW_SELECTOR), el => this.getViewByEl(el))\n if(view){ callback(view) }\n }\n\n withinOwners(childEl, callback){\n this.owner(childEl, view => {\n let phxTarget = childEl.getAttribute(this.binding(\"target\"))\n if(phxTarget === null){\n callback(view, childEl)\n } else {\n view.withinTargets(phxTarget, callback)\n }\n })\n }\n\n getViewByEl(el){\n let rootId = el.getAttribute(PHX_ROOT_ID)\n return maybe(this.getRootById(rootId), root => root.getDescendentByEl(el))\n }\n\n getRootById(id){ return this.roots[id] }\n\n destroyAllViews(){\n for(let id in this.roots){\n this.roots[id].destroy()\n delete this.roots[id]\n }\n }\n\n destroyViewByEl(el){\n let root = this.getRootById(el.getAttribute(PHX_ROOT_ID))\n if(root){ root.destroyDescendent(el.id) }\n }\n\n setActiveElement(target){\n if(this.activeElement === target){ return }\n this.activeElement = target\n let cancel = () => {\n if(target === this.activeElement){ this.activeElement = null }\n target.removeEventListener(\"mouseup\", this)\n target.removeEventListener(\"touchend\", this)\n }\n target.addEventListener(\"mouseup\", cancel)\n target.addEventListener(\"touchend\", cancel)\n }\n\n getActiveElement(){\n if(document.activeElement === document.body){\n return this.activeElement || document.activeElement\n } else {\n // document.activeElement can be null in Internet Explorer 11\n return document.activeElement || document.body\n }\n }\n\n dropActiveElement(view){\n if(this.prevActive && view.ownsElement(this.prevActive)){\n this.prevActive = null\n }\n }\n\n restorePreviouslyActiveFocus(){\n if(this.prevActive && this.prevActive !== document.body){\n this.prevActive.focus()\n }\n }\n\n blurActiveElement(){\n this.prevActive = this.getActiveElement()\n if(this.prevActive !== document.body){ this.prevActive.blur() }\n }\n\n bindTopLevelEvents(){\n if(this.boundTopLevelEvents){ return }\n\n this.boundTopLevelEvents = true\n document.body.addEventListener(\"click\", function (){ }) // ensure all click events bubble for mobile Safari\n window.addEventListener(\"pageshow\", e => {\n if(e.persisted){ // reload page if being restored from back/forward cache\n this.getSocket().disconnect()\n this.withPageLoading({to: window.location.href, kind: \"redirect\"})\n window.location.reload()\n }\n }, true)\n this.bindNav()\n this.bindClicks()\n this.bindForms()\n this.bind({keyup: \"keyup\", keydown: \"keydown\"}, (e, type, view, target, targetCtx, phxEvent, _phxTarget) => {\n let matchKey = target.getAttribute(this.binding(PHX_KEY))\n let pressedKey = e.key && e.key.toLowerCase() // chrome clicked autocompletes send a keydown without key\n if(matchKey && matchKey.toLowerCase() !== pressedKey){ return }\n\n view.pushKey(target, targetCtx, type, phxEvent, {key: e.key, ...this.eventMeta(type, e, target)})\n })\n this.bind({blur: \"focusout\", focus: \"focusin\"}, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {\n if(!phxTarget){\n view.pushEvent(type, targetEl, targetCtx, phxEvent, this.eventMeta(type, e, targetEl))\n }\n })\n this.bind({blur: \"blur\", focus: \"focus\"}, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {\n // blur and focus are triggered on document and window. Discard one to avoid dups\n if(phxTarget && !phxTarget !== \"window\"){\n view.pushEvent(type, targetEl, targetCtx, phxEvent, this.eventMeta(type, e, targetEl))\n }\n })\n window.addEventListener(\"dragover\", e => e.preventDefault())\n window.addEventListener(\"drop\", e => {\n e.preventDefault()\n let dropTargetId = maybe(closestPhxBinding(e.target, this.binding(PHX_DROP_TARGET)), trueTarget => {\n return trueTarget.getAttribute(this.binding(PHX_DROP_TARGET))\n })\n let dropTarget = dropTargetId && document.getElementById(dropTargetId)\n let files = Array.from(e.dataTransfer.files || [])\n if(!dropTarget || dropTarget.disabled || files.length === 0 || !(dropTarget.files instanceof FileList)){ return }\n\n LiveUploader.trackFiles(dropTarget, files)\n dropTarget.dispatchEvent(new Event(\"input\", {bubbles: true}))\n })\n this.on(PHX_TRACK_UPLOADS, e => {\n let uploadTarget = e.target\n if(!DOM.isUploadInput(uploadTarget)){ return }\n let files = Array.from(e.detail.files || []).filter(f => f instanceof File || f instanceof Blob)\n LiveUploader.trackFiles(uploadTarget, files)\n uploadTarget.dispatchEvent(new Event(\"input\", {bubbles: true}))\n })\n }\n\n eventMeta(eventName, e, targetEl){\n let callback = this.metadataCallbacks[eventName]\n return callback ? callback(e, targetEl) : {}\n }\n\n setPendingLink(href){\n this.linkRef++\n this.pendingLink = href\n return this.linkRef\n }\n\n commitPendingLink(linkRef){\n if(this.linkRef !== linkRef){\n return false\n } else {\n this.href = this.pendingLink\n this.pendingLink = null\n return true\n }\n }\n\n getHref(){ return this.href }\n\n hasPendingLink(){ return !!this.pendingLink }\n\n bind(events, callback){\n for(let event in events){\n let browserEventName = events[event]\n\n this.on(browserEventName, e => {\n let binding = this.binding(event)\n let windowBinding = this.binding(`window-${event}`)\n let targetPhxEvent = e.target.getAttribute && e.target.getAttribute(binding)\n if(targetPhxEvent){\n this.debounce(e.target, e, () => {\n this.withinOwners(e.target, (view, targetCtx) => {\n callback(e, event, view, e.target, targetCtx, targetPhxEvent, null)\n })\n })\n } else {\n DOM.all(document, `[${windowBinding}]`, el => {\n let phxEvent = el.getAttribute(windowBinding)\n this.debounce(el, e, () => {\n this.withinOwners(el, (view, targetCtx) => {\n callback(e, event, view, el, targetCtx, phxEvent, \"window\")\n })\n })\n })\n }\n })\n }\n }\n\n bindClicks(){\n this.bindClick(\"click\", \"click\", false)\n this.bindClick(\"mousedown\", \"capture-click\", true)\n }\n\n bindClick(eventName, bindingName, capture){\n let click = this.binding(bindingName)\n window.addEventListener(eventName, e => {\n if(!this.isConnected()){ return }\n let target = null\n if(capture){\n target = e.target.matches(`[${click}]`) ? e.target : e.target.querySelector(`[${click}]`)\n } else {\n target = closestPhxBinding(e.target, click)\n }\n let phxEvent = target && target.getAttribute(click)\n if(!phxEvent){ return }\n if(target.getAttribute(\"href\") === \"#\"){ e.preventDefault() }\n\n this.debounce(target, e, () => {\n this.withinOwners(target, (view, targetCtx) => {\n view.pushEvent(\"click\", target, targetCtx, phxEvent, this.eventMeta(\"click\", e, target))\n })\n })\n }, capture)\n }\n\n bindNav(){\n if(!Browser.canPushState()){ return }\n if(history.scrollRestoration){ history.scrollRestoration = \"manual\" }\n let scrollTimer = null\n window.addEventListener(\"scroll\", _e => {\n clearTimeout(scrollTimer)\n scrollTimer = setTimeout(() => {\n Browser.updateCurrentState(state => Object.assign(state, {scroll: window.scrollY}))\n }, 100)\n })\n window.addEventListener(\"popstate\", event => {\n if(!this.registerNewLocation(window.location)){ return }\n let {type, id, root, scroll} = event.state || {}\n let href = window.location.href\n\n if(this.main.isConnected() && (type === \"patch\" && id === this.main.id)){\n this.main.pushLinkPatch(href, null)\n } else {\n this.replaceMain(href, null, () => {\n if(root){ this.replaceRootHistory() }\n if(typeof(scroll) === \"number\"){\n setTimeout(() => {\n window.scrollTo(0, scroll)\n }, 0) // the body needs to render before we scroll.\n }\n })\n }\n }, false)\n window.addEventListener(\"click\", e => {\n let target = closestPhxBinding(e.target, PHX_LIVE_LINK)\n let type = target && target.getAttribute(PHX_LIVE_LINK)\n let wantsNewTab = e.metaKey || e.ctrlKey || e.button === 1\n if(!type || !this.isConnected() || !this.main || wantsNewTab){ return }\n let href = target.href\n let linkState = target.getAttribute(PHX_LINK_STATE)\n e.preventDefault()\n if(this.pendingLink === href){ return }\n\n if(type === \"patch\"){\n this.pushHistoryPatch(href, linkState, target)\n } else if(type === \"redirect\"){\n this.historyRedirect(href, linkState)\n } else {\n throw new Error(`expected ${PHX_LIVE_LINK} to be \"patch\" or \"redirect\", got: ${type}`)\n }\n }, false)\n }\n\n withPageLoading(info, callback){\n DOM.dispatchEvent(window, \"phx:page-loading-start\", info)\n let done = () => DOM.dispatchEvent(window, \"phx:page-loading-stop\", info)\n return callback ? callback(done) : done\n }\n\n pushHistoryPatch(href, linkState, targetEl){\n this.withPageLoading({to: href, kind: \"patch\"}, done => {\n this.main.pushLinkPatch(href, targetEl, linkRef => {\n this.historyPatch(href, linkState, linkRef)\n done()\n })\n })\n }\n\n historyPatch(href, linkState, linkRef = this.setPendingLink(href)){\n if(!this.commitPendingLink(linkRef)){ return }\n\n Browser.pushState(linkState, {type: \"patch\", id: this.main.id}, href)\n this.registerNewLocation(window.location)\n }\n\n historyRedirect(href, linkState, flash){\n let scroll = window.scrollY\n this.withPageLoading({to: href, kind: \"redirect\"}, done => {\n this.replaceMain(href, flash, () => {\n Browser.pushState(linkState, {type: \"redirect\", id: this.main.id, scroll: scroll}, href)\n this.registerNewLocation(window.location)\n done()\n })\n })\n }\n\n replaceRootHistory(){\n Browser.pushState(\"replace\", {root: true, type: \"patch\", id: this.main.id})\n }\n\n registerNewLocation(newLocation){\n let {pathname, search} = this.currentLocation\n if(pathname + search === newLocation.pathname + newLocation.search){\n return false\n } else {\n this.currentLocation = clone(newLocation)\n return true\n }\n }\n\n bindForms(){\n let iterations = 0\n this.on(\"submit\", e => {\n let phxEvent = e.target.getAttribute(this.binding(\"submit\"))\n if(!phxEvent){ return }\n e.preventDefault()\n e.target.disabled = true\n this.withinOwners(e.target, (view, targetCtx) => view.submitForm(e.target, targetCtx, phxEvent))\n }, false)\n\n for(let type of [\"change\", \"input\"]){\n this.on(type, e => {\n let input = e.target\n let phxEvent = input.form && input.form.getAttribute(this.binding(\"change\"))\n if(!phxEvent){ return }\n if(input.type === \"number\" && input.validity && input.validity.badInput){ return }\n let currentIterations = iterations\n iterations++\n let {at: at, type: lastType} = DOM.private(input, \"prev-iteration\") || {}\n // detect dup because some browsers dispatch both \"input\" and \"change\"\n if(at === currentIterations - 1 && type !== lastType){ return }\n\n DOM.putPrivate(input, \"prev-iteration\", {at: currentIterations, type: type})\n\n this.debounce(input, e, () => {\n this.withinOwners(input.form, (view, targetCtx) => {\n DOM.putPrivate(input, PHX_HAS_FOCUSED, true)\n if(!DOM.isTextualInput(input)){\n this.setActiveElement(input)\n }\n view.pushInput(input, targetCtx, null, phxEvent, e.target)\n })\n })\n }, false)\n }\n }\n\n debounce(el, event, callback){\n let phxDebounce = this.binding(PHX_DEBOUNCE)\n let phxThrottle = this.binding(PHX_THROTTLE)\n let defaultDebounce = this.defaults.debounce.toString()\n let defaultThrottle = this.defaults.throttle.toString()\n DOM.debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, callback)\n }\n\n silenceEvents(callback){\n this.silenced = true\n callback()\n this.silenced = false\n }\n\n on(event, callback){\n window.addEventListener(event, e => {\n if(!this.silenced){ callback(e) }\n })\n }\n}\n"],
- "mappings": "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",
+ "sources": ["../../assets/js/phoenix_live_view/index.js", "../../assets/js/phoenix_live_view/constants.js", "../../assets/js/phoenix_live_view/entry_uploader.js", "../../assets/js/phoenix_live_view/utils.js", "../../assets/js/phoenix_live_view/browser.js", "../../assets/js/phoenix_live_view/dom.js", "../../assets/js/phoenix_live_view/upload_entry.js", "../../assets/js/phoenix_live_view/live_uploader.js", "../../assets/js/phoenix_live_view/aria.js", "../../assets/js/phoenix_live_view/hooks.js", "../../assets/js/phoenix_live_view/dom_post_morph_restorer.js", "../../assets/node_modules/morphdom/dist/morphdom-esm.js", "../../assets/js/phoenix_live_view/dom_patch.js", "../../assets/js/phoenix_live_view/rendered.js", "../../assets/js/phoenix_live_view/view_hook.js", "../../assets/js/phoenix_live_view/js.js", "../../assets/js/phoenix_live_view/view.js", "../../assets/js/phoenix_live_view/live_socket.js"],
+ "sourcesContent": ["/*\n================================================================================\nPhoenix LiveView JavaScript Client\n================================================================================\n\nSee the hexdocs at `https://hexdocs.pm/phoenix_live_view` for documentation.\n\n*/\n\nimport LiveSocket from \"./live_socket\"\nexport {\n LiveSocket\n}\n", "export const CONSECUTIVE_RELOADS = \"consecutive-reloads\"\nexport const MAX_RELOADS = 10\nexport const RELOAD_JITTER_MIN = 5000\nexport const RELOAD_JITTER_MAX = 10000\nexport const FAILSAFE_JITTER = 30000\nexport const PHX_EVENT_CLASSES = [\n \"phx-click-loading\", \"phx-change-loading\", \"phx-submit-loading\",\n \"phx-keydown-loading\", \"phx-keyup-loading\", \"phx-blur-loading\", \"phx-focus-loading\"\n]\nexport const PHX_COMPONENT = \"data-phx-component\"\nexport const PHX_LIVE_LINK = \"data-phx-link\"\nexport const PHX_TRACK_STATIC = \"track-static\"\nexport const PHX_LINK_STATE = \"data-phx-link-state\"\nexport const PHX_REF = \"data-phx-ref\"\nexport const PHX_REF_SRC = \"data-phx-ref-src\"\nexport const PHX_TRACK_UPLOADS = \"track-uploads\"\nexport const PHX_UPLOAD_REF = \"data-phx-upload-ref\"\nexport const PHX_PREFLIGHTED_REFS = \"data-phx-preflighted-refs\"\nexport const PHX_DONE_REFS = \"data-phx-done-refs\"\nexport const PHX_DROP_TARGET = \"drop-target\"\nexport const PHX_ACTIVE_ENTRY_REFS = \"data-phx-active-refs\"\nexport const PHX_LIVE_FILE_UPDATED = \"phx:live-file:updated\"\nexport const PHX_SKIP = \"data-phx-skip\"\nexport const PHX_PRUNE = \"data-phx-prune\"\nexport const PHX_PAGE_LOADING = \"page-loading\"\nexport const PHX_CONNECTED_CLASS = \"phx-connected\"\nexport const PHX_DISCONNECTED_CLASS = \"phx-loading\"\nexport const PHX_NO_FEEDBACK_CLASS = \"phx-no-feedback\"\nexport const PHX_ERROR_CLASS = \"phx-error\"\nexport const PHX_PARENT_ID = \"data-phx-parent-id\"\nexport const PHX_MAIN = \"data-phx-main\"\nexport const PHX_ROOT_ID = \"data-phx-root-id\"\nexport const PHX_TRIGGER_ACTION = \"trigger-action\"\nexport const PHX_FEEDBACK_FOR = \"feedback-for\"\nexport const PHX_HAS_FOCUSED = \"phx-has-focused\"\nexport const FOCUSABLE_INPUTS = [\"text\", \"textarea\", \"number\", \"email\", \"password\", \"search\", \"tel\", \"url\", \"date\", \"time\", \"datetime-local\", \"color\", \"range\"]\nexport const CHECKABLE_INPUTS = [\"checkbox\", \"radio\"]\nexport const PHX_HAS_SUBMITTED = \"phx-has-submitted\"\nexport const PHX_SESSION = \"data-phx-session\"\nexport const PHX_VIEW_SELECTOR = `[${PHX_SESSION}]`\nexport const PHX_STICKY = \"data-phx-sticky\"\nexport const PHX_STATIC = \"data-phx-static\"\nexport const PHX_READONLY = \"data-phx-readonly\"\nexport const PHX_DISABLED = \"data-phx-disabled\"\nexport const PHX_DISABLE_WITH = \"disable-with\"\nexport const PHX_DISABLE_WITH_RESTORE = \"data-phx-disable-with-restore\"\nexport const PHX_HOOK = \"hook\"\nexport const PHX_DEBOUNCE = \"debounce\"\nexport const PHX_THROTTLE = \"throttle\"\nexport const PHX_UPDATE = \"update\"\nexport const PHX_STREAM = \"stream\"\nexport const PHX_KEY = \"key\"\nexport const PHX_PRIVATE = \"phxPrivate\"\nexport const PHX_AUTO_RECOVER = \"auto-recover\"\nexport const PHX_LV_DEBUG = \"phx:live-socket:debug\"\nexport const PHX_LV_PROFILE = \"phx:live-socket:profiling\"\nexport const PHX_LV_LATENCY_SIM = \"phx:live-socket:latency-sim\"\nexport const PHX_PROGRESS = \"progress\"\nexport const PHX_MOUNTED = \"mounted\"\nexport const LOADER_TIMEOUT = 1\nexport const BEFORE_UNLOAD_LOADER_TIMEOUT = 200\nexport const BINDING_PREFIX = \"phx-\"\nexport const PUSH_TIMEOUT = 30000\nexport const LINK_HEADER = \"x-requested-with\"\nexport const RESPONSE_URL_HEADER = \"x-response-url\"\nexport const DEBOUNCE_TRIGGER = \"debounce-trigger\"\nexport const THROTTLED = \"throttled\"\nexport const DEBOUNCE_PREV_KEY = \"debounce-prev-key\"\nexport const DEFAULTS = {\n debounce: 300,\n throttle: 300\n}\n\n// Rendered\nexport const DYNAMICS = \"d\"\nexport const STATIC = \"s\"\nexport const COMPONENTS = \"c\"\nexport const EVENTS = \"e\"\nexport const REPLY = \"r\"\nexport const TITLE = \"t\"\nexport const TEMPLATES = \"p\"\nexport const STREAM = \"stream\"\n", "import {\n logError\n} from \"./utils\"\n\nexport default class EntryUploader {\n constructor(entry, chunkSize, liveSocket){\n this.liveSocket = liveSocket\n this.entry = entry\n this.offset = 0\n this.chunkSize = chunkSize\n this.chunkTimer = null\n this.uploadChannel = liveSocket.channel(`lvu:${entry.ref}`, {token: entry.metadata()})\n }\n\n error(reason){\n clearTimeout(this.chunkTimer)\n this.uploadChannel.leave()\n this.entry.error(reason)\n }\n\n upload(){\n this.uploadChannel.onError(reason => this.error(reason))\n this.uploadChannel.join()\n .receive(\"ok\", _data => this.readNextChunk())\n .receive(\"error\", reason => this.error(reason))\n }\n\n isDone(){ return this.offset >= this.entry.file.size }\n\n readNextChunk(){\n let reader = new window.FileReader()\n let blob = this.entry.file.slice(this.offset, this.chunkSize + this.offset)\n reader.onload = (e) => {\n if(e.target.error === null){\n this.offset += e.target.result.byteLength\n this.pushChunk(e.target.result)\n } else {\n return logError(\"Read error: \" + e.target.error)\n }\n }\n reader.readAsArrayBuffer(blob)\n }\n\n pushChunk(chunk){\n if(!this.uploadChannel.isJoined()){ return }\n this.uploadChannel.push(\"chunk\", chunk)\n .receive(\"ok\", () => {\n this.entry.progress((this.offset / this.entry.file.size) * 100)\n if(!this.isDone()){\n this.chunkTimer = setTimeout(() => this.readNextChunk(), this.liveSocket.getLatencySim() || 0)\n }\n })\n }\n}\n", "import {\n PHX_VIEW_SELECTOR\n} from \"./constants\"\n\nimport EntryUploader from \"./entry_uploader\"\n\nexport let logError = (msg, obj) => console.error && console.error(msg, obj)\n\nexport let isCid = (cid) => {\n let type = typeof(cid)\n return type === \"number\" || (type === \"string\" && /^(0|[1-9]\\d*)$/.test(cid))\n}\n\nexport function detectDuplicateIds(){\n let ids = new Set()\n let elems = document.querySelectorAll(\"*[id]\")\n for(let i = 0, len = elems.length; i < len; i++){\n if(ids.has(elems[i].id)){\n console.error(`Multiple IDs detected: ${elems[i].id}. Ensure unique element ids.`)\n } else {\n ids.add(elems[i].id)\n }\n }\n}\n\nexport let debug = (view, kind, msg, obj) => {\n if(view.liveSocket.isDebugEnabled()){\n console.log(`${view.id} ${kind}: ${msg} - `, obj)\n }\n}\n\n// wraps value in closure or returns closure\nexport let closure = (val) => typeof val === \"function\" ? val : function (){ return val }\n\nexport let clone = (obj) => { return JSON.parse(JSON.stringify(obj)) }\n\nexport let closestPhxBinding = (el, binding, borderEl) => {\n do {\n if(el.matches(`[${binding}]`) && !el.disabled){ return el }\n el = el.parentElement || el.parentNode\n } while(el !== null && el.nodeType === 1 && !((borderEl && borderEl.isSameNode(el)) || el.matches(PHX_VIEW_SELECTOR)))\n return null\n}\n\nexport let isObject = (obj) => {\n return obj !== null && typeof obj === \"object\" && !(obj instanceof Array)\n}\n\nexport let isEqualObj = (obj1, obj2) => JSON.stringify(obj1) === JSON.stringify(obj2)\n\nexport let isEmpty = (obj) => {\n for(let x in obj){ return false }\n return true\n}\n\nexport let maybe = (el, callback) => el && callback(el)\n\nexport let channelUploader = function (entries, onError, resp, liveSocket){\n entries.forEach(entry => {\n let entryUploader = new EntryUploader(entry, resp.config.chunk_size, liveSocket)\n entryUploader.upload()\n })\n}\n", "let Browser = {\n canPushState(){ return (typeof (history.pushState) !== \"undefined\") },\n\n dropLocal(localStorage, namespace, subkey){\n return localStorage.removeItem(this.localKey(namespace, subkey))\n },\n\n updateLocal(localStorage, namespace, subkey, initial, func){\n let current = this.getLocal(localStorage, namespace, subkey)\n let key = this.localKey(namespace, subkey)\n let newVal = current === null ? initial : func(current)\n localStorage.setItem(key, JSON.stringify(newVal))\n return newVal\n },\n\n getLocal(localStorage, namespace, subkey){\n return JSON.parse(localStorage.getItem(this.localKey(namespace, subkey)))\n },\n\n updateCurrentState(callback){\n if(!this.canPushState()){ return }\n history.replaceState(callback(history.state || {}), \"\", window.location.href)\n },\n\n pushState(kind, meta, to){\n if(this.canPushState()){\n if(to !== window.location.href){\n if(meta.type == \"redirect\" && meta.scroll){\n // If we're redirecting store the current scrollY for the current history state.\n let currentState = history.state || {}\n currentState.scroll = meta.scroll\n history.replaceState(currentState, \"\", window.location.href)\n }\n\n delete meta.scroll // Only store the scroll in the redirect case.\n history[kind + \"State\"](meta, \"\", to || null) // IE will coerce undefined to string\n let hashEl = this.getHashTargetEl(window.location.hash)\n\n if(hashEl){\n hashEl.scrollIntoView()\n } else if(meta.type === \"redirect\"){\n window.scroll(0, 0)\n }\n }\n } else {\n this.redirect(to)\n }\n },\n\n setCookie(name, value){\n document.cookie = `${name}=${value}`\n },\n\n getCookie(name){\n return document.cookie.replace(new RegExp(`(?:(?:^|.*;\\s*)${name}\\s*\\=\\s*([^;]*).*$)|^.*$`), \"$1\")\n },\n\n redirect(toURL, flash){\n if(flash){ Browser.setCookie(\"__phoenix_flash__\", flash + \"; max-age=60000; path=/\") }\n window.location = toURL\n },\n\n localKey(namespace, subkey){ return `${namespace}-${subkey}` },\n\n getHashTargetEl(maybeHash){\n let hash = maybeHash.toString().substring(1)\n if(hash === \"\"){ return }\n return document.getElementById(hash) || document.querySelector(`a[name=\"${hash}\"]`)\n }\n}\n\nexport default Browser\n", "import {\n CHECKABLE_INPUTS,\n DEBOUNCE_PREV_KEY,\n DEBOUNCE_TRIGGER,\n FOCUSABLE_INPUTS,\n PHX_COMPONENT,\n PHX_EVENT_CLASSES,\n PHX_HAS_FOCUSED,\n PHX_HAS_SUBMITTED,\n PHX_MAIN,\n PHX_NO_FEEDBACK_CLASS,\n PHX_PARENT_ID,\n PHX_PRIVATE,\n PHX_REF,\n PHX_REF_SRC,\n PHX_ROOT_ID,\n PHX_SESSION,\n PHX_STATIC,\n PHX_UPLOAD_REF,\n PHX_VIEW_SELECTOR,\n PHX_STICKY,\n THROTTLED\n} from \"./constants\"\n\nimport {\n logError\n} from \"./utils\"\n\nlet DOM = {\n byId(id){ return document.getElementById(id) || logError(`no id found for ${id}`) },\n\n removeClass(el, className){\n el.classList.remove(className)\n if(el.classList.length === 0){ el.removeAttribute(\"class\") }\n },\n\n all(node, query, callback){\n if(!node){ return [] }\n let array = Array.from(node.querySelectorAll(query))\n return callback ? array.forEach(callback) : array\n },\n\n childNodeLength(html){\n let template = document.createElement(\"template\")\n template.innerHTML = html\n return template.content.childElementCount\n },\n\n isUploadInput(el){ return el.type === \"file\" && el.getAttribute(PHX_UPLOAD_REF) !== null },\n\n findUploadInputs(node){ return this.all(node, `input[type=\"file\"][${PHX_UPLOAD_REF}]`) },\n\n findComponentNodeList(node, cid){\n return this.filterWithinSameLiveView(this.all(node, `[${PHX_COMPONENT}=\"${cid}\"]`), node)\n },\n\n isPhxDestroyed(node){\n return node.id && DOM.private(node, \"destroyed\") ? true : false\n },\n\n wantsNewTab(e){\n let wantsNewTab = e.ctrlKey || e.shiftKey || e.metaKey || (e.button && e.button === 1)\n return wantsNewTab || e.target.getAttribute(\"target\") === \"_blank\"\n },\n\n isUnloadableFormSubmit(e){\n return !e.defaultPrevented && !this.wantsNewTab(e)\n },\n\n isNewPageHref(href, currentLocation){\n let url\n try {\n url = new URL(href)\n } catch(e) {\n try {\n url = new URL(href, currentLocation)\n } catch(e) {\n // bad URL, fallback to let browser try it as external\n return true\n }\n }\n\n if(url.host === currentLocation.host && url.protocol === currentLocation.protocol){\n if(url.pathname === currentLocation.pathname && url.search === currentLocation.search){\n return url.hash === \"\" && !url.href.endsWith(\"#\")\n }\n }\n return true\n },\n\n markPhxChildDestroyed(el){\n if(this.isPhxChild(el)){ el.setAttribute(PHX_SESSION, \"\") }\n this.putPrivate(el, \"destroyed\", true)\n },\n\n findPhxChildrenInFragment(html, parentId){\n let template = document.createElement(\"template\")\n template.innerHTML = html\n return this.findPhxChildren(template.content, parentId)\n },\n\n isIgnored(el, phxUpdate){\n return (el.getAttribute(phxUpdate) || el.getAttribute(\"data-phx-update\")) === \"ignore\"\n },\n\n isPhxUpdate(el, phxUpdate, updateTypes){\n return el.getAttribute && updateTypes.indexOf(el.getAttribute(phxUpdate)) >= 0\n },\n\n findPhxSticky(el){ return this.all(el, `[${PHX_STICKY}]`) },\n\n findPhxChildren(el, parentId){\n return this.all(el, `${PHX_VIEW_SELECTOR}[${PHX_PARENT_ID}=\"${parentId}\"]`)\n },\n\n findParentCIDs(node, cids){\n let initial = new Set(cids)\n let parentCids =\n cids.reduce((acc, cid) => {\n let selector = `[${PHX_COMPONENT}=\"${cid}\"] [${PHX_COMPONENT}]`\n\n this.filterWithinSameLiveView(this.all(node, selector), node)\n .map(el => parseInt(el.getAttribute(PHX_COMPONENT)))\n .forEach(childCID => acc.delete(childCID))\n\n return acc\n }, initial)\n\n return parentCids.size === 0 ? new Set(cids) : parentCids\n },\n\n filterWithinSameLiveView(nodes, parent){\n if(parent.querySelector(PHX_VIEW_SELECTOR)){\n return nodes.filter(el => this.withinSameLiveView(el, parent))\n } else {\n return nodes\n }\n },\n\n withinSameLiveView(node, parent){\n while(node = node.parentNode){\n if(node.isSameNode(parent)){ return true }\n if(node.getAttribute(PHX_SESSION) !== null){ return false }\n }\n },\n\n private(el, key){ return el[PHX_PRIVATE] && el[PHX_PRIVATE][key] },\n\n deletePrivate(el, key){ el[PHX_PRIVATE] && delete (el[PHX_PRIVATE][key]) },\n\n putPrivate(el, key, value){\n if(!el[PHX_PRIVATE]){ el[PHX_PRIVATE] = {} }\n el[PHX_PRIVATE][key] = value\n },\n\n updatePrivate(el, key, defaultVal, updateFunc){\n let existing = this.private(el, key)\n if(existing === undefined){\n this.putPrivate(el, key, updateFunc(defaultVal))\n } else {\n this.putPrivate(el, key, updateFunc(existing))\n }\n },\n\n copyPrivates(target, source){\n if(source[PHX_PRIVATE]){\n target[PHX_PRIVATE] = source[PHX_PRIVATE]\n }\n },\n\n putTitle(str){\n let titleEl = document.querySelector(\"title\")\n if(titleEl){\n let {prefix, suffix} = titleEl.dataset\n document.title = `${prefix || \"\"}${str}${suffix || \"\"}`\n } else {\n document.title = str\n }\n },\n\n debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, asyncFilter, callback){\n let debounce = el.getAttribute(phxDebounce)\n let throttle = el.getAttribute(phxThrottle)\n if(debounce === \"\"){ debounce = defaultDebounce }\n if(throttle === \"\"){ throttle = defaultThrottle }\n let value = debounce || throttle\n switch(value){\n case null: return callback()\n\n case \"blur\":\n if(this.once(el, \"debounce-blur\")){\n el.addEventListener(\"blur\", () => callback())\n }\n return\n\n default:\n let timeout = parseInt(value)\n let trigger = () => throttle ? this.deletePrivate(el, THROTTLED) : callback()\n let currentCycle = this.incCycle(el, DEBOUNCE_TRIGGER, trigger)\n if(isNaN(timeout)){ return logError(`invalid throttle/debounce value: ${value}`) }\n if(throttle){\n let newKeyDown = false\n if(event.type === \"keydown\"){\n let prevKey = this.private(el, DEBOUNCE_PREV_KEY)\n this.putPrivate(el, DEBOUNCE_PREV_KEY, event.key)\n newKeyDown = prevKey !== event.key\n }\n\n if(!newKeyDown && this.private(el, THROTTLED)){\n return false\n } else {\n callback()\n this.putPrivate(el, THROTTLED, true)\n setTimeout(() => {\n if(asyncFilter()){ this.triggerCycle(el, DEBOUNCE_TRIGGER) }\n }, timeout)\n }\n } else {\n setTimeout(() => {\n if(asyncFilter()){ this.triggerCycle(el, DEBOUNCE_TRIGGER, currentCycle) }\n }, timeout)\n }\n\n let form = el.form\n if(form && this.once(form, \"bind-debounce\")){\n form.addEventListener(\"submit\", () => {\n Array.from((new FormData(form)).entries(), ([name]) => {\n let input = form.querySelector(`[name=\"${name}\"]`)\n this.incCycle(input, DEBOUNCE_TRIGGER)\n this.deletePrivate(input, THROTTLED)\n })\n })\n }\n if(this.once(el, \"bind-debounce\")){\n el.addEventListener(\"blur\", () => this.triggerCycle(el, DEBOUNCE_TRIGGER))\n }\n }\n },\n\n triggerCycle(el, key, currentCycle){\n let [cycle, trigger] = this.private(el, key)\n if(!currentCycle){ currentCycle = cycle }\n if(currentCycle === cycle){\n this.incCycle(el, key)\n trigger()\n }\n },\n\n once(el, key){\n if(this.private(el, key) === true){ return false }\n this.putPrivate(el, key, true)\n return true\n },\n\n incCycle(el, key, trigger = function (){ }){\n let [currentCycle] = this.private(el, key) || [0, trigger]\n currentCycle++\n this.putPrivate(el, key, [currentCycle, trigger])\n return currentCycle\n },\n\n discardError(container, el, phxFeedbackFor){\n let field = el.getAttribute && el.getAttribute(phxFeedbackFor)\n // TODO: Remove id lookup after we update Phoenix to use input_name instead of input_id\n let input = field && container.querySelector(`[id=\"${field}\"], [name=\"${field}\"], [name=\"${field}[]\"]`)\n if(!input){ return }\n\n if(!(this.private(input, PHX_HAS_FOCUSED) || this.private(input, PHX_HAS_SUBMITTED))){\n el.classList.add(PHX_NO_FEEDBACK_CLASS)\n }\n },\n\n resetForm(form, phxFeedbackFor){\n Array.from(form.elements).forEach(input => {\n let query = `[${phxFeedbackFor}=\"${input.id}\"],\n [${phxFeedbackFor}=\"${input.name}\"],\n [${phxFeedbackFor}=\"${input.name.replace(/\\[\\]$/, \"\")}\"]`\n\n this.deletePrivate(input, PHX_HAS_FOCUSED)\n this.deletePrivate(input, PHX_HAS_SUBMITTED)\n this.all(document, query, feedbackEl => {\n feedbackEl.classList.add(PHX_NO_FEEDBACK_CLASS)\n })\n })\n },\n\n showError(inputEl, phxFeedbackFor){\n if(inputEl.id || inputEl.name){\n this.all(inputEl.form, `[${phxFeedbackFor}=\"${inputEl.id}\"], [${phxFeedbackFor}=\"${inputEl.name}\"]`, (el) => {\n this.removeClass(el, PHX_NO_FEEDBACK_CLASS)\n })\n }\n },\n\n isPhxChild(node){\n return node.getAttribute && node.getAttribute(PHX_PARENT_ID)\n },\n\n isPhxSticky(node){\n return node.getAttribute && node.getAttribute(PHX_STICKY) !== null\n },\n\n firstPhxChild(el){\n return this.isPhxChild(el) ? el : this.all(el, `[${PHX_PARENT_ID}]`)[0]\n },\n\n dispatchEvent(target, name, opts = {}){\n let bubbles = opts.bubbles === undefined ? true : !!opts.bubbles\n let eventOpts = {bubbles: bubbles, cancelable: true, detail: opts.detail || {}}\n let event = name === \"click\" ? new MouseEvent(\"click\", eventOpts) : new CustomEvent(name, eventOpts)\n target.dispatchEvent(event)\n },\n\n cloneNode(node, html){\n if(typeof (html) === \"undefined\"){\n return node.cloneNode(true)\n } else {\n let cloned = node.cloneNode(false)\n cloned.innerHTML = html\n return cloned\n }\n },\n\n mergeAttrs(target, source, opts = {}){\n let exclude = opts.exclude || []\n let isIgnored = opts.isIgnored\n let sourceAttrs = source.attributes\n for(let i = sourceAttrs.length - 1; i >= 0; i--){\n let name = sourceAttrs[i].name\n if(exclude.indexOf(name) < 0){ target.setAttribute(name, source.getAttribute(name)) }\n }\n\n let targetAttrs = target.attributes\n for(let i = targetAttrs.length - 1; i >= 0; i--){\n let name = targetAttrs[i].name\n if(isIgnored){\n if(name.startsWith(\"data-\") && !source.hasAttribute(name)){ target.removeAttribute(name) }\n } else {\n if(!source.hasAttribute(name)){ target.removeAttribute(name) }\n }\n }\n },\n\n mergeFocusedInput(target, source){\n // skip selects because FF will reset highlighted index for any setAttribute\n if(!(target instanceof HTMLSelectElement)){ DOM.mergeAttrs(target, source, {exclude: [\"value\"]}) }\n if(source.readOnly){\n target.setAttribute(\"readonly\", true)\n } else {\n target.removeAttribute(\"readonly\")\n }\n },\n\n hasSelectionRange(el){\n return el.setSelectionRange && (el.type === \"text\" || el.type === \"textarea\")\n },\n\n restoreFocus(focused, selectionStart, selectionEnd){\n if(!DOM.isTextualInput(focused)){ return }\n let wasFocused = focused.matches(\":focus\")\n if(focused.readOnly){ focused.blur() }\n if(!wasFocused){ focused.focus() }\n if(this.hasSelectionRange(focused)){\n focused.setSelectionRange(selectionStart, selectionEnd)\n }\n },\n\n isFormInput(el){ return /^(?:input|select|textarea)$/i.test(el.tagName) && el.type !== \"button\" },\n\n syncAttrsToProps(el){\n if(el instanceof HTMLInputElement && CHECKABLE_INPUTS.indexOf(el.type.toLocaleLowerCase()) >= 0){\n el.checked = el.getAttribute(\"checked\") !== null\n }\n },\n\n isTextualInput(el){ return FOCUSABLE_INPUTS.indexOf(el.type) >= 0 },\n\n isNowTriggerFormExternal(el, phxTriggerExternal){\n return el.getAttribute && el.getAttribute(phxTriggerExternal) !== null\n },\n\n syncPendingRef(fromEl, toEl, disableWith){\n let ref = fromEl.getAttribute(PHX_REF)\n if(ref === null){ return true }\n let refSrc = fromEl.getAttribute(PHX_REF_SRC)\n\n if(DOM.isFormInput(fromEl) || fromEl.getAttribute(disableWith) !== null){\n if(DOM.isUploadInput(fromEl)){ DOM.mergeAttrs(fromEl, toEl, {isIgnored: true}) }\n DOM.putPrivate(fromEl, PHX_REF, toEl)\n return false\n } else {\n PHX_EVENT_CLASSES.forEach(className => {\n fromEl.classList.contains(className) && toEl.classList.add(className)\n })\n toEl.setAttribute(PHX_REF, ref)\n toEl.setAttribute(PHX_REF_SRC, refSrc)\n return true\n }\n },\n\n cleanChildNodes(container, phxUpdate){\n if(DOM.isPhxUpdate(container, phxUpdate, [\"append\", \"prepend\"])){\n let toRemove = []\n container.childNodes.forEach(childNode => {\n if(!childNode.id){\n // Skip warning if it's an empty text node (e.g. a new-line)\n let isEmptyTextNode = childNode.nodeType === Node.TEXT_NODE && childNode.nodeValue.trim() === \"\"\n if(!isEmptyTextNode){\n logError(\"only HTML element tags with an id are allowed inside containers with phx-update.\\n\\n\" +\n `removing illegal node: \"${(childNode.outerHTML || childNode.nodeValue).trim()}\"\\n\\n`)\n }\n toRemove.push(childNode)\n }\n })\n toRemove.forEach(childNode => childNode.remove())\n }\n },\n\n replaceRootContainer(container, tagName, attrs){\n let retainedAttrs = new Set([\"id\", PHX_SESSION, PHX_STATIC, PHX_MAIN, PHX_ROOT_ID])\n if(container.tagName.toLowerCase() === tagName.toLowerCase()){\n Array.from(container.attributes)\n .filter(attr => !retainedAttrs.has(attr.name.toLowerCase()))\n .forEach(attr => container.removeAttribute(attr.name))\n\n Object.keys(attrs)\n .filter(name => !retainedAttrs.has(name.toLowerCase()))\n .forEach(attr => container.setAttribute(attr, attrs[attr]))\n\n return container\n\n } else {\n let newContainer = document.createElement(tagName)\n Object.keys(attrs).forEach(attr => newContainer.setAttribute(attr, attrs[attr]))\n retainedAttrs.forEach(attr => newContainer.setAttribute(attr, container.getAttribute(attr)))\n newContainer.innerHTML = container.innerHTML\n container.replaceWith(newContainer)\n return newContainer\n }\n },\n\n getSticky(el, name, defaultVal){\n let op = (DOM.private(el, \"sticky\") || []).find(([existingName, ]) => name === existingName)\n if(op){\n let [_name, _op, stashedResult] = op\n return stashedResult\n } else {\n return typeof(defaultVal) === \"function\" ? defaultVal() : defaultVal\n }\n },\n\n deleteSticky(el, name){\n this.updatePrivate(el, \"sticky\", [], ops => {\n return ops.filter(([existingName, _]) => existingName !== name)\n })\n },\n\n putSticky(el, name, op){\n let stashedResult = op(el)\n this.updatePrivate(el, \"sticky\", [], ops => {\n let existingIndex = ops.findIndex(([existingName, ]) => name === existingName)\n if(existingIndex >= 0){\n ops[existingIndex] = [name, op, stashedResult]\n } else {\n ops.push([name, op, stashedResult])\n }\n return ops\n })\n },\n\n applyStickyOperations(el){\n let ops = DOM.private(el, \"sticky\")\n if(!ops){ return }\n\n ops.forEach(([name, op, _stashed]) => this.putSticky(el, name, op))\n }\n}\n\nexport default DOM", "import {\n PHX_ACTIVE_ENTRY_REFS,\n PHX_LIVE_FILE_UPDATED,\n PHX_PREFLIGHTED_REFS\n} from \"./constants\"\n\nimport {\n channelUploader,\n logError\n} from \"./utils\"\n\nimport LiveUploader from \"./live_uploader\"\n\nexport default class UploadEntry {\n static isActive(fileEl, file){\n let isNew = file._phxRef === undefined\n let activeRefs = fileEl.getAttribute(PHX_ACTIVE_ENTRY_REFS).split(\",\")\n let isActive = activeRefs.indexOf(LiveUploader.genFileRef(file)) >= 0\n return file.size > 0 && (isNew || isActive)\n }\n\n static isPreflighted(fileEl, file){\n let preflightedRefs = fileEl.getAttribute(PHX_PREFLIGHTED_REFS).split(\",\")\n let isPreflighted = preflightedRefs.indexOf(LiveUploader.genFileRef(file)) >= 0\n return isPreflighted && this.isActive(fileEl, file)\n }\n\n constructor(fileEl, file, view){\n this.ref = LiveUploader.genFileRef(file)\n this.fileEl = fileEl\n this.file = file\n this.view = view\n this.meta = null\n this._isCancelled = false\n this._isDone = false\n this._progress = 0\n this._lastProgressSent = -1\n this._onDone = function (){ }\n this._onElUpdated = this.onElUpdated.bind(this)\n this.fileEl.addEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated)\n }\n\n metadata(){ return this.meta }\n\n progress(progress){\n this._progress = Math.floor(progress)\n if(this._progress > this._lastProgressSent){\n if(this._progress >= 100){\n this._progress = 100\n this._lastProgressSent = 100\n this._isDone = true\n this.view.pushFileProgress(this.fileEl, this.ref, 100, () => {\n LiveUploader.untrackFile(this.fileEl, this.file)\n this._onDone()\n })\n } else {\n this._lastProgressSent = this._progress\n this.view.pushFileProgress(this.fileEl, this.ref, this._progress)\n }\n }\n }\n\n cancel(){\n this._isCancelled = true\n this._isDone = true\n this._onDone()\n }\n\n isDone(){ return this._isDone }\n\n error(reason = \"failed\"){\n this.fileEl.removeEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated)\n this.view.pushFileProgress(this.fileEl, this.ref, {error: reason})\n LiveUploader.clearFiles(this.fileEl)\n }\n\n //private\n\n onDone(callback){\n this._onDone = () => {\n this.fileEl.removeEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated)\n callback()\n }\n }\n\n onElUpdated(){\n let activeRefs = this.fileEl.getAttribute(PHX_ACTIVE_ENTRY_REFS).split(\",\")\n if(activeRefs.indexOf(this.ref) === -1){ this.cancel() }\n }\n\n toPreflightPayload(){\n return {\n last_modified: this.file.lastModified,\n name: this.file.name,\n relative_path: this.file.webkitRelativePath,\n size: this.file.size,\n type: this.file.type,\n ref: this.ref\n }\n }\n\n uploader(uploaders){\n if(this.meta.uploader){\n let callback = uploaders[this.meta.uploader] || logError(`no uploader configured for ${this.meta.uploader}`)\n return {name: this.meta.uploader, callback: callback}\n } else {\n return {name: \"channel\", callback: channelUploader}\n }\n }\n\n zipPostFlight(resp){\n this.meta = resp.entries[this.ref]\n if(!this.meta){ logError(`no preflight upload response returned with ref ${this.ref}`, {input: this.fileEl, response: resp}) }\n }\n}\n", "import {\n PHX_DONE_REFS,\n PHX_PREFLIGHTED_REFS,\n PHX_UPLOAD_REF\n} from \"./constants\"\n\nimport {\n} from \"./utils\"\n\nimport DOM from \"./dom\"\nimport UploadEntry from \"./upload_entry\"\n\nlet liveUploaderFileRef = 0\n\nexport default class LiveUploader {\n static genFileRef(file){\n let ref = file._phxRef\n if(ref !== undefined){\n return ref\n } else {\n file._phxRef = (liveUploaderFileRef++).toString()\n return file._phxRef\n }\n }\n\n static getEntryDataURL(inputEl, ref, callback){\n let file = this.activeFiles(inputEl).find(file => this.genFileRef(file) === ref)\n callback(URL.createObjectURL(file))\n }\n\n static hasUploadsInProgress(formEl){\n let active = 0\n DOM.findUploadInputs(formEl).forEach(input => {\n if(input.getAttribute(PHX_PREFLIGHTED_REFS) !== input.getAttribute(PHX_DONE_REFS)){\n active++\n }\n })\n return active > 0\n }\n\n static serializeUploads(inputEl){\n let files = this.activeFiles(inputEl)\n let fileData = {}\n files.forEach(file => {\n let entry = {path: inputEl.name}\n let uploadRef = inputEl.getAttribute(PHX_UPLOAD_REF)\n fileData[uploadRef] = fileData[uploadRef] || []\n entry.ref = this.genFileRef(file)\n entry.last_modified = file.lastModified\n entry.name = file.name || entry.ref\n entry.relative_path = file.webkitRelativePath\n entry.type = file.type\n entry.size = file.size\n fileData[uploadRef].push(entry)\n })\n return fileData\n }\n\n static clearFiles(inputEl){\n inputEl.value = null\n inputEl.removeAttribute(PHX_UPLOAD_REF)\n DOM.putPrivate(inputEl, \"files\", [])\n }\n\n static untrackFile(inputEl, file){\n DOM.putPrivate(inputEl, \"files\", DOM.private(inputEl, \"files\").filter(f => !Object.is(f, file)))\n }\n\n static trackFiles(inputEl, files, dataTransfer){\n if(inputEl.getAttribute(\"multiple\") !== null){\n let newFiles = files.filter(file => !this.activeFiles(inputEl).find(f => Object.is(f, file)))\n DOM.putPrivate(inputEl, \"files\", this.activeFiles(inputEl).concat(newFiles))\n inputEl.value = null\n } else {\n // Reset inputEl files to align output with programmatic changes (i.e. drag and drop)\n if(dataTransfer && dataTransfer.files.length > 0){ inputEl.files = dataTransfer.files }\n DOM.putPrivate(inputEl, \"files\", files)\n }\n }\n\n static activeFileInputs(formEl){\n let fileInputs = DOM.findUploadInputs(formEl)\n return Array.from(fileInputs).filter(el => el.files && this.activeFiles(el).length > 0)\n }\n\n static activeFiles(input){\n return (DOM.private(input, \"files\") || []).filter(f => UploadEntry.isActive(input, f))\n }\n\n static inputsAwaitingPreflight(formEl){\n let fileInputs = DOM.findUploadInputs(formEl)\n return Array.from(fileInputs).filter(input => this.filesAwaitingPreflight(input).length > 0)\n }\n\n static filesAwaitingPreflight(input){\n return this.activeFiles(input).filter(f => !UploadEntry.isPreflighted(input, f))\n }\n\n constructor(inputEl, view, onComplete){\n this.view = view\n this.onComplete = onComplete\n this._entries =\n Array.from(LiveUploader.filesAwaitingPreflight(inputEl) || [])\n .map(file => new UploadEntry(inputEl, file, view))\n\n this.numEntriesInProgress = this._entries.length\n }\n\n entries(){ return this._entries }\n\n initAdapterUpload(resp, onError, liveSocket){\n this._entries =\n this._entries.map(entry => {\n entry.zipPostFlight(resp)\n entry.onDone(() => {\n this.numEntriesInProgress--\n if(this.numEntriesInProgress === 0){ this.onComplete() }\n })\n return entry\n })\n\n let groupedEntries = this._entries.reduce((acc, entry) => {\n let {name, callback} = entry.uploader(liveSocket.uploaders)\n acc[name] = acc[name] || {callback: callback, entries: []}\n acc[name].entries.push(entry)\n return acc\n }, {})\n\n for(let name in groupedEntries){\n let {callback, entries} = groupedEntries[name]\n callback(entries, onError, resp, liveSocket)\n }\n }\n}\n", "let ARIA = {\n focusMain(){\n let target = document.querySelector(\"main h1, main, h1\")\n if(target){\n let origTabIndex = target.tabIndex\n target.tabIndex = -1\n target.focus()\n target.tabIndex = origTabIndex\n }\n },\n\n anyOf(instance, classes){ return classes.find(name => instance instanceof name) },\n\n isFocusable(el, interactiveOnly){\n return(\n (el instanceof HTMLAnchorElement && el.rel !== \"ignore\") ||\n (el instanceof HTMLAreaElement && el.href !== undefined) ||\n (!el.disabled && (this.anyOf(el, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, HTMLButtonElement]))) ||\n (el instanceof HTMLIFrameElement) ||\n (el.tabIndex > 0 || (!interactiveOnly && el.tabIndex === 0 && el.getAttribute(\"tabindex\") !== null && el.getAttribute(\"aria-hidden\") !== \"true\"))\n )\n },\n\n attemptFocus(el, interactiveOnly){\n if(this.isFocusable(el, interactiveOnly)){ try{ el.focus() } catch(e){} }\n return !!document.activeElement && document.activeElement.isSameNode(el)\n },\n\n focusFirstInteractive(el){\n let child = el.firstElementChild\n while(child){\n if(this.attemptFocus(child, true) || this.focusFirstInteractive(child, true)){\n return true\n }\n child = child.nextElementSibling\n }\n },\n\n focusFirst(el){\n let child = el.firstElementChild\n while(child){\n if(this.attemptFocus(child) || this.focusFirst(child)){\n return true\n }\n child = child.nextElementSibling\n }\n },\n\n focusLast(el){\n let child = el.lastElementChild\n while(child){\n if(this.attemptFocus(child) || this.focusLast(child)){\n return true\n }\n child = child.previousElementSibling\n }\n }\n}\nexport default ARIA", "import {\n PHX_ACTIVE_ENTRY_REFS,\n PHX_LIVE_FILE_UPDATED,\n PHX_PREFLIGHTED_REFS,\n PHX_UPLOAD_REF\n} from \"./constants\"\n\nimport LiveUploader from \"./live_uploader\"\nimport ARIA from \"./aria\"\n\nlet Hooks = {\n LiveFileUpload: {\n activeRefs(){ return this.el.getAttribute(PHX_ACTIVE_ENTRY_REFS) },\n\n preflightedRefs(){ return this.el.getAttribute(PHX_PREFLIGHTED_REFS) },\n\n mounted(){ this.preflightedWas = this.preflightedRefs() },\n\n updated(){\n let newPreflights = this.preflightedRefs()\n if(this.preflightedWas !== newPreflights){\n this.preflightedWas = newPreflights\n if(newPreflights === \"\"){\n this.__view.cancelSubmit(this.el.form)\n }\n }\n\n if(this.activeRefs() === \"\"){ this.el.value = null }\n this.el.dispatchEvent(new CustomEvent(PHX_LIVE_FILE_UPDATED))\n }\n },\n\n LiveImgPreview: {\n mounted(){\n this.ref = this.el.getAttribute(\"data-phx-entry-ref\")\n this.inputEl = document.getElementById(this.el.getAttribute(PHX_UPLOAD_REF))\n LiveUploader.getEntryDataURL(this.inputEl, this.ref, url => {\n this.url = url\n this.el.src = url\n })\n },\n destroyed(){\n URL.revokeObjectURL(this.url)\n }\n },\n FocusWrap: {\n mounted(){\n this.focusStart = this.el.firstElementChild\n this.focusEnd = this.el.lastElementChild\n this.focusStart.addEventListener(\"focus\", () => ARIA.focusLast(this.el))\n this.focusEnd.addEventListener(\"focus\", () => ARIA.focusFirst(this.el))\n this.el.addEventListener(\"phx:show-end\", () => this.el.focus())\n if(window.getComputedStyle(this.el).display !== \"none\"){\n ARIA.focusFirst(this.el)\n }\n }\n }\n}\n\nexport default Hooks\n", "import {\n maybe\n} from \"./utils\"\n\nimport DOM from \"./dom\"\n\nexport default class DOMPostMorphRestorer {\n constructor(containerBefore, containerAfter, updateType){\n let idsBefore = new Set()\n let idsAfter = new Set([...containerAfter.children].map(child => child.id))\n\n let elementsToModify = []\n\n Array.from(containerBefore.children).forEach(child => {\n if(child.id){ // all of our children should be elements with ids\n idsBefore.add(child.id)\n if(idsAfter.has(child.id)){\n let previousElementId = child.previousElementSibling && child.previousElementSibling.id\n elementsToModify.push({elementId: child.id, previousElementId: previousElementId})\n }\n }\n })\n\n this.containerId = containerAfter.id\n this.updateType = updateType\n this.elementsToModify = elementsToModify\n this.elementIdsToAdd = [...idsAfter].filter(id => !idsBefore.has(id))\n }\n\n // We do the following to optimize append/prepend operations:\n // 1) Track ids of modified elements & of new elements\n // 2) All the modified elements are put back in the correct position in the DOM tree\n // by storing the id of their previous sibling\n // 3) New elements are going to be put in the right place by morphdom during append.\n // For prepend, we move them to the first position in the container\n perform(){\n let container = DOM.byId(this.containerId)\n this.elementsToModify.forEach(elementToModify => {\n if(elementToModify.previousElementId){\n maybe(document.getElementById(elementToModify.previousElementId), previousElem => {\n maybe(document.getElementById(elementToModify.elementId), elem => {\n let isInRightPlace = elem.previousElementSibling && elem.previousElementSibling.id == previousElem.id\n if(!isInRightPlace){\n previousElem.insertAdjacentElement(\"afterend\", elem)\n }\n })\n })\n } else {\n // This is the first element in the container\n maybe(document.getElementById(elementToModify.elementId), elem => {\n let isInRightPlace = elem.previousElementSibling == null\n if(!isInRightPlace){\n container.insertAdjacentElement(\"afterbegin\", elem)\n }\n })\n }\n })\n\n if(this.updateType == \"prepend\"){\n this.elementIdsToAdd.reverse().forEach(elemId => {\n maybe(document.getElementById(elemId), elem => container.insertAdjacentElement(\"afterbegin\", elem))\n })\n }\n }\n}\n", "var DOCUMENT_FRAGMENT_NODE = 11;\n\nfunction morphAttrs(fromNode, toNode) {\n var toNodeAttrs = toNode.attributes;\n var attr;\n var attrName;\n var attrNamespaceURI;\n var attrValue;\n var fromValue;\n\n // document-fragments dont have attributes so lets not do anything\n if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE || fromNode.nodeType === DOCUMENT_FRAGMENT_NODE) {\n return;\n }\n\n // update attributes on original DOM element\n for (var i = toNodeAttrs.length - 1; i >= 0; i--) {\n attr = toNodeAttrs[i];\n attrName = attr.name;\n attrNamespaceURI = attr.namespaceURI;\n attrValue = attr.value;\n\n if (attrNamespaceURI) {\n attrName = attr.localName || attrName;\n fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName);\n\n if (fromValue !== attrValue) {\n if (attr.prefix === 'xmlns'){\n attrName = attr.name; // It's not allowed to set an attribute with the XMLNS namespace without specifying the `xmlns` prefix\n }\n fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue);\n }\n } else {\n fromValue = fromNode.getAttribute(attrName);\n\n if (fromValue !== attrValue) {\n fromNode.setAttribute(attrName, attrValue);\n }\n }\n }\n\n // Remove any extra attributes found on the original DOM element that\n // weren't found on the target element.\n var fromNodeAttrs = fromNode.attributes;\n\n for (var d = fromNodeAttrs.length - 1; d >= 0; d--) {\n attr = fromNodeAttrs[d];\n attrName = attr.name;\n attrNamespaceURI = attr.namespaceURI;\n\n if (attrNamespaceURI) {\n attrName = attr.localName || attrName;\n\n if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) {\n fromNode.removeAttributeNS(attrNamespaceURI, attrName);\n }\n } else {\n if (!toNode.hasAttribute(attrName)) {\n fromNode.removeAttribute(attrName);\n }\n }\n }\n}\n\nvar range; // Create a range object for efficently rendering strings to elements.\nvar NS_XHTML = 'http://www.w3.org/1999/xhtml';\n\nvar doc = typeof document === 'undefined' ? undefined : document;\nvar HAS_TEMPLATE_SUPPORT = !!doc && 'content' in doc.createElement('template');\nvar HAS_RANGE_SUPPORT = !!doc && doc.createRange && 'createContextualFragment' in doc.createRange();\n\nfunction createFragmentFromTemplate(str) {\n var template = doc.createElement('template');\n template.innerHTML = str;\n return template.content.childNodes[0];\n}\n\nfunction createFragmentFromRange(str) {\n if (!range) {\n range = doc.createRange();\n range.selectNode(doc.body);\n }\n\n var fragment = range.createContextualFragment(str);\n return fragment.childNodes[0];\n}\n\nfunction createFragmentFromWrap(str) {\n var fragment = doc.createElement('body');\n fragment.innerHTML = str;\n return fragment.childNodes[0];\n}\n\n/**\n * This is about the same\n * var html = new DOMParser().parseFromString(str, 'text/html');\n * return html.body.firstChild;\n *\n * @method toElement\n * @param {String} str\n */\nfunction toElement(str) {\n str = str.trim();\n if (HAS_TEMPLATE_SUPPORT) {\n // avoid restrictions on content for things like `Hi ` which\n // createContextualFragment doesn't support\n // support not available in IE\n return createFragmentFromTemplate(str);\n } else if (HAS_RANGE_SUPPORT) {\n return createFragmentFromRange(str);\n }\n\n return createFragmentFromWrap(str);\n}\n\n/**\n * Returns true if two node's names are the same.\n *\n * NOTE: We don't bother checking `namespaceURI` because you will never find two HTML elements with the same\n * nodeName and different namespace URIs.\n *\n * @param {Element} a\n * @param {Element} b The target element\n * @return {boolean}\n */\nfunction compareNodeNames(fromEl, toEl) {\n var fromNodeName = fromEl.nodeName;\n var toNodeName = toEl.nodeName;\n var fromCodeStart, toCodeStart;\n\n if (fromNodeName === toNodeName) {\n return true;\n }\n\n fromCodeStart = fromNodeName.charCodeAt(0);\n toCodeStart = toNodeName.charCodeAt(0);\n\n // If the target element is a virtual DOM node or SVG node then we may\n // need to normalize the tag name before comparing. Normal HTML elements that are\n // in the \"http://www.w3.org/1999/xhtml\"\n // are converted to upper case\n if (fromCodeStart <= 90 && toCodeStart >= 97) { // from is upper and to is lower\n return fromNodeName === toNodeName.toUpperCase();\n } else if (toCodeStart <= 90 && fromCodeStart >= 97) { // to is upper and from is lower\n return toNodeName === fromNodeName.toUpperCase();\n } else {\n return false;\n }\n}\n\n/**\n * Create an element, optionally with a known namespace URI.\n *\n * @param {string} name the element name, e.g. 'div' or 'svg'\n * @param {string} [namespaceURI] the element's namespace URI, i.e. the value of\n * its `xmlns` attribute or its inferred namespace.\n *\n * @return {Element}\n */\nfunction createElementNS(name, namespaceURI) {\n return !namespaceURI || namespaceURI === NS_XHTML ?\n doc.createElement(name) :\n doc.createElementNS(namespaceURI, name);\n}\n\n/**\n * Copies the children of one DOM element to another DOM element\n */\nfunction moveChildren(fromEl, toEl) {\n var curChild = fromEl.firstChild;\n while (curChild) {\n var nextChild = curChild.nextSibling;\n toEl.appendChild(curChild);\n curChild = nextChild;\n }\n return toEl;\n}\n\nfunction syncBooleanAttrProp(fromEl, toEl, name) {\n if (fromEl[name] !== toEl[name]) {\n fromEl[name] = toEl[name];\n if (fromEl[name]) {\n fromEl.setAttribute(name, '');\n } else {\n fromEl.removeAttribute(name);\n }\n }\n}\n\nvar specialElHandlers = {\n OPTION: function(fromEl, toEl) {\n var parentNode = fromEl.parentNode;\n if (parentNode) {\n var parentName = parentNode.nodeName.toUpperCase();\n if (parentName === 'OPTGROUP') {\n parentNode = parentNode.parentNode;\n parentName = parentNode && parentNode.nodeName.toUpperCase();\n }\n if (parentName === 'SELECT' && !parentNode.hasAttribute('multiple')) {\n if (fromEl.hasAttribute('selected') && !toEl.selected) {\n // Workaround for MS Edge bug where the 'selected' attribute can only be\n // removed if set to a non-empty value:\n // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12087679/\n fromEl.setAttribute('selected', 'selected');\n fromEl.removeAttribute('selected');\n }\n // We have to reset select element's selectedIndex to -1, otherwise setting\n // fromEl.selected using the syncBooleanAttrProp below has no effect.\n // The correct selectedIndex will be set in the SELECT special handler below.\n parentNode.selectedIndex = -1;\n }\n }\n syncBooleanAttrProp(fromEl, toEl, 'selected');\n },\n /**\n * The \"value\" attribute is special for the element since it sets\n * the initial value. Changing the \"value\" attribute without changing the\n * \"value\" property will have no effect since it is only used to the set the\n * initial value. Similar for the \"checked\" attribute, and \"disabled\".\n */\n INPUT: function(fromEl, toEl) {\n syncBooleanAttrProp(fromEl, toEl, 'checked');\n syncBooleanAttrProp(fromEl, toEl, 'disabled');\n\n if (fromEl.value !== toEl.value) {\n fromEl.value = toEl.value;\n }\n\n if (!toEl.hasAttribute('value')) {\n fromEl.removeAttribute('value');\n }\n },\n\n TEXTAREA: function(fromEl, toEl) {\n var newValue = toEl.value;\n if (fromEl.value !== newValue) {\n fromEl.value = newValue;\n }\n\n var firstChild = fromEl.firstChild;\n if (firstChild) {\n // Needed for IE. Apparently IE sets the placeholder as the\n // node value and vise versa. This ignores an empty update.\n var oldValue = firstChild.nodeValue;\n\n if (oldValue == newValue || (!newValue && oldValue == fromEl.placeholder)) {\n return;\n }\n\n firstChild.nodeValue = newValue;\n }\n },\n SELECT: function(fromEl, toEl) {\n if (!toEl.hasAttribute('multiple')) {\n var selectedIndex = -1;\n var i = 0;\n // We have to loop through children of fromEl, not toEl since nodes can be moved\n // from toEl to fromEl directly when morphing.\n // At the time this special handler is invoked, all children have already been morphed\n // and appended to / removed from fromEl, so using fromEl here is safe and correct.\n var curChild = fromEl.firstChild;\n var optgroup;\n var nodeName;\n while(curChild) {\n nodeName = curChild.nodeName && curChild.nodeName.toUpperCase();\n if (nodeName === 'OPTGROUP') {\n optgroup = curChild;\n curChild = optgroup.firstChild;\n } else {\n if (nodeName === 'OPTION') {\n if (curChild.hasAttribute('selected')) {\n selectedIndex = i;\n break;\n }\n i++;\n }\n curChild = curChild.nextSibling;\n if (!curChild && optgroup) {\n curChild = optgroup.nextSibling;\n optgroup = null;\n }\n }\n }\n\n fromEl.selectedIndex = selectedIndex;\n }\n }\n};\n\nvar ELEMENT_NODE = 1;\nvar DOCUMENT_FRAGMENT_NODE$1 = 11;\nvar TEXT_NODE = 3;\nvar COMMENT_NODE = 8;\n\nfunction noop() {}\n\nfunction defaultGetNodeKey(node) {\n if (node) {\n return (node.getAttribute && node.getAttribute('id')) || node.id;\n }\n}\n\nfunction morphdomFactory(morphAttrs) {\n\n return function morphdom(fromNode, toNode, options) {\n if (!options) {\n options = {};\n }\n\n if (typeof toNode === 'string') {\n if (fromNode.nodeName === '#document' || fromNode.nodeName === 'HTML' || fromNode.nodeName === 'BODY') {\n var toNodeHtml = toNode;\n toNode = doc.createElement('html');\n toNode.innerHTML = toNodeHtml;\n } else {\n toNode = toElement(toNode);\n }\n } else if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE$1) {\n toNode = toNode.firstElementChild;\n }\n\n var getNodeKey = options.getNodeKey || defaultGetNodeKey;\n var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;\n var onNodeAdded = options.onNodeAdded || noop;\n var onBeforeElUpdated = options.onBeforeElUpdated || noop;\n var onElUpdated = options.onElUpdated || noop;\n var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;\n var onNodeDiscarded = options.onNodeDiscarded || noop;\n var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;\n var skipFromChildren = options.skipFromChildren || noop;\n var addChild = options.addChild || function(parent, child){ return parent.appendChild(child); };\n var childrenOnly = options.childrenOnly === true;\n\n // This object is used as a lookup to quickly find all keyed elements in the original DOM tree.\n var fromNodesLookup = Object.create(null);\n var keyedRemovalList = [];\n\n function addKeyedRemoval(key) {\n keyedRemovalList.push(key);\n }\n\n function walkDiscardedChildNodes(node, skipKeyedNodes) {\n if (node.nodeType === ELEMENT_NODE) {\n var curChild = node.firstChild;\n while (curChild) {\n\n var key = undefined;\n\n if (skipKeyedNodes && (key = getNodeKey(curChild))) {\n // If we are skipping keyed nodes then we add the key\n // to a list so that it can be handled at the very end.\n addKeyedRemoval(key);\n } else {\n // Only report the node as discarded if it is not keyed. We do this because\n // at the end we loop through all keyed elements that were unmatched\n // and then discard them in one final pass.\n onNodeDiscarded(curChild);\n if (curChild.firstChild) {\n walkDiscardedChildNodes(curChild, skipKeyedNodes);\n }\n }\n\n curChild = curChild.nextSibling;\n }\n }\n }\n\n /**\n * Removes a DOM node out of the original DOM\n *\n * @param {Node} node The node to remove\n * @param {Node} parentNode The nodes parent\n * @param {Boolean} skipKeyedNodes If true then elements with keys will be skipped and not discarded.\n * @return {undefined}\n */\n function removeNode(node, parentNode, skipKeyedNodes) {\n if (onBeforeNodeDiscarded(node) === false) {\n return;\n }\n\n if (parentNode) {\n parentNode.removeChild(node);\n }\n\n onNodeDiscarded(node);\n walkDiscardedChildNodes(node, skipKeyedNodes);\n }\n\n // // TreeWalker implementation is no faster, but keeping this around in case this changes in the future\n // function indexTree(root) {\n // var treeWalker = document.createTreeWalker(\n // root,\n // NodeFilter.SHOW_ELEMENT);\n //\n // var el;\n // while((el = treeWalker.nextNode())) {\n // var key = getNodeKey(el);\n // if (key) {\n // fromNodesLookup[key] = el;\n // }\n // }\n // }\n\n // // NodeIterator implementation is no faster, but keeping this around in case this changes in the future\n //\n // function indexTree(node) {\n // var nodeIterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT);\n // var el;\n // while((el = nodeIterator.nextNode())) {\n // var key = getNodeKey(el);\n // if (key) {\n // fromNodesLookup[key] = el;\n // }\n // }\n // }\n\n function indexTree(node) {\n if (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE$1) {\n var curChild = node.firstChild;\n while (curChild) {\n var key = getNodeKey(curChild);\n if (key) {\n fromNodesLookup[key] = curChild;\n }\n\n // Walk recursively\n indexTree(curChild);\n\n curChild = curChild.nextSibling;\n }\n }\n }\n\n indexTree(fromNode);\n\n function handleNodeAdded(el) {\n onNodeAdded(el);\n\n var curChild = el.firstChild;\n while (curChild) {\n var nextSibling = curChild.nextSibling;\n\n var key = getNodeKey(curChild);\n if (key) {\n var unmatchedFromEl = fromNodesLookup[key];\n // if we find a duplicate #id node in cache, replace `el` with cache value\n // and morph it to the child node.\n if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) {\n curChild.parentNode.replaceChild(unmatchedFromEl, curChild);\n morphEl(unmatchedFromEl, curChild);\n } else {\n handleNodeAdded(curChild);\n }\n } else {\n // recursively call for curChild and it's children to see if we find something in\n // fromNodesLookup\n handleNodeAdded(curChild);\n }\n\n curChild = nextSibling;\n }\n }\n\n function cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey) {\n // We have processed all of the \"to nodes\". If curFromNodeChild is\n // non-null then we still have some from nodes left over that need\n // to be removed\n while (curFromNodeChild) {\n var fromNextSibling = curFromNodeChild.nextSibling;\n if ((curFromNodeKey = getNodeKey(curFromNodeChild))) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n curFromNodeChild = fromNextSibling;\n }\n }\n\n function morphEl(fromEl, toEl, childrenOnly) {\n var toElKey = getNodeKey(toEl);\n\n if (toElKey) {\n // If an element with an ID is being morphed then it will be in the final\n // DOM so clear it out of the saved elements collection\n delete fromNodesLookup[toElKey];\n }\n\n if (!childrenOnly) {\n // optional\n if (onBeforeElUpdated(fromEl, toEl) === false) {\n return;\n }\n\n // update attributes on original DOM element first\n morphAttrs(fromEl, toEl);\n // optional\n onElUpdated(fromEl);\n\n if (onBeforeElChildrenUpdated(fromEl, toEl) === false) {\n return;\n }\n }\n\n if (fromEl.nodeName !== 'TEXTAREA') {\n morphChildren(fromEl, toEl);\n } else {\n specialElHandlers.TEXTAREA(fromEl, toEl);\n }\n }\n\n function morphChildren(fromEl, toEl) {\n var skipFrom = skipFromChildren(fromEl);\n var curToNodeChild = toEl.firstChild;\n var curFromNodeChild = fromEl.firstChild;\n var curToNodeKey;\n var curFromNodeKey;\n\n var fromNextSibling;\n var toNextSibling;\n var matchingFromEl;\n\n // walk the children\n outer: while (curToNodeChild) {\n toNextSibling = curToNodeChild.nextSibling;\n curToNodeKey = getNodeKey(curToNodeChild);\n\n // walk the fromNode children all the way through\n while (!skipFrom && curFromNodeChild) {\n fromNextSibling = curFromNodeChild.nextSibling;\n\n if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n continue outer;\n }\n\n curFromNodeKey = getNodeKey(curFromNodeChild);\n\n var curFromNodeType = curFromNodeChild.nodeType;\n\n // this means if the curFromNodeChild doesnt have a match with the curToNodeChild\n var isCompatible = undefined;\n\n if (curFromNodeType === curToNodeChild.nodeType) {\n if (curFromNodeType === ELEMENT_NODE) {\n // Both nodes being compared are Element nodes\n\n if (curToNodeKey) {\n // The target node has a key so we want to match it up with the correct element\n // in the original DOM tree\n if (curToNodeKey !== curFromNodeKey) {\n // The current element in the original DOM tree does not have a matching key so\n // let's check our lookup to see if there is a matching element in the original\n // DOM tree\n if ((matchingFromEl = fromNodesLookup[curToNodeKey])) {\n if (fromNextSibling === matchingFromEl) {\n // Special case for single element removals. To avoid removing the original\n // DOM node out of the tree (since that can break CSS transitions, etc.),\n // we will instead discard the current node and wait until the next\n // iteration to properly match up the keyed target element with its matching\n // element in the original tree\n isCompatible = false;\n } else {\n // We found a matching keyed element somewhere in the original DOM tree.\n // Let's move the original DOM node into the current position and morph\n // it.\n\n // NOTE: We use insertBefore instead of replaceChild because we want to go through\n // the `removeNode()` function for the node that is being discarded so that\n // all lifecycle hooks are correctly invoked\n fromEl.insertBefore(matchingFromEl, curFromNodeChild);\n\n // fromNextSibling = curFromNodeChild.nextSibling;\n\n if (curFromNodeKey) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n\n curFromNodeChild = matchingFromEl;\n }\n } else {\n // The nodes are not compatible since the \"to\" node has a key and there\n // is no matching keyed node in the source tree\n isCompatible = false;\n }\n }\n } else if (curFromNodeKey) {\n // The original has a key\n isCompatible = false;\n }\n\n isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild);\n if (isCompatible) {\n // We found compatible DOM elements so transform\n // the current \"from\" node to match the current\n // target DOM node.\n // MORPH\n morphEl(curFromNodeChild, curToNodeChild);\n }\n\n } else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) {\n // Both nodes being compared are Text or Comment nodes\n isCompatible = true;\n // Simply update nodeValue on the original node to\n // change the text value\n if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) {\n curFromNodeChild.nodeValue = curToNodeChild.nodeValue;\n }\n\n }\n }\n\n if (isCompatible) {\n // Advance both the \"to\" child and the \"from\" child since we found a match\n // Nothing else to do as we already recursively called morphChildren above\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n continue outer;\n }\n\n // No compatible match so remove the old node from the DOM and continue trying to find a\n // match in the original DOM. However, we only do this if the from node is not keyed\n // since it is possible that a keyed node might match up with a node somewhere else in the\n // target tree and we don't want to discard it just yet since it still might find a\n // home in the final DOM tree. After everything is done we will remove any keyed nodes\n // that didn't find a home\n if (curFromNodeKey) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n\n curFromNodeChild = fromNextSibling;\n } // END: while(curFromNodeChild) {}\n\n // If we got this far then we did not find a candidate match for\n // our \"to node\" and we exhausted all of the children \"from\"\n // nodes. Therefore, we will just append the current \"to\" node\n // to the end\n if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {\n // MORPH\n if(!skipFrom){ addChild(fromEl, matchingFromEl); }\n morphEl(matchingFromEl, curToNodeChild);\n } else {\n var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);\n if (onBeforeNodeAddedResult !== false) {\n if (onBeforeNodeAddedResult) {\n curToNodeChild = onBeforeNodeAddedResult;\n }\n\n if (curToNodeChild.actualize) {\n curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);\n }\n addChild(fromEl, curToNodeChild);\n handleNodeAdded(curToNodeChild);\n }\n }\n\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n }\n\n cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey);\n\n var specialElHandler = specialElHandlers[fromEl.nodeName];\n if (specialElHandler) {\n specialElHandler(fromEl, toEl);\n }\n } // END: morphChildren(...)\n\n var morphedNode = fromNode;\n var morphedNodeType = morphedNode.nodeType;\n var toNodeType = toNode.nodeType;\n\n if (!childrenOnly) {\n // Handle the case where we are given two DOM nodes that are not\n // compatible (e.g. -->
or --> TEXT)\n if (morphedNodeType === ELEMENT_NODE) {\n if (toNodeType === ELEMENT_NODE) {\n if (!compareNodeNames(fromNode, toNode)) {\n onNodeDiscarded(fromNode);\n morphedNode = moveChildren(fromNode, createElementNS(toNode.nodeName, toNode.namespaceURI));\n }\n } else {\n // Going from an element node to a text node\n morphedNode = toNode;\n }\n } else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node\n if (toNodeType === morphedNodeType) {\n if (morphedNode.nodeValue !== toNode.nodeValue) {\n morphedNode.nodeValue = toNode.nodeValue;\n }\n\n return morphedNode;\n } else {\n // Text node to something else\n morphedNode = toNode;\n }\n }\n }\n\n if (morphedNode === toNode) {\n // The \"to node\" was not compatible with the \"from node\" so we had to\n // toss out the \"from node\" and use the \"to node\"\n onNodeDiscarded(fromNode);\n } else {\n if (toNode.isSameNode && toNode.isSameNode(morphedNode)) {\n return;\n }\n\n morphEl(morphedNode, toNode, childrenOnly);\n\n // We now need to loop over any keyed nodes that might need to be\n // removed. We only do the removal if we know that the keyed node\n // never found a match. When a keyed node is matched up we remove\n // it out of fromNodesLookup and we use fromNodesLookup to determine\n // if a keyed node has been matched up or not\n if (keyedRemovalList) {\n for (var i=0, len=keyedRemovalList.length; i
{\n if(activeElement && activeElement.isSameNode(fromEl) && DOM.isFormInput(fromEl)){\n DOM.mergeFocusedInput(fromEl, toEl)\n return false\n }\n }\n })\n }\n\n constructor(view, container, id, html, streams, targetCID){\n this.view = view\n this.liveSocket = view.liveSocket\n this.container = container\n this.id = id\n this.rootID = view.root.id\n this.html = html\n this.streams = streams\n this.streamInserts = {}\n this.targetCID = targetCID\n this.cidPatch = isCid(this.targetCID)\n this.pendingRemoves = []\n this.phxRemove = this.liveSocket.binding(\"remove\")\n this.callbacks = {\n beforeadded: [], beforeupdated: [], beforephxChildAdded: [],\n afteradded: [], afterupdated: [], afterdiscarded: [], afterphxChildAdded: [],\n aftertransitionsDiscarded: []\n }\n }\n\n before(kind, callback){ this.callbacks[`before${kind}`].push(callback) }\n after(kind, callback){ this.callbacks[`after${kind}`].push(callback) }\n\n trackBefore(kind, ...args){\n this.callbacks[`before${kind}`].forEach(callback => callback(...args))\n }\n\n trackAfter(kind, ...args){\n this.callbacks[`after${kind}`].forEach(callback => callback(...args))\n }\n\n markPrunableContentForRemoval(){\n let phxUpdate = this.liveSocket.binding(PHX_UPDATE)\n DOM.all(this.container, `[${phxUpdate}=${PHX_STREAM}]`, el => el.innerHTML = \"\")\n DOM.all(this.container, `[${phxUpdate}=append] > *, [${phxUpdate}=prepend] > *`, el => {\n el.setAttribute(PHX_PRUNE, \"\")\n })\n }\n\n perform(){\n let {view, liveSocket, container, html} = this\n let targetContainer = this.isCIDPatch() ? this.targetCIDContainer(html) : container\n if(this.isCIDPatch() && !targetContainer){ return }\n\n let focused = liveSocket.getActiveElement()\n let {selectionStart, selectionEnd} = focused && DOM.hasSelectionRange(focused) ? focused : {}\n let phxUpdate = liveSocket.binding(PHX_UPDATE)\n let phxFeedbackFor = liveSocket.binding(PHX_FEEDBACK_FOR)\n let disableWith = liveSocket.binding(PHX_DISABLE_WITH)\n let phxTriggerExternal = liveSocket.binding(PHX_TRIGGER_ACTION)\n let added = []\n let updates = []\n let appendPrependUpdates = []\n\n let externalFormTriggered = null\n\n let diffHTML = liveSocket.time(\"premorph container prep\", () => {\n return this.buildDiffHTML(container, html, phxUpdate, targetContainer)\n })\n\n this.trackBefore(\"added\", container)\n this.trackBefore(\"updated\", container, container)\n\n liveSocket.time(\"morphdom\", () => {\n\n this.streams.forEach(([inserts, deleteIds]) => {\n this.streamInserts = Object.assign(this.streamInserts, inserts)\n deleteIds.forEach(id => {\n let child = container.querySelector(`[id=\"${id}\"]`)\n if(child){\n if(!this.maybePendingRemove(child)){\n child.remove()\n this.onNodeDiscarded(child)\n }\n }\n })\n })\n\n morphdom(targetContainer, diffHTML, {\n childrenOnly: targetContainer.getAttribute(PHX_COMPONENT) === null,\n getNodeKey: (node) => {\n return DOM.isPhxDestroyed(node) ? null : node.id\n },\n // skip indexing from children when container is stream\n skipFromChildren: (from) => { return from.getAttribute(phxUpdate) === PHX_STREAM },\n // tell morphdom how to add a child\n addChild: (parent, child) => {\n let streamAt = child.id ? this.streamInserts[child.id] : undefined\n if(streamAt === undefined) { return parent.appendChild(child) }\n\n //streaming\n DOM.putPrivate(child, PHX_STREAM, true)\n if(streamAt === 0){\n parent.insertAdjacentElement(\"afterbegin\", child)\n } else if(streamAt === -1){\n parent.appendChild(child)\n } else if(streamAt > 0){\n let sibling = Array.from(parent.children)[streamAt]\n parent.insertBefore(child, sibling)\n }\n },\n onBeforeNodeAdded: (el) => {\n this.trackBefore(\"added\", el)\n return el\n },\n onNodeAdded: (el) => {\n // hack to fix Safari handling of img srcset and video tags\n if(el instanceof HTMLImageElement && el.srcset){\n el.srcset = el.srcset\n } else if(el instanceof HTMLVideoElement && el.autoplay){\n el.play()\n }\n if(DOM.isNowTriggerFormExternal(el, phxTriggerExternal)){\n externalFormTriggered = el\n }\n //input handling\n DOM.discardError(targetContainer, el, phxFeedbackFor)\n // nested view handling\n if((DOM.isPhxChild(el) && view.ownsElement(el)) || DOM.isPhxSticky(el) && view.ownsElement(el.parentNode)){\n this.trackAfter(\"phxChildAdded\", el)\n }\n added.push(el)\n },\n onNodeDiscarded: (el) => this.onNodeDiscarded(el),\n onBeforeNodeDiscarded: (el) => {\n if(el.getAttribute && el.getAttribute(PHX_PRUNE) !== null){ return true }\n if(DOM.private(el, PHX_STREAM)){ return false }\n if(el.parentElement !== null && DOM.isPhxUpdate(el.parentElement, phxUpdate, [\"append\", \"prepend\"]) && el.id){ return false }\n if(this.maybePendingRemove(el)){ return false }\n if(this.skipCIDSibling(el)){ return false }\n return true\n },\n onElUpdated: (el) => {\n if(DOM.isNowTriggerFormExternal(el, phxTriggerExternal)){\n externalFormTriggered = el\n }\n updates.push(el)\n this.maybeReOrderStream(el)\n },\n onBeforeElUpdated: (fromEl, toEl) => {\n DOM.cleanChildNodes(toEl, phxUpdate)\n if(this.skipCIDSibling(toEl)){ return false }\n if(DOM.isPhxSticky(fromEl)){ return false }\n if(DOM.isIgnored(fromEl, phxUpdate) || (fromEl.form && fromEl.form.isSameNode(externalFormTriggered))){\n this.trackBefore(\"updated\", fromEl, toEl)\n DOM.mergeAttrs(fromEl, toEl, {isIgnored: true})\n updates.push(fromEl)\n DOM.applyStickyOperations(fromEl)\n return false\n }\n if(fromEl.type === \"number\" && (fromEl.validity && fromEl.validity.badInput)){ return false }\n if(!DOM.syncPendingRef(fromEl, toEl, disableWith)){\n if(DOM.isUploadInput(fromEl)){\n this.trackBefore(\"updated\", fromEl, toEl)\n updates.push(fromEl)\n }\n DOM.applyStickyOperations(fromEl)\n return false\n }\n\n // nested view handling\n if(DOM.isPhxChild(toEl)){\n let prevSession = fromEl.getAttribute(PHX_SESSION)\n DOM.mergeAttrs(fromEl, toEl, {exclude: [PHX_STATIC]})\n if(prevSession !== \"\"){ fromEl.setAttribute(PHX_SESSION, prevSession) }\n fromEl.setAttribute(PHX_ROOT_ID, this.rootID)\n DOM.applyStickyOperations(fromEl)\n return false\n }\n\n // input handling\n DOM.copyPrivates(toEl, fromEl)\n DOM.discardError(targetContainer, toEl, phxFeedbackFor)\n\n let isFocusedFormEl = focused && fromEl.isSameNode(focused) && DOM.isFormInput(fromEl)\n if(isFocusedFormEl && fromEl.type !== \"hidden\"){\n this.trackBefore(\"updated\", fromEl, toEl)\n DOM.mergeFocusedInput(fromEl, toEl)\n DOM.syncAttrsToProps(fromEl)\n updates.push(fromEl)\n DOM.applyStickyOperations(fromEl)\n return false\n } else {\n if(DOM.isPhxUpdate(toEl, phxUpdate, [\"append\", \"prepend\"])){\n appendPrependUpdates.push(new DOMPostMorphRestorer(fromEl, toEl, toEl.getAttribute(phxUpdate)))\n }\n DOM.syncAttrsToProps(toEl)\n DOM.applyStickyOperations(toEl)\n this.trackBefore(\"updated\", fromEl, toEl)\n return true\n }\n }\n })\n })\n\n if(liveSocket.isDebugEnabled()){ detectDuplicateIds() }\n\n if(appendPrependUpdates.length > 0){\n liveSocket.time(\"post-morph append/prepend restoration\", () => {\n appendPrependUpdates.forEach(update => update.perform())\n })\n }\n\n liveSocket.silenceEvents(() => DOM.restoreFocus(focused, selectionStart, selectionEnd))\n DOM.dispatchEvent(document, \"phx:update\")\n added.forEach(el => this.trackAfter(\"added\", el))\n updates.forEach(el => this.trackAfter(\"updated\", el))\n\n this.transitionPendingRemoves()\n\n if(externalFormTriggered){\n liveSocket.unload()\n externalFormTriggered.submit()\n }\n return true\n }\n\n onNodeDiscarded(el){\n // nested view handling\n if(DOM.isPhxChild(el) || DOM.isPhxSticky(el)){ this.liveSocket.destroyViewByEl(el) }\n this.trackAfter(\"discarded\", el)\n }\n\n maybePendingRemove(node){\n if(node.getAttribute && node.getAttribute(this.phxRemove) !== null){\n this.pendingRemoves.push(node)\n return true\n } else {\n return false\n }\n }\n\n maybeReOrderStream(el){\n let streamAt = el.id ? this.streamInserts[el.id] : undefined\n if(streamAt === undefined){ return }\n\n DOM.putPrivate(el, PHX_STREAM, true)\n if(streamAt === 0){\n el.parentElement.insertBefore(el, el.parentElement.firstElementChild)\n } else if(streamAt > 0){\n let children = Array.from(el.parentElement.children)\n let oldIndex = children.indexOf(el)\n if(streamAt >= children.length - 1){\n el.parentElement.appendChild(el)\n } else {\n let sibling = children[streamAt]\n if(oldIndex > streamAt){\n el.parentElement.insertBefore(el, sibling)\n } else {\n el.parentElement.insertBefore(el, sibling.nextElementSibling)\n }\n }\n }\n }\n\n transitionPendingRemoves(){\n let {pendingRemoves, liveSocket} = this\n if(pendingRemoves.length > 0){\n liveSocket.transitionRemoves(pendingRemoves)\n liveSocket.requestDOMUpdate(() => {\n pendingRemoves.forEach(el => {\n let child = DOM.firstPhxChild(el)\n if(child){ liveSocket.destroyViewByEl(child) }\n el.remove()\n })\n this.trackAfter(\"transitionsDiscarded\", pendingRemoves)\n })\n }\n }\n\n isCIDPatch(){ return this.cidPatch }\n\n skipCIDSibling(el){\n return el.nodeType === Node.ELEMENT_NODE && el.getAttribute(PHX_SKIP) !== null\n }\n\n targetCIDContainer(html){\n if(!this.isCIDPatch()){ return }\n let [first, ...rest] = DOM.findComponentNodeList(this.container, this.targetCID)\n if(rest.length === 0 && DOM.childNodeLength(html) === 1){\n return first\n } else {\n return first && first.parentNode\n }\n }\n\n // builds HTML for morphdom patch\n // - for full patches of LiveView or a component with a single\n // root node, simply returns the HTML\n // - for patches of a component with multiple root nodes, the\n // parent node becomes the target container and non-component\n // siblings are marked as skip.\n buildDiffHTML(container, html, phxUpdate, targetContainer){\n let isCIDPatch = this.isCIDPatch()\n let isCIDWithSingleRoot = isCIDPatch && targetContainer.getAttribute(PHX_COMPONENT) === this.targetCID.toString()\n if(!isCIDPatch || isCIDWithSingleRoot){\n return html\n } else {\n // component patch with multiple CID roots\n let diffContainer = null\n let template = document.createElement(\"template\")\n diffContainer = DOM.cloneNode(targetContainer)\n let [firstComponent, ...rest] = DOM.findComponentNodeList(diffContainer, this.targetCID)\n template.innerHTML = html\n rest.forEach(el => el.remove())\n Array.from(diffContainer.childNodes).forEach(child => {\n // we can only skip trackable nodes with an ID\n if(child.id && child.nodeType === Node.ELEMENT_NODE && child.getAttribute(PHX_COMPONENT) !== this.targetCID.toString()){\n child.setAttribute(PHX_SKIP, \"\")\n child.innerHTML = \"\"\n }\n })\n Array.from(template.content.childNodes).forEach(el => diffContainer.insertBefore(el, firstComponent))\n firstComponent.remove()\n return diffContainer.outerHTML\n }\n }\n\n indexOf(parent, child){ return Array.from(parent.children).indexOf(child) }\n}\n", "import {\n COMPONENTS,\n DYNAMICS,\n TEMPLATES,\n EVENTS,\n PHX_COMPONENT,\n PHX_SKIP,\n REPLY,\n STATIC,\n TITLE,\n STREAM,\n} from \"./constants\"\n\nimport {\n isObject,\n logError,\n isCid,\n} from \"./utils\"\n\nexport default class Rendered {\n static extract(diff){\n let {[REPLY]: reply, [EVENTS]: events, [TITLE]: title} = diff\n delete diff[REPLY]\n delete diff[EVENTS]\n delete diff[TITLE]\n return {diff, title, reply: reply || null, events: events || []}\n }\n\n constructor(viewId, rendered){\n this.viewId = viewId\n this.rendered = {}\n this.mergeDiff(rendered)\n }\n\n parentViewId(){ return this.viewId }\n\n toString(onlyCids){\n let [str, streams] = this.recursiveToString(this.rendered, this.rendered[COMPONENTS], onlyCids)\n return [str, streams]\n }\n\n recursiveToString(rendered, components = rendered[COMPONENTS], onlyCids){\n onlyCids = onlyCids ? new Set(onlyCids) : null\n let output = {buffer: \"\", components: components, onlyCids: onlyCids, streams: new Set()}\n this.toOutputBuffer(rendered, null, output)\n return [output.buffer, output.streams]\n }\n\n componentCIDs(diff){ return Object.keys(diff[COMPONENTS] || {}).map(i => parseInt(i)) }\n\n isComponentOnlyDiff(diff){\n if(!diff[COMPONENTS]){ return false }\n return Object.keys(diff).length === 1\n }\n\n getComponent(diff, cid){ return diff[COMPONENTS][cid] }\n\n mergeDiff(diff){\n let newc = diff[COMPONENTS]\n let cache = {}\n delete diff[COMPONENTS]\n this.rendered = this.mutableMerge(this.rendered, diff)\n this.rendered[COMPONENTS] = this.rendered[COMPONENTS] || {}\n\n if(newc){\n let oldc = this.rendered[COMPONENTS]\n\n for(let cid in newc){\n newc[cid] = this.cachedFindComponent(cid, newc[cid], oldc, newc, cache)\n }\n\n for(let cid in newc){ oldc[cid] = newc[cid] }\n diff[COMPONENTS] = newc\n }\n }\n\n cachedFindComponent(cid, cdiff, oldc, newc, cache){\n if(cache[cid]){\n return cache[cid]\n } else {\n let ndiff, stat, scid = cdiff[STATIC]\n\n if(isCid(scid)){\n let tdiff\n\n if(scid > 0){\n tdiff = this.cachedFindComponent(scid, newc[scid], oldc, newc, cache)\n } else {\n tdiff = oldc[-scid]\n }\n\n stat = tdiff[STATIC]\n ndiff = this.cloneMerge(tdiff, cdiff)\n ndiff[STATIC] = stat\n } else {\n ndiff = cdiff[STATIC] !== undefined ? cdiff : this.cloneMerge(oldc[cid] || {}, cdiff)\n }\n\n cache[cid] = ndiff\n return ndiff\n }\n }\n\n mutableMerge(target, source){\n if(source[STATIC] !== undefined){\n return source\n } else {\n this.doMutableMerge(target, source)\n return target\n }\n }\n\n doMutableMerge(target, source){\n for(let key in source){\n let val = source[key]\n let targetVal = target[key]\n let isObjVal = isObject(val)\n if(isObjVal && val[STATIC] === undefined && isObject(targetVal)){\n this.doMutableMerge(targetVal, val)\n } else {\n target[key] = val\n }\n }\n }\n\n cloneMerge(target, source){\n let merged = {...target, ...source}\n for(let key in merged){\n let val = source[key]\n let targetVal = target[key]\n if(isObject(val) && val[STATIC] === undefined && isObject(targetVal)){\n merged[key] = this.cloneMerge(targetVal, val)\n }\n }\n return merged\n }\n\n componentToString(cid){\n let [str, streams] = this.recursiveCIDToString(this.rendered[COMPONENTS], cid)\n return [str, streams]\n }\n\n pruneCIDs(cids){\n cids.forEach(cid => delete this.rendered[COMPONENTS][cid])\n }\n\n // private\n\n get(){ return this.rendered }\n\n isNewFingerprint(diff = {}){ return !!diff[STATIC] }\n\n templateStatic(part, templates){\n if(typeof (part) === \"number\") {\n return templates[part]\n } else {\n return part\n }\n }\n\n toOutputBuffer(rendered, templates, output){\n if(rendered[DYNAMICS]){ return this.comprehensionToBuffer(rendered, templates, output) }\n let {[STATIC]: statics} = rendered\n statics = this.templateStatic(statics, templates)\n\n output.buffer += statics[0]\n for(let i = 1; i < statics.length; i++){\n this.dynamicToBuffer(rendered[i - 1], templates, output)\n output.buffer += statics[i]\n }\n }\n\n comprehensionToBuffer(rendered, templates, output){\n let {[DYNAMICS]: dynamics, [STATIC]: statics, [STREAM]: stream} = rendered\n let [_inserts, deleteIds] = stream || [{}, []]\n statics = this.templateStatic(statics, templates)\n let compTemplates = templates || rendered[TEMPLATES]\n for(let d = 0; d < dynamics.length; d++){\n let dynamic = dynamics[d]\n output.buffer += statics[0]\n for(let i = 1; i < statics.length; i++){\n this.dynamicToBuffer(dynamic[i - 1], compTemplates, output)\n output.buffer += statics[i]\n }\n }\n\n if(stream !== undefined && (rendered[DYNAMICS].length > 0 || deleteIds.length > 0)){\n rendered[DYNAMICS] = []\n output.streams.add(stream)\n }\n }\n\n dynamicToBuffer(rendered, templates, output){\n if(typeof (rendered) === \"number\"){\n let [str, streams] = this.recursiveCIDToString(output.components, rendered, output.onlyCids)\n output.buffer += str\n output.streams = new Set([...output.streams, ...streams])\n } else if(isObject(rendered)){\n this.toOutputBuffer(rendered, templates, output)\n } else {\n output.buffer += rendered\n }\n }\n\n recursiveCIDToString(components, cid, onlyCids){\n let component = components[cid] || logError(`no component for CID ${cid}`, components)\n let template = document.createElement(\"template\")\n let [html, streams] = this.recursiveToString(component, components, onlyCids)\n template.innerHTML = html\n let container = template.content\n let skip = onlyCids && !onlyCids.has(cid)\n\n let [hasChildNodes, hasChildComponents] =\n Array.from(container.childNodes).reduce(([hasNodes, hasComponents], child, i) => {\n if(child.nodeType === Node.ELEMENT_NODE){\n if(child.getAttribute(PHX_COMPONENT)){\n return [hasNodes, true]\n }\n child.setAttribute(PHX_COMPONENT, cid)\n if(!child.id){ child.id = `${this.parentViewId()}-${cid}-${i}` }\n if(skip){\n child.setAttribute(PHX_SKIP, \"\")\n child.innerHTML = \"\"\n }\n return [true, hasComponents]\n } else {\n if(child.nodeValue.trim() !== \"\"){\n logError(\"only HTML element tags are allowed at the root of components.\\n\\n\" +\n `got: \"${child.nodeValue.trim()}\"\\n\\n` +\n \"within:\\n\", template.innerHTML.trim())\n child.replaceWith(this.createSpan(child.nodeValue, cid))\n return [true, hasComponents]\n } else {\n child.remove()\n return [hasNodes, hasComponents]\n }\n }\n }, [false, false])\n\n if(!hasChildNodes && !hasChildComponents){\n logError(\"expected at least one HTML element tag inside a component, but the component is empty:\\n\",\n template.innerHTML.trim())\n return [this.createSpan(\"\", cid).outerHTML, streams]\n } else if(!hasChildNodes && hasChildComponents){\n logError(\"expected at least one HTML element tag directly inside a component, but only subcomponents were found. A component must render at least one HTML tag directly inside itself.\",\n template.innerHTML.trim())\n return [template.innerHTML, streams]\n } else {\n return [template.innerHTML, streams]\n }\n }\n\n createSpan(text, cid){\n let span = document.createElement(\"span\")\n span.innerText = text\n span.setAttribute(PHX_COMPONENT, cid)\n return span\n }\n}\n", "let viewHookID = 1\nexport default class ViewHook {\n static makeID(){ return viewHookID++ }\n static elementID(el){ return el.phxHookId }\n\n constructor(view, el, callbacks){\n this.__view = view\n this.liveSocket = view.liveSocket\n this.__callbacks = callbacks\n this.__listeners = new Set()\n this.__isDisconnected = false\n this.el = el\n this.el.phxHookId = this.constructor.makeID()\n for(let key in this.__callbacks){ this[key] = this.__callbacks[key] }\n }\n\n __mounted(){ this.mounted && this.mounted() }\n __updated(){ this.updated && this.updated() }\n __beforeUpdate(){ this.beforeUpdate && this.beforeUpdate() }\n __destroyed(){ this.destroyed && this.destroyed() }\n __reconnected(){\n if(this.__isDisconnected){\n this.__isDisconnected = false\n this.reconnected && this.reconnected()\n }\n }\n __disconnected(){\n this.__isDisconnected = true\n this.disconnected && this.disconnected()\n }\n\n pushEvent(event, payload = {}, onReply = function (){ }){\n return this.__view.pushHookEvent(null, event, payload, onReply)\n }\n\n pushEventTo(phxTarget, event, payload = {}, onReply = function (){ }){\n return this.__view.withinTargets(phxTarget, (view, targetCtx) => {\n return view.pushHookEvent(targetCtx, event, payload, onReply)\n })\n }\n\n handleEvent(event, callback){\n let callbackRef = (customEvent, bypass) => bypass ? event : callback(customEvent.detail)\n window.addEventListener(`phx:${event}`, callbackRef)\n this.__listeners.add(callbackRef)\n return callbackRef\n }\n\n removeHandleEvent(callbackRef){\n let event = callbackRef(null, true)\n window.removeEventListener(`phx:${event}`, callbackRef)\n this.__listeners.delete(callbackRef)\n }\n\n upload(name, files){\n return this.__view.dispatchUploads(name, files)\n }\n\n uploadTo(phxTarget, name, files){\n return this.__view.withinTargets(phxTarget, view => view.dispatchUploads(name, files))\n }\n\n __cleanup__(){\n this.__listeners.forEach(callbackRef => this.removeHandleEvent(callbackRef))\n }\n}\n", "import DOM from \"./dom\"\nimport ARIA from \"./aria\"\n\nlet focusStack = null\n\nlet JS = {\n exec(eventType, phxEvent, view, sourceEl, defaults){\n let [defaultKind, defaultArgs] = defaults || [null, {}]\n let commands = phxEvent.charAt(0) === \"[\" ?\n JSON.parse(phxEvent) : [[defaultKind, defaultArgs]]\n\n commands.forEach(([kind, args]) => {\n if(kind === defaultKind && defaultArgs.data){\n args.data = Object.assign(args.data || {}, defaultArgs.data)\n }\n this.filterToEls(sourceEl, args).forEach(el => {\n this[`exec_${kind}`](eventType, phxEvent, view, sourceEl, el, args)\n })\n })\n },\n\n isVisible(el){\n return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length > 0)\n },\n\n // private\n\n // commands\n\n exec_dispatch(eventType, phxEvent, view, sourceEl, el, {to, event, detail, bubbles}){\n detail = detail || {}\n detail.dispatcher = sourceEl\n DOM.dispatchEvent(el, event, {detail, bubbles})\n },\n\n exec_push(eventType, phxEvent, view, sourceEl, el, args){\n if(!view.isConnected()){ return }\n\n let {event, data, target, page_loading, loading, value, dispatcher} = args\n let pushOpts = {loading, value, target, page_loading: !!page_loading}\n let targetSrc = eventType === \"change\" && dispatcher ? dispatcher : sourceEl\n let phxTarget = target || targetSrc.getAttribute(view.binding(\"target\")) || targetSrc\n view.withinTargets(phxTarget, (targetView, targetCtx) => {\n if(eventType === \"change\"){\n let {newCid, _target, callback} = args\n _target = _target || (DOM.isFormInput(sourceEl) ? sourceEl.name : undefined)\n if(_target){ pushOpts._target = _target }\n targetView.pushInput(sourceEl, targetCtx, newCid, event || phxEvent, pushOpts, callback)\n } else if(eventType === \"submit\"){\n targetView.submitForm(sourceEl, targetCtx, event || phxEvent, pushOpts)\n } else {\n targetView.pushEvent(eventType, sourceEl, targetCtx, event || phxEvent, data, pushOpts)\n }\n })\n },\n\n exec_navigate(eventType, phxEvent, view, sourceEl, el, {href, replace}){\n view.liveSocket.historyRedirect(href, replace ? \"replace\" : \"push\")\n },\n\n exec_patch(eventType, phxEvent, view, sourceEl, el, {href, replace}){\n view.liveSocket.pushHistoryPatch(href, replace ? \"replace\" : \"push\", sourceEl)\n },\n\n exec_focus(eventType, phxEvent, view, sourceEl, el){\n window.requestAnimationFrame(() => ARIA.attemptFocus(el))\n },\n\n exec_focus_first(eventType, phxEvent, view, sourceEl, el){\n window.requestAnimationFrame(() => ARIA.focusFirstInteractive(el) || ARIA.focusFirst(el))\n },\n\n exec_push_focus(eventType, phxEvent, view, sourceEl, el){\n window.requestAnimationFrame(() => focusStack = el || sourceEl)\n },\n\n exec_pop_focus(eventType, phxEvent, view, sourceEl, el){\n window.requestAnimationFrame(() => {\n if(focusStack){ focusStack.focus() }\n focusStack = null\n })\n },\n\n exec_add_class(eventType, phxEvent, view, sourceEl, el, {names, transition, time}){\n this.addOrRemoveClasses(el, names, [], transition, time, view)\n },\n\n exec_remove_class(eventType, phxEvent, view, sourceEl, el, {names, transition, time}){\n this.addOrRemoveClasses(el, [], names, transition, time, view)\n },\n\n exec_transition(eventType, phxEvent, view, sourceEl, el, {time, transition}){\n this.addOrRemoveClasses(el, [], [], transition, time, view)\n },\n\n exec_toggle(eventType, phxEvent, view, sourceEl, el, {display, ins, outs, time}){\n this.toggle(eventType, view, el, display, ins, outs, time)\n },\n\n exec_show(eventType, phxEvent, view, sourceEl, el, {display, transition, time}){\n this.show(eventType, view, el, display, transition, time)\n },\n\n exec_hide(eventType, phxEvent, view, sourceEl, el, {display, transition, time}){\n this.hide(eventType, view, el, display, transition, time)\n },\n\n exec_set_attr(eventType, phxEvent, view, sourceEl, el, {attr: [attr, val]}){\n this.setOrRemoveAttrs(el, [[attr, val]], [])\n },\n\n exec_remove_attr(eventType, phxEvent, view, sourceEl, el, {attr}){\n this.setOrRemoveAttrs(el, [], [attr])\n },\n\n // utils for commands\n\n show(eventType, view, el, display, transition, time){\n if(!this.isVisible(el)){\n this.toggle(eventType, view, el, display, transition, null, time)\n }\n },\n\n hide(eventType, view, el, display, transition, time){\n if(this.isVisible(el)){\n this.toggle(eventType, view, el, display, null, transition, time)\n }\n },\n\n toggle(eventType, view, el, display, ins, outs, time){\n let [inClasses, inStartClasses, inEndClasses] = ins || [[], [], []]\n let [outClasses, outStartClasses, outEndClasses] = outs || [[], [], []]\n if(inClasses.length > 0 || outClasses.length > 0){\n if(this.isVisible(el)){\n let onStart = () => {\n this.addOrRemoveClasses(el, outStartClasses, inClasses.concat(inStartClasses).concat(inEndClasses))\n window.requestAnimationFrame(() => {\n this.addOrRemoveClasses(el, outClasses, [])\n window.requestAnimationFrame(() => this.addOrRemoveClasses(el, outEndClasses, outStartClasses))\n })\n }\n el.dispatchEvent(new Event(\"phx:hide-start\"))\n view.transition(time, onStart, () => {\n this.addOrRemoveClasses(el, [], outClasses.concat(outEndClasses))\n DOM.putSticky(el, \"toggle\", currentEl => currentEl.style.display = \"none\")\n el.dispatchEvent(new Event(\"phx:hide-end\"))\n })\n } else {\n if(eventType === \"remove\"){ return }\n let onStart = () => {\n this.addOrRemoveClasses(el, inStartClasses, outClasses.concat(outStartClasses).concat(outEndClasses))\n let stickyDisplay = display || this.defaultDisplay(el)\n DOM.putSticky(el, \"toggle\", currentEl => currentEl.style.display = stickyDisplay)\n window.requestAnimationFrame(() => {\n this.addOrRemoveClasses(el, inClasses, [])\n window.requestAnimationFrame(() => this.addOrRemoveClasses(el, inEndClasses, inStartClasses))\n })\n }\n el.dispatchEvent(new Event(\"phx:show-start\"))\n view.transition(time, onStart, () => {\n this.addOrRemoveClasses(el, [], inClasses.concat(inEndClasses))\n el.dispatchEvent(new Event(\"phx:show-end\"))\n })\n }\n } else {\n if(this.isVisible(el)){\n window.requestAnimationFrame(() => {\n el.dispatchEvent(new Event(\"phx:hide-start\"))\n DOM.putSticky(el, \"toggle\", currentEl => currentEl.style.display = \"none\")\n el.dispatchEvent(new Event(\"phx:hide-end\"))\n })\n } else {\n window.requestAnimationFrame(() => {\n el.dispatchEvent(new Event(\"phx:show-start\"))\n let stickyDisplay = display || this.defaultDisplay(el)\n DOM.putSticky(el, \"toggle\", currentEl => currentEl.style.display = stickyDisplay)\n el.dispatchEvent(new Event(\"phx:show-end\"))\n })\n }\n }\n },\n\n addOrRemoveClasses(el, adds, removes, transition, time, view){\n let [transition_run, transition_start, transition_end] = transition || [[], [], []]\n if(transition_run.length > 0){\n let onStart = () => this.addOrRemoveClasses(el, transition_start.concat(transition_run), [])\n let onDone = () => this.addOrRemoveClasses(el, adds.concat(transition_end), removes.concat(transition_run).concat(transition_start))\n return view.transition(time, onStart, onDone)\n }\n window.requestAnimationFrame(() => {\n let [prevAdds, prevRemoves] = DOM.getSticky(el, \"classes\", [[], []])\n let keepAdds = adds.filter(name => prevAdds.indexOf(name) < 0 && !el.classList.contains(name))\n let keepRemoves = removes.filter(name => prevRemoves.indexOf(name) < 0 && el.classList.contains(name))\n let newAdds = prevAdds.filter(name => removes.indexOf(name) < 0).concat(keepAdds)\n let newRemoves = prevRemoves.filter(name => adds.indexOf(name) < 0).concat(keepRemoves)\n\n DOM.putSticky(el, \"classes\", currentEl => {\n currentEl.classList.remove(...newRemoves)\n currentEl.classList.add(...newAdds)\n return [newAdds, newRemoves]\n })\n })\n },\n\n setOrRemoveAttrs(el, sets, removes){\n let [prevSets, prevRemoves] = DOM.getSticky(el, \"attrs\", [[], []])\n\n let alteredAttrs = sets.map(([attr, _val]) => attr).concat(removes);\n let newSets = prevSets.filter(([attr, _val]) => !alteredAttrs.includes(attr)).concat(sets);\n let newRemoves = prevRemoves.filter((attr) => !alteredAttrs.includes(attr)).concat(removes);\n\n DOM.putSticky(el, \"attrs\", currentEl => {\n newRemoves.forEach(attr => currentEl.removeAttribute(attr))\n newSets.forEach(([attr, val]) => currentEl.setAttribute(attr, val))\n return [newSets, newRemoves]\n })\n },\n\n hasAllClasses(el, classes){ return classes.every(name => el.classList.contains(name)) },\n\n isToggledOut(el, outClasses){\n return !this.isVisible(el) || this.hasAllClasses(el, outClasses)\n },\n\n filterToEls(sourceEl, {to}){\n return to ? DOM.all(document, to) : [sourceEl]\n },\n\n defaultDisplay(el){\n return {tr: \"table-row\", td: \"table-cell\"}[el.tagName.toLowerCase()] || \"block\"\n }\n}\n\nexport default JS\n", "import {\n BEFORE_UNLOAD_LOADER_TIMEOUT,\n CHECKABLE_INPUTS,\n CONSECUTIVE_RELOADS,\n PHX_AUTO_RECOVER,\n PHX_COMPONENT,\n PHX_CONNECTED_CLASS,\n PHX_DISABLE_WITH,\n PHX_DISABLE_WITH_RESTORE,\n PHX_DISABLED,\n PHX_DISCONNECTED_CLASS,\n PHX_EVENT_CLASSES,\n PHX_ERROR_CLASS,\n PHX_FEEDBACK_FOR,\n PHX_HAS_SUBMITTED,\n PHX_HOOK,\n PHX_PAGE_LOADING,\n PHX_PARENT_ID,\n PHX_PROGRESS,\n PHX_READONLY,\n PHX_REF,\n PHX_REF_SRC,\n PHX_ROOT_ID,\n PHX_SESSION,\n PHX_STATIC,\n PHX_TRACK_STATIC,\n PHX_TRACK_UPLOADS,\n PHX_UPDATE,\n PHX_UPLOAD_REF,\n PHX_VIEW_SELECTOR,\n PHX_MAIN,\n PHX_MOUNTED,\n PUSH_TIMEOUT,\n} from \"./constants\"\n\nimport {\n clone,\n closestPhxBinding,\n isEmpty,\n isEqualObj,\n logError,\n maybe,\n isCid,\n} from \"./utils\"\n\nimport Browser from \"./browser\"\nimport DOM from \"./dom\"\nimport DOMPatch from \"./dom_patch\"\nimport LiveUploader from \"./live_uploader\"\nimport Rendered from \"./rendered\"\nimport ViewHook from \"./view_hook\"\nimport JS from \"./js\"\n\nlet serializeForm = (form, meta, onlyNames = []) => {\n let formData = new FormData(form)\n let toRemove = []\n\n formData.forEach((val, key, _index) => {\n if(val instanceof File){ toRemove.push(key) }\n })\n\n // Cleanup after building fileData\n toRemove.forEach(key => formData.delete(key))\n\n let params = new URLSearchParams()\n for(let [key, val] of formData.entries()){\n if(onlyNames.length === 0 || onlyNames.indexOf(key) >= 0){\n params.append(key, val)\n }\n }\n for(let metaKey in meta){ params.append(metaKey, meta[metaKey]) }\n\n return params.toString()\n}\n\nexport default class View {\n constructor(el, liveSocket, parentView, flash, liveReferer){\n this.isDead = false\n this.liveSocket = liveSocket\n this.flash = flash\n this.parent = parentView\n this.root = parentView ? parentView.root : this\n this.el = el\n this.id = this.el.id\n this.ref = 0\n this.childJoins = 0\n this.loaderTimer = null\n this.pendingDiffs = []\n this.pruningCIDs = []\n this.redirect = false\n this.href = null\n this.joinCount = this.parent ? this.parent.joinCount - 1 : 0\n this.joinPending = true\n this.destroyed = false\n this.joinCallback = function(onDone){ onDone && onDone() }\n this.stopCallback = function(){ }\n this.pendingJoinOps = this.parent ? null : []\n this.viewHooks = {}\n this.uploaders = {}\n this.formSubmits = []\n this.children = this.parent ? null : {}\n this.root.children[this.id] = {}\n this.channel = this.liveSocket.channel(`lv:${this.id}`, () => {\n return {\n redirect: this.redirect ? this.href : undefined,\n url: this.redirect ? undefined : this.href || undefined,\n params: this.connectParams(liveReferer),\n session: this.getSession(),\n static: this.getStatic(),\n flash: this.flash,\n }\n })\n }\n\n setHref(href){ this.href = href }\n\n setRedirect(href){\n this.redirect = true\n this.href = href\n }\n\n isMain(){ return this.el.hasAttribute(PHX_MAIN) }\n\n connectParams(liveReferer){\n let params = this.liveSocket.params(this.el)\n let manifest =\n DOM.all(document, `[${this.binding(PHX_TRACK_STATIC)}]`)\n .map(node => node.src || node.href).filter(url => typeof (url) === \"string\")\n\n if(manifest.length > 0){ params[\"_track_static\"] = manifest }\n params[\"_mounts\"] = this.joinCount\n params[\"_live_referer\"] = liveReferer\n\n return params\n }\n\n isConnected(){ return this.channel.canPush() }\n\n getSession(){ return this.el.getAttribute(PHX_SESSION) }\n\n getStatic(){\n let val = this.el.getAttribute(PHX_STATIC)\n return val === \"\" ? null : val\n }\n\n destroy(callback = function (){ }){\n this.destroyAllChildren()\n this.destroyed = true\n delete this.root.children[this.id]\n if(this.parent){ delete this.root.children[this.parent.id][this.id] }\n clearTimeout(this.loaderTimer)\n let onFinished = () => {\n callback()\n for(let id in this.viewHooks){\n this.destroyHook(this.viewHooks[id])\n }\n }\n\n DOM.markPhxChildDestroyed(this.el)\n\n this.log(\"destroyed\", () => [\"the child has been removed from the parent\"])\n this.channel.leave()\n .receive(\"ok\", onFinished)\n .receive(\"error\", onFinished)\n .receive(\"timeout\", onFinished)\n }\n\n setContainerClasses(...classes){\n this.el.classList.remove(\n PHX_CONNECTED_CLASS,\n PHX_DISCONNECTED_CLASS,\n PHX_ERROR_CLASS\n )\n this.el.classList.add(...classes)\n }\n\n showLoader(timeout){\n clearTimeout(this.loaderTimer)\n if(timeout){\n this.loaderTimer = setTimeout(() => this.showLoader(), timeout)\n } else {\n for(let id in this.viewHooks){ this.viewHooks[id].__disconnected() }\n this.setContainerClasses(PHX_DISCONNECTED_CLASS)\n }\n }\n\n execAll(binding){\n DOM.all(this.el, `[${binding}]`, el => this.liveSocket.execJS(el, el.getAttribute(binding)))\n }\n\n hideLoader(){\n clearTimeout(this.loaderTimer)\n this.setContainerClasses(PHX_CONNECTED_CLASS)\n this.execAll(this.binding(\"connected\"))\n }\n\n triggerReconnected(){\n for(let id in this.viewHooks){ this.viewHooks[id].__reconnected() }\n }\n\n log(kind, msgCallback){\n this.liveSocket.log(this, kind, msgCallback)\n }\n\n transition(time, onStart, onDone = function(){}){\n this.liveSocket.transition(time, onStart, onDone)\n }\n\n withinTargets(phxTarget, callback){\n if(phxTarget instanceof HTMLElement || phxTarget instanceof SVGElement){\n return this.liveSocket.owner(phxTarget, view => callback(view, phxTarget))\n }\n\n if(isCid(phxTarget)){\n let targets = DOM.findComponentNodeList(this.el, phxTarget)\n if(targets.length === 0){\n logError(`no component found matching phx-target of ${phxTarget}`)\n } else {\n callback(this, parseInt(phxTarget))\n }\n } else {\n let targets = Array.from(document.querySelectorAll(phxTarget))\n if(targets.length === 0){ logError(`nothing found matching the phx-target selector \"${phxTarget}\"`) }\n targets.forEach(target => this.liveSocket.owner(target, view => callback(view, target)))\n }\n }\n\n applyDiff(type, rawDiff, callback){\n this.log(type, () => [\"\", clone(rawDiff)])\n let {diff, reply, events, title} = Rendered.extract(rawDiff)\n callback({diff, reply, events})\n if(title){ window.requestAnimationFrame(() => DOM.putTitle(title)) }\n }\n\n onJoin(resp){\n let {rendered, container} = resp\n if(container){\n let [tag, attrs] = container\n this.el = DOM.replaceRootContainer(this.el, tag, attrs)\n }\n this.childJoins = 0\n this.joinPending = true\n this.flash = null\n\n Browser.dropLocal(this.liveSocket.localStorage, window.location.pathname, CONSECUTIVE_RELOADS)\n this.applyDiff(\"mount\", rendered, ({diff, events}) => {\n this.rendered = new Rendered(this.id, diff)\n let [html, streams] = this.renderContainer(null, \"join\")\n this.dropPendingRefs()\n let forms = this.formsForRecovery(html)\n this.joinCount++\n\n if(forms.length > 0){\n forms.forEach(([form, newForm, newCid], i) => {\n this.pushFormRecovery(form, newCid, resp => {\n if(i === forms.length - 1){\n this.onJoinComplete(resp, html, streams, events)\n }\n })\n })\n } else {\n this.onJoinComplete(resp, html, streams, events)\n }\n })\n }\n\n dropPendingRefs(){\n DOM.all(document, `[${PHX_REF_SRC}=\"${this.id}\"][${PHX_REF}]`, el => {\n el.removeAttribute(PHX_REF)\n el.removeAttribute(PHX_REF_SRC)\n })\n }\n\n onJoinComplete({live_patch}, html, streams, events){\n // In order to provide a better experience, we want to join\n // all LiveViews first and only then apply their patches.\n if(this.joinCount > 1 || (this.parent && !this.parent.isJoinPending())){\n return this.applyJoinPatch(live_patch, html, streams, events)\n }\n\n // One downside of this approach is that we need to find phxChildren\n // in the html fragment, instead of directly on the DOM. The fragment\n // also does not include PHX_STATIC, so we need to copy it over from\n // the DOM.\n let newChildren = DOM.findPhxChildrenInFragment(html, this.id).filter(toEl => {\n let fromEl = toEl.id && this.el.querySelector(`[id=\"${toEl.id}\"]`)\n let phxStatic = fromEl && fromEl.getAttribute(PHX_STATIC)\n if(phxStatic){ toEl.setAttribute(PHX_STATIC, phxStatic) }\n return this.joinChild(toEl)\n })\n\n if(newChildren.length === 0){\n if(this.parent){\n this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, streams, events)])\n this.parent.ackJoin(this)\n } else {\n this.onAllChildJoinsComplete()\n this.applyJoinPatch(live_patch, html, streams, events)\n }\n } else {\n this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, streams, events)])\n }\n }\n\n attachTrueDocEl(){\n this.el = DOM.byId(this.id)\n this.el.setAttribute(PHX_ROOT_ID, this.root.id)\n }\n\n execNewMounted(){\n DOM.all(this.el, `[${this.binding(PHX_HOOK)}], [data-phx-${PHX_HOOK}]`, hookEl => {\n this.maybeAddNewHook(hookEl)\n })\n DOM.all(this.el, `[${this.binding(PHX_MOUNTED)}]`, el => this.maybeMounted(el))\n }\n\n applyJoinPatch(live_patch, html, streams, events){\n this.attachTrueDocEl()\n let patch = new DOMPatch(this, this.el, this.id, html, streams, null)\n patch.markPrunableContentForRemoval()\n this.performPatch(patch, false)\n this.joinNewChildren()\n this.execNewMounted()\n\n this.joinPending = false\n this.liveSocket.dispatchEvents(events)\n this.applyPendingUpdates()\n\n if(live_patch){\n let {kind, to} = live_patch\n this.liveSocket.historyPatch(to, kind)\n }\n this.hideLoader()\n if(this.joinCount > 1){ this.triggerReconnected() }\n this.stopCallback()\n }\n\n triggerBeforeUpdateHook(fromEl, toEl){\n this.liveSocket.triggerDOM(\"onBeforeElUpdated\", [fromEl, toEl])\n let hook = this.getHook(fromEl)\n let isIgnored = hook && DOM.isIgnored(fromEl, this.binding(PHX_UPDATE))\n if(hook && !fromEl.isEqualNode(toEl) && !(isIgnored && isEqualObj(fromEl.dataset, toEl.dataset))){\n hook.__beforeUpdate()\n return hook\n }\n }\n\n maybeMounted(el){\n let phxMounted = el.getAttribute(this.binding(PHX_MOUNTED))\n let hasBeenInvoked = phxMounted && DOM.private(el, \"mounted\")\n if(phxMounted && !hasBeenInvoked){\n this.liveSocket.execJS(el, phxMounted)\n DOM.putPrivate(el, \"mounted\", true)\n }\n }\n\n maybeAddNewHook(el, force){\n let newHook = this.addHook(el)\n if(newHook){ newHook.__mounted() }\n }\n\n performPatch(patch, pruneCids){\n let removedEls = []\n let phxChildrenAdded = false\n let updatedHookIds = new Set()\n\n patch.after(\"added\", el => {\n this.liveSocket.triggerDOM(\"onNodeAdded\", [el])\n this.maybeAddNewHook(el)\n if(el.getAttribute){ this.maybeMounted(el) }\n })\n\n patch.after(\"phxChildAdded\", el => {\n if(DOM.isPhxSticky(el)){\n this.liveSocket.joinRootViews()\n } else {\n phxChildrenAdded = true\n }\n })\n\n patch.before(\"updated\", (fromEl, toEl) => {\n let hook = this.triggerBeforeUpdateHook(fromEl, toEl)\n if(hook){ updatedHookIds.add(fromEl.id) }\n })\n\n patch.after(\"updated\", el => {\n if(updatedHookIds.has(el.id)){ this.getHook(el).__updated() }\n })\n\n patch.after(\"discarded\", (el) => {\n if(el.nodeType === Node.ELEMENT_NODE){ removedEls.push(el) }\n })\n\n patch.after(\"transitionsDiscarded\", els => this.afterElementsRemoved(els, pruneCids))\n patch.perform()\n this.afterElementsRemoved(removedEls, pruneCids)\n\n return phxChildrenAdded\n }\n\n afterElementsRemoved(elements, pruneCids){\n let destroyedCIDs = []\n elements.forEach(parent => {\n let components = DOM.all(parent, `[${PHX_COMPONENT}]`)\n let hooks = DOM.all(parent, `[${this.binding(PHX_HOOK)}]`)\n components.concat(parent).forEach(el => {\n let cid = this.componentID(el)\n if(isCid(cid) && destroyedCIDs.indexOf(cid) === -1){ destroyedCIDs.push(cid) }\n })\n hooks.concat(parent).forEach(hookEl => {\n let hook = this.getHook(hookEl)\n hook && this.destroyHook(hook)\n })\n })\n // We should not pruneCids on joins. Otherwise, in case of\n // rejoins, we may notify cids that no longer belong to the\n // current LiveView to be removed.\n if(pruneCids){\n this.maybePushComponentsDestroyed(destroyedCIDs)\n }\n }\n\n joinNewChildren(){\n DOM.findPhxChildren(this.el, this.id).forEach(el => this.joinChild(el))\n }\n\n getChildById(id){ return this.root.children[this.id][id] }\n\n getDescendentByEl(el){\n if(el.id === this.id){\n return this\n } else {\n return this.children[el.getAttribute(PHX_PARENT_ID)][el.id]\n }\n }\n\n destroyDescendent(id){\n for(let parentId in this.root.children){\n for(let childId in this.root.children[parentId]){\n if(childId === id){ return this.root.children[parentId][childId].destroy() }\n }\n }\n }\n\n joinChild(el){\n let child = this.getChildById(el.id)\n if(!child){\n let view = new View(el, this.liveSocket, this)\n this.root.children[this.id][view.id] = view\n view.join()\n this.childJoins++\n return true\n }\n }\n\n isJoinPending(){ return this.joinPending }\n\n ackJoin(_child){\n this.childJoins--\n\n if(this.childJoins === 0){\n if(this.parent){\n this.parent.ackJoin(this)\n } else {\n this.onAllChildJoinsComplete()\n }\n }\n }\n\n onAllChildJoinsComplete(){\n this.joinCallback(() => {\n this.pendingJoinOps.forEach(([view, op]) => {\n if(!view.isDestroyed()){ op() }\n })\n this.pendingJoinOps = []\n })\n }\n\n update(diff, events){\n if(this.isJoinPending() || (this.liveSocket.hasPendingLink() && this.root.isMain())){\n return this.pendingDiffs.push({diff, events})\n }\n\n this.rendered.mergeDiff(diff)\n let phxChildrenAdded = false\n\n // When the diff only contains component diffs, then walk components\n // and patch only the parent component containers found in the diff.\n // Otherwise, patch entire LV container.\n if(this.rendered.isComponentOnlyDiff(diff)){\n this.liveSocket.time(\"component patch complete\", () => {\n let parentCids = DOM.findParentCIDs(this.el, this.rendered.componentCIDs(diff))\n parentCids.forEach(parentCID => {\n if(this.componentPatch(this.rendered.getComponent(diff, parentCID), parentCID)){ phxChildrenAdded = true }\n })\n })\n } else if(!isEmpty(diff)){\n this.liveSocket.time(\"full patch complete\", () => {\n let [html, streams] = this.renderContainer(diff, \"update\")\n let patch = new DOMPatch(this, this.el, this.id, html, streams, null)\n phxChildrenAdded = this.performPatch(patch, true)\n })\n }\n\n this.liveSocket.dispatchEvents(events)\n if(phxChildrenAdded){ this.joinNewChildren() }\n }\n\n renderContainer(diff, kind){\n return this.liveSocket.time(`toString diff (${kind})`, () => {\n let tag = this.el.tagName\n // Don't skip any component in the diff nor any marked as pruned\n // (as they may have been added back)\n let cids = diff ? this.rendered.componentCIDs(diff).concat(this.pruningCIDs) : null\n let [html, streams] = this.rendered.toString(cids)\n return [`<${tag}>${html}${tag}>`, streams]\n })\n }\n\n componentPatch(diff, cid){\n if(isEmpty(diff)) return false\n let [html, streams] = this.rendered.componentToString(cid)\n let patch = new DOMPatch(this, this.el, this.id, html, streams, cid)\n let childrenAdded = this.performPatch(patch, true)\n return childrenAdded\n }\n\n getHook(el){ return this.viewHooks[ViewHook.elementID(el)] }\n\n addHook(el){\n if(ViewHook.elementID(el) || !el.getAttribute){ return }\n let hookName = el.getAttribute(`data-phx-${PHX_HOOK}`) || el.getAttribute(this.binding(PHX_HOOK))\n if(hookName && !this.ownsElement(el)){ return }\n let callbacks = this.liveSocket.getHookCallbacks(hookName)\n\n if(callbacks){\n if(!el.id){ logError(`no DOM ID for hook \"${hookName}\". Hooks require a unique ID on each element.`, el) }\n let hook = new ViewHook(this, el, callbacks)\n this.viewHooks[ViewHook.elementID(hook.el)] = hook\n return hook\n } else if(hookName !== null){\n logError(`unknown hook found for \"${hookName}\"`, el)\n }\n }\n\n destroyHook(hook){\n hook.__destroyed()\n hook.__cleanup__()\n delete this.viewHooks[ViewHook.elementID(hook.el)]\n }\n\n applyPendingUpdates(){\n this.pendingDiffs.forEach(({diff, events}) => this.update(diff, events))\n this.pendingDiffs = []\n this.eachChild(child => child.applyPendingUpdates())\n }\n\n eachChild(callback){\n let children = this.root.children[this.id] || {}\n for(let id in children){ callback(this.getChildById(id)) }\n }\n\n onChannel(event, cb){\n this.liveSocket.onChannel(this.channel, event, resp => {\n if(this.isJoinPending()){\n this.root.pendingJoinOps.push([this, () => cb(resp)])\n } else {\n this.liveSocket.requestDOMUpdate(() => cb(resp))\n }\n })\n }\n\n bindChannel(){\n // The diff event should be handled by the regular update operations.\n // All other operations are queued to be applied only after join.\n this.liveSocket.onChannel(this.channel, \"diff\", (rawDiff) => {\n this.liveSocket.requestDOMUpdate(() => {\n this.applyDiff(\"update\", rawDiff, ({diff, events}) => this.update(diff, events))\n })\n })\n this.onChannel(\"redirect\", ({to, flash}) => this.onRedirect({to, flash}))\n this.onChannel(\"live_patch\", (redir) => this.onLivePatch(redir))\n this.onChannel(\"live_redirect\", (redir) => this.onLiveRedirect(redir))\n this.channel.onError(reason => this.onError(reason))\n this.channel.onClose(reason => this.onClose(reason))\n }\n\n destroyAllChildren(){ this.eachChild(child => child.destroy()) }\n\n onLiveRedirect(redir){\n let {to, kind, flash} = redir\n let url = this.expandURL(to)\n this.liveSocket.historyRedirect(url, kind, flash)\n }\n\n onLivePatch(redir){\n let {to, kind} = redir\n this.href = this.expandURL(to)\n this.liveSocket.historyPatch(to, kind)\n }\n\n expandURL(to){\n return to.startsWith(\"/\") ? `${window.location.protocol}//${window.location.host}${to}` : to\n }\n\n onRedirect({to, flash}){ this.liveSocket.redirect(to, flash) }\n\n isDestroyed(){ return this.destroyed }\n\n joinDead(){ this.isDead = true }\n\n join(callback){\n this.showLoader(this.liveSocket.loaderTimeout)\n this.bindChannel()\n if(this.isMain()){\n this.stopCallback = this.liveSocket.withPageLoading({to: this.href, kind: \"initial\"})\n }\n this.joinCallback = (onDone) => {\n onDone = onDone || function(){}\n callback ? callback(this.joinCount, onDone) : onDone()\n }\n this.liveSocket.wrapPush(this, {timeout: false}, () => {\n return this.channel.join()\n .receive(\"ok\", data => {\n if(!this.isDestroyed()){\n this.liveSocket.requestDOMUpdate(() => this.onJoin(data))\n }\n })\n .receive(\"error\", resp => !this.isDestroyed() && this.onJoinError(resp))\n .receive(\"timeout\", () => !this.isDestroyed() && this.onJoinError({reason: \"timeout\"}))\n })\n }\n\n onJoinError(resp){\n if(resp.reason === \"reload\"){\n this.log(\"error\", () => [`failed mount with ${resp.status}. Falling back to page request`, resp])\n return this.onRedirect({to: this.href})\n } else if(resp.reason === \"unauthorized\" || resp.reason === \"stale\"){\n this.log(\"error\", () => [\"unauthorized live_redirect. Falling back to page request\", resp])\n return this.onRedirect({to: this.href})\n }\n if(resp.redirect || resp.live_redirect){\n this.joinPending = false\n this.channel.leave()\n }\n if(resp.redirect){ return this.onRedirect(resp.redirect) }\n if(resp.live_redirect){ return this.onLiveRedirect(resp.live_redirect) }\n this.log(\"error\", () => [\"unable to join\", resp])\n if(this.liveSocket.isConnected()){ this.liveSocket.reloadWithJitter(this) }\n }\n\n onClose(reason){\n if(this.isDestroyed()){ return }\n if(this.liveSocket.hasPendingLink() && reason !== \"leave\"){\n return this.liveSocket.reloadWithJitter(this)\n }\n this.destroyAllChildren()\n this.liveSocket.dropActiveElement(this)\n // document.activeElement can be null in Internet Explorer 11\n if(document.activeElement){ document.activeElement.blur() }\n if(this.liveSocket.isUnloaded()){\n this.showLoader(BEFORE_UNLOAD_LOADER_TIMEOUT)\n }\n }\n\n onError(reason){\n this.onClose(reason)\n if(this.liveSocket.isConnected()){ this.log(\"error\", () => [\"view crashed\", reason]) }\n if(!this.liveSocket.isUnloaded()){ this.displayError() }\n }\n\n displayError(){\n if(this.isMain()){ DOM.dispatchEvent(window, \"phx:page-loading-start\", {detail: {to: this.href, kind: \"error\"}}) }\n this.showLoader()\n this.setContainerClasses(PHX_DISCONNECTED_CLASS, PHX_ERROR_CLASS)\n this.execAll(this.binding(\"disconnected\"))\n }\n\n pushWithReply(refGenerator, event, payload, onReply = function (){ }){\n if(!this.isConnected()){ return }\n\n let [ref, [el], opts] = refGenerator ? refGenerator() : [null, [], {}]\n let onLoadingDone = function(){ }\n if(opts.page_loading || (el && (el.getAttribute(this.binding(PHX_PAGE_LOADING)) !== null))){\n onLoadingDone = this.liveSocket.withPageLoading({kind: \"element\", target: el})\n }\n\n if(typeof (payload.cid) !== \"number\"){ delete payload.cid }\n return (\n this.liveSocket.wrapPush(this, {timeout: true}, () => {\n return this.channel.push(event, payload, PUSH_TIMEOUT).receive(\"ok\", resp => {\n let finish = (hookReply) => {\n if(resp.redirect){ this.onRedirect(resp.redirect) }\n if(resp.live_patch){ this.onLivePatch(resp.live_patch) }\n if(resp.live_redirect){ this.onLiveRedirect(resp.live_redirect) }\n if(ref !== null){ this.undoRefs(ref) }\n onLoadingDone()\n onReply(resp, hookReply)\n }\n if(resp.diff){\n this.liveSocket.requestDOMUpdate(() => {\n this.applyDiff(\"update\", resp.diff, ({diff, reply, events}) => {\n this.update(diff, events)\n finish(reply)\n })\n })\n } else {\n finish(null)\n }\n })\n })\n )\n }\n\n undoRefs(ref){\n if(!this.isConnected()){ return } // exit if external form triggered\n\n DOM.all(document, `[${PHX_REF_SRC}=\"${this.id}\"][${PHX_REF}=\"${ref}\"]`, el => {\n let disabledVal = el.getAttribute(PHX_DISABLED)\n // remove refs\n el.removeAttribute(PHX_REF)\n el.removeAttribute(PHX_REF_SRC)\n // restore inputs\n if(el.getAttribute(PHX_READONLY) !== null){\n el.readOnly = false\n el.removeAttribute(PHX_READONLY)\n }\n if(disabledVal !== null){\n el.disabled = disabledVal === \"true\" ? true : false\n el.removeAttribute(PHX_DISABLED)\n }\n // remove classes\n PHX_EVENT_CLASSES.forEach(className => DOM.removeClass(el, className))\n // restore disables\n let disableRestore = el.getAttribute(PHX_DISABLE_WITH_RESTORE)\n if(disableRestore !== null){\n el.innerText = disableRestore\n el.removeAttribute(PHX_DISABLE_WITH_RESTORE)\n }\n let toEl = DOM.private(el, PHX_REF)\n if(toEl){\n let hook = this.triggerBeforeUpdateHook(el, toEl)\n DOMPatch.patchEl(el, toEl, this.liveSocket.getActiveElement())\n if(hook){ hook.__updated() }\n DOM.deletePrivate(el, PHX_REF)\n }\n })\n }\n\n putRef(elements, event, opts = {}){\n let newRef = this.ref++\n let disableWith = this.binding(PHX_DISABLE_WITH)\n if(opts.loading){ elements = elements.concat(DOM.all(document, opts.loading))}\n\n elements.forEach(el => {\n el.classList.add(`phx-${event}-loading`)\n el.setAttribute(PHX_REF, newRef)\n el.setAttribute(PHX_REF_SRC, this.el.id)\n let disableText = el.getAttribute(disableWith)\n if(disableText !== null){\n if(!el.getAttribute(PHX_DISABLE_WITH_RESTORE)){\n el.setAttribute(PHX_DISABLE_WITH_RESTORE, el.innerText)\n }\n if(disableText !== \"\"){ el.innerText = disableText }\n el.setAttribute(\"disabled\", \"\")\n }\n })\n return [newRef, elements, opts]\n }\n\n componentID(el){\n let cid = el.getAttribute && el.getAttribute(PHX_COMPONENT)\n return cid ? parseInt(cid) : null\n }\n\n targetComponentID(target, targetCtx, opts = {}){\n if(isCid(targetCtx)){ return targetCtx }\n\n let cidOrSelector = target.getAttribute(this.binding(\"target\"))\n if(isCid(cidOrSelector)){\n return parseInt(cidOrSelector)\n } else if(targetCtx && (cidOrSelector !== null || opts.target)){\n return this.closestComponentID(targetCtx)\n } else {\n return null\n }\n }\n\n closestComponentID(targetCtx){\n if(isCid(targetCtx)){\n return targetCtx\n } else if(targetCtx){\n return maybe(targetCtx.closest(`[${PHX_COMPONENT}]`), el => this.ownsElement(el) && this.componentID(el))\n } else {\n return null\n }\n }\n\n pushHookEvent(targetCtx, event, payload, onReply){\n if(!this.isConnected()){\n this.log(\"hook\", () => [\"unable to push hook event. LiveView not connected\", event, payload])\n return false\n }\n let [ref, els, opts] = this.putRef([], \"hook\")\n this.pushWithReply(() => [ref, els, opts], \"event\", {\n type: \"hook\",\n event: event,\n value: payload,\n cid: this.closestComponentID(targetCtx)\n }, (resp, reply) => onReply(reply, ref))\n\n return ref\n }\n\n extractMeta(el, meta, value){\n let prefix = this.binding(\"value-\")\n for(let i = 0; i < el.attributes.length; i++){\n if(!meta){ meta = {} }\n let name = el.attributes[i].name\n if(name.startsWith(prefix)){ meta[name.replace(prefix, \"\")] = el.getAttribute(name) }\n }\n if(el.value !== undefined){\n if(!meta){ meta = {} }\n meta.value = el.value\n\n if(el.tagName === \"INPUT\" && CHECKABLE_INPUTS.indexOf(el.type) >= 0 && !el.checked){\n delete meta.value\n }\n }\n if(value){\n if(!meta){ meta = {} }\n for(let key in value){ meta[key] = value[key] }\n }\n return meta\n }\n\n pushEvent(type, el, targetCtx, phxEvent, meta, opts = {}){\n this.pushWithReply(() => this.putRef([el], type, opts), \"event\", {\n type: type,\n event: phxEvent,\n value: this.extractMeta(el, meta, opts.value),\n cid: this.targetComponentID(el, targetCtx, opts)\n })\n }\n\n pushFileProgress(fileEl, entryRef, progress, onReply = function (){ }){\n this.liveSocket.withinOwners(fileEl.form, (view, targetCtx) => {\n view.pushWithReply(null, \"progress\", {\n event: fileEl.getAttribute(view.binding(PHX_PROGRESS)),\n ref: fileEl.getAttribute(PHX_UPLOAD_REF),\n entry_ref: entryRef,\n progress: progress,\n cid: view.targetComponentID(fileEl.form, targetCtx)\n }, onReply)\n })\n }\n\n pushInput(inputEl, targetCtx, forceCid, phxEvent, opts, callback){\n let uploads\n let cid = isCid(forceCid) ? forceCid : this.targetComponentID(inputEl.form, targetCtx)\n let refGenerator = () => this.putRef([inputEl, inputEl.form], \"change\", opts)\n let formData\n if(inputEl.getAttribute(this.binding(\"change\"))){\n formData = serializeForm(inputEl.form, {_target: opts._target}, [inputEl.name])\n } else {\n formData = serializeForm(inputEl.form, {_target: opts._target})\n }\n if(DOM.isUploadInput(inputEl) && inputEl.files && inputEl.files.length > 0){\n LiveUploader.trackFiles(inputEl, Array.from(inputEl.files))\n }\n uploads = LiveUploader.serializeUploads(inputEl)\n let event = {\n type: \"form\",\n event: phxEvent,\n value: formData,\n uploads: uploads,\n cid: cid\n }\n this.pushWithReply(refGenerator, \"event\", event, resp => {\n DOM.showError(inputEl, this.liveSocket.binding(PHX_FEEDBACK_FOR))\n if(DOM.isUploadInput(inputEl) && inputEl.getAttribute(\"data-phx-auto-upload\") !== null){\n if(LiveUploader.filesAwaitingPreflight(inputEl).length > 0){\n let [ref, _els] = refGenerator()\n this.uploadFiles(inputEl.form, targetCtx, ref, cid, (_uploads) => {\n callback && callback(resp)\n this.triggerAwaitingSubmit(inputEl.form)\n })\n }\n } else {\n callback && callback(resp)\n }\n })\n }\n\n triggerAwaitingSubmit(formEl){\n let awaitingSubmit = this.getScheduledSubmit(formEl)\n if(awaitingSubmit){\n let [_el, _ref, _opts, callback] = awaitingSubmit\n this.cancelSubmit(formEl)\n callback()\n }\n }\n\n getScheduledSubmit(formEl){\n return this.formSubmits.find(([el, _ref, _opts, _callback]) => el.isSameNode(formEl))\n }\n\n scheduleSubmit(formEl, ref, opts, callback){\n if(this.getScheduledSubmit(formEl)){ return true }\n this.formSubmits.push([formEl, ref, opts, callback])\n }\n\n cancelSubmit(formEl){\n this.formSubmits = this.formSubmits.filter(([el, ref, _callback]) => {\n if(el.isSameNode(formEl)){\n this.undoRefs(ref)\n return false\n } else {\n return true\n }\n })\n }\n\n disableForm(formEl, opts = {}){\n let filterIgnored = el => {\n let userIgnored = closestPhxBinding(el, `${this.binding(PHX_UPDATE)}=ignore`, el.form)\n return !(userIgnored || closestPhxBinding(el, \"data-phx-update=ignore\", el.form))\n }\n let filterDisables = el => {\n return el.hasAttribute(this.binding(PHX_DISABLE_WITH))\n }\n let filterButton = el => el.tagName == \"BUTTON\"\n\n let filterInput = el => [\"INPUT\", \"TEXTAREA\", \"SELECT\"].includes(el.tagName)\n\n let formElements = Array.from(formEl.elements)\n let disables = formElements.filter(filterDisables)\n let buttons = formElements.filter(filterButton).filter(filterIgnored)\n let inputs = formElements.filter(filterInput).filter(filterIgnored)\n\n buttons.forEach(button => {\n button.setAttribute(PHX_DISABLED, button.disabled)\n button.disabled = true\n })\n inputs.forEach(input => {\n input.setAttribute(PHX_READONLY, input.readOnly)\n input.readOnly = true\n if(input.files){\n input.setAttribute(PHX_DISABLED, input.disabled)\n input.disabled = true\n }\n })\n formEl.setAttribute(this.binding(PHX_PAGE_LOADING), \"\")\n return this.putRef([formEl].concat(disables).concat(buttons).concat(inputs), \"submit\", opts)\n }\n\n pushFormSubmit(formEl, targetCtx, phxEvent, opts, onReply){\n let refGenerator = () => this.disableForm(formEl, opts)\n let cid = this.targetComponentID(formEl, targetCtx)\n if(LiveUploader.hasUploadsInProgress(formEl)){\n let [ref, _els] = refGenerator()\n let push = () => this.pushFormSubmit(formEl, targetCtx, phxEvent, opts, onReply)\n return this.scheduleSubmit(formEl, ref, opts, push)\n } else if(LiveUploader.inputsAwaitingPreflight(formEl).length > 0){\n let [ref, els] = refGenerator()\n let proxyRefGen = () => [ref, els, opts]\n this.uploadFiles(formEl, targetCtx, ref, cid, (_uploads) => {\n let formData = serializeForm(formEl, {})\n this.pushWithReply(proxyRefGen, \"event\", {\n type: \"form\",\n event: phxEvent,\n value: formData,\n cid: cid\n }, onReply)\n })\n } else {\n let formData = serializeForm(formEl, {})\n this.pushWithReply(refGenerator, \"event\", {\n type: \"form\",\n event: phxEvent,\n value: formData,\n cid: cid\n }, onReply)\n }\n }\n\n uploadFiles(formEl, targetCtx, ref, cid, onComplete){\n let joinCountAtUpload = this.joinCount\n let inputEls = LiveUploader.activeFileInputs(formEl)\n let numFileInputsInProgress = inputEls.length\n\n // get each file input\n inputEls.forEach(inputEl => {\n let uploader = new LiveUploader(inputEl, this, () => {\n numFileInputsInProgress--\n if(numFileInputsInProgress === 0){ onComplete() }\n });\n\n this.uploaders[inputEl] = uploader\n let entries = uploader.entries().map(entry => entry.toPreflightPayload())\n\n let payload = {\n ref: inputEl.getAttribute(PHX_UPLOAD_REF),\n entries: entries,\n cid: this.targetComponentID(inputEl.form, targetCtx)\n }\n\n this.log(\"upload\", () => [\"sending preflight request\", payload])\n\n this.pushWithReply(null, \"allow_upload\", payload, resp => {\n this.log(\"upload\", () => [\"got preflight response\", resp])\n if(resp.error){\n this.undoRefs(ref)\n let [entry_ref, reason] = resp.error\n this.log(\"upload\", () => [`error for entry ${entry_ref}`, reason])\n } else {\n let onError = (callback) => {\n this.channel.onError(() => {\n if(this.joinCount === joinCountAtUpload){ callback() }\n })\n }\n uploader.initAdapterUpload(resp, onError, this.liveSocket)\n }\n })\n })\n }\n\n dispatchUploads(name, filesOrBlobs){\n let inputs = DOM.findUploadInputs(this.el).filter(el => el.name === name)\n if(inputs.length === 0){ logError(`no live file inputs found matching the name \"${name}\"`) }\n else if(inputs.length > 1){ logError(`duplicate live file inputs found matching the name \"${name}\"`) }\n else { DOM.dispatchEvent(inputs[0], PHX_TRACK_UPLOADS, {detail: {files: filesOrBlobs}}) }\n }\n\n pushFormRecovery(form, newCid, callback){\n this.liveSocket.withinOwners(form, (view, targetCtx) => {\n let input = Array.from(form.elements).find(el => {\n return DOM.isFormInput(el) && el.type !== \"hidden\" && !el.hasAttribute(this.binding(\"change\"))\n })\n let phxEvent = form.getAttribute(this.binding(PHX_AUTO_RECOVER)) || form.getAttribute(this.binding(\"change\"))\n\n JS.exec(\"change\", phxEvent, view, input, [\"push\", {_target: input.name, newCid: newCid, callback: callback}])\n })\n }\n\n pushLinkPatch(href, targetEl, callback){\n let linkRef = this.liveSocket.setPendingLink(href)\n let refGen = targetEl ? () => this.putRef([targetEl], \"click\") : null\n let fallback = () => this.liveSocket.redirect(window.location.href)\n\n let push = this.pushWithReply(refGen, \"live_patch\", {url: href}, resp => {\n this.liveSocket.requestDOMUpdate(() => {\n if(resp.link_redirect){\n this.liveSocket.replaceMain(href, null, callback, linkRef)\n } else {\n if(this.liveSocket.commitPendingLink(linkRef)){\n this.href = href\n }\n this.applyPendingUpdates()\n callback && callback(linkRef)\n }\n })\n })\n\n if(push){\n push.receive(\"timeout\", fallback)\n } else {\n fallback()\n }\n }\n\n formsForRecovery(html){\n if(this.joinCount === 0){ return [] }\n\n let phxChange = this.binding(\"change\")\n let template = document.createElement(\"template\")\n template.innerHTML = html\n\n return (\n DOM.all(this.el, `form[${phxChange}]`)\n .filter(form => form.id && this.ownsElement(form))\n .filter(form => form.elements.length > 0)\n .filter(form => form.getAttribute(this.binding(PHX_AUTO_RECOVER)) !== \"ignore\")\n .map(form => {\n let newForm = template.content.querySelector(`form[id=\"${form.id}\"][${phxChange}=\"${form.getAttribute(phxChange)}\"]`)\n if(newForm){\n return [form, newForm, this.targetComponentID(newForm)]\n } else {\n return [form, null, null]\n }\n })\n .filter(([form, newForm, newCid]) => newForm)\n )\n }\n\n maybePushComponentsDestroyed(destroyedCIDs){\n let willDestroyCIDs = destroyedCIDs.filter(cid => {\n return DOM.findComponentNodeList(this.el, cid).length === 0\n })\n if(willDestroyCIDs.length > 0){\n this.pruningCIDs.push(...willDestroyCIDs)\n\n this.pushWithReply(null, \"cids_will_destroy\", {cids: willDestroyCIDs}, () => {\n // The cids are either back on the page or they will be fully removed,\n // so we can remove them from the pruningCIDs.\n this.pruningCIDs = this.pruningCIDs.filter(cid => willDestroyCIDs.indexOf(cid) !== -1)\n\n // See if any of the cids we wanted to destroy were added back,\n // if they were added back, we don't actually destroy them.\n let completelyDestroyCIDs = willDestroyCIDs.filter(cid => {\n return DOM.findComponentNodeList(this.el, cid).length === 0\n })\n\n if(completelyDestroyCIDs.length > 0){\n this.pushWithReply(null, \"cids_destroyed\", {cids: completelyDestroyCIDs}, (resp) => {\n this.rendered.pruneCIDs(resp.cids)\n })\n }\n })\n }\n }\n\n ownsElement(el){\n let parentViewEl = el.closest(PHX_VIEW_SELECTOR)\n return el.getAttribute(PHX_PARENT_ID) === this.id ||\n (parentViewEl && parentViewEl.id === this.id) ||\n (!parentViewEl && this.isDead)\n }\n\n submitForm(form, targetCtx, phxEvent, opts = {}){\n DOM.putPrivate(form, PHX_HAS_SUBMITTED, true)\n let phxFeedback = this.liveSocket.binding(PHX_FEEDBACK_FOR)\n let inputs = Array.from(form.elements)\n inputs.forEach(input => DOM.putPrivate(input, PHX_HAS_SUBMITTED, true))\n this.liveSocket.blurActiveElement(this)\n this.pushFormSubmit(form, targetCtx, phxEvent, opts, () => {\n inputs.forEach(input => DOM.showError(input, phxFeedback))\n this.liveSocket.restorePreviouslyActiveFocus()\n })\n }\n\n binding(kind){ return this.liveSocket.binding(kind) }\n}\n", "/** Initializes the LiveSocket\n *\n *\n * @param {string} endPoint - The string WebSocket endpoint, ie, `\"wss://example.com/live\"`,\n * `\"/live\"` (inherited host & protocol)\n * @param {Phoenix.Socket} socket - the required Phoenix Socket class imported from \"phoenix\". For example:\n *\n * import {Socket} from \"phoenix\"\n * import {LiveSocket} from \"phoenix_live_view\"\n * let liveSocket = new LiveSocket(\"/live\", Socket, {...})\n *\n * @param {Object} [opts] - Optional configuration. Outside of keys listed below, all\n * configuration is passed directly to the Phoenix Socket constructor.\n * @param {Object} [opts.defaults] - The optional defaults to use for various bindings,\n * such as `phx-debounce`. Supports the following keys:\n *\n * - debounce - the millisecond phx-debounce time. Defaults 300\n * - throttle - the millisecond phx-throttle time. Defaults 300\n *\n * @param {Function} [opts.params] - The optional function for passing connect params.\n * The function receives the element associated with a given LiveView. For example:\n *\n * (el) => {view: el.getAttribute(\"data-my-view-name\", token: window.myToken}\n *\n * @param {string} [opts.bindingPrefix] - The optional prefix to use for all phx DOM annotations.\n * Defaults to \"phx-\".\n * @param {Object} [opts.hooks] - The optional object for referencing LiveView hook callbacks.\n * @param {Object} [opts.uploaders] - The optional object for referencing LiveView uploader callbacks.\n * @param {integer} [opts.loaderTimeout] - The optional delay in milliseconds to wait before apply\n * loading states.\n * @param {integer} [opts.maxReloads] - The maximum reloads before entering failsafe mode.\n * @param {integer} [opts.reloadJitterMin] - The minimum time between normal reload attempts.\n * @param {integer} [opts.reloadJitterMax] - The maximum time between normal reload attempts.\n * @param {integer} [opts.failsafeJitter] - The time between reload attempts in failsafe mode.\n * @param {Function} [opts.viewLogger] - The optional function to log debug information. For example:\n *\n * (view, kind, msg, obj) => console.log(`${view.id} ${kind}: ${msg} - `, obj)\n *\n * @param {Object} [opts.metadata] - The optional object mapping event names to functions for\n * populating event metadata. For example:\n *\n * metadata: {\n * click: (e, el) => {\n * return {\n * ctrlKey: e.ctrlKey,\n * metaKey: e.metaKey,\n * detail: e.detail || 1,\n * }\n * },\n * keydown: (e, el) => {\n * return {\n * key: e.key,\n * ctrlKey: e.ctrlKey,\n * metaKey: e.metaKey,\n * shiftKey: e.shiftKey\n * }\n * }\n * }\n * @param {Object} [opts.sessionStorage] - An optional Storage compatible object\n * Useful when LiveView won't have access to `sessionStorage`. For example, This could\n * happen if a site loads a cross-domain LiveView in an iframe. Example usage:\n *\n * class InMemoryStorage {\n * constructor() { this.storage = {} }\n * getItem(keyName) { return this.storage[keyName] || null }\n * removeItem(keyName) { delete this.storage[keyName] }\n * setItem(keyName, keyValue) { this.storage[keyName] = keyValue }\n * }\n *\n * @param {Object} [opts.localStorage] - An optional Storage compatible object\n * Useful for when LiveView won't have access to `localStorage`.\n * See `opts.sessionStorage` for examples.\n*/\n\nimport {\n BINDING_PREFIX,\n CONSECUTIVE_RELOADS,\n DEFAULTS,\n FAILSAFE_JITTER,\n LOADER_TIMEOUT,\n MAX_RELOADS,\n PHX_DEBOUNCE,\n PHX_DROP_TARGET,\n PHX_HAS_FOCUSED,\n PHX_KEY,\n PHX_LINK_STATE,\n PHX_LIVE_LINK,\n PHX_LV_DEBUG,\n PHX_LV_LATENCY_SIM,\n PHX_LV_PROFILE,\n PHX_MAIN,\n PHX_PARENT_ID,\n PHX_VIEW_SELECTOR,\n PHX_ROOT_ID,\n PHX_THROTTLE,\n PHX_TRACK_UPLOADS,\n PHX_SESSION,\n PHX_FEEDBACK_FOR,\n RELOAD_JITTER_MIN,\n RELOAD_JITTER_MAX,\n} from \"./constants\"\n\nimport {\n clone,\n closestPhxBinding,\n closure,\n debug,\n isObject,\n maybe\n} from \"./utils\"\n\nimport Browser from \"./browser\"\nimport DOM from \"./dom\"\nimport Hooks from \"./hooks\"\nimport LiveUploader from \"./live_uploader\"\nimport View from \"./view\"\nimport JS from \"./js\"\n\nexport default class LiveSocket {\n constructor(url, phxSocket, opts = {}){\n this.unloaded = false\n if(!phxSocket || phxSocket.constructor.name === \"Object\"){\n throw new Error(`\n a phoenix Socket must be provided as the second argument to the LiveSocket constructor. For example:\n\n import {Socket} from \"phoenix\"\n import {LiveSocket} from \"phoenix_live_view\"\n let liveSocket = new LiveSocket(\"/live\", Socket, {...})\n `)\n }\n this.socket = new phxSocket(url, opts)\n this.bindingPrefix = opts.bindingPrefix || BINDING_PREFIX\n this.opts = opts\n this.params = closure(opts.params || {})\n this.viewLogger = opts.viewLogger\n this.metadataCallbacks = opts.metadata || {}\n this.defaults = Object.assign(clone(DEFAULTS), opts.defaults || {})\n this.activeElement = null\n this.prevActive = null\n this.silenced = false\n this.main = null\n this.outgoingMainEl = null\n this.clickStartedAtTarget = null\n this.linkRef = 1\n this.roots = {}\n this.href = window.location.href\n this.pendingLink = null\n this.currentLocation = clone(window.location)\n this.hooks = opts.hooks || {}\n this.uploaders = opts.uploaders || {}\n this.loaderTimeout = opts.loaderTimeout || LOADER_TIMEOUT\n this.reloadWithJitterTimer = null\n this.maxReloads = opts.maxReloads || MAX_RELOADS\n this.reloadJitterMin = opts.reloadJitterMin || RELOAD_JITTER_MIN\n this.reloadJitterMax = opts.reloadJitterMax || RELOAD_JITTER_MAX\n this.failsafeJitter = opts.failsafeJitter || FAILSAFE_JITTER\n this.localStorage = opts.localStorage || window.localStorage\n this.sessionStorage = opts.sessionStorage || window.sessionStorage\n this.boundTopLevelEvents = false\n this.domCallbacks = Object.assign({onNodeAdded: closure(), onBeforeElUpdated: closure()}, opts.dom || {})\n this.transitions = new TransitionSet()\n window.addEventListener(\"pagehide\", _e => {\n this.unloaded = true\n })\n this.socket.onOpen(() => {\n if(this.isUnloaded()){\n // reload page if being restored from back/forward cache and browser does not emit \"pageshow\"\n window.location.reload()\n }\n })\n }\n\n // public\n\n isProfileEnabled(){ return this.sessionStorage.getItem(PHX_LV_PROFILE) === \"true\" }\n\n isDebugEnabled(){ return this.sessionStorage.getItem(PHX_LV_DEBUG) === \"true\" }\n\n isDebugDisabled(){ return this.sessionStorage.getItem(PHX_LV_DEBUG) === \"false\" }\n\n enableDebug(){ this.sessionStorage.setItem(PHX_LV_DEBUG, \"true\") }\n\n enableProfiling(){ this.sessionStorage.setItem(PHX_LV_PROFILE, \"true\") }\n\n disableDebug(){ this.sessionStorage.setItem(PHX_LV_DEBUG, \"false\") }\n\n disableProfiling(){ this.sessionStorage.removeItem(PHX_LV_PROFILE) }\n\n enableLatencySim(upperBoundMs){\n this.enableDebug()\n console.log(\"latency simulator enabled for the duration of this browser session. Call disableLatencySim() to disable\")\n this.sessionStorage.setItem(PHX_LV_LATENCY_SIM, upperBoundMs)\n }\n\n disableLatencySim(){ this.sessionStorage.removeItem(PHX_LV_LATENCY_SIM) }\n\n getLatencySim(){\n let str = this.sessionStorage.getItem(PHX_LV_LATENCY_SIM)\n return str ? parseInt(str) : null\n }\n\n getSocket(){ return this.socket }\n\n connect(){\n // enable debug by default if on localhost and not explicitly disabled\n if(window.location.hostname === \"localhost\" && !this.isDebugDisabled()){ this.enableDebug() }\n let doConnect = () => {\n if(this.joinRootViews()){\n this.bindTopLevelEvents()\n this.socket.connect()\n } else if(this.main){\n this.socket.connect()\n } else {\n this.bindTopLevelEvents({dead: true})\n }\n this.joinDeadView()\n }\n if([\"complete\", \"loaded\", \"interactive\"].indexOf(document.readyState) >= 0){\n doConnect()\n } else {\n document.addEventListener(\"DOMContentLoaded\", () => doConnect())\n }\n }\n\n disconnect(callback){\n clearTimeout(this.reloadWithJitterTimer)\n this.socket.disconnect(callback)\n }\n\n replaceTransport(transport){\n clearTimeout(this.reloadWithJitterTimer)\n this.socket.replaceTransport(transport)\n this.connect()\n }\n\n execJS(el, encodedJS, eventType = null){\n this.owner(el, view => JS.exec(eventType, encodedJS, view, el))\n }\n\n // private\n\n unload(){\n if(this.unloaded){ return }\n if(this.main && this.isConnected()){ this.log(this.main, \"socket\", () => [\"disconnect for page nav\"]) }\n this.unloaded = true\n this.destroyAllViews()\n this.disconnect()\n }\n\n triggerDOM(kind, args){ this.domCallbacks[kind](...args) }\n\n time(name, func){\n if(!this.isProfileEnabled() || !console.time){ return func() }\n console.time(name)\n let result = func()\n console.timeEnd(name)\n return result\n }\n\n log(view, kind, msgCallback){\n if(this.viewLogger){\n let [msg, obj] = msgCallback()\n this.viewLogger(view, kind, msg, obj)\n } else if(this.isDebugEnabled()){\n let [msg, obj] = msgCallback()\n debug(view, kind, msg, obj)\n }\n }\n\n requestDOMUpdate(callback){\n this.transitions.after(callback)\n }\n\n transition(time, onStart, onDone = function(){}){\n this.transitions.addTransition(time, onStart, onDone)\n }\n\n onChannel(channel, event, cb){\n channel.on(event, data => {\n let latency = this.getLatencySim()\n if(!latency){\n cb(data)\n } else {\n setTimeout(() => cb(data), latency)\n }\n })\n }\n\n wrapPush(view, opts, push){\n let latency = this.getLatencySim()\n let oldJoinCount = view.joinCount\n if(!latency){\n if(this.isConnected() && opts.timeout){\n return push().receive(\"timeout\", () => {\n if(view.joinCount === oldJoinCount && !view.isDestroyed()){\n this.reloadWithJitter(view, () => {\n this.log(view, \"timeout\", () => [\"received timeout while communicating with server. Falling back to hard refresh for recovery\"])\n })\n }\n })\n } else {\n return push()\n }\n }\n\n let fakePush = {\n receives: [],\n receive(kind, cb){ this.receives.push([kind, cb]) }\n }\n setTimeout(() => {\n if(view.isDestroyed()){ return }\n fakePush.receives.reduce((acc, [kind, cb]) => acc.receive(kind, cb), push())\n }, latency)\n return fakePush\n }\n\n reloadWithJitter(view, log){\n clearTimeout(this.reloadWithJitterTimer)\n this.disconnect()\n let minMs = this.reloadJitterMin\n let maxMs = this.reloadJitterMax\n let afterMs = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs\n let tries = Browser.updateLocal(this.localStorage, window.location.pathname, CONSECUTIVE_RELOADS, 0, count => count + 1)\n if(tries > this.maxReloads){\n afterMs = this.failsafeJitter\n }\n this.reloadWithJitterTimer = setTimeout(() => {\n // if view has recovered, such as transport replaced, then cancel\n if(view.isDestroyed() || view.isConnected()){ return }\n view.destroy()\n log ? log() : this.log(view, \"join\", () => [`encountered ${tries} consecutive reloads`])\n if(tries > this.maxReloads){\n this.log(view, \"join\", () => [`exceeded ${this.maxReloads} consecutive reloads. Entering failsafe mode`])\n }\n if(this.hasPendingLink()){\n window.location = this.pendingLink\n } else {\n window.location.reload()\n }\n }, afterMs)\n }\n\n getHookCallbacks(name){\n return name && name.startsWith(\"Phoenix.\") ? Hooks[name.split(\".\")[1]] : this.hooks[name]\n }\n\n isUnloaded(){ return this.unloaded }\n\n isConnected(){ return this.socket.isConnected() }\n\n getBindingPrefix(){ return this.bindingPrefix }\n\n binding(kind){ return `${this.getBindingPrefix()}${kind}` }\n\n channel(topic, params){ return this.socket.channel(topic, params) }\n\n joinDeadView(){\n let body = document.body\n if(body && !this.isPhxView(body) && !this.isPhxView(document.firstElementChild)){\n let view = this.newRootView(body)\n view.setHref(this.getHref())\n view.joinDead()\n if(!this.main){ this.main = view }\n window.requestAnimationFrame(() => view.execNewMounted())\n }\n }\n\n joinRootViews(){\n let rootsFound = false\n DOM.all(document, `${PHX_VIEW_SELECTOR}:not([${PHX_PARENT_ID}])`, rootEl => {\n if(!this.getRootById(rootEl.id)){\n let view = this.newRootView(rootEl)\n view.setHref(this.getHref())\n view.join()\n if(rootEl.hasAttribute(PHX_MAIN)){ this.main = view }\n }\n rootsFound = true\n })\n return rootsFound\n }\n\n redirect(to, flash){\n this.unload()\n Browser.redirect(to, flash)\n }\n\n replaceMain(href, flash, callback = null, linkRef = this.setPendingLink(href)){\n let liveReferer = this.currentLocation.href\n this.outgoingMainEl = this.outgoingMainEl || this.main.el\n let newMainEl = DOM.cloneNode(this.outgoingMainEl, \"\")\n this.main.showLoader(this.loaderTimeout)\n this.main.destroy()\n\n this.main = this.newRootView(newMainEl, flash, liveReferer)\n this.main.setRedirect(href)\n this.transitionRemoves()\n this.main.join((joinCount, onDone) => {\n if(joinCount === 1 && this.commitPendingLink(linkRef)){\n this.requestDOMUpdate(() => {\n DOM.findPhxSticky(document).forEach(el => newMainEl.appendChild(el))\n this.outgoingMainEl.replaceWith(newMainEl)\n this.outgoingMainEl = null\n callback && requestAnimationFrame(callback)\n onDone()\n })\n }\n })\n }\n\n transitionRemoves(elements){\n let removeAttr = this.binding(\"remove\")\n elements = elements || DOM.all(document, `[${removeAttr}]`)\n elements.forEach(el => {\n if(document.body.contains(el)){ // skip children already removed\n this.execJS(el, el.getAttribute(removeAttr), \"remove\")\n }\n })\n }\n\n isPhxView(el){ return el.getAttribute && el.getAttribute(PHX_SESSION) !== null }\n\n newRootView(el, flash, liveReferer){\n let view = new View(el, this, null, flash, liveReferer)\n this.roots[view.id] = view\n return view\n }\n\n owner(childEl, callback){\n let view = maybe(childEl.closest(PHX_VIEW_SELECTOR), el => this.getViewByEl(el)) || this.main\n if(view){ callback(view) }\n }\n\n withinOwners(childEl, callback){\n this.owner(childEl, view => callback(view, childEl))\n }\n\n getViewByEl(el){\n let rootId = el.getAttribute(PHX_ROOT_ID)\n return maybe(this.getRootById(rootId), root => root.getDescendentByEl(el))\n }\n\n getRootById(id){ return this.roots[id] }\n\n destroyAllViews(){\n for(let id in this.roots){\n this.roots[id].destroy()\n delete this.roots[id]\n }\n this.main = null\n }\n\n destroyViewByEl(el){\n let root = this.getRootById(el.getAttribute(PHX_ROOT_ID))\n if(root && root.id === el.id){\n root.destroy()\n delete this.roots[root.id]\n } else if(root){\n root.destroyDescendent(el.id)\n }\n }\n\n setActiveElement(target){\n if(this.activeElement === target){ return }\n this.activeElement = target\n let cancel = () => {\n if(target === this.activeElement){ this.activeElement = null }\n target.removeEventListener(\"mouseup\", this)\n target.removeEventListener(\"touchend\", this)\n }\n target.addEventListener(\"mouseup\", cancel)\n target.addEventListener(\"touchend\", cancel)\n }\n\n getActiveElement(){\n if(document.activeElement === document.body){\n return this.activeElement || document.activeElement\n } else {\n // document.activeElement can be null in Internet Explorer 11\n return document.activeElement || document.body\n }\n }\n\n dropActiveElement(view){\n if(this.prevActive && view.ownsElement(this.prevActive)){\n this.prevActive = null\n }\n }\n\n restorePreviouslyActiveFocus(){\n if(this.prevActive && this.prevActive !== document.body){\n this.prevActive.focus()\n }\n }\n\n blurActiveElement(){\n this.prevActive = this.getActiveElement()\n if(this.prevActive !== document.body){ this.prevActive.blur() }\n }\n\n bindTopLevelEvents({dead} = {}){\n if(this.boundTopLevelEvents){ return }\n\n this.boundTopLevelEvents = true\n // enter failsafe reload if server has gone away intentionally, such as \"disconnect\" broadcast\n this.socket.onClose(event => {\n // unload when navigating href or form submit (such as for firefox)\n if(event && event.code === 1001){ return this.unload() }\n // failsafe reload if normal closure and we still have a main LV\n if(event && event.code === 1000 && this.main){ return this.reloadWithJitter(this.main) }\n })\n document.body.addEventListener(\"click\", function (){ }) // ensure all click events bubble for mobile Safari\n window.addEventListener(\"pageshow\", e => {\n if(e.persisted){ // reload page if being restored from back/forward cache\n this.getSocket().disconnect()\n this.withPageLoading({to: window.location.href, kind: \"redirect\"})\n window.location.reload()\n }\n }, true)\n if(!dead){ this.bindNav() }\n this.bindClicks()\n if(!dead){ this.bindForms() }\n this.bind({keyup: \"keyup\", keydown: \"keydown\"}, (e, type, view, targetEl, phxEvent, eventTarget) => {\n let matchKey = targetEl.getAttribute(this.binding(PHX_KEY))\n let pressedKey = e.key && e.key.toLowerCase() // chrome clicked autocompletes send a keydown without key\n if(matchKey && matchKey.toLowerCase() !== pressedKey){ return }\n\n let data = {key: e.key, ...this.eventMeta(type, e, targetEl)}\n JS.exec(type, phxEvent, view, targetEl, [\"push\", {data}])\n })\n this.bind({blur: \"focusout\", focus: \"focusin\"}, (e, type, view, targetEl, phxEvent, eventTarget) => {\n if(!eventTarget){\n let data = {key: e.key, ...this.eventMeta(type, e, targetEl)}\n JS.exec(type, phxEvent, view, targetEl, [\"push\", {data}])\n }\n })\n this.bind({blur: \"blur\", focus: \"focus\"}, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {\n // blur and focus are triggered on document and window. Discard one to avoid dups\n if(phxTarget === \"window\"){\n let data = this.eventMeta(type, e, targetEl)\n JS.exec(type, phxEvent, view, targetEl, [\"push\", {data}])\n }\n })\n window.addEventListener(\"dragover\", e => e.preventDefault())\n window.addEventListener(\"drop\", e => {\n e.preventDefault()\n let dropTargetId = maybe(closestPhxBinding(e.target, this.binding(PHX_DROP_TARGET)), trueTarget => {\n return trueTarget.getAttribute(this.binding(PHX_DROP_TARGET))\n })\n let dropTarget = dropTargetId && document.getElementById(dropTargetId)\n let files = Array.from(e.dataTransfer.files || [])\n if(!dropTarget || dropTarget.disabled || files.length === 0 || !(dropTarget.files instanceof FileList)){ return }\n\n LiveUploader.trackFiles(dropTarget, files, e.dataTransfer)\n dropTarget.dispatchEvent(new Event(\"input\", {bubbles: true}))\n })\n this.on(PHX_TRACK_UPLOADS, e => {\n let uploadTarget = e.target\n if(!DOM.isUploadInput(uploadTarget)){ return }\n let files = Array.from(e.detail.files || []).filter(f => f instanceof File || f instanceof Blob)\n LiveUploader.trackFiles(uploadTarget, files)\n uploadTarget.dispatchEvent(new Event(\"input\", {bubbles: true}))\n })\n }\n\n eventMeta(eventName, e, targetEl){\n let callback = this.metadataCallbacks[eventName]\n return callback ? callback(e, targetEl) : {}\n }\n\n setPendingLink(href){\n this.linkRef++\n this.pendingLink = href\n return this.linkRef\n }\n\n commitPendingLink(linkRef){\n if(this.linkRef !== linkRef){\n return false\n } else {\n this.href = this.pendingLink\n this.pendingLink = null\n return true\n }\n }\n\n getHref(){ return this.href }\n\n hasPendingLink(){ return !!this.pendingLink }\n\n bind(events, callback){\n for(let event in events){\n let browserEventName = events[event]\n\n this.on(browserEventName, e => {\n let binding = this.binding(event)\n let windowBinding = this.binding(`window-${event}`)\n let targetPhxEvent = e.target.getAttribute && e.target.getAttribute(binding)\n if(targetPhxEvent){\n this.debounce(e.target, e, browserEventName, () => {\n this.withinOwners(e.target, view => {\n callback(e, event, view, e.target, targetPhxEvent, null)\n })\n })\n } else {\n DOM.all(document, `[${windowBinding}]`, el => {\n let phxEvent = el.getAttribute(windowBinding)\n this.debounce(el, e, browserEventName, () => {\n this.withinOwners(el, view => {\n callback(e, event, view, el, phxEvent, \"window\")\n })\n })\n })\n }\n })\n }\n }\n\n bindClicks(){\n window.addEventListener(\"click\", e => this.clickStartedAtTarget = e.target)\n this.bindClick(\"click\", \"click\", false)\n this.bindClick(\"mousedown\", \"capture-click\", true)\n }\n\n bindClick(eventName, bindingName, capture){\n let click = this.binding(bindingName)\n window.addEventListener(eventName, e => {\n let target = null\n if(capture){\n target = e.target.matches(`[${click}]`) ? e.target : e.target.querySelector(`[${click}]`)\n } else {\n let clickStartedAtTarget = this.clickStartedAtTarget || e.target\n target = closestPhxBinding(clickStartedAtTarget, click)\n this.dispatchClickAway(e, clickStartedAtTarget)\n this.clickStartedAtTarget = null\n }\n let phxEvent = target && target.getAttribute(click)\n if(!phxEvent){\n let href = e.target instanceof HTMLAnchorElement ? e.target.getAttribute(\"href\") : null\n if(!capture && href !== null && !DOM.wantsNewTab(e) && DOM.isNewPageHref(href, window.location)){\n this.unload()\n }\n return\n }\n if(target.getAttribute(\"href\") === \"#\"){ e.preventDefault() }\n\n this.debounce(target, e, \"click\", () => {\n this.withinOwners(target, view => {\n JS.exec(\"click\", phxEvent, view, target, [\"push\", {data: this.eventMeta(\"click\", e, target)}])\n })\n })\n }, capture)\n }\n\n dispatchClickAway(e, clickStartedAt){\n let phxClickAway = this.binding(\"click-away\")\n DOM.all(document, `[${phxClickAway}]`, el => {\n if(!(el.isSameNode(clickStartedAt) || el.contains(clickStartedAt))){\n this.withinOwners(e.target, view => {\n let phxEvent = el.getAttribute(phxClickAway)\n if(JS.isVisible(el)){\n JS.exec(\"click\", phxEvent, view, el, [\"push\", {data: this.eventMeta(\"click\", e, e.target)}])\n }\n })\n }\n })\n }\n\n bindNav(){\n if(!Browser.canPushState()){ return }\n if(history.scrollRestoration){ history.scrollRestoration = \"manual\" }\n let scrollTimer = null\n window.addEventListener(\"scroll\", _e => {\n clearTimeout(scrollTimer)\n scrollTimer = setTimeout(() => {\n Browser.updateCurrentState(state => Object.assign(state, {scroll: window.scrollY}))\n }, 100)\n })\n window.addEventListener(\"popstate\", event => {\n if(!this.registerNewLocation(window.location)){ return }\n let {type, id, root, scroll} = event.state || {}\n let href = window.location.href\n\n this.requestDOMUpdate(() => {\n if(this.main.isConnected() && (type === \"patch\" && id === this.main.id)){\n this.main.pushLinkPatch(href, null, () => {\n this.maybeScroll(scroll)\n })\n } else {\n this.replaceMain(href, null, () => {\n if(root){ this.replaceRootHistory() }\n this.maybeScroll(scroll)\n })\n }\n })\n }, false)\n window.addEventListener(\"click\", e => {\n let target = closestPhxBinding(e.target, PHX_LIVE_LINK)\n let type = target && target.getAttribute(PHX_LIVE_LINK)\n if(!type || !this.isConnected() || !this.main || DOM.wantsNewTab(e)){ return }\n\n let href = target.href\n let linkState = target.getAttribute(PHX_LINK_STATE)\n e.preventDefault()\n e.stopImmediatePropagation() // do not bubble click to regular phx-click bindings\n if(this.pendingLink === href){ return }\n\n this.requestDOMUpdate(() => {\n if(type === \"patch\"){\n this.pushHistoryPatch(href, linkState, target)\n } else if(type === \"redirect\"){\n this.historyRedirect(href, linkState)\n } else {\n throw new Error(`expected ${PHX_LIVE_LINK} to be \"patch\" or \"redirect\", got: ${type}`)\n }\n let phxClick = target.getAttribute(this.binding(\"click\"))\n if(phxClick){\n this.requestDOMUpdate(() => this.execJS(target, phxClick, \"click\"))\n }\n })\n }, false)\n }\n\n maybeScroll(scroll) {\n if(typeof(scroll) === \"number\"){\n requestAnimationFrame(() => {\n window.scrollTo(0, scroll)\n }) // the body needs to render before we scroll.\n }\n }\n\n dispatchEvent(event, payload = {}){\n DOM.dispatchEvent(window, `phx:${event}`, {detail: payload})\n }\n\n dispatchEvents(events){\n events.forEach(([event, payload]) => this.dispatchEvent(event, payload))\n }\n\n withPageLoading(info, callback){\n DOM.dispatchEvent(window, \"phx:page-loading-start\", {detail: info})\n let done = () => DOM.dispatchEvent(window, \"phx:page-loading-stop\", {detail: info})\n return callback ? callback(done) : done\n }\n\n pushHistoryPatch(href, linkState, targetEl){\n if(!this.isConnected()){ return Browser.redirect(href) }\n\n this.withPageLoading({to: href, kind: \"patch\"}, done => {\n this.main.pushLinkPatch(href, targetEl, linkRef => {\n this.historyPatch(href, linkState, linkRef)\n done()\n })\n })\n }\n\n historyPatch(href, linkState, linkRef = this.setPendingLink(href)){\n if(!this.commitPendingLink(linkRef)){ return }\n\n Browser.pushState(linkState, {type: \"patch\", id: this.main.id}, href)\n this.registerNewLocation(window.location)\n }\n\n historyRedirect(href, linkState, flash){\n // convert to full href if only path prefix\n if(!this.isConnected()){ return Browser.redirect(href, flash) }\n if(/^\\/$|^\\/[^\\/]+.*$/.test(href)){\n let {protocol, host} = window.location\n href = `${protocol}//${host}${href}`\n }\n let scroll = window.scrollY\n this.withPageLoading({to: href, kind: \"redirect\"}, done => {\n this.replaceMain(href, flash, () => {\n Browser.pushState(linkState, {type: \"redirect\", id: this.main.id, scroll: scroll}, href)\n this.registerNewLocation(window.location)\n done()\n })\n })\n }\n\n replaceRootHistory(){\n Browser.pushState(\"replace\", {root: true, type: \"patch\", id: this.main.id})\n }\n\n registerNewLocation(newLocation){\n let {pathname, search} = this.currentLocation\n if(pathname + search === newLocation.pathname + newLocation.search){\n return false\n } else {\n this.currentLocation = clone(newLocation)\n return true\n }\n }\n\n bindForms(){\n let iterations = 0\n let externalFormSubmitted = false\n\n // disable forms on submit that track phx-change but perform external submit\n this.on(\"submit\", e => {\n let phxSubmit = e.target.getAttribute(this.binding(\"submit\"))\n let phxChange = e.target.getAttribute(this.binding(\"change\"))\n if(!externalFormSubmitted && phxChange && !phxSubmit){\n externalFormSubmitted = true\n e.preventDefault()\n this.withinOwners(e.target, view => {\n view.disableForm(e.target)\n // safari needs next tick\n window.requestAnimationFrame(() => {\n if(DOM.isUnloadableFormSubmit(e)){ this.unload() }\n e.target.submit()\n })\n })\n }\n }, true)\n\n this.on(\"submit\", e => {\n let phxEvent = e.target.getAttribute(this.binding(\"submit\"))\n if(!phxEvent){\n if(DOM.isUnloadableFormSubmit(e)){ this.unload() }\n return\n }\n e.preventDefault()\n e.target.disabled = true\n this.withinOwners(e.target, view => {\n JS.exec(\"submit\", phxEvent, view, e.target, [\"push\", {}])\n })\n }, false)\n\n for(let type of [\"change\", \"input\"]){\n this.on(type, e => {\n let phxChange = this.binding(\"change\")\n let input = e.target\n let inputEvent = input.getAttribute(phxChange)\n let formEvent = input.form && input.form.getAttribute(phxChange)\n let phxEvent = inputEvent || formEvent\n if(!phxEvent){ return }\n if(input.type === \"number\" && input.validity && input.validity.badInput){ return }\n\n let dispatcher = inputEvent ? input : input.form\n let currentIterations = iterations\n iterations++\n let {at: at, type: lastType} = DOM.private(input, \"prev-iteration\") || {}\n // detect dup because some browsers dispatch both \"input\" and \"change\"\n if(at === currentIterations - 1 && type !== lastType){ return }\n\n DOM.putPrivate(input, \"prev-iteration\", {at: currentIterations, type: type})\n\n this.debounce(input, e, type, () => {\n this.withinOwners(dispatcher, view => {\n DOM.putPrivate(input, PHX_HAS_FOCUSED, true)\n if(!DOM.isTextualInput(input)){\n this.setActiveElement(input)\n }\n JS.exec(\"change\", phxEvent, view, input, [\"push\", {_target: e.target.name, dispatcher: dispatcher}])\n })\n })\n }, false)\n }\n this.on(\"reset\", (e) => {\n let form = e.target\n DOM.resetForm(form, this.binding(PHX_FEEDBACK_FOR))\n let input = Array.from(form.elements).find(el => el.type === \"reset\")\n // wait until next tick to get updated input value\n window.requestAnimationFrame(() => {\n input.dispatchEvent(new Event(\"input\", {bubbles: true, cancelable: false}))\n })\n })\n }\n\n debounce(el, event, eventType, callback){\n if(eventType === \"blur\" || eventType === \"focusout\"){ return callback() }\n\n let phxDebounce = this.binding(PHX_DEBOUNCE)\n let phxThrottle = this.binding(PHX_THROTTLE)\n let defaultDebounce = this.defaults.debounce.toString()\n let defaultThrottle = this.defaults.throttle.toString()\n\n this.withinOwners(el, view => {\n let asyncFilter = () => !view.isDestroyed() && document.body.contains(el)\n DOM.debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, asyncFilter, () => {\n callback()\n })\n })\n }\n\n silenceEvents(callback){\n this.silenced = true\n callback()\n this.silenced = false\n }\n\n on(event, callback){\n window.addEventListener(event, e => {\n if(!this.silenced){ callback(e) }\n })\n }\n}\n\nclass TransitionSet {\n constructor(){\n this.transitions = new Set()\n this.pendingOps = []\n }\n\n reset(){\n this.transitions.forEach(timer => {\n clearTimeout(timer)\n this.transitions.delete(timer)\n })\n this.flushPendingOps()\n }\n\n after(callback){\n if(this.size() === 0){\n callback()\n } else {\n this.pushPendingOp(callback)\n }\n }\n\n addTransition(time, onStart, onDone){\n onStart()\n let timer = setTimeout(() => {\n this.transitions.delete(timer)\n onDone()\n this.flushPendingOps()\n }, time)\n this.transitions.add(timer)\n }\n\n pushPendingOp(op){ this.pendingOps.push(op) }\n\n size(){ return this.transitions.size }\n\n flushPendingOps(){\n if(this.size() > 0){ return }\n let op = this.pendingOps.shift()\n if(op){\n op()\n this.flushPendingOps()\n }\n }\n}\n"],
+ "mappings": "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",
"names": []
}
diff --git a/priv/static/phoenix_live_view.esm.js b/priv/static/phoenix_live_view.esm.js
index 51b679ec0a..4e2dbb958c 100644
--- a/priv/static/phoenix_live_view.esm.js
+++ b/priv/static/phoenix_live_view.esm.js
@@ -1,7 +1,8 @@
// js/phoenix_live_view/constants.js
var CONSECUTIVE_RELOADS = "consecutive-reloads";
var MAX_RELOADS = 10;
-var RELOAD_JITTER = [1e3, 3e3];
+var RELOAD_JITTER_MIN = 5e3;
+var RELOAD_JITTER_MAX = 1e4;
var FAILSAFE_JITTER = 3e4;
var PHX_EVENT_CLASSES = [
"phx-click-loading",
@@ -17,6 +18,7 @@ var PHX_LIVE_LINK = "data-phx-link";
var PHX_TRACK_STATIC = "track-static";
var PHX_LINK_STATE = "data-phx-link-state";
var PHX_REF = "data-phx-ref";
+var PHX_REF_SRC = "data-phx-ref-src";
var PHX_TRACK_UPLOADS = "track-uploads";
var PHX_UPLOAD_REF = "data-phx-upload-ref";
var PHX_PREFLIGHTED_REFS = "data-phx-preflighted-refs";
@@ -25,10 +27,10 @@ var PHX_DROP_TARGET = "drop-target";
var PHX_ACTIVE_ENTRY_REFS = "data-phx-active-refs";
var PHX_LIVE_FILE_UPDATED = "phx:live-file:updated";
var PHX_SKIP = "data-phx-skip";
-var PHX_REMOVE = "data-phx-remove";
+var PHX_PRUNE = "data-phx-prune";
var PHX_PAGE_LOADING = "page-loading";
var PHX_CONNECTED_CLASS = "phx-connected";
-var PHX_DISCONNECTED_CLASS = "phx-disconnected";
+var PHX_DISCONNECTED_CLASS = "phx-loading";
var PHX_NO_FEEDBACK_CLASS = "phx-no-feedback";
var PHX_ERROR_CLASS = "phx-error";
var PHX_PARENT_ID = "data-phx-parent-id";
@@ -37,11 +39,12 @@ var PHX_ROOT_ID = "data-phx-root-id";
var PHX_TRIGGER_ACTION = "trigger-action";
var PHX_FEEDBACK_FOR = "feedback-for";
var PHX_HAS_FOCUSED = "phx-has-focused";
-var FOCUSABLE_INPUTS = ["text", "textarea", "number", "email", "password", "search", "tel", "url", "date", "time"];
+var FOCUSABLE_INPUTS = ["text", "textarea", "number", "email", "password", "search", "tel", "url", "date", "time", "datetime-local", "color", "range"];
var CHECKABLE_INPUTS = ["checkbox", "radio"];
var PHX_HAS_SUBMITTED = "phx-has-submitted";
var PHX_SESSION = "data-phx-session";
var PHX_VIEW_SELECTOR = `[${PHX_SESSION}]`;
+var PHX_STICKY = "data-phx-sticky";
var PHX_STATIC = "data-phx-static";
var PHX_READONLY = "data-phx-readonly";
var PHX_DISABLED = "data-phx-disabled";
@@ -51,6 +54,7 @@ var PHX_HOOK = "hook";
var PHX_DEBOUNCE = "debounce";
var PHX_THROTTLE = "throttle";
var PHX_UPDATE = "update";
+var PHX_STREAM = "stream";
var PHX_KEY = "key";
var PHX_PRIVATE = "phxPrivate";
var PHX_AUTO_RECOVER = "auto-recover";
@@ -58,6 +62,7 @@ var PHX_LV_DEBUG = "phx:live-socket:debug";
var PHX_LV_PROFILE = "phx:live-socket:profiling";
var PHX_LV_LATENCY_SIM = "phx:live-socket:latency-sim";
var PHX_PROGRESS = "progress";
+var PHX_MOUNTED = "mounted";
var LOADER_TIMEOUT = 1;
var BEFORE_UNLOAD_LOADER_TIMEOUT = 200;
var BINDING_PREFIX = "phx-";
@@ -75,6 +80,8 @@ var COMPONENTS = "c";
var EVENTS = "e";
var REPLY = "r";
var TITLE = "t";
+var TEMPLATES = "p";
+var STREAM = "stream";
// js/phoenix_live_view/entry_uploader.js
var EntryUploader = class {
@@ -126,7 +133,10 @@ var EntryUploader = class {
// js/phoenix_live_view/utils.js
var logError = (msg, obj) => console.error && console.error(msg, obj);
-var isCid = (cid) => typeof cid === "number";
+var isCid = (cid) => {
+ let type = typeof cid;
+ return type === "number" || type === "string" && /^(0|[1-9]\d*)$/.test(cid);
+};
function detectDuplicateIds() {
let ids = new Set();
let elems = document.querySelectorAll("*[id]");
@@ -151,7 +161,7 @@ var clone = (obj) => {
};
var closestPhxBinding = (el, binding, borderEl) => {
do {
- if (el.matches(`[${binding}]`)) {
+ if (el.matches(`[${binding}]`) && !el.disabled) {
return el;
}
el = el.parentElement || el.parentNode;
@@ -281,8 +291,35 @@ var DOM = {
isPhxDestroyed(node) {
return node.id && DOM.private(node, "destroyed") ? true : false;
},
+ wantsNewTab(e) {
+ let wantsNewTab = e.ctrlKey || e.shiftKey || e.metaKey || e.button && e.button === 1;
+ return wantsNewTab || e.target.getAttribute("target") === "_blank";
+ },
+ isUnloadableFormSubmit(e) {
+ return !e.defaultPrevented && !this.wantsNewTab(e);
+ },
+ isNewPageHref(href, currentLocation) {
+ let url;
+ try {
+ url = new URL(href);
+ } catch (e) {
+ try {
+ url = new URL(href, currentLocation);
+ } catch (e2) {
+ return true;
+ }
+ }
+ if (url.host === currentLocation.host && url.protocol === currentLocation.protocol) {
+ if (url.pathname === currentLocation.pathname && url.search === currentLocation.search) {
+ return url.hash === "" && !url.href.endsWith("#");
+ }
+ }
+ return true;
+ },
markPhxChildDestroyed(el) {
- el.setAttribute(PHX_SESSION, "");
+ if (this.isPhxChild(el)) {
+ el.setAttribute(PHX_SESSION, "");
+ }
this.putPrivate(el, "destroyed", true);
},
findPhxChildrenInFragment(html, parentId) {
@@ -296,16 +333,20 @@ var DOM = {
isPhxUpdate(el, phxUpdate, updateTypes) {
return el.getAttribute && updateTypes.indexOf(el.getAttribute(phxUpdate)) >= 0;
},
+ findPhxSticky(el) {
+ return this.all(el, `[${PHX_STICKY}]`);
+ },
findPhxChildren(el, parentId) {
return this.all(el, `${PHX_VIEW_SELECTOR}[${PHX_PARENT_ID}="${parentId}"]`);
},
findParentCIDs(node, cids) {
let initial = new Set(cids);
- return cids.reduce((acc, cid) => {
+ let parentCids = cids.reduce((acc, cid) => {
let selector = `[${PHX_COMPONENT}="${cid}"] [${PHX_COMPONENT}]`;
this.filterWithinSameLiveView(this.all(node, selector), node).map((el) => parseInt(el.getAttribute(PHX_COMPONENT))).forEach((childCID) => acc.delete(childCID));
return acc;
}, initial);
+ return parentCids.size === 0 ? new Set(cids) : parentCids;
},
filterWithinSameLiveView(nodes, parent) {
if (parent.querySelector(PHX_VIEW_SELECTOR)) {
@@ -336,17 +377,29 @@ var DOM = {
}
el[PHX_PRIVATE][key] = value;
},
+ updatePrivate(el, key, defaultVal, updateFunc) {
+ let existing = this.private(el, key);
+ if (existing === void 0) {
+ this.putPrivate(el, key, updateFunc(defaultVal));
+ } else {
+ this.putPrivate(el, key, updateFunc(existing));
+ }
+ },
copyPrivates(target, source) {
if (source[PHX_PRIVATE]) {
- target[PHX_PRIVATE] = clone(source[PHX_PRIVATE]);
+ target[PHX_PRIVATE] = source[PHX_PRIVATE];
}
},
putTitle(str) {
let titleEl = document.querySelector("title");
- let { prefix, suffix } = titleEl.dataset;
- document.title = `${prefix || ""}${str}${suffix || ""}`;
+ if (titleEl) {
+ let { prefix, suffix } = titleEl.dataset;
+ document.title = `${prefix || ""}${str}${suffix || ""}`;
+ } else {
+ document.title = str;
+ }
},
- debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, callback) {
+ debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, asyncFilter, callback) {
let debounce = el.getAttribute(phxDebounce);
let throttle = el.getAttribute(phxThrottle);
if (debounce === "") {
@@ -383,10 +436,18 @@ var DOM = {
} else {
callback();
this.putPrivate(el, THROTTLED, true);
- setTimeout(() => this.triggerCycle(el, DEBOUNCE_TRIGGER), timeout);
+ setTimeout(() => {
+ if (asyncFilter()) {
+ this.triggerCycle(el, DEBOUNCE_TRIGGER);
+ }
+ }, timeout);
}
} else {
- setTimeout(() => this.triggerCycle(el, DEBOUNCE_TRIGGER, currentCycle), timeout);
+ setTimeout(() => {
+ if (asyncFilter()) {
+ this.triggerCycle(el, DEBOUNCE_TRIGGER, currentCycle);
+ }
+ }, timeout);
}
let form = el.form;
if (form && this.once(form, "bind-debounce")) {
@@ -429,14 +490,26 @@ var DOM = {
},
discardError(container, el, phxFeedbackFor) {
let field = el.getAttribute && el.getAttribute(phxFeedbackFor);
- let input = field && container.querySelector(`[id="${field}"], [name="${field}"]`);
+ let input = field && container.querySelector(`[id="${field}"], [name="${field}"], [name="${field}[]"]`);
if (!input) {
return;
}
- if (!(this.private(input, PHX_HAS_FOCUSED) || this.private(input.form, PHX_HAS_SUBMITTED))) {
+ if (!(this.private(input, PHX_HAS_FOCUSED) || this.private(input, PHX_HAS_SUBMITTED))) {
el.classList.add(PHX_NO_FEEDBACK_CLASS);
}
},
+ resetForm(form, phxFeedbackFor) {
+ Array.from(form.elements).forEach((input) => {
+ let query = `[${phxFeedbackFor}="${input.id}"],
+ [${phxFeedbackFor}="${input.name}"],
+ [${phxFeedbackFor}="${input.name.replace(/\[\]$/, "")}"]`;
+ this.deletePrivate(input, PHX_HAS_FOCUSED);
+ this.deletePrivate(input, PHX_HAS_SUBMITTED);
+ this.all(document, query, (feedbackEl) => {
+ feedbackEl.classList.add(PHX_NO_FEEDBACK_CLASS);
+ });
+ });
+ },
showError(inputEl, phxFeedbackFor) {
if (inputEl.id || inputEl.name) {
this.all(inputEl.form, `[${phxFeedbackFor}="${inputEl.id}"], [${phxFeedbackFor}="${inputEl.name}"]`, (el) => {
@@ -447,8 +520,16 @@ var DOM = {
isPhxChild(node) {
return node.getAttribute && node.getAttribute(PHX_PARENT_ID);
},
- dispatchEvent(target, eventString, detail = {}) {
- let event = new CustomEvent(eventString, { bubbles: true, cancelable: true, detail });
+ isPhxSticky(node) {
+ return node.getAttribute && node.getAttribute(PHX_STICKY) !== null;
+ },
+ firstPhxChild(el) {
+ return this.isPhxChild(el) ? el : this.all(el, `[${PHX_PARENT_ID}]`)[0];
+ },
+ dispatchEvent(target, name, opts = {}) {
+ let bubbles = opts.bubbles === void 0 ? true : !!opts.bubbles;
+ let eventOpts = { bubbles, cancelable: true, detail: opts.detail || {} };
+ let event = name === "click" ? new MouseEvent("click", eventOpts) : new CustomEvent(name, eventOpts);
target.dispatchEvent(event);
},
cloneNode(node, html) {
@@ -486,7 +567,7 @@ var DOM = {
},
mergeFocusedInput(target, source) {
if (!(target instanceof HTMLSelectElement)) {
- DOM.mergeAttrs(target, source, { except: ["value"] });
+ DOM.mergeAttrs(target, source, { exclude: ["value"] });
}
if (source.readOnly) {
target.setAttribute("readonly", true);
@@ -520,14 +601,6 @@ var DOM = {
el.checked = el.getAttribute("checked") !== null;
}
},
- syncPropsToAttrs(el) {
- if (el instanceof HTMLSelectElement) {
- let selectedItem = el.options.item(el.selectedIndex);
- if (selectedItem && selectedItem.getAttribute("selected") === null) {
- selectedItem.setAttribute("selected", "");
- }
- }
- },
isTextualInput(el) {
return FOCUSABLE_INPUTS.indexOf(el.type) >= 0;
},
@@ -539,6 +612,7 @@ var DOM = {
if (ref === null) {
return true;
}
+ let refSrc = fromEl.getAttribute(PHX_REF_SRC);
if (DOM.isFormInput(fromEl) || fromEl.getAttribute(disableWith) !== null) {
if (DOM.isUploadInput(fromEl)) {
DOM.mergeAttrs(fromEl, toEl, { isIgnored: true });
@@ -550,6 +624,7 @@ var DOM = {
fromEl.classList.contains(className) && toEl.classList.add(className);
});
toEl.setAttribute(PHX_REF, ref);
+ toEl.setAttribute(PHX_REF_SRC, refSrc);
return true;
}
},
@@ -573,7 +648,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
}
},
replaceRootContainer(container, tagName, attrs) {
- let retainedAttrs = new Set(["id", PHX_SESSION, PHX_STATIC, PHX_MAIN]);
+ let retainedAttrs = new Set(["id", PHX_SESSION, PHX_STATIC, PHX_MAIN, PHX_ROOT_ID]);
if (container.tagName.toLowerCase() === tagName.toLowerCase()) {
Array.from(container.attributes).filter((attr) => !retainedAttrs.has(attr.name.toLowerCase())).forEach((attr) => container.removeAttribute(attr.name));
Object.keys(attrs).filter((name) => !retainedAttrs.has(name.toLowerCase())).forEach((attr) => container.setAttribute(attr, attrs[attr]));
@@ -586,6 +661,39 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
container.replaceWith(newContainer);
return newContainer;
}
+ },
+ getSticky(el, name, defaultVal) {
+ let op = (DOM.private(el, "sticky") || []).find(([existingName]) => name === existingName);
+ if (op) {
+ let [_name, _op, stashedResult] = op;
+ return stashedResult;
+ } else {
+ return typeof defaultVal === "function" ? defaultVal() : defaultVal;
+ }
+ },
+ deleteSticky(el, name) {
+ this.updatePrivate(el, "sticky", [], (ops) => {
+ return ops.filter(([existingName, _]) => existingName !== name);
+ });
+ },
+ putSticky(el, name, op) {
+ let stashedResult = op(el);
+ this.updatePrivate(el, "sticky", [], (ops) => {
+ let existingIndex = ops.findIndex(([existingName]) => name === existingName);
+ if (existingIndex >= 0) {
+ ops[existingIndex] = [name, op, stashedResult];
+ } else {
+ ops.push([name, op, stashedResult]);
+ }
+ return ops;
+ });
+ },
+ applyStickyOperations(el) {
+ let ops = DOM.private(el, "sticky");
+ if (!ops) {
+ return;
+ }
+ ops.forEach(([name, op, _stashed]) => this.putSticky(el, name, op));
}
};
var dom_default = DOM;
@@ -647,6 +755,7 @@ var UploadEntry = class {
return this._isDone;
}
error(reason = "failed") {
+ this.fileEl.removeEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated);
this.view.pushFileProgress(this.fileEl, this.ref, { error: reason });
LiveUploader.clearFiles(this.fileEl);
}
@@ -666,6 +775,7 @@ var UploadEntry = class {
return {
last_modified: this.file.lastModified,
name: this.file.name,
+ relative_path: this.file.webkitRelativePath,
size: this.file.size,
type: this.file.type,
ref: this.ref
@@ -720,7 +830,9 @@ var LiveUploader = class {
let uploadRef = inputEl.getAttribute(PHX_UPLOAD_REF);
fileData[uploadRef] = fileData[uploadRef] || [];
entry.ref = this.genFileRef(file);
+ entry.last_modified = file.lastModified;
entry.name = file.name || entry.ref;
+ entry.relative_path = file.webkitRelativePath;
entry.type = file.type;
entry.size = file.size;
fileData[uploadRef].push(entry);
@@ -735,12 +847,15 @@ var LiveUploader = class {
static untrackFile(inputEl, file) {
dom_default.putPrivate(inputEl, "files", dom_default.private(inputEl, "files").filter((f) => !Object.is(f, file)));
}
- static trackFiles(inputEl, files) {
+ static trackFiles(inputEl, files, dataTransfer) {
if (inputEl.getAttribute("multiple") !== null) {
let newFiles = files.filter((file) => !this.activeFiles(inputEl).find((f) => Object.is(f, file)));
dom_default.putPrivate(inputEl, "files", this.activeFiles(inputEl).concat(newFiles));
inputEl.value = null;
} else {
+ if (dataTransfer && dataTransfer.files.length > 0) {
+ inputEl.files = dataTransfer.files;
+ }
dom_default.putPrivate(inputEl, "files", files);
}
}
@@ -791,6 +906,62 @@ var LiveUploader = class {
}
};
+// js/phoenix_live_view/aria.js
+var ARIA = {
+ focusMain() {
+ let target = document.querySelector("main h1, main, h1");
+ if (target) {
+ let origTabIndex = target.tabIndex;
+ target.tabIndex = -1;
+ target.focus();
+ target.tabIndex = origTabIndex;
+ }
+ },
+ anyOf(instance, classes) {
+ return classes.find((name) => instance instanceof name);
+ },
+ isFocusable(el, interactiveOnly) {
+ return el instanceof HTMLAnchorElement && el.rel !== "ignore" || el instanceof HTMLAreaElement && el.href !== void 0 || !el.disabled && this.anyOf(el, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, HTMLButtonElement]) || el instanceof HTMLIFrameElement || (el.tabIndex > 0 || !interactiveOnly && el.tabIndex === 0 && el.getAttribute("tabindex") !== null && el.getAttribute("aria-hidden") !== "true");
+ },
+ attemptFocus(el, interactiveOnly) {
+ if (this.isFocusable(el, interactiveOnly)) {
+ try {
+ el.focus();
+ } catch (e) {
+ }
+ }
+ return !!document.activeElement && document.activeElement.isSameNode(el);
+ },
+ focusFirstInteractive(el) {
+ let child = el.firstElementChild;
+ while (child) {
+ if (this.attemptFocus(child, true) || this.focusFirstInteractive(child, true)) {
+ return true;
+ }
+ child = child.nextElementSibling;
+ }
+ },
+ focusFirst(el) {
+ let child = el.firstElementChild;
+ while (child) {
+ if (this.attemptFocus(child) || this.focusFirst(child)) {
+ return true;
+ }
+ child = child.nextElementSibling;
+ }
+ },
+ focusLast(el) {
+ let child = el.lastElementChild;
+ while (child) {
+ if (this.attemptFocus(child) || this.focusLast(child)) {
+ return true;
+ }
+ child = child.previousElementSibling;
+ }
+ }
+};
+var aria_default = ARIA;
+
// js/phoenix_live_view/hooks.js
var Hooks = {
LiveFileUpload: {
@@ -829,6 +1000,18 @@ var Hooks = {
destroyed() {
URL.revokeObjectURL(this.url);
}
+ },
+ FocusWrap: {
+ mounted() {
+ this.focusStart = this.el.firstElementChild;
+ this.focusEnd = this.el.lastElementChild;
+ this.focusStart.addEventListener("focus", () => aria_default.focusLast(this.el));
+ this.focusEnd.addEventListener("focus", () => aria_default.focusFirst(this.el));
+ this.el.addEventListener("phx:show-end", () => this.el.focus());
+ if (window.getComputedStyle(this.el).display !== "none") {
+ aria_default.focusFirst(this.el);
+ }
+ }
}
};
var hooks_default = Hooks;
@@ -1101,6 +1284,8 @@ function morphdomFactory(morphAttrs2) {
} else {
toNode = toElement(toNode);
}
+ } else if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE$1) {
+ toNode = toNode.firstElementChild;
}
var getNodeKey = options.getNodeKey || defaultGetNodeKey;
var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;
@@ -1110,6 +1295,10 @@ function morphdomFactory(morphAttrs2) {
var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;
var onNodeDiscarded = options.onNodeDiscarded || noop;
var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;
+ var skipFromChildren = options.skipFromChildren || noop;
+ var addChild = options.addChild || function(parent, child) {
+ return parent.appendChild(child);
+ };
var childrenOnly = options.childrenOnly === true;
var fromNodesLookup = Object.create(null);
var keyedRemovalList = [];
@@ -1210,6 +1399,7 @@ function morphdomFactory(morphAttrs2) {
}
}
function morphChildren(fromEl, toEl) {
+ var skipFrom = skipFromChildren(fromEl);
var curToNodeChild = toEl.firstChild;
var curFromNodeChild = fromEl.firstChild;
var curToNodeKey;
@@ -1221,7 +1411,7 @@ function morphdomFactory(morphAttrs2) {
while (curToNodeChild) {
toNextSibling = curToNodeChild.nextSibling;
curToNodeKey = getNodeKey(curToNodeChild);
- while (curFromNodeChild) {
+ while (!skipFrom && curFromNodeChild) {
fromNextSibling = curFromNodeChild.nextSibling;
if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {
curToNodeChild = toNextSibling;
@@ -1278,7 +1468,9 @@ function morphdomFactory(morphAttrs2) {
curFromNodeChild = fromNextSibling;
}
if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {
- fromEl.appendChild(matchingFromEl);
+ if (!skipFrom) {
+ addChild(fromEl, matchingFromEl);
+ }
morphEl(matchingFromEl, curToNodeChild);
} else {
var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);
@@ -1289,7 +1481,7 @@ function morphdomFactory(morphAttrs2) {
if (curToNodeChild.actualize) {
curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);
}
- fromEl.appendChild(curToNodeChild);
+ addChild(fromEl, curToNodeChild);
handleNodeAdded(curToNodeChild);
}
}
@@ -1367,15 +1559,19 @@ var DOMPatch = class {
}
});
}
- constructor(view, container, id, html, targetCID) {
+ constructor(view, container, id, html, streams, targetCID) {
this.view = view;
this.liveSocket = view.liveSocket;
this.container = container;
this.id = id;
this.rootID = view.root.id;
this.html = html;
+ this.streams = streams;
+ this.streamInserts = {};
this.targetCID = targetCID;
this.cidPatch = isCid(this.targetCID);
+ this.pendingRemoves = [];
+ this.phxRemove = this.liveSocket.binding("remove");
this.callbacks = {
beforeadded: [],
beforeupdated: [],
@@ -1383,7 +1579,8 @@ var DOMPatch = class {
afteradded: [],
afterupdated: [],
afterdiscarded: [],
- afterphxChildAdded: []
+ afterphxChildAdded: [],
+ aftertransitionsDiscarded: []
};
}
before(kind, callback) {
@@ -1399,8 +1596,10 @@ var DOMPatch = class {
this.callbacks[`after${kind}`].forEach((callback) => callback(...args));
}
markPrunableContentForRemoval() {
- dom_default.all(this.container, "[phx-update=append] > *, [phx-update=prepend] > *", (el) => {
- el.setAttribute(PHX_REMOVE, "");
+ let phxUpdate = this.liveSocket.binding(PHX_UPDATE);
+ dom_default.all(this.container, `[${phxUpdate}=${PHX_STREAM}]`, (el) => el.innerHTML = "");
+ dom_default.all(this.container, `[${phxUpdate}=append] > *, [${phxUpdate}=prepend] > *`, (el) => {
+ el.setAttribute(PHX_PRUNE, "");
});
}
perform() {
@@ -1425,11 +1624,41 @@ var DOMPatch = class {
this.trackBefore("added", container);
this.trackBefore("updated", container, container);
liveSocket.time("morphdom", () => {
+ this.streams.forEach(([inserts, deleteIds]) => {
+ this.streamInserts = Object.assign(this.streamInserts, inserts);
+ deleteIds.forEach((id) => {
+ let child = container.querySelector(`[id="${id}"]`);
+ if (child) {
+ if (!this.maybePendingRemove(child)) {
+ child.remove();
+ this.onNodeDiscarded(child);
+ }
+ }
+ });
+ });
morphdom_esm_default(targetContainer, diffHTML, {
childrenOnly: targetContainer.getAttribute(PHX_COMPONENT) === null,
getNodeKey: (node) => {
return dom_default.isPhxDestroyed(node) ? null : node.id;
},
+ skipFromChildren: (from) => {
+ return from.getAttribute(phxUpdate) === PHX_STREAM;
+ },
+ addChild: (parent, child) => {
+ let streamAt = child.id ? this.streamInserts[child.id] : void 0;
+ if (streamAt === void 0) {
+ return parent.appendChild(child);
+ }
+ dom_default.putPrivate(child, PHX_STREAM, true);
+ if (streamAt === 0) {
+ parent.insertAdjacentElement("afterbegin", child);
+ } else if (streamAt === -1) {
+ parent.appendChild(child);
+ } else if (streamAt > 0) {
+ let sibling = Array.from(parent.children)[streamAt];
+ parent.insertBefore(child, sibling);
+ }
+ },
onBeforeNodeAdded: (el) => {
this.trackBefore("added", el);
return el;
@@ -1444,22 +1673,23 @@ var DOMPatch = class {
externalFormTriggered = el;
}
dom_default.discardError(targetContainer, el, phxFeedbackFor);
- if (dom_default.isPhxChild(el) && view.ownsElement(el)) {
+ if (dom_default.isPhxChild(el) && view.ownsElement(el) || dom_default.isPhxSticky(el) && view.ownsElement(el.parentNode)) {
this.trackAfter("phxChildAdded", el);
}
added.push(el);
},
- onNodeDiscarded: (el) => {
- if (dom_default.isPhxChild(el)) {
- liveSocket.destroyViewByEl(el);
- }
- this.trackAfter("discarded", el);
- },
+ onNodeDiscarded: (el) => this.onNodeDiscarded(el),
onBeforeNodeDiscarded: (el) => {
- if (el.getAttribute && el.getAttribute(PHX_REMOVE) !== null) {
+ if (el.getAttribute && el.getAttribute(PHX_PRUNE) !== null) {
return true;
}
- if (el.parentNode !== null && dom_default.isPhxUpdate(el.parentNode, phxUpdate, ["append", "prepend"]) && el.id) {
+ if (dom_default.private(el, PHX_STREAM)) {
+ return false;
+ }
+ if (el.parentElement !== null && dom_default.isPhxUpdate(el.parentElement, phxUpdate, ["append", "prepend"]) && el.id) {
+ return false;
+ }
+ if (this.maybePendingRemove(el)) {
return false;
}
if (this.skipCIDSibling(el)) {
@@ -1472,16 +1702,21 @@ var DOMPatch = class {
externalFormTriggered = el;
}
updates.push(el);
+ this.maybeReOrderStream(el);
},
onBeforeElUpdated: (fromEl, toEl) => {
dom_default.cleanChildNodes(toEl, phxUpdate);
if (this.skipCIDSibling(toEl)) {
return false;
}
- if (dom_default.isIgnored(fromEl, phxUpdate)) {
+ if (dom_default.isPhxSticky(fromEl)) {
+ return false;
+ }
+ if (dom_default.isIgnored(fromEl, phxUpdate) || fromEl.form && fromEl.form.isSameNode(externalFormTriggered)) {
this.trackBefore("updated", fromEl, toEl);
dom_default.mergeAttrs(fromEl, toEl, { isIgnored: true });
updates.push(fromEl);
+ dom_default.applyStickyOperations(fromEl);
return false;
}
if (fromEl.type === "number" && (fromEl.validity && fromEl.validity.badInput)) {
@@ -1492,6 +1727,7 @@ var DOMPatch = class {
this.trackBefore("updated", fromEl, toEl);
updates.push(fromEl);
}
+ dom_default.applyStickyOperations(fromEl);
return false;
}
if (dom_default.isPhxChild(toEl)) {
@@ -1501,23 +1737,25 @@ var DOMPatch = class {
fromEl.setAttribute(PHX_SESSION, prevSession);
}
fromEl.setAttribute(PHX_ROOT_ID, this.rootID);
+ dom_default.applyStickyOperations(fromEl);
return false;
}
dom_default.copyPrivates(toEl, fromEl);
dom_default.discardError(targetContainer, toEl, phxFeedbackFor);
- dom_default.syncPropsToAttrs(toEl);
let isFocusedFormEl = focused && fromEl.isSameNode(focused) && dom_default.isFormInput(fromEl);
- if (isFocusedFormEl && !this.forceFocusedSelectUpdate(fromEl, toEl)) {
+ if (isFocusedFormEl && fromEl.type !== "hidden") {
this.trackBefore("updated", fromEl, toEl);
dom_default.mergeFocusedInput(fromEl, toEl);
dom_default.syncAttrsToProps(fromEl);
updates.push(fromEl);
+ dom_default.applyStickyOperations(fromEl);
return false;
} else {
if (dom_default.isPhxUpdate(toEl, phxUpdate, ["append", "prepend"])) {
appendPrependUpdates.push(new DOMPostMorphRestorer(fromEl, toEl, toEl.getAttribute(phxUpdate)));
}
dom_default.syncAttrsToProps(toEl);
+ dom_default.applyStickyOperations(toEl);
this.trackBefore("updated", fromEl, toEl);
return true;
}
@@ -1536,15 +1774,65 @@ var DOMPatch = class {
dom_default.dispatchEvent(document, "phx:update");
added.forEach((el) => this.trackAfter("added", el));
updates.forEach((el) => this.trackAfter("updated", el));
+ this.transitionPendingRemoves();
if (externalFormTriggered) {
- liveSocket.disconnect();
+ liveSocket.unload();
externalFormTriggered.submit();
}
return true;
}
- forceFocusedSelectUpdate(fromEl, toEl) {
- let isSelect = ["select", "select-one", "select-multiple"].find((t) => t === fromEl.type);
- return fromEl.multiple === true || isSelect && fromEl.innerHTML != toEl.innerHTML;
+ onNodeDiscarded(el) {
+ if (dom_default.isPhxChild(el) || dom_default.isPhxSticky(el)) {
+ this.liveSocket.destroyViewByEl(el);
+ }
+ this.trackAfter("discarded", el);
+ }
+ maybePendingRemove(node) {
+ if (node.getAttribute && node.getAttribute(this.phxRemove) !== null) {
+ this.pendingRemoves.push(node);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ maybeReOrderStream(el) {
+ let streamAt = el.id ? this.streamInserts[el.id] : void 0;
+ if (streamAt === void 0) {
+ return;
+ }
+ dom_default.putPrivate(el, PHX_STREAM, true);
+ if (streamAt === 0) {
+ el.parentElement.insertBefore(el, el.parentElement.firstElementChild);
+ } else if (streamAt > 0) {
+ let children = Array.from(el.parentElement.children);
+ let oldIndex = children.indexOf(el);
+ if (streamAt >= children.length - 1) {
+ el.parentElement.appendChild(el);
+ } else {
+ let sibling = children[streamAt];
+ if (oldIndex > streamAt) {
+ el.parentElement.insertBefore(el, sibling);
+ } else {
+ el.parentElement.insertBefore(el, sibling.nextElementSibling);
+ }
+ }
+ }
+ }
+ transitionPendingRemoves() {
+ let { pendingRemoves, liveSocket } = this;
+ if (pendingRemoves.length > 0) {
+ liveSocket.transitionRemoves(pendingRemoves);
+ liveSocket.requestDOMUpdate(() => {
+ pendingRemoves.forEach((el) => {
+ let child = dom_default.firstPhxChild(el);
+ if (child) {
+ liveSocket.destroyViewByEl(child);
+ }
+ el.remove();
+ });
+ this.trackAfter("transitionsDiscarded", pendingRemoves);
+ });
+ }
}
isCIDPatch() {
return this.cidPatch;
@@ -1586,6 +1874,9 @@ var DOMPatch = class {
return diffContainer.outerHTML;
}
}
+ indexOf(parent, child) {
+ return Array.from(parent.children).indexOf(child);
+ }
};
// js/phoenix_live_view/rendered.js
@@ -1606,13 +1897,14 @@ var Rendered = class {
return this.viewId;
}
toString(onlyCids) {
- return this.recursiveToString(this.rendered, this.rendered[COMPONENTS], onlyCids);
+ let [str, streams] = this.recursiveToString(this.rendered, this.rendered[COMPONENTS], onlyCids);
+ return [str, streams];
}
recursiveToString(rendered, components = rendered[COMPONENTS], onlyCids) {
onlyCids = onlyCids ? new Set(onlyCids) : null;
- let output = { buffer: "", components, onlyCids };
- this.toOutputBuffer(rendered, output);
- return output.buffer;
+ let output = { buffer: "", components, onlyCids, streams: new Set() };
+ this.toOutputBuffer(rendered, null, output);
+ return [output.buffer, output.streams];
}
componentCIDs(diff) {
return Object.keys(diff[COMPONENTS] || {}).map((i) => parseInt(i));
@@ -1637,8 +1929,8 @@ var Rendered = class {
for (let cid in newc) {
newc[cid] = this.cachedFindComponent(cid, newc[cid], oldc, newc, cache);
}
- for (var key in newc) {
- oldc[key] = newc[key];
+ for (let cid in newc) {
+ oldc[cid] = newc[cid];
}
diff[COMPONENTS] = newc;
}
@@ -1677,7 +1969,8 @@ var Rendered = class {
for (let key in source) {
let val = source[key];
let targetVal = target[key];
- if (isObject(val) && val[STATIC] === void 0 && isObject(targetVal)) {
+ let isObjVal = isObject(val);
+ if (isObjVal && val[STATIC] === void 0 && isObject(targetVal)) {
this.doMutableMerge(targetVal, val);
} else {
target[key] = val;
@@ -1696,7 +1989,8 @@ var Rendered = class {
return merged;
}
componentToString(cid) {
- return this.recursiveCIDToString(this.rendered[COMPONENTS], cid);
+ let [str, streams] = this.recursiveCIDToString(this.rendered[COMPONENTS], cid);
+ return [str, streams];
}
pruneCIDs(cids) {
cids.forEach((cid) => delete this.rendered[COMPONENTS][cid]);
@@ -1707,33 +2001,50 @@ var Rendered = class {
isNewFingerprint(diff = {}) {
return !!diff[STATIC];
}
- toOutputBuffer(rendered, output) {
+ templateStatic(part, templates) {
+ if (typeof part === "number") {
+ return templates[part];
+ } else {
+ return part;
+ }
+ }
+ toOutputBuffer(rendered, templates, output) {
if (rendered[DYNAMICS]) {
- return this.comprehensionToBuffer(rendered, output);
+ return this.comprehensionToBuffer(rendered, templates, output);
}
let { [STATIC]: statics } = rendered;
+ statics = this.templateStatic(statics, templates);
output.buffer += statics[0];
for (let i = 1; i < statics.length; i++) {
- this.dynamicToBuffer(rendered[i - 1], output);
+ this.dynamicToBuffer(rendered[i - 1], templates, output);
output.buffer += statics[i];
}
}
- comprehensionToBuffer(rendered, output) {
- let { [DYNAMICS]: dynamics, [STATIC]: statics } = rendered;
+ comprehensionToBuffer(rendered, templates, output) {
+ let { [DYNAMICS]: dynamics, [STATIC]: statics, [STREAM]: stream } = rendered;
+ let [_inserts, deleteIds] = stream || [{}, []];
+ statics = this.templateStatic(statics, templates);
+ let compTemplates = templates || rendered[TEMPLATES];
for (let d = 0; d < dynamics.length; d++) {
let dynamic = dynamics[d];
output.buffer += statics[0];
for (let i = 1; i < statics.length; i++) {
- this.dynamicToBuffer(dynamic[i - 1], output);
+ this.dynamicToBuffer(dynamic[i - 1], compTemplates, output);
output.buffer += statics[i];
}
}
+ if (stream !== void 0 && (rendered[DYNAMICS].length > 0 || deleteIds.length > 0)) {
+ rendered[DYNAMICS] = [];
+ output.streams.add(stream);
+ }
}
- dynamicToBuffer(rendered, output) {
+ dynamicToBuffer(rendered, templates, output) {
if (typeof rendered === "number") {
- output.buffer += this.recursiveCIDToString(output.components, rendered, output.onlyCids);
+ let [str, streams] = this.recursiveCIDToString(output.components, rendered, output.onlyCids);
+ output.buffer += str;
+ output.streams = new Set([...output.streams, ...streams]);
} else if (isObject(rendered)) {
- this.toOutputBuffer(rendered, output);
+ this.toOutputBuffer(rendered, templates, output);
} else {
output.buffer += rendered;
}
@@ -1741,7 +2052,8 @@ var Rendered = class {
recursiveCIDToString(components, cid, onlyCids) {
let component = components[cid] || logError(`no component for CID ${cid}`, components);
let template = document.createElement("template");
- template.innerHTML = this.recursiveToString(component, components, onlyCids);
+ let [html, streams] = this.recursiveToString(component, components, onlyCids);
+ template.innerHTML = html;
let container = template.content;
let skip = onlyCids && !onlyCids.has(cid);
let [hasChildNodes, hasChildComponents] = Array.from(container.childNodes).reduce(([hasNodes, hasComponents], child, i) => {
@@ -1776,12 +2088,12 @@ within:
}, [false, false]);
if (!hasChildNodes && !hasChildComponents) {
logError("expected at least one HTML element tag inside a component, but the component is empty:\n", template.innerHTML.trim());
- return this.createSpan("", cid).outerHTML;
+ return [this.createSpan("", cid).outerHTML, streams];
} else if (!hasChildNodes && hasChildComponents) {
logError("expected at least one HTML element tag directly inside a component, but only subcomponents were found. A component must render at least one HTML tag directly inside itself.", template.innerHTML.trim());
- return template.innerHTML;
+ return [template.innerHTML, streams];
} else {
- return template.innerHTML;
+ return [template.innerHTML, streams];
}
}
createSpan(text, cid) {
@@ -1803,7 +2115,7 @@ var ViewHook = class {
}
constructor(view, el, callbacks) {
this.__view = view;
- this.__liveSocket = view.liveSocket;
+ this.liveSocket = view.liveSocket;
this.__callbacks = callbacks;
this.__listeners = new Set();
this.__isDisconnected = false;
@@ -1847,13 +2159,13 @@ var ViewHook = class {
}
handleEvent(event, callback) {
let callbackRef = (customEvent, bypass) => bypass ? event : callback(customEvent.detail);
- window.addEventListener(`phx:hook:${event}`, callbackRef);
+ window.addEventListener(`phx:${event}`, callbackRef);
this.__listeners.add(callbackRef);
return callbackRef;
}
removeHandleEvent(callbackRef) {
let event = callbackRef(null, true);
- window.removeEventListener(`phx:hook:${event}`, callbackRef);
+ window.removeEventListener(`phx:${event}`, callbackRef);
this.__listeners.delete(callbackRef);
}
upload(name, files) {
@@ -1867,8 +2179,211 @@ var ViewHook = class {
}
};
+// js/phoenix_live_view/js.js
+var focusStack = null;
+var JS = {
+ exec(eventType, phxEvent, view, sourceEl, defaults) {
+ let [defaultKind, defaultArgs] = defaults || [null, {}];
+ let commands = phxEvent.charAt(0) === "[" ? JSON.parse(phxEvent) : [[defaultKind, defaultArgs]];
+ commands.forEach(([kind, args]) => {
+ if (kind === defaultKind && defaultArgs.data) {
+ args.data = Object.assign(args.data || {}, defaultArgs.data);
+ }
+ this.filterToEls(sourceEl, args).forEach((el) => {
+ this[`exec_${kind}`](eventType, phxEvent, view, sourceEl, el, args);
+ });
+ });
+ },
+ isVisible(el) {
+ return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length > 0);
+ },
+ exec_dispatch(eventType, phxEvent, view, sourceEl, el, { to, event, detail, bubbles }) {
+ detail = detail || {};
+ detail.dispatcher = sourceEl;
+ dom_default.dispatchEvent(el, event, { detail, bubbles });
+ },
+ exec_push(eventType, phxEvent, view, sourceEl, el, args) {
+ if (!view.isConnected()) {
+ return;
+ }
+ let { event, data, target, page_loading, loading, value, dispatcher } = args;
+ let pushOpts = { loading, value, target, page_loading: !!page_loading };
+ let targetSrc = eventType === "change" && dispatcher ? dispatcher : sourceEl;
+ let phxTarget = target || targetSrc.getAttribute(view.binding("target")) || targetSrc;
+ view.withinTargets(phxTarget, (targetView, targetCtx) => {
+ if (eventType === "change") {
+ let { newCid, _target, callback } = args;
+ _target = _target || (dom_default.isFormInput(sourceEl) ? sourceEl.name : void 0);
+ if (_target) {
+ pushOpts._target = _target;
+ }
+ targetView.pushInput(sourceEl, targetCtx, newCid, event || phxEvent, pushOpts, callback);
+ } else if (eventType === "submit") {
+ targetView.submitForm(sourceEl, targetCtx, event || phxEvent, pushOpts);
+ } else {
+ targetView.pushEvent(eventType, sourceEl, targetCtx, event || phxEvent, data, pushOpts);
+ }
+ });
+ },
+ exec_navigate(eventType, phxEvent, view, sourceEl, el, { href, replace }) {
+ view.liveSocket.historyRedirect(href, replace ? "replace" : "push");
+ },
+ exec_patch(eventType, phxEvent, view, sourceEl, el, { href, replace }) {
+ view.liveSocket.pushHistoryPatch(href, replace ? "replace" : "push", sourceEl);
+ },
+ exec_focus(eventType, phxEvent, view, sourceEl, el) {
+ window.requestAnimationFrame(() => aria_default.attemptFocus(el));
+ },
+ exec_focus_first(eventType, phxEvent, view, sourceEl, el) {
+ window.requestAnimationFrame(() => aria_default.focusFirstInteractive(el) || aria_default.focusFirst(el));
+ },
+ exec_push_focus(eventType, phxEvent, view, sourceEl, el) {
+ window.requestAnimationFrame(() => focusStack = el || sourceEl);
+ },
+ exec_pop_focus(eventType, phxEvent, view, sourceEl, el) {
+ window.requestAnimationFrame(() => {
+ if (focusStack) {
+ focusStack.focus();
+ }
+ focusStack = null;
+ });
+ },
+ exec_add_class(eventType, phxEvent, view, sourceEl, el, { names, transition, time }) {
+ this.addOrRemoveClasses(el, names, [], transition, time, view);
+ },
+ exec_remove_class(eventType, phxEvent, view, sourceEl, el, { names, transition, time }) {
+ this.addOrRemoveClasses(el, [], names, transition, time, view);
+ },
+ exec_transition(eventType, phxEvent, view, sourceEl, el, { time, transition }) {
+ this.addOrRemoveClasses(el, [], [], transition, time, view);
+ },
+ exec_toggle(eventType, phxEvent, view, sourceEl, el, { display, ins, outs, time }) {
+ this.toggle(eventType, view, el, display, ins, outs, time);
+ },
+ exec_show(eventType, phxEvent, view, sourceEl, el, { display, transition, time }) {
+ this.show(eventType, view, el, display, transition, time);
+ },
+ exec_hide(eventType, phxEvent, view, sourceEl, el, { display, transition, time }) {
+ this.hide(eventType, view, el, display, transition, time);
+ },
+ exec_set_attr(eventType, phxEvent, view, sourceEl, el, { attr: [attr, val] }) {
+ this.setOrRemoveAttrs(el, [[attr, val]], []);
+ },
+ exec_remove_attr(eventType, phxEvent, view, sourceEl, el, { attr }) {
+ this.setOrRemoveAttrs(el, [], [attr]);
+ },
+ show(eventType, view, el, display, transition, time) {
+ if (!this.isVisible(el)) {
+ this.toggle(eventType, view, el, display, transition, null, time);
+ }
+ },
+ hide(eventType, view, el, display, transition, time) {
+ if (this.isVisible(el)) {
+ this.toggle(eventType, view, el, display, null, transition, time);
+ }
+ },
+ toggle(eventType, view, el, display, ins, outs, time) {
+ let [inClasses, inStartClasses, inEndClasses] = ins || [[], [], []];
+ let [outClasses, outStartClasses, outEndClasses] = outs || [[], [], []];
+ if (inClasses.length > 0 || outClasses.length > 0) {
+ if (this.isVisible(el)) {
+ let onStart = () => {
+ this.addOrRemoveClasses(el, outStartClasses, inClasses.concat(inStartClasses).concat(inEndClasses));
+ window.requestAnimationFrame(() => {
+ this.addOrRemoveClasses(el, outClasses, []);
+ window.requestAnimationFrame(() => this.addOrRemoveClasses(el, outEndClasses, outStartClasses));
+ });
+ };
+ el.dispatchEvent(new Event("phx:hide-start"));
+ view.transition(time, onStart, () => {
+ this.addOrRemoveClasses(el, [], outClasses.concat(outEndClasses));
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = "none");
+ el.dispatchEvent(new Event("phx:hide-end"));
+ });
+ } else {
+ if (eventType === "remove") {
+ return;
+ }
+ let onStart = () => {
+ this.addOrRemoveClasses(el, inStartClasses, outClasses.concat(outStartClasses).concat(outEndClasses));
+ let stickyDisplay = display || this.defaultDisplay(el);
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = stickyDisplay);
+ window.requestAnimationFrame(() => {
+ this.addOrRemoveClasses(el, inClasses, []);
+ window.requestAnimationFrame(() => this.addOrRemoveClasses(el, inEndClasses, inStartClasses));
+ });
+ };
+ el.dispatchEvent(new Event("phx:show-start"));
+ view.transition(time, onStart, () => {
+ this.addOrRemoveClasses(el, [], inClasses.concat(inEndClasses));
+ el.dispatchEvent(new Event("phx:show-end"));
+ });
+ }
+ } else {
+ if (this.isVisible(el)) {
+ window.requestAnimationFrame(() => {
+ el.dispatchEvent(new Event("phx:hide-start"));
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = "none");
+ el.dispatchEvent(new Event("phx:hide-end"));
+ });
+ } else {
+ window.requestAnimationFrame(() => {
+ el.dispatchEvent(new Event("phx:show-start"));
+ let stickyDisplay = display || this.defaultDisplay(el);
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = stickyDisplay);
+ el.dispatchEvent(new Event("phx:show-end"));
+ });
+ }
+ }
+ },
+ addOrRemoveClasses(el, adds, removes, transition, time, view) {
+ let [transition_run, transition_start, transition_end] = transition || [[], [], []];
+ if (transition_run.length > 0) {
+ let onStart = () => this.addOrRemoveClasses(el, transition_start.concat(transition_run), []);
+ let onDone = () => this.addOrRemoveClasses(el, adds.concat(transition_end), removes.concat(transition_run).concat(transition_start));
+ return view.transition(time, onStart, onDone);
+ }
+ window.requestAnimationFrame(() => {
+ let [prevAdds, prevRemoves] = dom_default.getSticky(el, "classes", [[], []]);
+ let keepAdds = adds.filter((name) => prevAdds.indexOf(name) < 0 && !el.classList.contains(name));
+ let keepRemoves = removes.filter((name) => prevRemoves.indexOf(name) < 0 && el.classList.contains(name));
+ let newAdds = prevAdds.filter((name) => removes.indexOf(name) < 0).concat(keepAdds);
+ let newRemoves = prevRemoves.filter((name) => adds.indexOf(name) < 0).concat(keepRemoves);
+ dom_default.putSticky(el, "classes", (currentEl) => {
+ currentEl.classList.remove(...newRemoves);
+ currentEl.classList.add(...newAdds);
+ return [newAdds, newRemoves];
+ });
+ });
+ },
+ setOrRemoveAttrs(el, sets, removes) {
+ let [prevSets, prevRemoves] = dom_default.getSticky(el, "attrs", [[], []]);
+ let alteredAttrs = sets.map(([attr, _val]) => attr).concat(removes);
+ let newSets = prevSets.filter(([attr, _val]) => !alteredAttrs.includes(attr)).concat(sets);
+ let newRemoves = prevRemoves.filter((attr) => !alteredAttrs.includes(attr)).concat(removes);
+ dom_default.putSticky(el, "attrs", (currentEl) => {
+ newRemoves.forEach((attr) => currentEl.removeAttribute(attr));
+ newSets.forEach(([attr, val]) => currentEl.setAttribute(attr, val));
+ return [newSets, newRemoves];
+ });
+ },
+ hasAllClasses(el, classes) {
+ return classes.every((name) => el.classList.contains(name));
+ },
+ isToggledOut(el, outClasses) {
+ return !this.isVisible(el) || this.hasAllClasses(el, outClasses);
+ },
+ filterToEls(sourceEl, { to }) {
+ return to ? dom_default.all(document, to) : [sourceEl];
+ },
+ defaultDisplay(el) {
+ return { tr: "table-row", td: "table-cell" }[el.tagName.toLowerCase()] || "block";
+ }
+};
+var js_default = JS;
+
// js/phoenix_live_view/view.js
-var serializeForm = (form, meta = {}) => {
+var serializeForm = (form, meta, onlyNames = []) => {
let formData = new FormData(form);
let toRemove = [];
formData.forEach((val, key, _index) => {
@@ -1879,7 +2394,9 @@ var serializeForm = (form, meta = {}) => {
toRemove.forEach((key) => formData.delete(key));
let params = new URLSearchParams();
for (let [key, val] of formData.entries()) {
- params.append(key, val);
+ if (onlyNames.length === 0 || onlyNames.indexOf(key) >= 0) {
+ params.append(key, val);
+ }
}
for (let metaKey in meta) {
params.append(metaKey, meta[metaKey]);
@@ -1887,7 +2404,8 @@ var serializeForm = (form, meta = {}) => {
return params.toString();
};
var View = class {
- constructor(el, liveSocket, parentView, flash) {
+ constructor(el, liveSocket, parentView, flash, liveReferer) {
+ this.isDead = false;
this.liveSocket = liveSocket;
this.flash = flash;
this.parent = parentView;
@@ -1904,7 +2422,8 @@ var View = class {
this.joinCount = this.parent ? this.parent.joinCount - 1 : 0;
this.joinPending = true;
this.destroyed = false;
- this.joinCallback = function() {
+ this.joinCallback = function(onDone) {
+ onDone && onDone();
};
this.stopCallback = function() {
};
@@ -1918,14 +2437,12 @@ var View = class {
return {
redirect: this.redirect ? this.href : void 0,
url: this.redirect ? void 0 : this.href || void 0,
- params: this.connectParams(),
+ params: this.connectParams(liveReferer),
session: this.getSession(),
static: this.getStatic(),
flash: this.flash
};
});
- this.showLoader(this.liveSocket.loaderTimeout);
- this.bindChannel();
}
setHref(href) {
this.href = href;
@@ -1935,15 +2452,16 @@ var View = class {
this.href = href;
}
isMain() {
- return this.liveSocket.main === this;
+ return this.el.hasAttribute(PHX_MAIN);
}
- connectParams() {
+ connectParams(liveReferer) {
let params = this.liveSocket.params(this.el);
let manifest = dom_default.all(document, `[${this.binding(PHX_TRACK_STATIC)}]`).map((node) => node.src || node.href).filter((url) => typeof url === "string");
if (manifest.length > 0) {
params["_track_static"] = manifest;
}
params["_mounts"] = this.joinCount;
+ params["_live_referer"] = liveReferer;
return params;
}
isConnected() {
@@ -1979,9 +2497,6 @@ var View = class {
this.el.classList.remove(PHX_CONNECTED_CLASS, PHX_DISCONNECTED_CLASS, PHX_ERROR_CLASS);
this.el.classList.add(...classes);
}
- isLoading() {
- return this.el.classList.contains(PHX_DISCONNECTED_CLASS);
- }
showLoader(timeout) {
clearTimeout(this.loaderTimer);
if (timeout) {
@@ -1993,9 +2508,13 @@ var View = class {
this.setContainerClasses(PHX_DISCONNECTED_CLASS);
}
}
+ execAll(binding) {
+ dom_default.all(this.el, `[${binding}]`, (el) => this.liveSocket.execJS(el, el.getAttribute(binding)));
+ }
hideLoader() {
clearTimeout(this.loaderTimer);
this.setContainerClasses(PHX_CONNECTED_CLASS);
+ this.execAll(this.binding("connected"));
}
triggerReconnected() {
for (let id in this.viewHooks) {
@@ -2005,16 +2524,20 @@ var View = class {
log(kind, msgCallback) {
this.liveSocket.log(this, kind, msgCallback);
}
+ transition(time, onStart, onDone = function() {
+ }) {
+ this.liveSocket.transition(time, onStart, onDone);
+ }
withinTargets(phxTarget, callback) {
- if (phxTarget instanceof HTMLElement) {
+ if (phxTarget instanceof HTMLElement || phxTarget instanceof SVGElement) {
return this.liveSocket.owner(phxTarget, (view) => callback(view, phxTarget));
}
- if (/^(0|[1-9]\d*)$/.test(phxTarget)) {
+ if (isCid(phxTarget)) {
let targets = dom_default.findComponentNodeList(this.el, phxTarget);
if (targets.length === 0) {
logError(`no component found matching phx-target of ${phxTarget}`);
} else {
- callback(this, targets[0]);
+ callback(this, parseInt(phxTarget));
}
} else {
let targets = Array.from(document.querySelectorAll(phxTarget));
@@ -2027,11 +2550,10 @@ var View = class {
applyDiff(type, rawDiff, callback) {
this.log(type, () => ["", clone(rawDiff)]);
let { diff, reply, events, title } = Rendered.extract(rawDiff);
+ callback({ diff, reply, events });
if (title) {
- dom_default.putTitle(title);
+ window.requestAnimationFrame(() => dom_default.putTitle(title));
}
- callback({ diff, reply, events });
- return reply;
}
onJoin(resp) {
let { rendered, container } = resp;
@@ -2045,7 +2567,7 @@ var View = class {
browser_default.dropLocal(this.liveSocket.localStorage, window.location.pathname, CONSECUTIVE_RELOADS);
this.applyDiff("mount", rendered, ({ diff, events }) => {
this.rendered = new Rendered(this.id, diff);
- let html = this.renderContainer(null, "join");
+ let [html, streams] = this.renderContainer(null, "join");
this.dropPendingRefs();
let forms = this.formsForRecovery(html);
this.joinCount++;
@@ -2053,21 +2575,24 @@ var View = class {
forms.forEach(([form, newForm, newCid], i) => {
this.pushFormRecovery(form, newCid, (resp2) => {
if (i === forms.length - 1) {
- this.onJoinComplete(resp2, html, events);
+ this.onJoinComplete(resp2, html, streams, events);
}
});
});
} else {
- this.onJoinComplete(resp, html, events);
+ this.onJoinComplete(resp, html, streams, events);
}
});
}
dropPendingRefs() {
- dom_default.all(this.el, `[${PHX_REF}]`, (el) => el.removeAttribute(PHX_REF));
+ dom_default.all(document, `[${PHX_REF_SRC}="${this.id}"][${PHX_REF}]`, (el) => {
+ el.removeAttribute(PHX_REF);
+ el.removeAttribute(PHX_REF_SRC);
+ });
}
- onJoinComplete({ live_patch }, html, events) {
+ onJoinComplete({ live_patch }, html, streams, events) {
if (this.joinCount > 1 || this.parent && !this.parent.isJoinPending()) {
- return this.applyJoinPatch(live_patch, html, events);
+ return this.applyJoinPatch(live_patch, html, streams, events);
}
let newChildren = dom_default.findPhxChildrenInFragment(html, this.id).filter((toEl) => {
let fromEl = toEl.id && this.el.querySelector(`[id="${toEl.id}"]`);
@@ -2079,39 +2604,35 @@ var View = class {
});
if (newChildren.length === 0) {
if (this.parent) {
- this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, events)]);
+ this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, streams, events)]);
this.parent.ackJoin(this);
} else {
this.onAllChildJoinsComplete();
- this.applyJoinPatch(live_patch, html, events);
+ this.applyJoinPatch(live_patch, html, streams, events);
}
} else {
- this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, events)]);
+ this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, streams, events)]);
}
}
attachTrueDocEl() {
this.el = dom_default.byId(this.id);
this.el.setAttribute(PHX_ROOT_ID, this.root.id);
}
- dispatchEvents(events) {
- events.forEach(([event, payload]) => {
- window.dispatchEvent(new CustomEvent(`phx:hook:${event}`, { detail: payload }));
+ execNewMounted() {
+ dom_default.all(this.el, `[${this.binding(PHX_HOOK)}], [data-phx-${PHX_HOOK}]`, (hookEl) => {
+ this.maybeAddNewHook(hookEl);
});
+ dom_default.all(this.el, `[${this.binding(PHX_MOUNTED)}]`, (el) => this.maybeMounted(el));
}
- applyJoinPatch(live_patch, html, events) {
+ applyJoinPatch(live_patch, html, streams, events) {
this.attachTrueDocEl();
- let patch = new DOMPatch(this, this.el, this.id, html, null);
+ let patch = new DOMPatch(this, this.el, this.id, html, streams, null);
patch.markPrunableContentForRemoval();
this.performPatch(patch, false);
this.joinNewChildren();
- dom_default.all(this.el, `[${this.binding(PHX_HOOK)}], [data-phx-${PHX_HOOK}]`, (hookEl) => {
- let hook = this.addHook(hookEl);
- if (hook) {
- hook.__mounted();
- }
- });
+ this.execNewMounted();
this.joinPending = false;
- this.dispatchEvents(events);
+ this.liveSocket.dispatchEvents(events);
this.applyPendingUpdates();
if (live_patch) {
let { kind, to } = live_patch;
@@ -2132,18 +2653,38 @@ var View = class {
return hook;
}
}
+ maybeMounted(el) {
+ let phxMounted = el.getAttribute(this.binding(PHX_MOUNTED));
+ let hasBeenInvoked = phxMounted && dom_default.private(el, "mounted");
+ if (phxMounted && !hasBeenInvoked) {
+ this.liveSocket.execJS(el, phxMounted);
+ dom_default.putPrivate(el, "mounted", true);
+ }
+ }
+ maybeAddNewHook(el, force) {
+ let newHook = this.addHook(el);
+ if (newHook) {
+ newHook.__mounted();
+ }
+ }
performPatch(patch, pruneCids) {
- let destroyedCIDs = [];
+ let removedEls = [];
let phxChildrenAdded = false;
let updatedHookIds = new Set();
patch.after("added", (el) => {
this.liveSocket.triggerDOM("onNodeAdded", [el]);
- let newHook = this.addHook(el);
- if (newHook) {
- newHook.__mounted();
+ this.maybeAddNewHook(el);
+ if (el.getAttribute) {
+ this.maybeMounted(el);
+ }
+ });
+ patch.after("phxChildAdded", (el) => {
+ if (dom_default.isPhxSticky(el)) {
+ this.liveSocket.joinRootViews();
+ } else {
+ phxChildrenAdded = true;
}
});
- patch.after("phxChildAdded", (_el) => phxChildrenAdded = true);
patch.before("updated", (fromEl, toEl) => {
let hook = this.triggerBeforeUpdateHook(fromEl, toEl);
if (hook) {
@@ -2156,18 +2697,34 @@ var View = class {
}
});
patch.after("discarded", (el) => {
- let cid = this.componentID(el);
- if (isCid(cid) && destroyedCIDs.indexOf(cid) === -1) {
- destroyedCIDs.push(cid);
+ if (el.nodeType === Node.ELEMENT_NODE) {
+ removedEls.push(el);
}
- let hook = this.getHook(el);
- hook && this.destroyHook(hook);
});
+ patch.after("transitionsDiscarded", (els) => this.afterElementsRemoved(els, pruneCids));
patch.perform();
+ this.afterElementsRemoved(removedEls, pruneCids);
+ return phxChildrenAdded;
+ }
+ afterElementsRemoved(elements, pruneCids) {
+ let destroyedCIDs = [];
+ elements.forEach((parent) => {
+ let components = dom_default.all(parent, `[${PHX_COMPONENT}]`);
+ let hooks = dom_default.all(parent, `[${this.binding(PHX_HOOK)}]`);
+ components.concat(parent).forEach((el) => {
+ let cid = this.componentID(el);
+ if (isCid(cid) && destroyedCIDs.indexOf(cid) === -1) {
+ destroyedCIDs.push(cid);
+ }
+ });
+ hooks.concat(parent).forEach((hookEl) => {
+ let hook = this.getHook(hookEl);
+ hook && this.destroyHook(hook);
+ });
+ });
if (pruneCids) {
this.maybePushComponentsDestroyed(destroyedCIDs);
}
- return phxChildrenAdded;
}
joinNewChildren() {
dom_default.findPhxChildren(this.el, this.id).forEach((el) => this.joinChild(el));
@@ -2215,16 +2772,17 @@ var View = class {
}
}
onAllChildJoinsComplete() {
- this.joinCallback();
- this.pendingJoinOps.forEach(([view, op]) => {
- if (!view.isDestroyed()) {
- op();
- }
+ this.joinCallback(() => {
+ this.pendingJoinOps.forEach(([view, op]) => {
+ if (!view.isDestroyed()) {
+ op();
+ }
+ });
+ this.pendingJoinOps = [];
});
- this.pendingJoinOps = [];
}
update(diff, events) {
- if (this.isJoinPending() || this.liveSocket.hasPendingLink()) {
+ if (this.isJoinPending() || this.liveSocket.hasPendingLink() && this.root.isMain()) {
return this.pendingDiffs.push({ diff, events });
}
this.rendered.mergeDiff(diff);
@@ -2240,12 +2798,12 @@ var View = class {
});
} else if (!isEmpty(diff)) {
this.liveSocket.time("full patch complete", () => {
- let html = this.renderContainer(diff, "update");
- let patch = new DOMPatch(this, this.el, this.id, html, null);
+ let [html, streams] = this.renderContainer(diff, "update");
+ let patch = new DOMPatch(this, this.el, this.id, html, streams, null);
phxChildrenAdded = this.performPatch(patch, true);
});
}
- this.dispatchEvents(events);
+ this.liveSocket.dispatchEvents(events);
if (phxChildrenAdded) {
this.joinNewChildren();
}
@@ -2254,15 +2812,15 @@ var View = class {
return this.liveSocket.time(`toString diff (${kind})`, () => {
let tag = this.el.tagName;
let cids = diff ? this.rendered.componentCIDs(diff).concat(this.pruningCIDs) : null;
- let html = this.rendered.toString(cids);
- return `<${tag}>${html}${tag}>`;
+ let [html, streams] = this.rendered.toString(cids);
+ return [`<${tag}>${html}${tag}>`, streams];
});
}
componentPatch(diff, cid) {
if (isEmpty(diff))
return false;
- let html = this.rendered.componentToString(cid);
- let patch = new DOMPatch(this, this.el, this.id, html, cid);
+ let [html, streams] = this.rendered.componentToString(cid);
+ let patch = new DOMPatch(this, this.el, this.id, html, streams, cid);
let childrenAdded = this.performPatch(patch, true);
return childrenAdded;
}
@@ -2297,19 +2855,28 @@ var View = class {
applyPendingUpdates() {
this.pendingDiffs.forEach(({ diff, events }) => this.update(diff, events));
this.pendingDiffs = [];
+ this.eachChild((child) => child.applyPendingUpdates());
+ }
+ eachChild(callback) {
+ let children = this.root.children[this.id] || {};
+ for (let id in children) {
+ callback(this.getChildById(id));
+ }
}
onChannel(event, cb) {
this.liveSocket.onChannel(this.channel, event, (resp) => {
if (this.isJoinPending()) {
this.root.pendingJoinOps.push([this, () => cb(resp)]);
} else {
- cb(resp);
+ this.liveSocket.requestDOMUpdate(() => cb(resp));
}
});
}
bindChannel() {
this.liveSocket.onChannel(this.channel, "diff", (rawDiff) => {
- this.applyDiff("update", rawDiff, ({ diff, events }) => this.update(diff, events));
+ this.liveSocket.requestDOMUpdate(() => {
+ this.applyDiff("update", rawDiff, ({ diff, events }) => this.update(diff, events));
+ });
});
this.onChannel("redirect", ({ to, flash }) => this.onRedirect({ to, flash }));
this.onChannel("live_patch", (redir) => this.onLivePatch(redir));
@@ -2318,9 +2885,7 @@ var View = class {
this.channel.onClose((reason) => this.onClose(reason));
}
destroyAllChildren() {
- for (let id in this.root.children[this.id]) {
- this.getChildById(id).destroy();
- }
+ this.eachChild((child) => child.destroy());
}
onLiveRedirect(redir) {
let { to, kind, flash } = redir;
@@ -2341,17 +2906,33 @@ var View = class {
isDestroyed() {
return this.destroyed;
}
+ joinDead() {
+ this.isDead = true;
+ }
join(callback) {
- if (!this.parent) {
+ this.showLoader(this.liveSocket.loaderTimeout);
+ this.bindChannel();
+ if (this.isMain()) {
this.stopCallback = this.liveSocket.withPageLoading({ to: this.href, kind: "initial" });
}
- this.joinCallback = () => callback && callback(this.joinCount);
+ this.joinCallback = (onDone) => {
+ onDone = onDone || function() {
+ };
+ callback ? callback(this.joinCount, onDone) : onDone();
+ };
this.liveSocket.wrapPush(this, { timeout: false }, () => {
- return this.channel.join().receive("ok", (data) => !this.isDestroyed() && this.onJoin(data)).receive("error", (resp) => !this.isDestroyed() && this.onJoinError(resp)).receive("timeout", () => !this.isDestroyed() && this.onJoinError({ reason: "timeout" }));
+ return this.channel.join().receive("ok", (data) => {
+ if (!this.isDestroyed()) {
+ this.liveSocket.requestDOMUpdate(() => this.onJoin(data));
+ }
+ }).receive("error", (resp) => !this.isDestroyed() && this.onJoinError(resp)).receive("timeout", () => !this.isDestroyed() && this.onJoinError({ reason: "timeout" }));
});
}
onJoinError(resp) {
- if (resp.reason === "unauthorized" || resp.reason === "stale") {
+ if (resp.reason === "reload") {
+ this.log("error", () => [`failed mount with ${resp.status}. Falling back to page request`, resp]);
+ return this.onRedirect({ to: this.href });
+ } else if (resp.reason === "unauthorized" || resp.reason === "stale") {
this.log("error", () => ["unauthorized live_redirect. Falling back to page request", resp]);
return this.onRedirect({ to: this.href });
}
@@ -2366,13 +2947,15 @@ var View = class {
return this.onLiveRedirect(resp.live_redirect);
}
this.log("error", () => ["unable to join", resp]);
- return this.liveSocket.reloadWithJitter(this);
+ if (this.liveSocket.isConnected()) {
+ this.liveSocket.reloadWithJitter(this);
+ }
}
onClose(reason) {
if (this.isDestroyed()) {
return;
}
- if (this.isJoinPending() && document.visibilityState !== "hidden" || this.liveSocket.hasPendingLink() && reason !== "leave") {
+ if (this.liveSocket.hasPendingLink() && reason !== "leave") {
return this.liveSocket.reloadWithJitter(this);
}
this.destroyAllChildren();
@@ -2386,27 +2969,30 @@ var View = class {
}
onError(reason) {
this.onClose(reason);
- this.log("error", () => ["view crashed", reason]);
+ if (this.liveSocket.isConnected()) {
+ this.log("error", () => ["view crashed", reason]);
+ }
if (!this.liveSocket.isUnloaded()) {
this.displayError();
}
}
displayError() {
if (this.isMain()) {
- dom_default.dispatchEvent(window, "phx:page-loading-start", { to: this.href, kind: "error" });
+ dom_default.dispatchEvent(window, "phx:page-loading-start", { detail: { to: this.href, kind: "error" } });
}
this.showLoader();
this.setContainerClasses(PHX_DISCONNECTED_CLASS, PHX_ERROR_CLASS);
+ this.execAll(this.binding("disconnected"));
}
pushWithReply(refGenerator, event, payload, onReply = function() {
}) {
if (!this.isConnected()) {
return;
}
- let [ref, [el]] = refGenerator ? refGenerator() : [null, []];
+ let [ref, [el], opts] = refGenerator ? refGenerator() : [null, [], {}];
let onLoadingDone = function() {
};
- if (el && el.getAttribute(this.binding(PHX_PAGE_LOADING)) !== null) {
+ if (opts.page_loading || el && el.getAttribute(this.binding(PHX_PAGE_LOADING)) !== null) {
onLoadingDone = this.liveSocket.withPageLoading({ kind: "element", target: el });
}
if (typeof payload.cid !== "number") {
@@ -2414,33 +3000,43 @@ var View = class {
}
return this.liveSocket.wrapPush(this, { timeout: true }, () => {
return this.channel.push(event, payload, PUSH_TIMEOUT).receive("ok", (resp) => {
- let hookReply = null;
- if (ref !== null) {
- this.undoRefs(ref);
- }
+ let finish = (hookReply) => {
+ if (resp.redirect) {
+ this.onRedirect(resp.redirect);
+ }
+ if (resp.live_patch) {
+ this.onLivePatch(resp.live_patch);
+ }
+ if (resp.live_redirect) {
+ this.onLiveRedirect(resp.live_redirect);
+ }
+ if (ref !== null) {
+ this.undoRefs(ref);
+ }
+ onLoadingDone();
+ onReply(resp, hookReply);
+ };
if (resp.diff) {
- hookReply = this.applyDiff("update", resp.diff, ({ diff, events }) => {
- this.update(diff, events);
+ this.liveSocket.requestDOMUpdate(() => {
+ this.applyDiff("update", resp.diff, ({ diff, reply, events }) => {
+ this.update(diff, events);
+ finish(reply);
+ });
});
+ } else {
+ finish(null);
}
- if (resp.redirect) {
- this.onRedirect(resp.redirect);
- }
- if (resp.live_patch) {
- this.onLivePatch(resp.live_patch);
- }
- if (resp.live_redirect) {
- this.onLiveRedirect(resp.live_redirect);
- }
- onLoadingDone();
- onReply(resp, hookReply);
});
});
}
undoRefs(ref) {
- dom_default.all(this.el, `[${PHX_REF}="${ref}"]`, (el) => {
+ if (!this.isConnected()) {
+ return;
+ }
+ dom_default.all(document, `[${PHX_REF_SRC}="${this.id}"][${PHX_REF}="${ref}"]`, (el) => {
let disabledVal = el.getAttribute(PHX_DISABLED);
el.removeAttribute(PHX_REF);
+ el.removeAttribute(PHX_REF_SRC);
if (el.getAttribute(PHX_READONLY) !== null) {
el.readOnly = false;
el.removeAttribute(PHX_READONLY);
@@ -2466,35 +3062,50 @@ var View = class {
}
});
}
- putRef(elements, event) {
+ putRef(elements, event, opts = {}) {
let newRef = this.ref++;
let disableWith = this.binding(PHX_DISABLE_WITH);
+ if (opts.loading) {
+ elements = elements.concat(dom_default.all(document, opts.loading));
+ }
elements.forEach((el) => {
el.classList.add(`phx-${event}-loading`);
el.setAttribute(PHX_REF, newRef);
+ el.setAttribute(PHX_REF_SRC, this.el.id);
let disableText = el.getAttribute(disableWith);
if (disableText !== null) {
if (!el.getAttribute(PHX_DISABLE_WITH_RESTORE)) {
el.setAttribute(PHX_DISABLE_WITH_RESTORE, el.innerText);
}
- el.innerText = disableText;
+ if (disableText !== "") {
+ el.innerText = disableText;
+ }
+ el.setAttribute("disabled", "");
}
});
- return [newRef, elements];
+ return [newRef, elements, opts];
}
componentID(el) {
let cid = el.getAttribute && el.getAttribute(PHX_COMPONENT);
return cid ? parseInt(cid) : null;
}
- targetComponentID(target, targetCtx) {
- if (target.getAttribute(this.binding("target"))) {
+ targetComponentID(target, targetCtx, opts = {}) {
+ if (isCid(targetCtx)) {
+ return targetCtx;
+ }
+ let cidOrSelector = target.getAttribute(this.binding("target"));
+ if (isCid(cidOrSelector)) {
+ return parseInt(cidOrSelector);
+ } else if (targetCtx && (cidOrSelector !== null || opts.target)) {
return this.closestComponentID(targetCtx);
} else {
return null;
}
}
closestComponentID(targetCtx) {
- if (targetCtx) {
+ if (isCid(targetCtx)) {
+ return targetCtx;
+ } else if (targetCtx) {
return maybe(targetCtx.closest(`[${PHX_COMPONENT}]`), (el) => this.ownsElement(el) && this.componentID(el));
} else {
return null;
@@ -2505,8 +3116,8 @@ var View = class {
this.log("hook", () => ["unable to push hook event. LiveView not connected", event, payload]);
return false;
}
- let [ref, els] = this.putRef([], "hook");
- this.pushWithReply(() => [ref, els], "event", {
+ let [ref, els, opts] = this.putRef([], "hook");
+ this.pushWithReply(() => [ref, els, opts], "event", {
type: "hook",
event,
value: payload,
@@ -2514,36 +3125,42 @@ var View = class {
}, (resp, reply) => onReply(reply, ref));
return ref;
}
- extractMeta(el, meta) {
+ extractMeta(el, meta, value) {
let prefix = this.binding("value-");
for (let i = 0; i < el.attributes.length; i++) {
+ if (!meta) {
+ meta = {};
+ }
let name = el.attributes[i].name;
if (name.startsWith(prefix)) {
meta[name.replace(prefix, "")] = el.getAttribute(name);
}
}
if (el.value !== void 0) {
+ if (!meta) {
+ meta = {};
+ }
meta.value = el.value;
if (el.tagName === "INPUT" && CHECKABLE_INPUTS.indexOf(el.type) >= 0 && !el.checked) {
delete meta.value;
}
}
+ if (value) {
+ if (!meta) {
+ meta = {};
+ }
+ for (let key in value) {
+ meta[key] = value[key];
+ }
+ }
return meta;
}
- pushEvent(type, el, targetCtx, phxEvent, meta) {
- this.pushWithReply(() => this.putRef([el], type), "event", {
+ pushEvent(type, el, targetCtx, phxEvent, meta, opts = {}) {
+ this.pushWithReply(() => this.putRef([el], type, opts), "event", {
type,
event: phxEvent,
- value: this.extractMeta(el, meta),
- cid: this.targetComponentID(el, targetCtx)
- });
- }
- pushKey(keyElement, targetCtx, kind, phxEvent, meta) {
- this.pushWithReply(() => this.putRef([keyElement], kind), "event", {
- type: kind,
- event: phxEvent,
- value: this.extractMeta(keyElement, meta),
- cid: this.targetComponentID(keyElement, targetCtx)
+ value: this.extractMeta(el, meta, opts.value),
+ cid: this.targetComponentID(el, targetCtx, opts)
});
}
pushFileProgress(fileEl, entryRef, progress, onReply = function() {
@@ -2558,11 +3175,16 @@ var View = class {
}, onReply);
});
}
- pushInput(inputEl, targetCtx, forceCid, phxEvent, eventTarget, callback) {
+ pushInput(inputEl, targetCtx, forceCid, phxEvent, opts, callback) {
let uploads;
let cid = isCid(forceCid) ? forceCid : this.targetComponentID(inputEl.form, targetCtx);
- let refGenerator = () => this.putRef([inputEl, inputEl.form], "change");
- let formData = serializeForm(inputEl.form, { _target: eventTarget.name });
+ let refGenerator = () => this.putRef([inputEl, inputEl.form], "change", opts);
+ let formData;
+ if (inputEl.getAttribute(this.binding("change"))) {
+ formData = serializeForm(inputEl.form, { _target: opts._target }, [inputEl.name]);
+ } else {
+ formData = serializeForm(inputEl.form, { _target: opts._target });
+ }
if (dom_default.isUploadInput(inputEl) && inputEl.files && inputEl.files.length > 0) {
LiveUploader.trackFiles(inputEl, Array.from(inputEl.files));
}
@@ -2592,19 +3214,19 @@ var View = class {
triggerAwaitingSubmit(formEl) {
let awaitingSubmit = this.getScheduledSubmit(formEl);
if (awaitingSubmit) {
- let [_el, _ref, callback] = awaitingSubmit;
+ let [_el, _ref, _opts, callback] = awaitingSubmit;
this.cancelSubmit(formEl);
callback();
}
}
getScheduledSubmit(formEl) {
- return this.formSubmits.find(([el, _callback]) => el.isSameNode(formEl));
+ return this.formSubmits.find(([el, _ref, _opts, _callback]) => el.isSameNode(formEl));
}
- scheduleSubmit(formEl, ref, callback) {
+ scheduleSubmit(formEl, ref, opts, callback) {
if (this.getScheduledSubmit(formEl)) {
return true;
}
- this.formSubmits.push([formEl, ref, callback]);
+ this.formSubmits.push([formEl, ref, opts, callback]);
}
cancelSubmit(formEl) {
this.formSubmits = this.formSubmits.filter(([el, ref, _callback]) => {
@@ -2616,7 +3238,7 @@ var View = class {
}
});
}
- pushFormSubmit(formEl, targetCtx, phxEvent, onReply) {
+ disableForm(formEl, opts = {}) {
let filterIgnored = (el) => {
let userIgnored = closestPhxBinding(el, `${this.binding(PHX_UPDATE)}=ignore`, el.form);
return !(userIgnored || closestPhxBinding(el, "data-phx-update=ignore", el.form));
@@ -2626,33 +3248,35 @@ var View = class {
};
let filterButton = (el) => el.tagName == "BUTTON";
let filterInput = (el) => ["INPUT", "TEXTAREA", "SELECT"].includes(el.tagName);
- let refGenerator = () => {
- let formElements = Array.from(formEl.elements);
- let disables = formElements.filter(filterDisables);
- let buttons = formElements.filter(filterButton).filter(filterIgnored);
- let inputs = formElements.filter(filterInput).filter(filterIgnored);
- buttons.forEach((button) => {
- button.setAttribute(PHX_DISABLED, button.disabled);
- button.disabled = true;
- });
- inputs.forEach((input) => {
- input.setAttribute(PHX_READONLY, input.readOnly);
- input.readOnly = true;
- if (input.files) {
- input.setAttribute(PHX_DISABLED, input.disabled);
- input.disabled = true;
- }
- });
- formEl.setAttribute(this.binding(PHX_PAGE_LOADING), "");
- return this.putRef([formEl].concat(disables).concat(buttons).concat(inputs), "submit");
- };
+ let formElements = Array.from(formEl.elements);
+ let disables = formElements.filter(filterDisables);
+ let buttons = formElements.filter(filterButton).filter(filterIgnored);
+ let inputs = formElements.filter(filterInput).filter(filterIgnored);
+ buttons.forEach((button) => {
+ button.setAttribute(PHX_DISABLED, button.disabled);
+ button.disabled = true;
+ });
+ inputs.forEach((input) => {
+ input.setAttribute(PHX_READONLY, input.readOnly);
+ input.readOnly = true;
+ if (input.files) {
+ input.setAttribute(PHX_DISABLED, input.disabled);
+ input.disabled = true;
+ }
+ });
+ formEl.setAttribute(this.binding(PHX_PAGE_LOADING), "");
+ return this.putRef([formEl].concat(disables).concat(buttons).concat(inputs), "submit", opts);
+ }
+ pushFormSubmit(formEl, targetCtx, phxEvent, opts, onReply) {
+ let refGenerator = () => this.disableForm(formEl, opts);
let cid = this.targetComponentID(formEl, targetCtx);
if (LiveUploader.hasUploadsInProgress(formEl)) {
let [ref, _els] = refGenerator();
- return this.scheduleSubmit(formEl, ref, () => this.pushFormSubmit(formEl, targetCtx, phxEvent, onReply));
+ let push = () => this.pushFormSubmit(formEl, targetCtx, phxEvent, opts, onReply);
+ return this.scheduleSubmit(formEl, ref, opts, push);
} else if (LiveUploader.inputsAwaitingPreflight(formEl).length > 0) {
let [ref, els] = refGenerator();
- let proxyRefGen = () => [ref, els];
+ let proxyRefGen = () => [ref, els, opts];
this.uploadFiles(formEl, targetCtx, ref, cid, (_uploads) => {
let formData = serializeForm(formEl, {});
this.pushWithReply(proxyRefGen, "event", {
@@ -2663,7 +3287,7 @@ var View = class {
}, onReply);
});
} else {
- let formData = serializeForm(formEl);
+ let formData = serializeForm(formEl, {});
this.pushWithReply(refGenerator, "event", {
type: "form",
event: phxEvent,
@@ -2717,30 +3341,40 @@ var View = class {
} else if (inputs.length > 1) {
logError(`duplicate live file inputs found matching the name "${name}"`);
} else {
- dom_default.dispatchEvent(inputs[0], PHX_TRACK_UPLOADS, { files: filesOrBlobs });
+ dom_default.dispatchEvent(inputs[0], PHX_TRACK_UPLOADS, { detail: { files: filesOrBlobs } });
}
}
pushFormRecovery(form, newCid, callback) {
this.liveSocket.withinOwners(form, (view, targetCtx) => {
- let input = form.elements[0];
+ let input = Array.from(form.elements).find((el) => {
+ return dom_default.isFormInput(el) && el.type !== "hidden" && !el.hasAttribute(this.binding("change"));
+ });
let phxEvent = form.getAttribute(this.binding(PHX_AUTO_RECOVER)) || form.getAttribute(this.binding("change"));
- view.pushInput(input, targetCtx, newCid, phxEvent, input, callback);
+ js_default.exec("change", phxEvent, view, input, ["push", { _target: input.name, newCid, callback }]);
});
}
pushLinkPatch(href, targetEl, callback) {
let linkRef = this.liveSocket.setPendingLink(href);
let refGen = targetEl ? () => this.putRef([targetEl], "click") : null;
- this.pushWithReply(refGen, "live_patch", { url: href }, (resp) => {
- if (resp.link_redirect) {
- this.liveSocket.replaceMain(href, null, callback, linkRef);
- } else {
- if (this.liveSocket.commitPendingLink(linkRef)) {
- this.href = href;
+ let fallback = () => this.liveSocket.redirect(window.location.href);
+ let push = this.pushWithReply(refGen, "live_patch", { url: href }, (resp) => {
+ this.liveSocket.requestDOMUpdate(() => {
+ if (resp.link_redirect) {
+ this.liveSocket.replaceMain(href, null, callback, linkRef);
+ } else {
+ if (this.liveSocket.commitPendingLink(linkRef)) {
+ this.href = href;
+ }
+ this.applyPendingUpdates();
+ callback && callback(linkRef);
}
- this.applyPendingUpdates();
- callback && callback(linkRef);
- }
- }).receive("timeout", () => this.liveSocket.redirect(window.location.href));
+ });
+ });
+ if (push) {
+ push.receive("timeout", fallback);
+ } else {
+ fallback();
+ }
}
formsForRecovery(html) {
if (this.joinCount === 0) {
@@ -2752,7 +3386,7 @@ var View = class {
return dom_default.all(this.el, `form[${phxChange}]`).filter((form) => form.id && this.ownsElement(form)).filter((form) => form.elements.length > 0).filter((form) => form.getAttribute(this.binding(PHX_AUTO_RECOVER)) !== "ignore").map((form) => {
let newForm = template.content.querySelector(`form[id="${form.id}"][${phxChange}="${form.getAttribute(phxChange)}"]`);
if (newForm) {
- return [form, newForm, this.componentID(newForm)];
+ return [form, newForm, this.targetComponentID(newForm)];
} else {
return [form, null, null];
}
@@ -2778,12 +3412,17 @@ var View = class {
}
}
ownsElement(el) {
- return el.getAttribute(PHX_PARENT_ID) === this.id || maybe(el.closest(PHX_VIEW_SELECTOR), (node) => node.id) === this.id;
+ let parentViewEl = el.closest(PHX_VIEW_SELECTOR);
+ return el.getAttribute(PHX_PARENT_ID) === this.id || parentViewEl && parentViewEl.id === this.id || !parentViewEl && this.isDead;
}
- submitForm(form, targetCtx, phxEvent) {
+ submitForm(form, targetCtx, phxEvent, opts = {}) {
dom_default.putPrivate(form, PHX_HAS_SUBMITTED, true);
+ let phxFeedback = this.liveSocket.binding(PHX_FEEDBACK_FOR);
+ let inputs = Array.from(form.elements);
+ inputs.forEach((input) => dom_default.putPrivate(input, PHX_HAS_SUBMITTED, true));
this.liveSocket.blurActiveElement(this);
- this.pushFormSubmit(form, targetCtx, phxEvent, () => {
+ this.pushFormSubmit(form, targetCtx, phxEvent, opts, () => {
+ inputs.forEach((input) => dom_default.showError(input, phxFeedback));
this.liveSocket.restorePreviouslyActiveFocus();
});
}
@@ -2801,7 +3440,7 @@ var LiveSocket = class {
a phoenix Socket must be provided as the second argument to the LiveSocket constructor. For example:
import {Socket} from "phoenix"
- import LiveSocket from "phoenix_live_view"
+ import {LiveSocket} from "phoenix_live_view"
let liveSocket = new LiveSocket("/live", Socket, {...})
`);
}
@@ -2816,6 +3455,8 @@ var LiveSocket = class {
this.prevActive = null;
this.silenced = false;
this.main = null;
+ this.outgoingMainEl = null;
+ this.clickStartedAtTarget = null;
this.linkRef = 1;
this.roots = {};
this.href = window.location.href;
@@ -2824,10 +3465,16 @@ var LiveSocket = class {
this.hooks = opts.hooks || {};
this.uploaders = opts.uploaders || {};
this.loaderTimeout = opts.loaderTimeout || LOADER_TIMEOUT;
+ this.reloadWithJitterTimer = null;
+ this.maxReloads = opts.maxReloads || MAX_RELOADS;
+ this.reloadJitterMin = opts.reloadJitterMin || RELOAD_JITTER_MIN;
+ this.reloadJitterMax = opts.reloadJitterMax || RELOAD_JITTER_MAX;
+ this.failsafeJitter = opts.failsafeJitter || FAILSAFE_JITTER;
this.localStorage = opts.localStorage || window.localStorage;
this.sessionStorage = opts.sessionStorage || window.sessionStorage;
this.boundTopLevelEvents = false;
this.domCallbacks = Object.assign({ onNodeAdded: closure(), onBeforeElUpdated: closure() }, opts.dom || {});
+ this.transitions = new TransitionSet();
window.addEventListener("pagehide", (_e) => {
this.unloaded = true;
});
@@ -2843,6 +3490,9 @@ var LiveSocket = class {
isDebugEnabled() {
return this.sessionStorage.getItem(PHX_LV_DEBUG) === "true";
}
+ isDebugDisabled() {
+ return this.sessionStorage.getItem(PHX_LV_DEBUG) === "false";
+ }
enableDebug() {
this.sessionStorage.setItem(PHX_LV_DEBUG, "true");
}
@@ -2850,7 +3500,7 @@ var LiveSocket = class {
this.sessionStorage.setItem(PHX_LV_PROFILE, "true");
}
disableDebug() {
- this.sessionStorage.removeItem(PHX_LV_DEBUG);
+ this.sessionStorage.setItem(PHX_LV_DEBUG, "false");
}
disableProfiling() {
this.sessionStorage.removeItem(PHX_LV_PROFILE);
@@ -2871,11 +3521,19 @@ var LiveSocket = class {
return this.socket;
}
connect() {
+ if (window.location.hostname === "localhost" && !this.isDebugDisabled()) {
+ this.enableDebug();
+ }
let doConnect = () => {
if (this.joinRootViews()) {
this.bindTopLevelEvents();
this.socket.connect();
+ } else if (this.main) {
+ this.socket.connect();
+ } else {
+ this.bindTopLevelEvents({ dead: true });
}
+ this.joinDeadView();
};
if (["complete", "loaded", "interactive"].indexOf(document.readyState) >= 0) {
doConnect();
@@ -2884,8 +3542,28 @@ var LiveSocket = class {
}
}
disconnect(callback) {
+ clearTimeout(this.reloadWithJitterTimer);
this.socket.disconnect(callback);
}
+ replaceTransport(transport) {
+ clearTimeout(this.reloadWithJitterTimer);
+ this.socket.replaceTransport(transport);
+ this.connect();
+ }
+ execJS(el, encodedJS, eventType = null) {
+ this.owner(el, (view) => js_default.exec(eventType, encodedJS, view, el));
+ }
+ unload() {
+ if (this.unloaded) {
+ return;
+ }
+ if (this.main && this.isConnected()) {
+ this.log(this.main, "socket", () => ["disconnect for page nav"]);
+ }
+ this.unloaded = true;
+ this.destroyAllViews();
+ this.disconnect();
+ }
triggerDOM(kind, args) {
this.domCallbacks[kind](...args);
}
@@ -2907,13 +3585,19 @@ var LiveSocket = class {
debug(view, kind, msg, obj);
}
}
+ requestDOMUpdate(callback) {
+ this.transitions.after(callback);
+ }
+ transition(time, onStart, onDone = function() {
+ }) {
+ this.transitions.addTransition(time, onStart, onDone);
+ }
onChannel(channel, event, cb) {
channel.on(event, (data) => {
let latency = this.getLatencySim();
if (!latency) {
cb(data);
} else {
- console.log(`simulating ${latency}ms of latency from server to client`);
setTimeout(() => cb(data), latency);
}
});
@@ -2922,7 +3606,7 @@ var LiveSocket = class {
let latency = this.getLatencySim();
let oldJoinCount = view.joinCount;
if (!latency) {
- if (opts.timeout) {
+ if (this.isConnected() && opts.timeout) {
return push().receive("timeout", () => {
if (view.joinCount === oldJoinCount && !view.isDestroyed()) {
this.reloadWithJitter(view, () => {
@@ -2934,7 +3618,6 @@ var LiveSocket = class {
return push();
}
}
- console.log(`simulating ${latency}ms of latency from client to server`);
let fakePush = {
receives: [],
receive(kind, cb) {
@@ -2950,17 +3633,24 @@ var LiveSocket = class {
return fakePush;
}
reloadWithJitter(view, log) {
- view.destroy();
+ clearTimeout(this.reloadWithJitterTimer);
this.disconnect();
- let [minMs, maxMs] = RELOAD_JITTER;
+ let minMs = this.reloadJitterMin;
+ let maxMs = this.reloadJitterMax;
let afterMs = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs;
let tries = browser_default.updateLocal(this.localStorage, window.location.pathname, CONSECUTIVE_RELOADS, 0, (count) => count + 1);
- log ? log() : this.log(view, "join", () => [`encountered ${tries} consecutive reloads`]);
- if (tries > MAX_RELOADS) {
- this.log(view, "join", () => [`exceeded ${MAX_RELOADS} consecutive reloads. Entering failsafe mode`]);
- afterMs = FAILSAFE_JITTER;
+ if (tries > this.maxReloads) {
+ afterMs = this.failsafeJitter;
}
- setTimeout(() => {
+ this.reloadWithJitterTimer = setTimeout(() => {
+ if (view.isDestroyed() || view.isConnected()) {
+ return;
+ }
+ view.destroy();
+ log ? log() : this.log(view, "join", () => [`encountered ${tries} consecutive reloads`]);
+ if (tries > this.maxReloads) {
+ this.log(view, "join", () => [`exceeded ${this.maxReloads} consecutive reloads. Entering failsafe mode`]);
+ }
if (this.hasPendingLink()) {
window.location = this.pendingLink;
} else {
@@ -2986,6 +3676,18 @@ var LiveSocket = class {
channel(topic, params) {
return this.socket.channel(topic, params);
}
+ joinDeadView() {
+ let body = document.body;
+ if (body && !this.isPhxView(body) && !this.isPhxView(document.firstElementChild)) {
+ let view = this.newRootView(body);
+ view.setHref(this.getHref());
+ view.joinDead();
+ if (!this.main) {
+ this.main = view;
+ }
+ window.requestAnimationFrame(() => view.execNewMounted());
+ }
+ }
joinRootViews() {
let rootsFound = false;
dom_default.all(document, `${PHX_VIEW_SELECTOR}:not([${PHX_PARENT_ID}])`, (rootEl) => {
@@ -2993,7 +3695,7 @@ var LiveSocket = class {
let view = this.newRootView(rootEl);
view.setHref(this.getHref());
view.join();
- if (rootEl.getAttribute(PHX_MAIN)) {
+ if (rootEl.hasAttribute(PHX_MAIN)) {
this.main = view;
}
}
@@ -3002,46 +3704,55 @@ var LiveSocket = class {
return rootsFound;
}
redirect(to, flash) {
- this.disconnect();
+ this.unload();
browser_default.redirect(to, flash);
}
replaceMain(href, flash, callback = null, linkRef = this.setPendingLink(href)) {
- let oldMainEl = this.main.el;
- let newMainEl = dom_default.cloneNode(oldMainEl, "");
+ let liveReferer = this.currentLocation.href;
+ this.outgoingMainEl = this.outgoingMainEl || this.main.el;
+ let newMainEl = dom_default.cloneNode(this.outgoingMainEl, "");
this.main.showLoader(this.loaderTimeout);
this.main.destroy();
- this.main = this.newRootView(newMainEl, flash);
+ this.main = this.newRootView(newMainEl, flash, liveReferer);
this.main.setRedirect(href);
- this.main.join((joinCount) => {
+ this.transitionRemoves();
+ this.main.join((joinCount, onDone) => {
if (joinCount === 1 && this.commitPendingLink(linkRef)) {
- oldMainEl.replaceWith(newMainEl);
- callback && callback();
+ this.requestDOMUpdate(() => {
+ dom_default.findPhxSticky(document).forEach((el) => newMainEl.appendChild(el));
+ this.outgoingMainEl.replaceWith(newMainEl);
+ this.outgoingMainEl = null;
+ callback && requestAnimationFrame(callback);
+ onDone();
+ });
+ }
+ });
+ }
+ transitionRemoves(elements) {
+ let removeAttr = this.binding("remove");
+ elements = elements || dom_default.all(document, `[${removeAttr}]`);
+ elements.forEach((el) => {
+ if (document.body.contains(el)) {
+ this.execJS(el, el.getAttribute(removeAttr), "remove");
}
});
}
isPhxView(el) {
return el.getAttribute && el.getAttribute(PHX_SESSION) !== null;
}
- newRootView(el, flash) {
- let view = new View(el, this, null, flash);
+ newRootView(el, flash, liveReferer) {
+ let view = new View(el, this, null, flash, liveReferer);
this.roots[view.id] = view;
return view;
}
owner(childEl, callback) {
- let view = maybe(childEl.closest(PHX_VIEW_SELECTOR), (el) => this.getViewByEl(el));
+ let view = maybe(childEl.closest(PHX_VIEW_SELECTOR), (el) => this.getViewByEl(el)) || this.main;
if (view) {
callback(view);
}
}
withinOwners(childEl, callback) {
- this.owner(childEl, (view) => {
- let phxTarget = childEl.getAttribute(this.binding("target"));
- if (phxTarget === null) {
- callback(view, childEl);
- } else {
- view.withinTargets(phxTarget, callback);
- }
- });
+ this.owner(childEl, (view) => callback(view, childEl));
}
getViewByEl(el) {
let rootId = el.getAttribute(PHX_ROOT_ID);
@@ -3055,10 +3766,14 @@ var LiveSocket = class {
this.roots[id].destroy();
delete this.roots[id];
}
+ this.main = null;
}
destroyViewByEl(el) {
let root = this.getRootById(el.getAttribute(PHX_ROOT_ID));
- if (root) {
+ if (root && root.id === el.id) {
+ root.destroy();
+ delete this.roots[root.id];
+ } else if (root) {
root.destroyDescendent(el.id);
}
}
@@ -3100,11 +3815,19 @@ var LiveSocket = class {
this.prevActive.blur();
}
}
- bindTopLevelEvents() {
+ bindTopLevelEvents({ dead } = {}) {
if (this.boundTopLevelEvents) {
return;
}
this.boundTopLevelEvents = true;
+ this.socket.onClose((event) => {
+ if (event && event.code === 1001) {
+ return this.unload();
+ }
+ if (event && event.code === 1e3 && this.main) {
+ return this.reloadWithJitter(this.main);
+ }
+ });
document.body.addEventListener("click", function() {
});
window.addEventListener("pageshow", (e) => {
@@ -3114,25 +3837,32 @@ var LiveSocket = class {
window.location.reload();
}
}, true);
- this.bindNav();
+ if (!dead) {
+ this.bindNav();
+ }
this.bindClicks();
- this.bindForms();
- this.bind({ keyup: "keyup", keydown: "keydown" }, (e, type, view, target, targetCtx, phxEvent, _phxTarget) => {
- let matchKey = target.getAttribute(this.binding(PHX_KEY));
+ if (!dead) {
+ this.bindForms();
+ }
+ this.bind({ keyup: "keyup", keydown: "keydown" }, (e, type, view, targetEl, phxEvent, eventTarget) => {
+ let matchKey = targetEl.getAttribute(this.binding(PHX_KEY));
let pressedKey = e.key && e.key.toLowerCase();
if (matchKey && matchKey.toLowerCase() !== pressedKey) {
return;
}
- view.pushKey(target, targetCtx, type, phxEvent, { key: e.key, ...this.eventMeta(type, e, target) });
+ let data = { key: e.key, ...this.eventMeta(type, e, targetEl) };
+ js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
});
- this.bind({ blur: "focusout", focus: "focusin" }, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {
- if (!phxTarget) {
- view.pushEvent(type, targetEl, targetCtx, phxEvent, this.eventMeta(type, e, targetEl));
+ this.bind({ blur: "focusout", focus: "focusin" }, (e, type, view, targetEl, phxEvent, eventTarget) => {
+ if (!eventTarget) {
+ let data = { key: e.key, ...this.eventMeta(type, e, targetEl) };
+ js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
}
});
this.bind({ blur: "blur", focus: "focus" }, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {
- if (phxTarget && !phxTarget !== "window") {
- view.pushEvent(type, targetEl, targetCtx, phxEvent, this.eventMeta(type, e, targetEl));
+ if (phxTarget === "window") {
+ let data = this.eventMeta(type, e, targetEl);
+ js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
}
});
window.addEventListener("dragover", (e) => e.preventDefault());
@@ -3146,7 +3876,7 @@ var LiveSocket = class {
if (!dropTarget || dropTarget.disabled || files.length === 0 || !(dropTarget.files instanceof FileList)) {
return;
}
- LiveUploader.trackFiles(dropTarget, files);
+ LiveUploader.trackFiles(dropTarget, files, e.dataTransfer);
dropTarget.dispatchEvent(new Event("input", { bubbles: true }));
});
this.on(PHX_TRACK_UPLOADS, (e) => {
@@ -3191,17 +3921,17 @@ var LiveSocket = class {
let windowBinding = this.binding(`window-${event}`);
let targetPhxEvent = e.target.getAttribute && e.target.getAttribute(binding);
if (targetPhxEvent) {
- this.debounce(e.target, e, () => {
- this.withinOwners(e.target, (view, targetCtx) => {
- callback(e, event, view, e.target, targetCtx, targetPhxEvent, null);
+ this.debounce(e.target, e, browserEventName, () => {
+ this.withinOwners(e.target, (view) => {
+ callback(e, event, view, e.target, targetPhxEvent, null);
});
});
} else {
dom_default.all(document, `[${windowBinding}]`, (el) => {
let phxEvent = el.getAttribute(windowBinding);
- this.debounce(el, e, () => {
- this.withinOwners(el, (view, targetCtx) => {
- callback(e, event, view, el, targetCtx, phxEvent, "window");
+ this.debounce(el, e, browserEventName, () => {
+ this.withinOwners(el, (view) => {
+ callback(e, event, view, el, phxEvent, "window");
});
});
});
@@ -3210,35 +3940,53 @@ var LiveSocket = class {
}
}
bindClicks() {
+ window.addEventListener("click", (e) => this.clickStartedAtTarget = e.target);
this.bindClick("click", "click", false);
this.bindClick("mousedown", "capture-click", true);
}
bindClick(eventName, bindingName, capture) {
let click = this.binding(bindingName);
window.addEventListener(eventName, (e) => {
- if (!this.isConnected()) {
- return;
- }
let target = null;
if (capture) {
target = e.target.matches(`[${click}]`) ? e.target : e.target.querySelector(`[${click}]`);
} else {
- target = closestPhxBinding(e.target, click);
+ let clickStartedAtTarget = this.clickStartedAtTarget || e.target;
+ target = closestPhxBinding(clickStartedAtTarget, click);
+ this.dispatchClickAway(e, clickStartedAtTarget);
+ this.clickStartedAtTarget = null;
}
let phxEvent = target && target.getAttribute(click);
if (!phxEvent) {
+ let href = e.target instanceof HTMLAnchorElement ? e.target.getAttribute("href") : null;
+ if (!capture && href !== null && !dom_default.wantsNewTab(e) && dom_default.isNewPageHref(href, window.location)) {
+ this.unload();
+ }
return;
}
if (target.getAttribute("href") === "#") {
e.preventDefault();
}
- this.debounce(target, e, () => {
- this.withinOwners(target, (view, targetCtx) => {
- view.pushEvent("click", target, targetCtx, phxEvent, this.eventMeta("click", e, target));
+ this.debounce(target, e, "click", () => {
+ this.withinOwners(target, (view) => {
+ js_default.exec("click", phxEvent, view, target, ["push", { data: this.eventMeta("click", e, target) }]);
});
});
}, capture);
}
+ dispatchClickAway(e, clickStartedAt) {
+ let phxClickAway = this.binding("click-away");
+ dom_default.all(document, `[${phxClickAway}]`, (el) => {
+ if (!(el.isSameNode(clickStartedAt) || el.contains(clickStartedAt))) {
+ this.withinOwners(e.target, (view) => {
+ let phxEvent = el.getAttribute(phxClickAway);
+ if (js_default.isVisible(el)) {
+ js_default.exec("click", phxEvent, view, el, ["push", { data: this.eventMeta("click", e, e.target) }]);
+ }
+ });
+ }
+ });
+ }
bindNav() {
if (!browser_default.canPushState()) {
return;
@@ -3259,49 +4007,71 @@ var LiveSocket = class {
}
let { type, id, root, scroll } = event.state || {};
let href = window.location.href;
- if (this.main.isConnected() && (type === "patch" && id === this.main.id)) {
- this.main.pushLinkPatch(href, null);
- } else {
- this.replaceMain(href, null, () => {
- if (root) {
- this.replaceRootHistory();
- }
- if (typeof scroll === "number") {
- setTimeout(() => {
- window.scrollTo(0, scroll);
- }, 0);
- }
- });
- }
+ this.requestDOMUpdate(() => {
+ if (this.main.isConnected() && (type === "patch" && id === this.main.id)) {
+ this.main.pushLinkPatch(href, null, () => {
+ this.maybeScroll(scroll);
+ });
+ } else {
+ this.replaceMain(href, null, () => {
+ if (root) {
+ this.replaceRootHistory();
+ }
+ this.maybeScroll(scroll);
+ });
+ }
+ });
}, false);
window.addEventListener("click", (e) => {
let target = closestPhxBinding(e.target, PHX_LIVE_LINK);
let type = target && target.getAttribute(PHX_LIVE_LINK);
- let wantsNewTab = e.metaKey || e.ctrlKey || e.button === 1;
- if (!type || !this.isConnected() || !this.main || wantsNewTab) {
+ if (!type || !this.isConnected() || !this.main || dom_default.wantsNewTab(e)) {
return;
}
let href = target.href;
let linkState = target.getAttribute(PHX_LINK_STATE);
e.preventDefault();
+ e.stopImmediatePropagation();
if (this.pendingLink === href) {
return;
}
- if (type === "patch") {
- this.pushHistoryPatch(href, linkState, target);
- } else if (type === "redirect") {
- this.historyRedirect(href, linkState);
- } else {
- throw new Error(`expected ${PHX_LIVE_LINK} to be "patch" or "redirect", got: ${type}`);
- }
+ this.requestDOMUpdate(() => {
+ if (type === "patch") {
+ this.pushHistoryPatch(href, linkState, target);
+ } else if (type === "redirect") {
+ this.historyRedirect(href, linkState);
+ } else {
+ throw new Error(`expected ${PHX_LIVE_LINK} to be "patch" or "redirect", got: ${type}`);
+ }
+ let phxClick = target.getAttribute(this.binding("click"));
+ if (phxClick) {
+ this.requestDOMUpdate(() => this.execJS(target, phxClick, "click"));
+ }
+ });
}, false);
}
+ maybeScroll(scroll) {
+ if (typeof scroll === "number") {
+ requestAnimationFrame(() => {
+ window.scrollTo(0, scroll);
+ });
+ }
+ }
+ dispatchEvent(event, payload = {}) {
+ dom_default.dispatchEvent(window, `phx:${event}`, { detail: payload });
+ }
+ dispatchEvents(events) {
+ events.forEach(([event, payload]) => this.dispatchEvent(event, payload));
+ }
withPageLoading(info, callback) {
- dom_default.dispatchEvent(window, "phx:page-loading-start", info);
- let done = () => dom_default.dispatchEvent(window, "phx:page-loading-stop", info);
+ dom_default.dispatchEvent(window, "phx:page-loading-start", { detail: info });
+ let done = () => dom_default.dispatchEvent(window, "phx:page-loading-stop", { detail: info });
return callback ? callback(done) : done;
}
pushHistoryPatch(href, linkState, targetEl) {
+ if (!this.isConnected()) {
+ return browser_default.redirect(href);
+ }
this.withPageLoading({ to: href, kind: "patch" }, (done) => {
this.main.pushLinkPatch(href, targetEl, (linkRef) => {
this.historyPatch(href, linkState, linkRef);
@@ -3317,6 +4087,13 @@ var LiveSocket = class {
this.registerNewLocation(window.location);
}
historyRedirect(href, linkState, flash) {
+ if (!this.isConnected()) {
+ return browser_default.redirect(href, flash);
+ }
+ if (/^\/$|^\/[^\/]+.*$/.test(href)) {
+ let { protocol, host } = window.location;
+ href = `${protocol}//${host}${href}`;
+ }
let scroll = window.scrollY;
this.withPageLoading({ to: href, kind: "redirect" }, (done) => {
this.replaceMain(href, flash, () => {
@@ -3340,25 +4117,52 @@ var LiveSocket = class {
}
bindForms() {
let iterations = 0;
+ let externalFormSubmitted = false;
+ this.on("submit", (e) => {
+ let phxSubmit = e.target.getAttribute(this.binding("submit"));
+ let phxChange = e.target.getAttribute(this.binding("change"));
+ if (!externalFormSubmitted && phxChange && !phxSubmit) {
+ externalFormSubmitted = true;
+ e.preventDefault();
+ this.withinOwners(e.target, (view) => {
+ view.disableForm(e.target);
+ window.requestAnimationFrame(() => {
+ if (dom_default.isUnloadableFormSubmit(e)) {
+ this.unload();
+ }
+ e.target.submit();
+ });
+ });
+ }
+ }, true);
this.on("submit", (e) => {
let phxEvent = e.target.getAttribute(this.binding("submit"));
if (!phxEvent) {
+ if (dom_default.isUnloadableFormSubmit(e)) {
+ this.unload();
+ }
return;
}
e.preventDefault();
e.target.disabled = true;
- this.withinOwners(e.target, (view, targetCtx) => view.submitForm(e.target, targetCtx, phxEvent));
+ this.withinOwners(e.target, (view) => {
+ js_default.exec("submit", phxEvent, view, e.target, ["push", {}]);
+ });
}, false);
for (let type of ["change", "input"]) {
this.on(type, (e) => {
+ let phxChange = this.binding("change");
let input = e.target;
- let phxEvent = input.form && input.form.getAttribute(this.binding("change"));
+ let inputEvent = input.getAttribute(phxChange);
+ let formEvent = input.form && input.form.getAttribute(phxChange);
+ let phxEvent = inputEvent || formEvent;
if (!phxEvent) {
return;
}
if (input.type === "number" && input.validity && input.validity.badInput) {
return;
}
+ let dispatcher = inputEvent ? input : input.form;
let currentIterations = iterations;
iterations++;
let { at, type: lastType } = dom_default.private(input, "prev-iteration") || {};
@@ -3366,24 +4170,40 @@ var LiveSocket = class {
return;
}
dom_default.putPrivate(input, "prev-iteration", { at: currentIterations, type });
- this.debounce(input, e, () => {
- this.withinOwners(input.form, (view, targetCtx) => {
+ this.debounce(input, e, type, () => {
+ this.withinOwners(dispatcher, (view) => {
dom_default.putPrivate(input, PHX_HAS_FOCUSED, true);
if (!dom_default.isTextualInput(input)) {
this.setActiveElement(input);
}
- view.pushInput(input, targetCtx, null, phxEvent, e.target);
+ js_default.exec("change", phxEvent, view, input, ["push", { _target: e.target.name, dispatcher }]);
});
});
}, false);
}
+ this.on("reset", (e) => {
+ let form = e.target;
+ dom_default.resetForm(form, this.binding(PHX_FEEDBACK_FOR));
+ let input = Array.from(form.elements).find((el) => el.type === "reset");
+ window.requestAnimationFrame(() => {
+ input.dispatchEvent(new Event("input", { bubbles: true, cancelable: false }));
+ });
+ });
}
- debounce(el, event, callback) {
+ debounce(el, event, eventType, callback) {
+ if (eventType === "blur" || eventType === "focusout") {
+ return callback();
+ }
let phxDebounce = this.binding(PHX_DEBOUNCE);
let phxThrottle = this.binding(PHX_THROTTLE);
let defaultDebounce = this.defaults.debounce.toString();
let defaultThrottle = this.defaults.throttle.toString();
- dom_default.debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, callback);
+ this.withinOwners(el, (view) => {
+ let asyncFilter = () => !view.isDestroyed() && document.body.contains(el);
+ dom_default.debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, asyncFilter, () => {
+ callback();
+ });
+ });
}
silenceEvents(callback) {
this.silenced = true;
@@ -3398,6 +4218,51 @@ var LiveSocket = class {
});
}
};
+var TransitionSet = class {
+ constructor() {
+ this.transitions = new Set();
+ this.pendingOps = [];
+ }
+ reset() {
+ this.transitions.forEach((timer) => {
+ clearTimeout(timer);
+ this.transitions.delete(timer);
+ });
+ this.flushPendingOps();
+ }
+ after(callback) {
+ if (this.size() === 0) {
+ callback();
+ } else {
+ this.pushPendingOp(callback);
+ }
+ }
+ addTransition(time, onStart, onDone) {
+ onStart();
+ let timer = setTimeout(() => {
+ this.transitions.delete(timer);
+ onDone();
+ this.flushPendingOps();
+ }, time);
+ this.transitions.add(timer);
+ }
+ pushPendingOp(op) {
+ this.pendingOps.push(op);
+ }
+ size() {
+ return this.transitions.size;
+ }
+ flushPendingOps() {
+ if (this.size() > 0) {
+ return;
+ }
+ let op = this.pendingOps.shift();
+ if (op) {
+ op();
+ this.flushPendingOps();
+ }
+ }
+};
export {
LiveSocket
};
diff --git a/priv/static/phoenix_live_view.esm.js.map b/priv/static/phoenix_live_view.esm.js.map
index 39905129b7..c84629da80 100644
--- a/priv/static/phoenix_live_view.esm.js.map
+++ b/priv/static/phoenix_live_view.esm.js.map
@@ -1,7 +1,7 @@
{
"version": 3,
- "sources": ["../../assets/js/phoenix_live_view/constants.js", "../../assets/js/phoenix_live_view/entry_uploader.js", "../../assets/js/phoenix_live_view/utils.js", "../../assets/js/phoenix_live_view/browser.js", "../../assets/js/phoenix_live_view/dom.js", "../../assets/js/phoenix_live_view/upload_entry.js", "../../assets/js/phoenix_live_view/live_uploader.js", "../../assets/js/phoenix_live_view/hooks.js", "../../assets/js/phoenix_live_view/dom_post_morph_restorer.js", "../../assets/node_modules/morphdom/dist/morphdom-esm.js", "../../assets/js/phoenix_live_view/dom_patch.js", "../../assets/js/phoenix_live_view/rendered.js", "../../assets/js/phoenix_live_view/view_hook.js", "../../assets/js/phoenix_live_view/view.js", "../../assets/js/phoenix_live_view/live_socket.js"],
- "sourcesContent": ["\nexport const CONSECUTIVE_RELOADS = \"consecutive-reloads\"\nexport const MAX_RELOADS = 10\nexport const RELOAD_JITTER = [1000, 3000]\nexport const FAILSAFE_JITTER = 30000\nexport const PHX_EVENT_CLASSES = [\n \"phx-click-loading\", \"phx-change-loading\", \"phx-submit-loading\",\n \"phx-keydown-loading\", \"phx-keyup-loading\", \"phx-blur-loading\", \"phx-focus-loading\"\n]\nexport const PHX_COMPONENT = \"data-phx-component\"\nexport const PHX_LIVE_LINK = \"data-phx-link\"\nexport const PHX_TRACK_STATIC = \"track-static\"\nexport const PHX_LINK_STATE = \"data-phx-link-state\"\nexport const PHX_REF = \"data-phx-ref\"\nexport const PHX_TRACK_UPLOADS = \"track-uploads\"\nexport const PHX_UPLOAD_REF = \"data-phx-upload-ref\"\nexport const PHX_PREFLIGHTED_REFS = \"data-phx-preflighted-refs\"\nexport const PHX_DONE_REFS = \"data-phx-done-refs\"\nexport const PHX_DROP_TARGET = \"drop-target\"\nexport const PHX_ACTIVE_ENTRY_REFS = \"data-phx-active-refs\"\nexport const PHX_LIVE_FILE_UPDATED = \"phx:live-file:updated\"\nexport const PHX_SKIP = \"data-phx-skip\"\nexport const PHX_REMOVE = \"data-phx-remove\"\nexport const PHX_PAGE_LOADING = \"page-loading\"\nexport const PHX_CONNECTED_CLASS = \"phx-connected\"\nexport const PHX_DISCONNECTED_CLASS = \"phx-disconnected\"\nexport const PHX_NO_FEEDBACK_CLASS = \"phx-no-feedback\"\nexport const PHX_ERROR_CLASS = \"phx-error\"\nexport const PHX_PARENT_ID = \"data-phx-parent-id\"\nexport const PHX_MAIN = \"data-phx-main\"\nexport const PHX_ROOT_ID = \"data-phx-root-id\"\nexport const PHX_TRIGGER_ACTION = \"trigger-action\"\nexport const PHX_FEEDBACK_FOR = \"feedback-for\"\nexport const PHX_HAS_FOCUSED = \"phx-has-focused\"\nexport const FOCUSABLE_INPUTS = [\"text\", \"textarea\", \"number\", \"email\", \"password\", \"search\", \"tel\", \"url\", \"date\", \"time\"]\nexport const CHECKABLE_INPUTS = [\"checkbox\", \"radio\"]\nexport const PHX_HAS_SUBMITTED = \"phx-has-submitted\"\nexport const PHX_SESSION = \"data-phx-session\"\nexport const PHX_VIEW_SELECTOR = `[${PHX_SESSION}]`\nexport const PHX_STATIC = \"data-phx-static\"\nexport const PHX_READONLY = \"data-phx-readonly\"\nexport const PHX_DISABLED = \"data-phx-disabled\"\nexport const PHX_DISABLE_WITH = \"disable-with\"\nexport const PHX_DISABLE_WITH_RESTORE = \"data-phx-disable-with-restore\"\nexport const PHX_HOOK = \"hook\"\nexport const PHX_DEBOUNCE = \"debounce\"\nexport const PHX_THROTTLE = \"throttle\"\nexport const PHX_UPDATE = \"update\"\nexport const PHX_KEY = \"key\"\nexport const PHX_PRIVATE = \"phxPrivate\"\nexport const PHX_AUTO_RECOVER = \"auto-recover\"\nexport const PHX_LV_DEBUG = \"phx:live-socket:debug\"\nexport const PHX_LV_PROFILE = \"phx:live-socket:profiling\"\nexport const PHX_LV_LATENCY_SIM = \"phx:live-socket:latency-sim\"\nexport const PHX_PROGRESS = \"progress\"\nexport const LOADER_TIMEOUT = 1\nexport const BEFORE_UNLOAD_LOADER_TIMEOUT = 200\nexport const BINDING_PREFIX = \"phx-\"\nexport const PUSH_TIMEOUT = 30000\nexport const LINK_HEADER = \"x-requested-with\"\nexport const RESPONSE_URL_HEADER = \"x-response-url\"\nexport const DEBOUNCE_TRIGGER = \"debounce-trigger\"\nexport const THROTTLED = \"throttled\"\nexport const DEBOUNCE_PREV_KEY = \"debounce-prev-key\"\nexport const DEFAULTS = {\n debounce: 300,\n throttle: 300\n}\n\n// Rendered\nexport const DYNAMICS = \"d\"\nexport const STATIC = \"s\"\nexport const COMPONENTS = \"c\"\nexport const EVENTS = \"e\"\nexport const REPLY = \"r\"\nexport const TITLE = \"t\"\n", "import {\n logError\n} from \"./utils\"\n\nexport default class EntryUploader {\n constructor(entry, chunkSize, liveSocket){\n this.liveSocket = liveSocket\n this.entry = entry\n this.offset = 0\n this.chunkSize = chunkSize\n this.chunkTimer = null\n this.uploadChannel = liveSocket.channel(`lvu:${entry.ref}`, {token: entry.metadata()})\n }\n\n error(reason){\n clearTimeout(this.chunkTimer)\n this.uploadChannel.leave()\n this.entry.error(reason)\n }\n\n upload(){\n this.uploadChannel.onError(reason => this.error(reason))\n this.uploadChannel.join()\n .receive(\"ok\", _data => this.readNextChunk())\n .receive(\"error\", reason => this.error(reason))\n }\n\n isDone(){ return this.offset >= this.entry.file.size }\n\n readNextChunk(){\n let reader = new window.FileReader()\n let blob = this.entry.file.slice(this.offset, this.chunkSize + this.offset)\n reader.onload = (e) => {\n if(e.target.error === null){\n this.offset += e.target.result.byteLength\n this.pushChunk(e.target.result)\n } else {\n return logError(\"Read error: \" + e.target.error)\n }\n }\n reader.readAsArrayBuffer(blob)\n }\n\n pushChunk(chunk){\n if(!this.uploadChannel.isJoined()){ return }\n this.uploadChannel.push(\"chunk\", chunk)\n .receive(\"ok\", () => {\n this.entry.progress((this.offset / this.entry.file.size) * 100)\n if(!this.isDone()){\n this.chunkTimer = setTimeout(() => this.readNextChunk(), this.liveSocket.getLatencySim() || 0)\n }\n })\n }\n}\n", "import {\n PHX_VIEW_SELECTOR\n} from \"./constants\"\n\nimport EntryUploader from \"./entry_uploader\"\n\nexport let logError = (msg, obj) => console.error && console.error(msg, obj)\n\nexport let isCid = (cid) => typeof(cid) === \"number\"\n\nexport function detectDuplicateIds(){\n let ids = new Set()\n let elems = document.querySelectorAll(\"*[id]\")\n for(let i = 0, len = elems.length; i < len; i++){\n if(ids.has(elems[i].id)){\n console.error(`Multiple IDs detected: ${elems[i].id}. Ensure unique element ids.`)\n } else {\n ids.add(elems[i].id)\n }\n }\n}\n\nexport let debug = (view, kind, msg, obj) => {\n if(view.liveSocket.isDebugEnabled()){\n console.log(`${view.id} ${kind}: ${msg} - `, obj)\n }\n}\n\n// wraps value in closure or returns closure\nexport let closure = (val) => typeof val === \"function\" ? val : function (){ return val }\n\nexport let clone = (obj) => { return JSON.parse(JSON.stringify(obj)) }\n\nexport let closestPhxBinding = (el, binding, borderEl) => {\n do {\n if(el.matches(`[${binding}]`)){ return el }\n el = el.parentElement || el.parentNode\n } while(el !== null && el.nodeType === 1 && !((borderEl && borderEl.isSameNode(el)) || el.matches(PHX_VIEW_SELECTOR)))\n return null\n}\n\nexport let isObject = (obj) => {\n return obj !== null && typeof obj === \"object\" && !(obj instanceof Array)\n}\n\nexport let isEqualObj = (obj1, obj2) => JSON.stringify(obj1) === JSON.stringify(obj2)\n\nexport let isEmpty = (obj) => {\n for(let x in obj){ return false }\n return true\n}\n\nexport let maybe = (el, callback) => el && callback(el)\n\nexport let channelUploader = function (entries, onError, resp, liveSocket){\n entries.forEach(entry => {\n let entryUploader = new EntryUploader(entry, resp.config.chunk_size, liveSocket)\n entryUploader.upload()\n })\n}\n", "let Browser = {\n canPushState(){ return (typeof (history.pushState) !== \"undefined\") },\n\n dropLocal(localStorage, namespace, subkey){\n return localStorage.removeItem(this.localKey(namespace, subkey))\n },\n\n updateLocal(localStorage, namespace, subkey, initial, func){\n let current = this.getLocal(localStorage, namespace, subkey)\n let key = this.localKey(namespace, subkey)\n let newVal = current === null ? initial : func(current)\n localStorage.setItem(key, JSON.stringify(newVal))\n return newVal\n },\n\n getLocal(localStorage, namespace, subkey){\n return JSON.parse(localStorage.getItem(this.localKey(namespace, subkey)))\n },\n\n updateCurrentState(callback){\n if(!this.canPushState()){ return }\n history.replaceState(callback(history.state || {}), \"\", window.location.href)\n },\n\n pushState(kind, meta, to){\n if(this.canPushState()){\n if(to !== window.location.href){\n if(meta.type == \"redirect\" && meta.scroll){\n // If we're redirecting store the current scrollY for the current history state.\n let currentState = history.state || {}\n currentState.scroll = meta.scroll\n history.replaceState(currentState, \"\", window.location.href)\n }\n\n delete meta.scroll // Only store the scroll in the redirect case.\n history[kind + \"State\"](meta, \"\", to || null) // IE will coerce undefined to string\n let hashEl = this.getHashTargetEl(window.location.hash)\n\n if(hashEl){\n hashEl.scrollIntoView()\n } else if(meta.type === \"redirect\"){\n window.scroll(0, 0)\n }\n }\n } else {\n this.redirect(to)\n }\n },\n\n setCookie(name, value){\n document.cookie = `${name}=${value}`\n },\n\n getCookie(name){\n return document.cookie.replace(new RegExp(`(?:(?:^|.*;\\s*)${name}\\s*\\=\\s*([^;]*).*$)|^.*$`), \"$1\")\n },\n\n redirect(toURL, flash){\n if(flash){ Browser.setCookie(\"__phoenix_flash__\", flash + \"; max-age=60000; path=/\") }\n window.location = toURL\n },\n\n localKey(namespace, subkey){ return `${namespace}-${subkey}` },\n\n getHashTargetEl(maybeHash){\n let hash = maybeHash.toString().substring(1)\n if(hash === \"\"){ return }\n return document.getElementById(hash) || document.querySelector(`a[name=\"${hash}\"]`)\n }\n}\n\nexport default Browser\n", "import {\n CHECKABLE_INPUTS,\n DEBOUNCE_PREV_KEY,\n DEBOUNCE_TRIGGER,\n FOCUSABLE_INPUTS,\n PHX_COMPONENT,\n PHX_EVENT_CLASSES,\n PHX_HAS_FOCUSED,\n PHX_HAS_SUBMITTED,\n PHX_MAIN,\n PHX_NO_FEEDBACK_CLASS,\n PHX_PARENT_ID,\n PHX_PRIVATE,\n PHX_REF,\n PHX_SESSION,\n PHX_STATIC,\n PHX_UPLOAD_REF,\n PHX_VIEW_SELECTOR,\n THROTTLED\n} from \"./constants\"\n\nimport {\n clone,\n logError\n} from \"./utils\"\n\nlet DOM = {\n byId(id){ return document.getElementById(id) || logError(`no id found for ${id}`) },\n\n removeClass(el, className){\n el.classList.remove(className)\n if(el.classList.length === 0){ el.removeAttribute(\"class\") }\n },\n\n all(node, query, callback){\n if(!node){ return [] }\n let array = Array.from(node.querySelectorAll(query))\n return callback ? array.forEach(callback) : array\n },\n\n childNodeLength(html){\n let template = document.createElement(\"template\")\n template.innerHTML = html\n return template.content.childElementCount\n },\n\n isUploadInput(el){ return el.type === \"file\" && el.getAttribute(PHX_UPLOAD_REF) !== null },\n\n findUploadInputs(node){ return this.all(node, `input[type=\"file\"][${PHX_UPLOAD_REF}]`) },\n\n findComponentNodeList(node, cid){\n return this.filterWithinSameLiveView(this.all(node, `[${PHX_COMPONENT}=\"${cid}\"]`), node)\n },\n\n isPhxDestroyed(node){\n return node.id && DOM.private(node, \"destroyed\") ? true : false\n },\n\n markPhxChildDestroyed(el){\n el.setAttribute(PHX_SESSION, \"\")\n this.putPrivate(el, \"destroyed\", true)\n },\n\n findPhxChildrenInFragment(html, parentId){\n let template = document.createElement(\"template\")\n template.innerHTML = html\n return this.findPhxChildren(template.content, parentId)\n },\n\n isIgnored(el, phxUpdate){\n return (el.getAttribute(phxUpdate) || el.getAttribute(\"data-phx-update\")) === \"ignore\"\n },\n\n isPhxUpdate(el, phxUpdate, updateTypes){\n return el.getAttribute && updateTypes.indexOf(el.getAttribute(phxUpdate)) >= 0\n },\n\n findPhxChildren(el, parentId){\n return this.all(el, `${PHX_VIEW_SELECTOR}[${PHX_PARENT_ID}=\"${parentId}\"]`)\n },\n\n findParentCIDs(node, cids){\n let initial = new Set(cids)\n return cids.reduce((acc, cid) => {\n let selector = `[${PHX_COMPONENT}=\"${cid}\"] [${PHX_COMPONENT}]`\n\n this.filterWithinSameLiveView(this.all(node, selector), node)\n .map(el => parseInt(el.getAttribute(PHX_COMPONENT)))\n .forEach(childCID => acc.delete(childCID))\n\n return acc\n }, initial)\n },\n\n filterWithinSameLiveView(nodes, parent){\n if(parent.querySelector(PHX_VIEW_SELECTOR)){\n return nodes.filter(el => this.withinSameLiveView(el, parent))\n } else {\n return nodes\n }\n },\n\n withinSameLiveView(node, parent){\n while(node = node.parentNode){\n if(node.isSameNode(parent)){ return true }\n if(node.getAttribute(PHX_SESSION) !== null){ return false }\n }\n },\n\n private(el, key){ return el[PHX_PRIVATE] && el[PHX_PRIVATE][key] },\n\n deletePrivate(el, key){ el[PHX_PRIVATE] && delete (el[PHX_PRIVATE][key]) },\n\n putPrivate(el, key, value){\n if(!el[PHX_PRIVATE]){ el[PHX_PRIVATE] = {} }\n el[PHX_PRIVATE][key] = value\n },\n\n copyPrivates(target, source){\n if(source[PHX_PRIVATE]){\n target[PHX_PRIVATE] = clone(source[PHX_PRIVATE])\n }\n },\n\n putTitle(str){\n let titleEl = document.querySelector(\"title\")\n let {prefix, suffix} = titleEl.dataset\n document.title = `${prefix || \"\"}${str}${suffix || \"\"}`\n },\n\n debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, callback){\n let debounce = el.getAttribute(phxDebounce)\n let throttle = el.getAttribute(phxThrottle)\n if(debounce === \"\"){ debounce = defaultDebounce }\n if(throttle === \"\"){ throttle = defaultThrottle }\n let value = debounce || throttle\n switch(value){\n case null: return callback()\n\n case \"blur\":\n if(this.once(el, \"debounce-blur\")){\n el.addEventListener(\"blur\", () => callback())\n }\n return\n\n default:\n let timeout = parseInt(value)\n let trigger = () => throttle ? this.deletePrivate(el, THROTTLED) : callback()\n let currentCycle = this.incCycle(el, DEBOUNCE_TRIGGER, trigger)\n if(isNaN(timeout)){ return logError(`invalid throttle/debounce value: ${value}`) }\n if(throttle){\n let newKeyDown = false\n if(event.type === \"keydown\"){\n let prevKey = this.private(el, DEBOUNCE_PREV_KEY)\n this.putPrivate(el, DEBOUNCE_PREV_KEY, event.key)\n newKeyDown = prevKey !== event.key\n }\n\n if(!newKeyDown && this.private(el, THROTTLED)){\n return false\n } else {\n callback()\n this.putPrivate(el, THROTTLED, true)\n setTimeout(() => this.triggerCycle(el, DEBOUNCE_TRIGGER), timeout)\n }\n } else {\n setTimeout(() => this.triggerCycle(el, DEBOUNCE_TRIGGER, currentCycle), timeout)\n }\n\n\n let form = el.form\n if(form && this.once(form, \"bind-debounce\")){\n form.addEventListener(\"submit\", () => {\n Array.from((new FormData(form)).entries(), ([name]) => {\n let input = form.querySelector(`[name=\"${name}\"]`)\n this.incCycle(input, DEBOUNCE_TRIGGER)\n this.deletePrivate(input, THROTTLED)\n })\n })\n }\n if(this.once(el, \"bind-debounce\")){\n el.addEventListener(\"blur\", () => this.triggerCycle(el, DEBOUNCE_TRIGGER))\n }\n }\n },\n\n triggerCycle(el, key, currentCycle){\n let [cycle, trigger] = this.private(el, key)\n if(!currentCycle){ currentCycle = cycle }\n if(currentCycle === cycle){\n this.incCycle(el, key)\n trigger()\n }\n },\n\n once(el, key){\n if(this.private(el, key) === true){ return false }\n this.putPrivate(el, key, true)\n return true\n },\n\n incCycle(el, key, trigger = function (){ }){\n let [currentCycle] = this.private(el, key) || [0, trigger]\n currentCycle++\n this.putPrivate(el, key, [currentCycle, trigger])\n return currentCycle\n },\n\n discardError(container, el, phxFeedbackFor){\n let field = el.getAttribute && el.getAttribute(phxFeedbackFor)\n // TODO: Remove id lookup after we update Phoenix to use input_name instead of input_id\n let input = field && container.querySelector(`[id=\"${field}\"], [name=\"${field}\"]`)\n if(!input){ return }\n\n if(!(this.private(input, PHX_HAS_FOCUSED) || this.private(input.form, PHX_HAS_SUBMITTED))){\n el.classList.add(PHX_NO_FEEDBACK_CLASS)\n }\n },\n\n showError(inputEl, phxFeedbackFor){\n if(inputEl.id || inputEl.name){\n this.all(inputEl.form, `[${phxFeedbackFor}=\"${inputEl.id}\"], [${phxFeedbackFor}=\"${inputEl.name}\"]`, (el) => {\n this.removeClass(el, PHX_NO_FEEDBACK_CLASS)\n })\n }\n },\n\n isPhxChild(node){\n return node.getAttribute && node.getAttribute(PHX_PARENT_ID)\n },\n\n dispatchEvent(target, eventString, detail = {}){\n let event = new CustomEvent(eventString, {bubbles: true, cancelable: true, detail: detail})\n target.dispatchEvent(event)\n },\n\n cloneNode(node, html){\n if(typeof (html) === \"undefined\"){\n return node.cloneNode(true)\n } else {\n let cloned = node.cloneNode(false)\n cloned.innerHTML = html\n return cloned\n }\n },\n\n mergeAttrs(target, source, opts = {}){\n let exclude = opts.exclude || []\n let isIgnored = opts.isIgnored\n let sourceAttrs = source.attributes\n for(let i = sourceAttrs.length - 1; i >= 0; i--){\n let name = sourceAttrs[i].name\n if(exclude.indexOf(name) < 0){ target.setAttribute(name, source.getAttribute(name)) }\n }\n\n let targetAttrs = target.attributes\n for(let i = targetAttrs.length - 1; i >= 0; i--){\n let name = targetAttrs[i].name\n if(isIgnored){\n if(name.startsWith(\"data-\") && !source.hasAttribute(name)){ target.removeAttribute(name) }\n } else {\n if(!source.hasAttribute(name)){ target.removeAttribute(name) }\n }\n }\n },\n\n mergeFocusedInput(target, source){\n // skip selects because FF will reset highlighted index for any setAttribute\n if(!(target instanceof HTMLSelectElement)){ DOM.mergeAttrs(target, source, {except: [\"value\"]}) }\n if(source.readOnly){\n target.setAttribute(\"readonly\", true)\n } else {\n target.removeAttribute(\"readonly\")\n }\n },\n\n hasSelectionRange(el){\n return el.setSelectionRange && (el.type === \"text\" || el.type === \"textarea\")\n },\n\n restoreFocus(focused, selectionStart, selectionEnd){\n if(!DOM.isTextualInput(focused)){ return }\n let wasFocused = focused.matches(\":focus\")\n if(focused.readOnly){ focused.blur() }\n if(!wasFocused){ focused.focus() }\n if(this.hasSelectionRange(focused)){\n focused.setSelectionRange(selectionStart, selectionEnd)\n }\n },\n\n isFormInput(el){ return /^(?:input|select|textarea)$/i.test(el.tagName) && el.type !== \"button\" },\n\n syncAttrsToProps(el){\n if(el instanceof HTMLInputElement && CHECKABLE_INPUTS.indexOf(el.type.toLocaleLowerCase()) >= 0){\n el.checked = el.getAttribute(\"checked\") !== null\n }\n },\n\n syncPropsToAttrs(el){\n if(el instanceof HTMLSelectElement){\n let selectedItem = el.options.item(el.selectedIndex)\n if(selectedItem && selectedItem.getAttribute(\"selected\") === null){\n selectedItem.setAttribute(\"selected\", \"\")\n }\n }\n },\n\n isTextualInput(el){ return FOCUSABLE_INPUTS.indexOf(el.type) >= 0 },\n\n isNowTriggerFormExternal(el, phxTriggerExternal){\n return el.getAttribute && el.getAttribute(phxTriggerExternal) !== null\n },\n\n syncPendingRef(fromEl, toEl, disableWith){\n let ref = fromEl.getAttribute(PHX_REF)\n if(ref === null){ return true }\n\n if(DOM.isFormInput(fromEl) || fromEl.getAttribute(disableWith) !== null){\n if(DOM.isUploadInput(fromEl)){ DOM.mergeAttrs(fromEl, toEl, {isIgnored: true}) }\n DOM.putPrivate(fromEl, PHX_REF, toEl)\n return false\n } else {\n PHX_EVENT_CLASSES.forEach(className => {\n fromEl.classList.contains(className) && toEl.classList.add(className)\n })\n toEl.setAttribute(PHX_REF, ref)\n return true\n }\n },\n\n cleanChildNodes(container, phxUpdate){\n if(DOM.isPhxUpdate(container, phxUpdate, [\"append\", \"prepend\"])){\n let toRemove = []\n container.childNodes.forEach(childNode => {\n if(!childNode.id){\n // Skip warning if it's an empty text node (e.g. a new-line)\n let isEmptyTextNode = childNode.nodeType === Node.TEXT_NODE && childNode.nodeValue.trim() === \"\"\n if(!isEmptyTextNode){\n logError(\"only HTML element tags with an id are allowed inside containers with phx-update.\\n\\n\" +\n `removing illegal node: \"${(childNode.outerHTML || childNode.nodeValue).trim()}\"\\n\\n`)\n }\n toRemove.push(childNode)\n }\n })\n toRemove.forEach(childNode => childNode.remove())\n }\n },\n\n replaceRootContainer(container, tagName, attrs){\n let retainedAttrs = new Set([\"id\", PHX_SESSION, PHX_STATIC, PHX_MAIN])\n if(container.tagName.toLowerCase() === tagName.toLowerCase()){\n Array.from(container.attributes)\n .filter(attr => !retainedAttrs.has(attr.name.toLowerCase()))\n .forEach(attr => container.removeAttribute(attr.name))\n\n Object.keys(attrs)\n .filter(name => !retainedAttrs.has(name.toLowerCase()))\n .forEach(attr => container.setAttribute(attr, attrs[attr]))\n\n return container\n\n } else {\n let newContainer = document.createElement(tagName)\n Object.keys(attrs).forEach(attr => newContainer.setAttribute(attr, attrs[attr]))\n retainedAttrs.forEach(attr => newContainer.setAttribute(attr, container.getAttribute(attr)))\n newContainer.innerHTML = container.innerHTML\n container.replaceWith(newContainer)\n return newContainer\n }\n }\n}\n\nexport default DOM\n", "import {\n PHX_ACTIVE_ENTRY_REFS,\n PHX_LIVE_FILE_UPDATED,\n PHX_PREFLIGHTED_REFS\n} from \"./constants\"\n\nimport {\n channelUploader,\n logError\n} from \"./utils\"\n\nimport LiveUploader from \"./live_uploader\"\n\nexport default class UploadEntry {\n static isActive(fileEl, file){\n let isNew = file._phxRef === undefined\n let activeRefs = fileEl.getAttribute(PHX_ACTIVE_ENTRY_REFS).split(\",\")\n let isActive = activeRefs.indexOf(LiveUploader.genFileRef(file)) >= 0\n return file.size > 0 && (isNew || isActive)\n }\n\n static isPreflighted(fileEl, file){\n let preflightedRefs = fileEl.getAttribute(PHX_PREFLIGHTED_REFS).split(\",\")\n let isPreflighted = preflightedRefs.indexOf(LiveUploader.genFileRef(file)) >= 0\n return isPreflighted && this.isActive(fileEl, file)\n }\n\n constructor(fileEl, file, view){\n this.ref = LiveUploader.genFileRef(file)\n this.fileEl = fileEl\n this.file = file\n this.view = view\n this.meta = null\n this._isCancelled = false\n this._isDone = false\n this._progress = 0\n this._lastProgressSent = -1\n this._onDone = function (){ }\n this._onElUpdated = this.onElUpdated.bind(this)\n this.fileEl.addEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated)\n }\n\n metadata(){ return this.meta }\n\n progress(progress){\n this._progress = Math.floor(progress)\n if(this._progress > this._lastProgressSent){\n if(this._progress >= 100){\n this._progress = 100\n this._lastProgressSent = 100\n this._isDone = true\n this.view.pushFileProgress(this.fileEl, this.ref, 100, () => {\n LiveUploader.untrackFile(this.fileEl, this.file)\n this._onDone()\n })\n } else {\n this._lastProgressSent = this._progress\n this.view.pushFileProgress(this.fileEl, this.ref, this._progress)\n }\n }\n }\n\n cancel(){\n this._isCancelled = true\n this._isDone = true\n this._onDone()\n }\n\n isDone(){ return this._isDone }\n\n error(reason = \"failed\"){\n this.view.pushFileProgress(this.fileEl, this.ref, {error: reason})\n LiveUploader.clearFiles(this.fileEl)\n }\n\n //private\n\n onDone(callback){\n this._onDone = () => {\n this.fileEl.removeEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated)\n callback()\n }\n }\n\n onElUpdated(){\n let activeRefs = this.fileEl.getAttribute(PHX_ACTIVE_ENTRY_REFS).split(\",\")\n if(activeRefs.indexOf(this.ref) === -1){ this.cancel() }\n }\n\n toPreflightPayload(){\n return {\n last_modified: this.file.lastModified,\n name: this.file.name,\n size: this.file.size,\n type: this.file.type,\n ref: this.ref\n }\n }\n\n uploader(uploaders){\n if(this.meta.uploader){\n let callback = uploaders[this.meta.uploader] || logError(`no uploader configured for ${this.meta.uploader}`)\n return {name: this.meta.uploader, callback: callback}\n } else {\n return {name: \"channel\", callback: channelUploader}\n }\n }\n\n zipPostFlight(resp){\n this.meta = resp.entries[this.ref]\n if(!this.meta){ logError(`no preflight upload response returned with ref ${this.ref}`, {input: this.fileEl, response: resp}) }\n }\n}\n", "import {\n PHX_DONE_REFS,\n PHX_PREFLIGHTED_REFS,\n PHX_UPLOAD_REF\n} from \"./constants\"\n\nimport {\n} from \"./utils\"\n\nimport DOM from \"./dom\"\nimport UploadEntry from \"./upload_entry\"\n\nlet liveUploaderFileRef = 0\n\nexport default class LiveUploader {\n static genFileRef(file){\n let ref = file._phxRef\n if(ref !== undefined){\n return ref\n } else {\n file._phxRef = (liveUploaderFileRef++).toString()\n return file._phxRef\n }\n }\n\n static getEntryDataURL(inputEl, ref, callback){\n let file = this.activeFiles(inputEl).find(file => this.genFileRef(file) === ref)\n callback(URL.createObjectURL(file))\n }\n\n static hasUploadsInProgress(formEl){\n let active = 0\n DOM.findUploadInputs(formEl).forEach(input => {\n if(input.getAttribute(PHX_PREFLIGHTED_REFS) !== input.getAttribute(PHX_DONE_REFS)){\n active++\n }\n })\n return active > 0\n }\n\n static serializeUploads(inputEl){\n let files = this.activeFiles(inputEl)\n let fileData = {}\n files.forEach(file => {\n let entry = {path: inputEl.name}\n let uploadRef = inputEl.getAttribute(PHX_UPLOAD_REF)\n fileData[uploadRef] = fileData[uploadRef] || []\n entry.ref = this.genFileRef(file)\n entry.name = file.name || entry.ref\n entry.type = file.type\n entry.size = file.size\n fileData[uploadRef].push(entry)\n })\n return fileData\n }\n\n static clearFiles(inputEl){\n inputEl.value = null\n inputEl.removeAttribute(PHX_UPLOAD_REF)\n DOM.putPrivate(inputEl, \"files\", [])\n }\n\n static untrackFile(inputEl, file){\n DOM.putPrivate(inputEl, \"files\", DOM.private(inputEl, \"files\").filter(f => !Object.is(f, file)))\n }\n\n static trackFiles(inputEl, files){\n if(inputEl.getAttribute(\"multiple\") !== null){\n let newFiles = files.filter(file => !this.activeFiles(inputEl).find(f => Object.is(f, file)))\n DOM.putPrivate(inputEl, \"files\", this.activeFiles(inputEl).concat(newFiles))\n inputEl.value = null\n } else {\n DOM.putPrivate(inputEl, \"files\", files)\n }\n }\n\n static activeFileInputs(formEl){\n let fileInputs = DOM.findUploadInputs(formEl)\n return Array.from(fileInputs).filter(el => el.files && this.activeFiles(el).length > 0)\n }\n\n static activeFiles(input){\n return (DOM.private(input, \"files\") || []).filter(f => UploadEntry.isActive(input, f))\n }\n\n static inputsAwaitingPreflight(formEl){\n let fileInputs = DOM.findUploadInputs(formEl)\n return Array.from(fileInputs).filter(input => this.filesAwaitingPreflight(input).length > 0)\n }\n\n static filesAwaitingPreflight(input){\n return this.activeFiles(input).filter(f => !UploadEntry.isPreflighted(input, f))\n }\n\n constructor(inputEl, view, onComplete){\n this.view = view\n this.onComplete = onComplete\n this._entries =\n Array.from(LiveUploader.filesAwaitingPreflight(inputEl) || [])\n .map(file => new UploadEntry(inputEl, file, view))\n\n this.numEntriesInProgress = this._entries.length\n }\n\n entries(){ return this._entries }\n\n initAdapterUpload(resp, onError, liveSocket){\n this._entries =\n this._entries.map(entry => {\n entry.zipPostFlight(resp)\n entry.onDone(() => {\n this.numEntriesInProgress--\n if(this.numEntriesInProgress === 0){ this.onComplete() }\n })\n return entry\n })\n\n let groupedEntries = this._entries.reduce((acc, entry) => {\n let {name, callback} = entry.uploader(liveSocket.uploaders)\n acc[name] = acc[name] || {callback: callback, entries: []}\n acc[name].entries.push(entry)\n return acc\n }, {})\n\n for(let name in groupedEntries){\n let {callback, entries} = groupedEntries[name]\n callback(entries, onError, resp, liveSocket)\n }\n }\n}\n", "import {\n PHX_ACTIVE_ENTRY_REFS,\n PHX_LIVE_FILE_UPDATED,\n PHX_PREFLIGHTED_REFS,\n PHX_UPLOAD_REF\n} from \"./constants\"\n\nimport LiveUploader from \"./live_uploader\"\n\nlet Hooks = {\n LiveFileUpload: {\n activeRefs(){ return this.el.getAttribute(PHX_ACTIVE_ENTRY_REFS) },\n\n preflightedRefs(){ return this.el.getAttribute(PHX_PREFLIGHTED_REFS) },\n\n mounted(){ this.preflightedWas = this.preflightedRefs() },\n\n updated(){\n let newPreflights = this.preflightedRefs()\n if(this.preflightedWas !== newPreflights){\n this.preflightedWas = newPreflights\n if(newPreflights === \"\"){\n this.__view.cancelSubmit(this.el.form)\n }\n }\n\n if(this.activeRefs() === \"\"){ this.el.value = null }\n this.el.dispatchEvent(new CustomEvent(PHX_LIVE_FILE_UPDATED))\n }\n },\n\n LiveImgPreview: {\n mounted(){\n this.ref = this.el.getAttribute(\"data-phx-entry-ref\")\n this.inputEl = document.getElementById(this.el.getAttribute(PHX_UPLOAD_REF))\n LiveUploader.getEntryDataURL(this.inputEl, this.ref, url => {\n this.url = url\n this.el.src = url\n })\n },\n destroyed(){\n URL.revokeObjectURL(this.url)\n }\n }\n}\n\nexport default Hooks\n", "import {\n maybe\n} from \"./utils\"\n\nimport DOM from \"./dom\"\n\nexport default class DOMPostMorphRestorer {\n constructor(containerBefore, containerAfter, updateType){\n let idsBefore = new Set()\n let idsAfter = new Set([...containerAfter.children].map(child => child.id))\n\n let elementsToModify = []\n\n Array.from(containerBefore.children).forEach(child => {\n if(child.id){ // all of our children should be elements with ids\n idsBefore.add(child.id)\n if(idsAfter.has(child.id)){\n let previousElementId = child.previousElementSibling && child.previousElementSibling.id\n elementsToModify.push({elementId: child.id, previousElementId: previousElementId})\n }\n }\n })\n\n this.containerId = containerAfter.id\n this.updateType = updateType\n this.elementsToModify = elementsToModify\n this.elementIdsToAdd = [...idsAfter].filter(id => !idsBefore.has(id))\n }\n\n // We do the following to optimize append/prepend operations:\n // 1) Track ids of modified elements & of new elements\n // 2) All the modified elements are put back in the correct position in the DOM tree\n // by storing the id of their previous sibling\n // 3) New elements are going to be put in the right place by morphdom during append.\n // For prepend, we move them to the first position in the container\n perform(){\n let container = DOM.byId(this.containerId)\n this.elementsToModify.forEach(elementToModify => {\n if(elementToModify.previousElementId){\n maybe(document.getElementById(elementToModify.previousElementId), previousElem => {\n maybe(document.getElementById(elementToModify.elementId), elem => {\n let isInRightPlace = elem.previousElementSibling && elem.previousElementSibling.id == previousElem.id\n if(!isInRightPlace){\n previousElem.insertAdjacentElement(\"afterend\", elem)\n }\n })\n })\n } else {\n // This is the first element in the container\n maybe(document.getElementById(elementToModify.elementId), elem => {\n let isInRightPlace = elem.previousElementSibling == null\n if(!isInRightPlace){\n container.insertAdjacentElement(\"afterbegin\", elem)\n }\n })\n }\n })\n\n if(this.updateType == \"prepend\"){\n this.elementIdsToAdd.reverse().forEach(elemId => {\n maybe(document.getElementById(elemId), elem => container.insertAdjacentElement(\"afterbegin\", elem))\n })\n }\n }\n}\n", "var DOCUMENT_FRAGMENT_NODE = 11;\n\nfunction morphAttrs(fromNode, toNode) {\n var toNodeAttrs = toNode.attributes;\n var attr;\n var attrName;\n var attrNamespaceURI;\n var attrValue;\n var fromValue;\n\n // document-fragments dont have attributes so lets not do anything\n if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE || fromNode.nodeType === DOCUMENT_FRAGMENT_NODE) {\n return;\n }\n\n // update attributes on original DOM element\n for (var i = toNodeAttrs.length - 1; i >= 0; i--) {\n attr = toNodeAttrs[i];\n attrName = attr.name;\n attrNamespaceURI = attr.namespaceURI;\n attrValue = attr.value;\n\n if (attrNamespaceURI) {\n attrName = attr.localName || attrName;\n fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName);\n\n if (fromValue !== attrValue) {\n if (attr.prefix === 'xmlns'){\n attrName = attr.name; // It's not allowed to set an attribute with the XMLNS namespace without specifying the `xmlns` prefix\n }\n fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue);\n }\n } else {\n fromValue = fromNode.getAttribute(attrName);\n\n if (fromValue !== attrValue) {\n fromNode.setAttribute(attrName, attrValue);\n }\n }\n }\n\n // Remove any extra attributes found on the original DOM element that\n // weren't found on the target element.\n var fromNodeAttrs = fromNode.attributes;\n\n for (var d = fromNodeAttrs.length - 1; d >= 0; d--) {\n attr = fromNodeAttrs[d];\n attrName = attr.name;\n attrNamespaceURI = attr.namespaceURI;\n\n if (attrNamespaceURI) {\n attrName = attr.localName || attrName;\n\n if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) {\n fromNode.removeAttributeNS(attrNamespaceURI, attrName);\n }\n } else {\n if (!toNode.hasAttribute(attrName)) {\n fromNode.removeAttribute(attrName);\n }\n }\n }\n}\n\nvar range; // Create a range object for efficently rendering strings to elements.\nvar NS_XHTML = 'http://www.w3.org/1999/xhtml';\n\nvar doc = typeof document === 'undefined' ? undefined : document;\nvar HAS_TEMPLATE_SUPPORT = !!doc && 'content' in doc.createElement('template');\nvar HAS_RANGE_SUPPORT = !!doc && doc.createRange && 'createContextualFragment' in doc.createRange();\n\nfunction createFragmentFromTemplate(str) {\n var template = doc.createElement('template');\n template.innerHTML = str;\n return template.content.childNodes[0];\n}\n\nfunction createFragmentFromRange(str) {\n if (!range) {\n range = doc.createRange();\n range.selectNode(doc.body);\n }\n\n var fragment = range.createContextualFragment(str);\n return fragment.childNodes[0];\n}\n\nfunction createFragmentFromWrap(str) {\n var fragment = doc.createElement('body');\n fragment.innerHTML = str;\n return fragment.childNodes[0];\n}\n\n/**\n * This is about the same\n * var html = new DOMParser().parseFromString(str, 'text/html');\n * return html.body.firstChild;\n *\n * @method toElement\n * @param {String} str\n */\nfunction toElement(str) {\n str = str.trim();\n if (HAS_TEMPLATE_SUPPORT) {\n // avoid restrictions on content for things like `Hi ` which\n // createContextualFragment doesn't support\n // support not available in IE\n return createFragmentFromTemplate(str);\n } else if (HAS_RANGE_SUPPORT) {\n return createFragmentFromRange(str);\n }\n\n return createFragmentFromWrap(str);\n}\n\n/**\n * Returns true if two node's names are the same.\n *\n * NOTE: We don't bother checking `namespaceURI` because you will never find two HTML elements with the same\n * nodeName and different namespace URIs.\n *\n * @param {Element} a\n * @param {Element} b The target element\n * @return {boolean}\n */\nfunction compareNodeNames(fromEl, toEl) {\n var fromNodeName = fromEl.nodeName;\n var toNodeName = toEl.nodeName;\n var fromCodeStart, toCodeStart;\n\n if (fromNodeName === toNodeName) {\n return true;\n }\n\n fromCodeStart = fromNodeName.charCodeAt(0);\n toCodeStart = toNodeName.charCodeAt(0);\n\n // If the target element is a virtual DOM node or SVG node then we may\n // need to normalize the tag name before comparing. Normal HTML elements that are\n // in the \"http://www.w3.org/1999/xhtml\"\n // are converted to upper case\n if (fromCodeStart <= 90 && toCodeStart >= 97) { // from is upper and to is lower\n return fromNodeName === toNodeName.toUpperCase();\n } else if (toCodeStart <= 90 && fromCodeStart >= 97) { // to is upper and from is lower\n return toNodeName === fromNodeName.toUpperCase();\n } else {\n return false;\n }\n}\n\n/**\n * Create an element, optionally with a known namespace URI.\n *\n * @param {string} name the element name, e.g. 'div' or 'svg'\n * @param {string} [namespaceURI] the element's namespace URI, i.e. the value of\n * its `xmlns` attribute or its inferred namespace.\n *\n * @return {Element}\n */\nfunction createElementNS(name, namespaceURI) {\n return !namespaceURI || namespaceURI === NS_XHTML ?\n doc.createElement(name) :\n doc.createElementNS(namespaceURI, name);\n}\n\n/**\n * Copies the children of one DOM element to another DOM element\n */\nfunction moveChildren(fromEl, toEl) {\n var curChild = fromEl.firstChild;\n while (curChild) {\n var nextChild = curChild.nextSibling;\n toEl.appendChild(curChild);\n curChild = nextChild;\n }\n return toEl;\n}\n\nfunction syncBooleanAttrProp(fromEl, toEl, name) {\n if (fromEl[name] !== toEl[name]) {\n fromEl[name] = toEl[name];\n if (fromEl[name]) {\n fromEl.setAttribute(name, '');\n } else {\n fromEl.removeAttribute(name);\n }\n }\n}\n\nvar specialElHandlers = {\n OPTION: function(fromEl, toEl) {\n var parentNode = fromEl.parentNode;\n if (parentNode) {\n var parentName = parentNode.nodeName.toUpperCase();\n if (parentName === 'OPTGROUP') {\n parentNode = parentNode.parentNode;\n parentName = parentNode && parentNode.nodeName.toUpperCase();\n }\n if (parentName === 'SELECT' && !parentNode.hasAttribute('multiple')) {\n if (fromEl.hasAttribute('selected') && !toEl.selected) {\n // Workaround for MS Edge bug where the 'selected' attribute can only be\n // removed if set to a non-empty value:\n // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12087679/\n fromEl.setAttribute('selected', 'selected');\n fromEl.removeAttribute('selected');\n }\n // We have to reset select element's selectedIndex to -1, otherwise setting\n // fromEl.selected using the syncBooleanAttrProp below has no effect.\n // The correct selectedIndex will be set in the SELECT special handler below.\n parentNode.selectedIndex = -1;\n }\n }\n syncBooleanAttrProp(fromEl, toEl, 'selected');\n },\n /**\n * The \"value\" attribute is special for the element since it sets\n * the initial value. Changing the \"value\" attribute without changing the\n * \"value\" property will have no effect since it is only used to the set the\n * initial value. Similar for the \"checked\" attribute, and \"disabled\".\n */\n INPUT: function(fromEl, toEl) {\n syncBooleanAttrProp(fromEl, toEl, 'checked');\n syncBooleanAttrProp(fromEl, toEl, 'disabled');\n\n if (fromEl.value !== toEl.value) {\n fromEl.value = toEl.value;\n }\n\n if (!toEl.hasAttribute('value')) {\n fromEl.removeAttribute('value');\n }\n },\n\n TEXTAREA: function(fromEl, toEl) {\n var newValue = toEl.value;\n if (fromEl.value !== newValue) {\n fromEl.value = newValue;\n }\n\n var firstChild = fromEl.firstChild;\n if (firstChild) {\n // Needed for IE. Apparently IE sets the placeholder as the\n // node value and vise versa. This ignores an empty update.\n var oldValue = firstChild.nodeValue;\n\n if (oldValue == newValue || (!newValue && oldValue == fromEl.placeholder)) {\n return;\n }\n\n firstChild.nodeValue = newValue;\n }\n },\n SELECT: function(fromEl, toEl) {\n if (!toEl.hasAttribute('multiple')) {\n var selectedIndex = -1;\n var i = 0;\n // We have to loop through children of fromEl, not toEl since nodes can be moved\n // from toEl to fromEl directly when morphing.\n // At the time this special handler is invoked, all children have already been morphed\n // and appended to / removed from fromEl, so using fromEl here is safe and correct.\n var curChild = fromEl.firstChild;\n var optgroup;\n var nodeName;\n while(curChild) {\n nodeName = curChild.nodeName && curChild.nodeName.toUpperCase();\n if (nodeName === 'OPTGROUP') {\n optgroup = curChild;\n curChild = optgroup.firstChild;\n } else {\n if (nodeName === 'OPTION') {\n if (curChild.hasAttribute('selected')) {\n selectedIndex = i;\n break;\n }\n i++;\n }\n curChild = curChild.nextSibling;\n if (!curChild && optgroup) {\n curChild = optgroup.nextSibling;\n optgroup = null;\n }\n }\n }\n\n fromEl.selectedIndex = selectedIndex;\n }\n }\n};\n\nvar ELEMENT_NODE = 1;\nvar DOCUMENT_FRAGMENT_NODE$1 = 11;\nvar TEXT_NODE = 3;\nvar COMMENT_NODE = 8;\n\nfunction noop() {}\n\nfunction defaultGetNodeKey(node) {\n if (node) {\n return (node.getAttribute && node.getAttribute('id')) || node.id;\n }\n}\n\nfunction morphdomFactory(morphAttrs) {\n\n return function morphdom(fromNode, toNode, options) {\n if (!options) {\n options = {};\n }\n\n if (typeof toNode === 'string') {\n if (fromNode.nodeName === '#document' || fromNode.nodeName === 'HTML' || fromNode.nodeName === 'BODY') {\n var toNodeHtml = toNode;\n toNode = doc.createElement('html');\n toNode.innerHTML = toNodeHtml;\n } else {\n toNode = toElement(toNode);\n }\n }\n\n var getNodeKey = options.getNodeKey || defaultGetNodeKey;\n var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;\n var onNodeAdded = options.onNodeAdded || noop;\n var onBeforeElUpdated = options.onBeforeElUpdated || noop;\n var onElUpdated = options.onElUpdated || noop;\n var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;\n var onNodeDiscarded = options.onNodeDiscarded || noop;\n var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;\n var childrenOnly = options.childrenOnly === true;\n\n // This object is used as a lookup to quickly find all keyed elements in the original DOM tree.\n var fromNodesLookup = Object.create(null);\n var keyedRemovalList = [];\n\n function addKeyedRemoval(key) {\n keyedRemovalList.push(key);\n }\n\n function walkDiscardedChildNodes(node, skipKeyedNodes) {\n if (node.nodeType === ELEMENT_NODE) {\n var curChild = node.firstChild;\n while (curChild) {\n\n var key = undefined;\n\n if (skipKeyedNodes && (key = getNodeKey(curChild))) {\n // If we are skipping keyed nodes then we add the key\n // to a list so that it can be handled at the very end.\n addKeyedRemoval(key);\n } else {\n // Only report the node as discarded if it is not keyed. We do this because\n // at the end we loop through all keyed elements that were unmatched\n // and then discard them in one final pass.\n onNodeDiscarded(curChild);\n if (curChild.firstChild) {\n walkDiscardedChildNodes(curChild, skipKeyedNodes);\n }\n }\n\n curChild = curChild.nextSibling;\n }\n }\n }\n\n /**\n * Removes a DOM node out of the original DOM\n *\n * @param {Node} node The node to remove\n * @param {Node} parentNode The nodes parent\n * @param {Boolean} skipKeyedNodes If true then elements with keys will be skipped and not discarded.\n * @return {undefined}\n */\n function removeNode(node, parentNode, skipKeyedNodes) {\n if (onBeforeNodeDiscarded(node) === false) {\n return;\n }\n\n if (parentNode) {\n parentNode.removeChild(node);\n }\n\n onNodeDiscarded(node);\n walkDiscardedChildNodes(node, skipKeyedNodes);\n }\n\n // // TreeWalker implementation is no faster, but keeping this around in case this changes in the future\n // function indexTree(root) {\n // var treeWalker = document.createTreeWalker(\n // root,\n // NodeFilter.SHOW_ELEMENT);\n //\n // var el;\n // while((el = treeWalker.nextNode())) {\n // var key = getNodeKey(el);\n // if (key) {\n // fromNodesLookup[key] = el;\n // }\n // }\n // }\n\n // // NodeIterator implementation is no faster, but keeping this around in case this changes in the future\n //\n // function indexTree(node) {\n // var nodeIterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT);\n // var el;\n // while((el = nodeIterator.nextNode())) {\n // var key = getNodeKey(el);\n // if (key) {\n // fromNodesLookup[key] = el;\n // }\n // }\n // }\n\n function indexTree(node) {\n if (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE$1) {\n var curChild = node.firstChild;\n while (curChild) {\n var key = getNodeKey(curChild);\n if (key) {\n fromNodesLookup[key] = curChild;\n }\n\n // Walk recursively\n indexTree(curChild);\n\n curChild = curChild.nextSibling;\n }\n }\n }\n\n indexTree(fromNode);\n\n function handleNodeAdded(el) {\n onNodeAdded(el);\n\n var curChild = el.firstChild;\n while (curChild) {\n var nextSibling = curChild.nextSibling;\n\n var key = getNodeKey(curChild);\n if (key) {\n var unmatchedFromEl = fromNodesLookup[key];\n // if we find a duplicate #id node in cache, replace `el` with cache value\n // and morph it to the child node.\n if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) {\n curChild.parentNode.replaceChild(unmatchedFromEl, curChild);\n morphEl(unmatchedFromEl, curChild);\n } else {\n handleNodeAdded(curChild);\n }\n } else {\n // recursively call for curChild and it's children to see if we find something in\n // fromNodesLookup\n handleNodeAdded(curChild);\n }\n\n curChild = nextSibling;\n }\n }\n\n function cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey) {\n // We have processed all of the \"to nodes\". If curFromNodeChild is\n // non-null then we still have some from nodes left over that need\n // to be removed\n while (curFromNodeChild) {\n var fromNextSibling = curFromNodeChild.nextSibling;\n if ((curFromNodeKey = getNodeKey(curFromNodeChild))) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n curFromNodeChild = fromNextSibling;\n }\n }\n\n function morphEl(fromEl, toEl, childrenOnly) {\n var toElKey = getNodeKey(toEl);\n\n if (toElKey) {\n // If an element with an ID is being morphed then it will be in the final\n // DOM so clear it out of the saved elements collection\n delete fromNodesLookup[toElKey];\n }\n\n if (!childrenOnly) {\n // optional\n if (onBeforeElUpdated(fromEl, toEl) === false) {\n return;\n }\n\n // update attributes on original DOM element first\n morphAttrs(fromEl, toEl);\n // optional\n onElUpdated(fromEl);\n\n if (onBeforeElChildrenUpdated(fromEl, toEl) === false) {\n return;\n }\n }\n\n if (fromEl.nodeName !== 'TEXTAREA') {\n morphChildren(fromEl, toEl);\n } else {\n specialElHandlers.TEXTAREA(fromEl, toEl);\n }\n }\n\n function morphChildren(fromEl, toEl) {\n var curToNodeChild = toEl.firstChild;\n var curFromNodeChild = fromEl.firstChild;\n var curToNodeKey;\n var curFromNodeKey;\n\n var fromNextSibling;\n var toNextSibling;\n var matchingFromEl;\n\n // walk the children\n outer: while (curToNodeChild) {\n toNextSibling = curToNodeChild.nextSibling;\n curToNodeKey = getNodeKey(curToNodeChild);\n\n // walk the fromNode children all the way through\n while (curFromNodeChild) {\n fromNextSibling = curFromNodeChild.nextSibling;\n\n if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n continue outer;\n }\n\n curFromNodeKey = getNodeKey(curFromNodeChild);\n\n var curFromNodeType = curFromNodeChild.nodeType;\n\n // this means if the curFromNodeChild doesnt have a match with the curToNodeChild\n var isCompatible = undefined;\n\n if (curFromNodeType === curToNodeChild.nodeType) {\n if (curFromNodeType === ELEMENT_NODE) {\n // Both nodes being compared are Element nodes\n\n if (curToNodeKey) {\n // The target node has a key so we want to match it up with the correct element\n // in the original DOM tree\n if (curToNodeKey !== curFromNodeKey) {\n // The current element in the original DOM tree does not have a matching key so\n // let's check our lookup to see if there is a matching element in the original\n // DOM tree\n if ((matchingFromEl = fromNodesLookup[curToNodeKey])) {\n if (fromNextSibling === matchingFromEl) {\n // Special case for single element removals. To avoid removing the original\n // DOM node out of the tree (since that can break CSS transitions, etc.),\n // we will instead discard the current node and wait until the next\n // iteration to properly match up the keyed target element with its matching\n // element in the original tree\n isCompatible = false;\n } else {\n // We found a matching keyed element somewhere in the original DOM tree.\n // Let's move the original DOM node into the current position and morph\n // it.\n\n // NOTE: We use insertBefore instead of replaceChild because we want to go through\n // the `removeNode()` function for the node that is being discarded so that\n // all lifecycle hooks are correctly invoked\n fromEl.insertBefore(matchingFromEl, curFromNodeChild);\n\n // fromNextSibling = curFromNodeChild.nextSibling;\n\n if (curFromNodeKey) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n\n curFromNodeChild = matchingFromEl;\n }\n } else {\n // The nodes are not compatible since the \"to\" node has a key and there\n // is no matching keyed node in the source tree\n isCompatible = false;\n }\n }\n } else if (curFromNodeKey) {\n // The original has a key\n isCompatible = false;\n }\n\n isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild);\n if (isCompatible) {\n // We found compatible DOM elements so transform\n // the current \"from\" node to match the current\n // target DOM node.\n // MORPH\n morphEl(curFromNodeChild, curToNodeChild);\n }\n\n } else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) {\n // Both nodes being compared are Text or Comment nodes\n isCompatible = true;\n // Simply update nodeValue on the original node to\n // change the text value\n if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) {\n curFromNodeChild.nodeValue = curToNodeChild.nodeValue;\n }\n\n }\n }\n\n if (isCompatible) {\n // Advance both the \"to\" child and the \"from\" child since we found a match\n // Nothing else to do as we already recursively called morphChildren above\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n continue outer;\n }\n\n // No compatible match so remove the old node from the DOM and continue trying to find a\n // match in the original DOM. However, we only do this if the from node is not keyed\n // since it is possible that a keyed node might match up with a node somewhere else in the\n // target tree and we don't want to discard it just yet since it still might find a\n // home in the final DOM tree. After everything is done we will remove any keyed nodes\n // that didn't find a home\n if (curFromNodeKey) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n\n curFromNodeChild = fromNextSibling;\n } // END: while(curFromNodeChild) {}\n\n // If we got this far then we did not find a candidate match for\n // our \"to node\" and we exhausted all of the children \"from\"\n // nodes. Therefore, we will just append the current \"to\" node\n // to the end\n if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {\n fromEl.appendChild(matchingFromEl);\n // MORPH\n morphEl(matchingFromEl, curToNodeChild);\n } else {\n var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);\n if (onBeforeNodeAddedResult !== false) {\n if (onBeforeNodeAddedResult) {\n curToNodeChild = onBeforeNodeAddedResult;\n }\n\n if (curToNodeChild.actualize) {\n curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);\n }\n fromEl.appendChild(curToNodeChild);\n handleNodeAdded(curToNodeChild);\n }\n }\n\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n }\n\n cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey);\n\n var specialElHandler = specialElHandlers[fromEl.nodeName];\n if (specialElHandler) {\n specialElHandler(fromEl, toEl);\n }\n } // END: morphChildren(...)\n\n var morphedNode = fromNode;\n var morphedNodeType = morphedNode.nodeType;\n var toNodeType = toNode.nodeType;\n\n if (!childrenOnly) {\n // Handle the case where we are given two DOM nodes that are not\n // compatible (e.g. -->
or --> TEXT)\n if (morphedNodeType === ELEMENT_NODE) {\n if (toNodeType === ELEMENT_NODE) {\n if (!compareNodeNames(fromNode, toNode)) {\n onNodeDiscarded(fromNode);\n morphedNode = moveChildren(fromNode, createElementNS(toNode.nodeName, toNode.namespaceURI));\n }\n } else {\n // Going from an element node to a text node\n morphedNode = toNode;\n }\n } else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node\n if (toNodeType === morphedNodeType) {\n if (morphedNode.nodeValue !== toNode.nodeValue) {\n morphedNode.nodeValue = toNode.nodeValue;\n }\n\n return morphedNode;\n } else {\n // Text node to something else\n morphedNode = toNode;\n }\n }\n }\n\n if (morphedNode === toNode) {\n // The \"to node\" was not compatible with the \"from node\" so we had to\n // toss out the \"from node\" and use the \"to node\"\n onNodeDiscarded(fromNode);\n } else {\n if (toNode.isSameNode && toNode.isSameNode(morphedNode)) {\n return;\n }\n\n morphEl(morphedNode, toNode, childrenOnly);\n\n // We now need to loop over any keyed nodes that might need to be\n // removed. We only do the removal if we know that the keyed node\n // never found a match. When a keyed node is matched up we remove\n // it out of fromNodesLookup and we use fromNodesLookup to determine\n // if a keyed node has been matched up or not\n if (keyedRemovalList) {\n for (var i=0, len=keyedRemovalList.length; i
{\n if(activeElement && activeElement.isSameNode(fromEl) && DOM.isFormInput(fromEl)){\n DOM.mergeFocusedInput(fromEl, toEl)\n return false\n }\n }\n })\n }\n\n constructor(view, container, id, html, targetCID){\n this.view = view\n this.liveSocket = view.liveSocket\n this.container = container\n this.id = id\n this.rootID = view.root.id\n this.html = html\n this.targetCID = targetCID\n this.cidPatch = isCid(this.targetCID)\n this.callbacks = {\n beforeadded: [], beforeupdated: [], beforephxChildAdded: [],\n afteradded: [], afterupdated: [], afterdiscarded: [], afterphxChildAdded: []\n }\n }\n\n before(kind, callback){ this.callbacks[`before${kind}`].push(callback) }\n after(kind, callback){ this.callbacks[`after${kind}`].push(callback) }\n\n trackBefore(kind, ...args){\n this.callbacks[`before${kind}`].forEach(callback => callback(...args))\n }\n\n trackAfter(kind, ...args){\n this.callbacks[`after${kind}`].forEach(callback => callback(...args))\n }\n\n markPrunableContentForRemoval(){\n DOM.all(this.container, \"[phx-update=append] > *, [phx-update=prepend] > *\", el => {\n el.setAttribute(PHX_REMOVE, \"\")\n })\n }\n\n perform(){\n let {view, liveSocket, container, html} = this\n let targetContainer = this.isCIDPatch() ? this.targetCIDContainer(html) : container\n if(this.isCIDPatch() && !targetContainer){ return }\n\n let focused = liveSocket.getActiveElement()\n let {selectionStart, selectionEnd} = focused && DOM.hasSelectionRange(focused) ? focused : {}\n let phxUpdate = liveSocket.binding(PHX_UPDATE)\n let phxFeedbackFor = liveSocket.binding(PHX_FEEDBACK_FOR)\n let disableWith = liveSocket.binding(PHX_DISABLE_WITH)\n let phxTriggerExternal = liveSocket.binding(PHX_TRIGGER_ACTION)\n let added = []\n let updates = []\n let appendPrependUpdates = []\n let externalFormTriggered = null\n\n let diffHTML = liveSocket.time(\"premorph container prep\", () => {\n return this.buildDiffHTML(container, html, phxUpdate, targetContainer)\n })\n\n this.trackBefore(\"added\", container)\n this.trackBefore(\"updated\", container, container)\n\n liveSocket.time(\"morphdom\", () => {\n morphdom(targetContainer, diffHTML, {\n childrenOnly: targetContainer.getAttribute(PHX_COMPONENT) === null,\n getNodeKey: (node) => {\n return DOM.isPhxDestroyed(node) ? null : node.id\n },\n onBeforeNodeAdded: (el) => {\n this.trackBefore(\"added\", el)\n return el\n },\n onNodeAdded: (el) => {\n // hack to fix Safari handling of img srcset and video tags\n if(el instanceof HTMLImageElement && el.srcset){\n el.srcset = el.srcset\n } else if(el instanceof HTMLVideoElement && el.autoplay){\n el.play()\n }\n if(DOM.isNowTriggerFormExternal(el, phxTriggerExternal)){\n externalFormTriggered = el\n }\n //input handling\n DOM.discardError(targetContainer, el, phxFeedbackFor)\n // nested view handling\n if(DOM.isPhxChild(el) && view.ownsElement(el)){\n this.trackAfter(\"phxChildAdded\", el)\n }\n added.push(el)\n },\n onNodeDiscarded: (el) => {\n // nested view handling\n if(DOM.isPhxChild(el)){ liveSocket.destroyViewByEl(el) }\n this.trackAfter(\"discarded\", el)\n },\n onBeforeNodeDiscarded: (el) => {\n if(el.getAttribute && el.getAttribute(PHX_REMOVE) !== null){ return true }\n if(el.parentNode !== null && DOM.isPhxUpdate(el.parentNode, phxUpdate, [\"append\", \"prepend\"]) && el.id){ return false }\n if(this.skipCIDSibling(el)){ return false }\n return true\n },\n onElUpdated: (el) => {\n if(DOM.isNowTriggerFormExternal(el, phxTriggerExternal)){\n externalFormTriggered = el\n }\n updates.push(el)\n },\n onBeforeElUpdated: (fromEl, toEl) => {\n DOM.cleanChildNodes(toEl, phxUpdate)\n if(this.skipCIDSibling(toEl)){ return false }\n if(DOM.isIgnored(fromEl, phxUpdate)){\n this.trackBefore(\"updated\", fromEl, toEl)\n DOM.mergeAttrs(fromEl, toEl, {isIgnored: true})\n updates.push(fromEl)\n return false\n }\n if(fromEl.type === \"number\" && (fromEl.validity && fromEl.validity.badInput)){ return false }\n if(!DOM.syncPendingRef(fromEl, toEl, disableWith)){\n if(DOM.isUploadInput(fromEl)){\n this.trackBefore(\"updated\", fromEl, toEl)\n updates.push(fromEl)\n }\n return false\n }\n\n // nested view handling\n if(DOM.isPhxChild(toEl)){\n let prevSession = fromEl.getAttribute(PHX_SESSION)\n DOM.mergeAttrs(fromEl, toEl, {exclude: [PHX_STATIC]})\n if(prevSession !== \"\"){ fromEl.setAttribute(PHX_SESSION, prevSession) }\n fromEl.setAttribute(PHX_ROOT_ID, this.rootID)\n return false\n }\n\n // input handling\n DOM.copyPrivates(toEl, fromEl)\n DOM.discardError(targetContainer, toEl, phxFeedbackFor)\n DOM.syncPropsToAttrs(toEl)\n\n let isFocusedFormEl = focused && fromEl.isSameNode(focused) && DOM.isFormInput(fromEl)\n if(isFocusedFormEl && !this.forceFocusedSelectUpdate(fromEl, toEl)){\n this.trackBefore(\"updated\", fromEl, toEl)\n DOM.mergeFocusedInput(fromEl, toEl)\n DOM.syncAttrsToProps(fromEl)\n updates.push(fromEl)\n return false\n } else {\n if(DOM.isPhxUpdate(toEl, phxUpdate, [\"append\", \"prepend\"])){\n appendPrependUpdates.push(new DOMPostMorphRestorer(fromEl, toEl, toEl.getAttribute(phxUpdate)))\n }\n DOM.syncAttrsToProps(toEl)\n this.trackBefore(\"updated\", fromEl, toEl)\n return true\n }\n }\n })\n })\n\n if(liveSocket.isDebugEnabled()){ detectDuplicateIds() }\n\n if(appendPrependUpdates.length > 0){\n liveSocket.time(\"post-morph append/prepend restoration\", () => {\n appendPrependUpdates.forEach(update => update.perform())\n })\n }\n\n liveSocket.silenceEvents(() => DOM.restoreFocus(focused, selectionStart, selectionEnd))\n DOM.dispatchEvent(document, \"phx:update\")\n added.forEach(el => this.trackAfter(\"added\", el))\n updates.forEach(el => this.trackAfter(\"updated\", el))\n\n if(externalFormTriggered){\n liveSocket.disconnect()\n externalFormTriggered.submit()\n }\n return true\n }\n\n forceFocusedSelectUpdate(fromEl, toEl){\n let isSelect = [\"select\", \"select-one\", \"select-multiple\"].find((t) => t === fromEl.type)\n return fromEl.multiple === true || (isSelect && fromEl.innerHTML != toEl.innerHTML)\n }\n\n isCIDPatch(){ return this.cidPatch }\n\n skipCIDSibling(el){\n return el.nodeType === Node.ELEMENT_NODE && el.getAttribute(PHX_SKIP) !== null\n }\n\n targetCIDContainer(html){\n if(!this.isCIDPatch()){ return }\n let [first, ...rest] = DOM.findComponentNodeList(this.container, this.targetCID)\n if(rest.length === 0 && DOM.childNodeLength(html) === 1){\n return first\n } else {\n return first && first.parentNode\n }\n }\n\n // builds HTML for morphdom patch\n // - for full patches of LiveView or a component with a single\n // root node, simply returns the HTML\n // - for patches of a component with multiple root nodes, the\n // parent node becomes the target container and non-component\n // siblings are marked as skip.\n buildDiffHTML(container, html, phxUpdate, targetContainer){\n let isCIDPatch = this.isCIDPatch()\n let isCIDWithSingleRoot = isCIDPatch && targetContainer.getAttribute(PHX_COMPONENT) === this.targetCID.toString()\n if(!isCIDPatch || isCIDWithSingleRoot){\n return html\n } else {\n // component patch with multiple CID roots\n let diffContainer = null\n let template = document.createElement(\"template\")\n diffContainer = DOM.cloneNode(targetContainer)\n let [firstComponent, ...rest] = DOM.findComponentNodeList(diffContainer, this.targetCID)\n template.innerHTML = html\n rest.forEach(el => el.remove())\n Array.from(diffContainer.childNodes).forEach(child => {\n // we can only skip trackable nodes with an ID\n if(child.id && child.nodeType === Node.ELEMENT_NODE && child.getAttribute(PHX_COMPONENT) !== this.targetCID.toString()){\n child.setAttribute(PHX_SKIP, \"\")\n child.innerHTML = \"\"\n }\n })\n Array.from(template.content.childNodes).forEach(el => diffContainer.insertBefore(el, firstComponent))\n firstComponent.remove()\n return diffContainer.outerHTML\n }\n }\n}\n", "import {\n COMPONENTS,\n DYNAMICS,\n EVENTS,\n PHX_COMPONENT,\n PHX_SKIP,\n REPLY,\n STATIC,\n TITLE\n} from \"./constants\"\n\nimport {\n isObject,\n logError,\n isCid,\n} from \"./utils\"\n\nexport default class Rendered {\n static extract(diff){\n let {[REPLY]: reply, [EVENTS]: events, [TITLE]: title} = diff\n delete diff[REPLY]\n delete diff[EVENTS]\n delete diff[TITLE]\n return {diff, title, reply: reply || null, events: events || []}\n }\n\n constructor(viewId, rendered){\n this.viewId = viewId\n this.rendered = {}\n this.mergeDiff(rendered)\n }\n\n parentViewId(){ return this.viewId }\n\n toString(onlyCids){\n return this.recursiveToString(this.rendered, this.rendered[COMPONENTS], onlyCids)\n }\n\n recursiveToString(rendered, components = rendered[COMPONENTS], onlyCids){\n onlyCids = onlyCids ? new Set(onlyCids) : null\n let output = {buffer: \"\", components: components, onlyCids: onlyCids}\n this.toOutputBuffer(rendered, output)\n return output.buffer\n }\n\n componentCIDs(diff){ return Object.keys(diff[COMPONENTS] || {}).map(i => parseInt(i)) }\n\n isComponentOnlyDiff(diff){\n if(!diff[COMPONENTS]){ return false }\n return Object.keys(diff).length === 1\n }\n\n getComponent(diff, cid){ return diff[COMPONENTS][cid] }\n\n mergeDiff(diff){\n let newc = diff[COMPONENTS]\n let cache = {}\n delete diff[COMPONENTS]\n this.rendered = this.mutableMerge(this.rendered, diff)\n this.rendered[COMPONENTS] = this.rendered[COMPONENTS] || {}\n\n if(newc){\n let oldc = this.rendered[COMPONENTS]\n\n for(let cid in newc){\n newc[cid] = this.cachedFindComponent(cid, newc[cid], oldc, newc, cache)\n }\n\n for(var key in newc){ oldc[key] = newc[key] }\n diff[COMPONENTS] = newc\n }\n }\n\n cachedFindComponent(cid, cdiff, oldc, newc, cache){\n if(cache[cid]){\n return cache[cid]\n } else {\n let ndiff, stat, scid = cdiff[STATIC]\n\n if(isCid(scid)){\n let tdiff\n\n if(scid > 0){\n tdiff = this.cachedFindComponent(scid, newc[scid], oldc, newc, cache)\n } else {\n tdiff = oldc[-scid]\n }\n\n stat = tdiff[STATIC]\n ndiff = this.cloneMerge(tdiff, cdiff)\n ndiff[STATIC] = stat\n } else {\n ndiff = cdiff[STATIC] !== undefined ? cdiff : this.cloneMerge(oldc[cid] || {}, cdiff)\n }\n\n cache[cid] = ndiff\n return ndiff\n }\n }\n\n mutableMerge(target, source){\n if(source[STATIC] !== undefined){\n return source\n } else {\n this.doMutableMerge(target, source)\n return target\n }\n }\n\n doMutableMerge(target, source){\n for(let key in source){\n let val = source[key]\n let targetVal = target[key]\n if(isObject(val) && val[STATIC] === undefined && isObject(targetVal)){\n this.doMutableMerge(targetVal, val)\n } else {\n target[key] = val\n }\n }\n }\n\n cloneMerge(target, source){\n let merged = {...target, ...source}\n for(let key in merged){\n let val = source[key]\n let targetVal = target[key]\n if(isObject(val) && val[STATIC] === undefined && isObject(targetVal)){\n merged[key] = this.cloneMerge(targetVal, val)\n }\n }\n return merged\n }\n\n componentToString(cid){ return this.recursiveCIDToString(this.rendered[COMPONENTS], cid) }\n\n pruneCIDs(cids){\n cids.forEach(cid => delete this.rendered[COMPONENTS][cid])\n }\n\n // private\n\n get(){ return this.rendered }\n\n isNewFingerprint(diff = {}){ return !!diff[STATIC] }\n\n toOutputBuffer(rendered, output){\n if(rendered[DYNAMICS]){ return this.comprehensionToBuffer(rendered, output) }\n let {[STATIC]: statics} = rendered\n\n output.buffer += statics[0]\n for(let i = 1; i < statics.length; i++){\n this.dynamicToBuffer(rendered[i - 1], output)\n output.buffer += statics[i]\n }\n }\n\n comprehensionToBuffer(rendered, output){\n let {[DYNAMICS]: dynamics, [STATIC]: statics} = rendered\n\n for(let d = 0; d < dynamics.length; d++){\n let dynamic = dynamics[d]\n output.buffer += statics[0]\n for(let i = 1; i < statics.length; i++){\n this.dynamicToBuffer(dynamic[i - 1], output)\n output.buffer += statics[i]\n }\n }\n }\n\n dynamicToBuffer(rendered, output){\n if(typeof (rendered) === \"number\"){\n output.buffer += this.recursiveCIDToString(output.components, rendered, output.onlyCids)\n } else if(isObject(rendered)){\n this.toOutputBuffer(rendered, output)\n } else {\n output.buffer += rendered\n }\n }\n\n recursiveCIDToString(components, cid, onlyCids){\n let component = components[cid] || logError(`no component for CID ${cid}`, components)\n let template = document.createElement(\"template\")\n template.innerHTML = this.recursiveToString(component, components, onlyCids)\n let container = template.content\n let skip = onlyCids && !onlyCids.has(cid)\n\n let [hasChildNodes, hasChildComponents] =\n Array.from(container.childNodes).reduce(([hasNodes, hasComponents], child, i) => {\n if(child.nodeType === Node.ELEMENT_NODE){\n if(child.getAttribute(PHX_COMPONENT)){\n return [hasNodes, true]\n }\n child.setAttribute(PHX_COMPONENT, cid)\n if(!child.id){ child.id = `${this.parentViewId()}-${cid}-${i}` }\n if(skip){\n child.setAttribute(PHX_SKIP, \"\")\n child.innerHTML = \"\"\n }\n return [true, hasComponents]\n } else {\n if(child.nodeValue.trim() !== \"\"){\n logError(\"only HTML element tags are allowed at the root of components.\\n\\n\" +\n `got: \"${child.nodeValue.trim()}\"\\n\\n` +\n \"within:\\n\", template.innerHTML.trim())\n child.replaceWith(this.createSpan(child.nodeValue, cid))\n return [true, hasComponents]\n } else {\n child.remove()\n return [hasNodes, hasComponents]\n }\n }\n }, [false, false])\n\n if(!hasChildNodes && !hasChildComponents){\n logError(\"expected at least one HTML element tag inside a component, but the component is empty:\\n\",\n template.innerHTML.trim())\n return this.createSpan(\"\", cid).outerHTML\n } else if(!hasChildNodes && hasChildComponents){\n logError(\"expected at least one HTML element tag directly inside a component, but only subcomponents were found. A component must render at least one HTML tag directly inside itself.\",\n template.innerHTML.trim())\n return template.innerHTML\n } else {\n return template.innerHTML\n }\n }\n\n createSpan(text, cid){\n let span = document.createElement(\"span\")\n span.innerText = text\n span.setAttribute(PHX_COMPONENT, cid)\n return span\n }\n}\n", "let viewHookID = 1\nexport default class ViewHook {\n static makeID(){ return viewHookID++ }\n static elementID(el){ return el.phxHookId }\n\n constructor(view, el, callbacks){\n this.__view = view\n this.__liveSocket = view.liveSocket\n this.__callbacks = callbacks\n this.__listeners = new Set()\n this.__isDisconnected = false\n this.el = el\n this.el.phxHookId = this.constructor.makeID()\n for(let key in this.__callbacks){ this[key] = this.__callbacks[key] }\n }\n\n __mounted(){ this.mounted && this.mounted() }\n __updated(){ this.updated && this.updated() }\n __beforeUpdate(){ this.beforeUpdate && this.beforeUpdate() }\n __destroyed(){ this.destroyed && this.destroyed() }\n __reconnected(){\n if(this.__isDisconnected){\n this.__isDisconnected = false\n this.reconnected && this.reconnected()\n }\n }\n __disconnected(){\n this.__isDisconnected = true\n this.disconnected && this.disconnected()\n }\n\n pushEvent(event, payload = {}, onReply = function (){ }){\n return this.__view.pushHookEvent(null, event, payload, onReply)\n }\n\n pushEventTo(phxTarget, event, payload = {}, onReply = function (){ }){\n return this.__view.withinTargets(phxTarget, (view, targetCtx) => {\n return view.pushHookEvent(targetCtx, event, payload, onReply)\n })\n }\n\n handleEvent(event, callback){\n let callbackRef = (customEvent, bypass) => bypass ? event : callback(customEvent.detail)\n window.addEventListener(`phx:hook:${event}`, callbackRef)\n this.__listeners.add(callbackRef)\n return callbackRef\n }\n\n removeHandleEvent(callbackRef){\n let event = callbackRef(null, true)\n window.removeEventListener(`phx:hook:${event}`, callbackRef)\n this.__listeners.delete(callbackRef)\n }\n\n upload(name, files){\n return this.__view.dispatchUploads(name, files)\n }\n\n uploadTo(phxTarget, name, files){\n return this.__view.withinTargets(phxTarget, view => view.dispatchUploads(name, files))\n }\n\n __cleanup__(){\n this.__listeners.forEach(callbackRef => this.removeHandleEvent(callbackRef))\n }\n}\n", "import {\n BEFORE_UNLOAD_LOADER_TIMEOUT,\n CHECKABLE_INPUTS,\n CONSECUTIVE_RELOADS,\n PHX_AUTO_RECOVER,\n PHX_COMPONENT,\n PHX_CONNECTED_CLASS,\n PHX_DISABLE_WITH,\n PHX_DISABLE_WITH_RESTORE,\n PHX_DISABLED,\n PHX_DISCONNECTED_CLASS,\n PHX_EVENT_CLASSES,\n PHX_ERROR_CLASS,\n PHX_FEEDBACK_FOR,\n PHX_HAS_SUBMITTED,\n PHX_HOOK,\n PHX_PAGE_LOADING,\n PHX_PARENT_ID,\n PHX_PROGRESS,\n PHX_READONLY,\n PHX_REF,\n PHX_ROOT_ID,\n PHX_SESSION,\n PHX_STATIC,\n PHX_TRACK_STATIC,\n PHX_TRACK_UPLOADS,\n PHX_UPDATE,\n PHX_UPLOAD_REF,\n PHX_VIEW_SELECTOR,\n PUSH_TIMEOUT,\n} from \"./constants\"\n\nimport {\n clone,\n closestPhxBinding,\n isEmpty,\n isEqualObj,\n logError,\n maybe,\n isCid,\n} from \"./utils\"\n\nimport Browser from \"./browser\"\nimport DOM from \"./dom\"\nimport DOMPatch from \"./dom_patch\"\nimport LiveUploader from \"./live_uploader\"\nimport Rendered from \"./rendered\"\nimport ViewHook from \"./view_hook\"\n\nlet serializeForm = (form, meta = {}) => {\n let formData = new FormData(form)\n let toRemove = []\n\n formData.forEach((val, key, _index) => {\n if(val instanceof File){ toRemove.push(key) }\n })\n\n // Cleanup after building fileData\n toRemove.forEach(key => formData.delete(key))\n\n let params = new URLSearchParams()\n for(let [key, val] of formData.entries()){ params.append(key, val) }\n for(let metaKey in meta){ params.append(metaKey, meta[metaKey]) }\n\n return params.toString()\n}\n\nexport default class View {\n constructor(el, liveSocket, parentView, flash){\n this.liveSocket = liveSocket\n this.flash = flash\n this.parent = parentView\n this.root = parentView ? parentView.root : this\n this.el = el\n this.id = this.el.id\n this.ref = 0\n this.childJoins = 0\n this.loaderTimer = null\n this.pendingDiffs = []\n this.pruningCIDs = []\n this.redirect = false\n this.href = null\n this.joinCount = this.parent ? this.parent.joinCount - 1 : 0\n this.joinPending = true\n this.destroyed = false\n this.joinCallback = function (){ }\n this.stopCallback = function (){ }\n this.pendingJoinOps = this.parent ? null : []\n this.viewHooks = {}\n this.uploaders = {}\n this.formSubmits = []\n this.children = this.parent ? null : {}\n this.root.children[this.id] = {}\n this.channel = this.liveSocket.channel(`lv:${this.id}`, () => {\n return {\n redirect: this.redirect ? this.href : undefined,\n url: this.redirect ? undefined : this.href || undefined,\n params: this.connectParams(),\n session: this.getSession(),\n static: this.getStatic(),\n flash: this.flash\n }\n })\n this.showLoader(this.liveSocket.loaderTimeout)\n this.bindChannel()\n }\n\n setHref(href){ this.href = href }\n\n setRedirect(href){\n this.redirect = true\n this.href = href\n }\n\n isMain(){ return this.liveSocket.main === this }\n\n connectParams(){\n let params = this.liveSocket.params(this.el)\n let manifest =\n DOM.all(document, `[${this.binding(PHX_TRACK_STATIC)}]`)\n .map(node => node.src || node.href).filter(url => typeof (url) === \"string\")\n\n if(manifest.length > 0){ params[\"_track_static\"] = manifest }\n params[\"_mounts\"] = this.joinCount\n\n return params\n }\n\n isConnected(){ return this.channel.canPush() }\n\n getSession(){ return this.el.getAttribute(PHX_SESSION) }\n\n getStatic(){\n let val = this.el.getAttribute(PHX_STATIC)\n return val === \"\" ? null : val\n }\n\n destroy(callback = function (){ }){\n this.destroyAllChildren()\n this.destroyed = true\n delete this.root.children[this.id]\n if(this.parent){ delete this.root.children[this.parent.id][this.id] }\n clearTimeout(this.loaderTimer)\n let onFinished = () => {\n callback()\n for(let id in this.viewHooks){\n this.destroyHook(this.viewHooks[id])\n }\n }\n\n DOM.markPhxChildDestroyed(this.el)\n\n this.log(\"destroyed\", () => [\"the child has been removed from the parent\"])\n this.channel.leave()\n .receive(\"ok\", onFinished)\n .receive(\"error\", onFinished)\n .receive(\"timeout\", onFinished)\n }\n\n setContainerClasses(...classes){\n this.el.classList.remove(\n PHX_CONNECTED_CLASS,\n PHX_DISCONNECTED_CLASS,\n PHX_ERROR_CLASS\n )\n this.el.classList.add(...classes)\n }\n\n isLoading(){ return this.el.classList.contains(PHX_DISCONNECTED_CLASS) }\n\n showLoader(timeout){\n clearTimeout(this.loaderTimer)\n if(timeout){\n this.loaderTimer = setTimeout(() => this.showLoader(), timeout)\n } else {\n for(let id in this.viewHooks){ this.viewHooks[id].__disconnected() }\n this.setContainerClasses(PHX_DISCONNECTED_CLASS)\n }\n }\n\n hideLoader(){\n clearTimeout(this.loaderTimer)\n this.setContainerClasses(PHX_CONNECTED_CLASS)\n }\n\n triggerReconnected(){\n for(let id in this.viewHooks){ this.viewHooks[id].__reconnected() }\n }\n\n log(kind, msgCallback){\n this.liveSocket.log(this, kind, msgCallback)\n }\n\n withinTargets(phxTarget, callback){\n if(phxTarget instanceof HTMLElement){\n return this.liveSocket.owner(phxTarget, view => callback(view, phxTarget))\n }\n\n if(/^(0|[1-9]\\d*)$/.test(phxTarget)){\n let targets = DOM.findComponentNodeList(this.el, phxTarget)\n if(targets.length === 0){\n logError(`no component found matching phx-target of ${phxTarget}`)\n } else {\n callback(this, targets[0])\n }\n } else {\n let targets = Array.from(document.querySelectorAll(phxTarget))\n if(targets.length === 0){ logError(`nothing found matching the phx-target selector \"${phxTarget}\"`) }\n targets.forEach(target => this.liveSocket.owner(target, view => callback(view, target)))\n }\n }\n\n applyDiff(type, rawDiff, callback){\n this.log(type, () => [\"\", clone(rawDiff)])\n let {diff, reply, events, title} = Rendered.extract(rawDiff)\n if(title){ DOM.putTitle(title) }\n\n callback({diff, reply, events})\n return reply\n }\n\n onJoin(resp){\n let {rendered, container} = resp\n if(container){\n let [tag, attrs] = container\n this.el = DOM.replaceRootContainer(this.el, tag, attrs)\n }\n this.childJoins = 0\n this.joinPending = true\n this.flash = null\n\n Browser.dropLocal(this.liveSocket.localStorage, window.location.pathname, CONSECUTIVE_RELOADS)\n this.applyDiff(\"mount\", rendered, ({diff, events}) => {\n this.rendered = new Rendered(this.id, diff)\n let html = this.renderContainer(null, \"join\")\n this.dropPendingRefs()\n let forms = this.formsForRecovery(html)\n this.joinCount++\n\n if(forms.length > 0){\n forms.forEach(([form, newForm, newCid], i) => {\n this.pushFormRecovery(form, newCid, resp => {\n if(i === forms.length - 1){\n this.onJoinComplete(resp, html, events)\n }\n })\n })\n } else {\n this.onJoinComplete(resp, html, events)\n }\n })\n }\n\n dropPendingRefs(){ DOM.all(this.el, `[${PHX_REF}]`, el => el.removeAttribute(PHX_REF)) }\n\n onJoinComplete({live_patch}, html, events){\n // In order to provide a better experience, we want to join\n // all LiveViews first and only then apply their patches.\n if(this.joinCount > 1 || (this.parent && !this.parent.isJoinPending())){\n return this.applyJoinPatch(live_patch, html, events)\n }\n\n // One downside of this approach is that we need to find phxChildren\n // in the html fragment, instead of directly on the DOM. The fragment\n // also does not include PHX_STATIC, so we need to copy it over from\n // the DOM.\n let newChildren = DOM.findPhxChildrenInFragment(html, this.id).filter(toEl => {\n let fromEl = toEl.id && this.el.querySelector(`[id=\"${toEl.id}\"]`)\n let phxStatic = fromEl && fromEl.getAttribute(PHX_STATIC)\n if(phxStatic){ toEl.setAttribute(PHX_STATIC, phxStatic) }\n return this.joinChild(toEl)\n })\n\n if(newChildren.length === 0){\n if(this.parent){\n this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, events)])\n this.parent.ackJoin(this)\n } else {\n this.onAllChildJoinsComplete()\n this.applyJoinPatch(live_patch, html, events)\n }\n } else {\n this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, events)])\n }\n }\n\n attachTrueDocEl(){\n this.el = DOM.byId(this.id)\n this.el.setAttribute(PHX_ROOT_ID, this.root.id)\n }\n\n dispatchEvents(events){\n events.forEach(([event, payload]) => {\n window.dispatchEvent(new CustomEvent(`phx:hook:${event}`, {detail: payload}))\n })\n }\n\n applyJoinPatch(live_patch, html, events){\n this.attachTrueDocEl()\n let patch = new DOMPatch(this, this.el, this.id, html, null)\n patch.markPrunableContentForRemoval()\n this.performPatch(patch, false)\n this.joinNewChildren()\n DOM.all(this.el, `[${this.binding(PHX_HOOK)}], [data-phx-${PHX_HOOK}]`, hookEl => {\n let hook = this.addHook(hookEl)\n if(hook){ hook.__mounted() }\n })\n\n this.joinPending = false\n this.dispatchEvents(events)\n this.applyPendingUpdates()\n\n if(live_patch){\n let {kind, to} = live_patch\n this.liveSocket.historyPatch(to, kind)\n }\n this.hideLoader()\n if(this.joinCount > 1){ this.triggerReconnected() }\n this.stopCallback()\n }\n\n triggerBeforeUpdateHook(fromEl, toEl){\n this.liveSocket.triggerDOM(\"onBeforeElUpdated\", [fromEl, toEl])\n let hook = this.getHook(fromEl)\n let isIgnored = hook && DOM.isIgnored(fromEl, this.binding(PHX_UPDATE))\n if(hook && !fromEl.isEqualNode(toEl) && !(isIgnored && isEqualObj(fromEl.dataset, toEl.dataset))){\n hook.__beforeUpdate()\n return hook\n }\n }\n\n performPatch(patch, pruneCids){\n let destroyedCIDs = []\n let phxChildrenAdded = false\n let updatedHookIds = new Set()\n\n patch.after(\"added\", el => {\n this.liveSocket.triggerDOM(\"onNodeAdded\", [el])\n\n let newHook = this.addHook(el)\n if(newHook){ newHook.__mounted() }\n })\n\n patch.after(\"phxChildAdded\", _el => phxChildrenAdded = true)\n\n patch.before(\"updated\", (fromEl, toEl) => {\n let hook = this.triggerBeforeUpdateHook(fromEl, toEl)\n if(hook){ updatedHookIds.add(fromEl.id) }\n })\n\n patch.after(\"updated\", el => {\n if(updatedHookIds.has(el.id)){ this.getHook(el).__updated() }\n })\n\n patch.after(\"discarded\", (el) => {\n let cid = this.componentID(el)\n if(isCid(cid) && destroyedCIDs.indexOf(cid) === -1){ destroyedCIDs.push(cid) }\n let hook = this.getHook(el)\n hook && this.destroyHook(hook)\n })\n\n patch.perform()\n\n // We should not pruneCids on joins. Otherwise, in case of\n // rejoins, we may notify cids that no longer belong to the\n // current LiveView to be removed.\n if(pruneCids){\n this.maybePushComponentsDestroyed(destroyedCIDs)\n }\n\n return phxChildrenAdded\n }\n\n joinNewChildren(){\n DOM.findPhxChildren(this.el, this.id).forEach(el => this.joinChild(el))\n }\n\n getChildById(id){ return this.root.children[this.id][id] }\n\n getDescendentByEl(el){\n if(el.id === this.id){\n return this\n } else {\n return this.children[el.getAttribute(PHX_PARENT_ID)][el.id]\n }\n }\n\n destroyDescendent(id){\n for(let parentId in this.root.children){\n for(let childId in this.root.children[parentId]){\n if(childId === id){ return this.root.children[parentId][childId].destroy() }\n }\n }\n }\n\n joinChild(el){\n let child = this.getChildById(el.id)\n if(!child){\n let view = new View(el, this.liveSocket, this)\n this.root.children[this.id][view.id] = view\n view.join()\n this.childJoins++\n return true\n }\n }\n\n isJoinPending(){ return this.joinPending }\n\n ackJoin(_child){\n this.childJoins--\n\n if(this.childJoins === 0){\n if(this.parent){\n this.parent.ackJoin(this)\n } else {\n this.onAllChildJoinsComplete()\n }\n }\n }\n\n onAllChildJoinsComplete(){\n this.joinCallback()\n this.pendingJoinOps.forEach(([view, op]) => {\n if(!view.isDestroyed()){ op() }\n })\n this.pendingJoinOps = []\n }\n\n update(diff, events){\n if(this.isJoinPending() || this.liveSocket.hasPendingLink()){\n return this.pendingDiffs.push({diff, events})\n }\n\n this.rendered.mergeDiff(diff)\n let phxChildrenAdded = false\n\n // When the diff only contains component diffs, then walk components\n // and patch only the parent component containers found in the diff.\n // Otherwise, patch entire LV container.\n if(this.rendered.isComponentOnlyDiff(diff)){\n this.liveSocket.time(\"component patch complete\", () => {\n let parentCids = DOM.findParentCIDs(this.el, this.rendered.componentCIDs(diff))\n parentCids.forEach(parentCID => {\n if(this.componentPatch(this.rendered.getComponent(diff, parentCID), parentCID)){ phxChildrenAdded = true }\n })\n })\n } else if(!isEmpty(diff)){\n this.liveSocket.time(\"full patch complete\", () => {\n let html = this.renderContainer(diff, \"update\")\n let patch = new DOMPatch(this, this.el, this.id, html, null)\n phxChildrenAdded = this.performPatch(patch, true)\n })\n }\n\n this.dispatchEvents(events)\n if(phxChildrenAdded){ this.joinNewChildren() }\n }\n\n renderContainer(diff, kind){\n return this.liveSocket.time(`toString diff (${kind})`, () => {\n let tag = this.el.tagName\n // Don't skip any component in the diff nor any marked as pruned\n // (as they may have been added back)\n let cids = diff ? this.rendered.componentCIDs(diff).concat(this.pruningCIDs) : null\n let html = this.rendered.toString(cids)\n return `<${tag}>${html}${tag}>`\n })\n }\n\n componentPatch(diff, cid){\n if(isEmpty(diff)) return false\n let html = this.rendered.componentToString(cid)\n let patch = new DOMPatch(this, this.el, this.id, html, cid)\n let childrenAdded = this.performPatch(patch, true)\n return childrenAdded\n }\n\n getHook(el){ return this.viewHooks[ViewHook.elementID(el)] }\n\n addHook(el){\n if(ViewHook.elementID(el) || !el.getAttribute){ return }\n let hookName = el.getAttribute(`data-phx-${PHX_HOOK}`) || el.getAttribute(this.binding(PHX_HOOK))\n if(hookName && !this.ownsElement(el)){ return }\n let callbacks = this.liveSocket.getHookCallbacks(hookName)\n\n if(callbacks){\n if(!el.id){ logError(`no DOM ID for hook \"${hookName}\". Hooks require a unique ID on each element.`, el) }\n let hook = new ViewHook(this, el, callbacks)\n this.viewHooks[ViewHook.elementID(hook.el)] = hook\n return hook\n } else if(hookName !== null){\n logError(`unknown hook found for \"${hookName}\"`, el)\n }\n }\n\n destroyHook(hook){\n hook.__destroyed()\n hook.__cleanup__()\n delete this.viewHooks[ViewHook.elementID(hook.el)]\n }\n\n applyPendingUpdates(){\n this.pendingDiffs.forEach(({diff, events}) => this.update(diff, events))\n this.pendingDiffs = []\n }\n\n onChannel(event, cb){\n this.liveSocket.onChannel(this.channel, event, resp => {\n if(this.isJoinPending()){\n this.root.pendingJoinOps.push([this, () => cb(resp)])\n } else {\n cb(resp)\n }\n })\n }\n\n bindChannel(){\n // The diff event should be handled by the regular update operations.\n // All other operations are queued to be applied only after join.\n this.liveSocket.onChannel(this.channel, \"diff\", (rawDiff) => {\n this.applyDiff(\"update\", rawDiff, ({diff, events}) => this.update(diff, events))\n })\n this.onChannel(\"redirect\", ({to, flash}) => this.onRedirect({to, flash}))\n this.onChannel(\"live_patch\", (redir) => this.onLivePatch(redir))\n this.onChannel(\"live_redirect\", (redir) => this.onLiveRedirect(redir))\n this.channel.onError(reason => this.onError(reason))\n this.channel.onClose(reason => this.onClose(reason))\n }\n\n destroyAllChildren(){\n for(let id in this.root.children[this.id]){\n this.getChildById(id).destroy()\n }\n }\n\n onLiveRedirect(redir){\n let {to, kind, flash} = redir\n let url = this.expandURL(to)\n this.liveSocket.historyRedirect(url, kind, flash)\n }\n\n onLivePatch(redir){\n let {to, kind} = redir\n this.href = this.expandURL(to)\n this.liveSocket.historyPatch(to, kind)\n }\n\n expandURL(to){\n return to.startsWith(\"/\") ? `${window.location.protocol}//${window.location.host}${to}` : to\n }\n\n onRedirect({to, flash}){ this.liveSocket.redirect(to, flash) }\n\n isDestroyed(){ return this.destroyed }\n\n join(callback){\n if(!this.parent){\n this.stopCallback = this.liveSocket.withPageLoading({to: this.href, kind: \"initial\"})\n }\n this.joinCallback = () => callback && callback(this.joinCount)\n this.liveSocket.wrapPush(this, {timeout: false}, () => {\n return this.channel.join()\n .receive(\"ok\", data => !this.isDestroyed() && this.onJoin(data))\n .receive(\"error\", resp => !this.isDestroyed() && this.onJoinError(resp))\n .receive(\"timeout\", () => !this.isDestroyed() && this.onJoinError({reason: \"timeout\"}))\n })\n }\n\n onJoinError(resp){\n if(resp.reason === \"unauthorized\" || resp.reason === \"stale\"){\n this.log(\"error\", () => [\"unauthorized live_redirect. Falling back to page request\", resp])\n return this.onRedirect({to: this.href})\n }\n if(resp.redirect || resp.live_redirect){\n this.joinPending = false\n this.channel.leave()\n }\n if(resp.redirect){ return this.onRedirect(resp.redirect) }\n if(resp.live_redirect){ return this.onLiveRedirect(resp.live_redirect) }\n this.log(\"error\", () => [\"unable to join\", resp])\n return this.liveSocket.reloadWithJitter(this)\n }\n\n onClose(reason){\n if(this.isDestroyed()){ return }\n if((this.isJoinPending() && document.visibilityState !== \"hidden\") ||\n (this.liveSocket.hasPendingLink() && reason !== \"leave\")){\n\n return this.liveSocket.reloadWithJitter(this)\n }\n this.destroyAllChildren()\n this.liveSocket.dropActiveElement(this)\n // document.activeElement can be null in Internet Explorer 11\n if(document.activeElement){ document.activeElement.blur() }\n if(this.liveSocket.isUnloaded()){\n this.showLoader(BEFORE_UNLOAD_LOADER_TIMEOUT)\n }\n }\n\n onError(reason){\n this.onClose(reason)\n this.log(\"error\", () => [\"view crashed\", reason])\n if(!this.liveSocket.isUnloaded()){ this.displayError() }\n }\n\n displayError(){\n if(this.isMain()){ DOM.dispatchEvent(window, \"phx:page-loading-start\", {to: this.href, kind: \"error\"}) }\n this.showLoader()\n this.setContainerClasses(PHX_DISCONNECTED_CLASS, PHX_ERROR_CLASS)\n }\n\n pushWithReply(refGenerator, event, payload, onReply = function (){ }){\n if(!this.isConnected()){ return }\n\n let [ref, [el]] = refGenerator ? refGenerator() : [null, []]\n let onLoadingDone = function (){ }\n if(el && (el.getAttribute(this.binding(PHX_PAGE_LOADING)) !== null)){\n onLoadingDone = this.liveSocket.withPageLoading({kind: \"element\", target: el})\n }\n\n if(typeof (payload.cid) !== \"number\"){ delete payload.cid }\n return (\n this.liveSocket.wrapPush(this, {timeout: true}, () => {\n return this.channel.push(event, payload, PUSH_TIMEOUT).receive(\"ok\", resp => {\n let hookReply = null\n if(ref !== null){ this.undoRefs(ref) }\n if(resp.diff){\n hookReply = this.applyDiff(\"update\", resp.diff, ({diff, events}) => {\n this.update(diff, events)\n })\n }\n if(resp.redirect){ this.onRedirect(resp.redirect) }\n if(resp.live_patch){ this.onLivePatch(resp.live_patch) }\n if(resp.live_redirect){ this.onLiveRedirect(resp.live_redirect) }\n onLoadingDone()\n onReply(resp, hookReply)\n })\n })\n )\n }\n\n undoRefs(ref){\n DOM.all(this.el, `[${PHX_REF}=\"${ref}\"]`, el => {\n let disabledVal = el.getAttribute(PHX_DISABLED)\n // remove refs\n el.removeAttribute(PHX_REF)\n // restore inputs\n if(el.getAttribute(PHX_READONLY) !== null){\n el.readOnly = false\n el.removeAttribute(PHX_READONLY)\n }\n if(disabledVal !== null){\n el.disabled = disabledVal === \"true\" ? true : false\n el.removeAttribute(PHX_DISABLED)\n }\n // remove classes\n PHX_EVENT_CLASSES.forEach(className => DOM.removeClass(el, className))\n // restore disables\n let disableRestore = el.getAttribute(PHX_DISABLE_WITH_RESTORE)\n if(disableRestore !== null){\n el.innerText = disableRestore\n el.removeAttribute(PHX_DISABLE_WITH_RESTORE)\n }\n let toEl = DOM.private(el, PHX_REF)\n if(toEl){\n let hook = this.triggerBeforeUpdateHook(el, toEl)\n DOMPatch.patchEl(el, toEl, this.liveSocket.getActiveElement())\n if(hook){ hook.__updated() }\n DOM.deletePrivate(el, PHX_REF)\n }\n })\n }\n\n putRef(elements, event){\n let newRef = this.ref++\n let disableWith = this.binding(PHX_DISABLE_WITH)\n\n elements.forEach(el => {\n el.classList.add(`phx-${event}-loading`)\n el.setAttribute(PHX_REF, newRef)\n let disableText = el.getAttribute(disableWith)\n if(disableText !== null){\n if(!el.getAttribute(PHX_DISABLE_WITH_RESTORE)){\n el.setAttribute(PHX_DISABLE_WITH_RESTORE, el.innerText)\n }\n el.innerText = disableText\n }\n })\n return [newRef, elements]\n }\n\n componentID(el){\n let cid = el.getAttribute && el.getAttribute(PHX_COMPONENT)\n return cid ? parseInt(cid) : null\n }\n\n targetComponentID(target, targetCtx){\n if(target.getAttribute(this.binding(\"target\"))){\n return this.closestComponentID(targetCtx)\n } else {\n return null\n }\n }\n\n closestComponentID(targetCtx){\n if(targetCtx){\n return maybe(targetCtx.closest(`[${PHX_COMPONENT}]`), el => this.ownsElement(el) && this.componentID(el))\n } else {\n return null\n }\n }\n\n pushHookEvent(targetCtx, event, payload, onReply){\n if(!this.isConnected()){\n this.log(\"hook\", () => [\"unable to push hook event. LiveView not connected\", event, payload])\n return false\n }\n let [ref, els] = this.putRef([], \"hook\")\n this.pushWithReply(() => [ref, els], \"event\", {\n type: \"hook\",\n event: event,\n value: payload,\n cid: this.closestComponentID(targetCtx)\n }, (resp, reply) => onReply(reply, ref))\n\n return ref\n }\n\n extractMeta(el, meta){\n let prefix = this.binding(\"value-\")\n for(let i = 0; i < el.attributes.length; i++){\n let name = el.attributes[i].name\n if(name.startsWith(prefix)){ meta[name.replace(prefix, \"\")] = el.getAttribute(name) }\n }\n if(el.value !== undefined){\n meta.value = el.value\n\n if(el.tagName === \"INPUT\" && CHECKABLE_INPUTS.indexOf(el.type) >= 0 && !el.checked){\n delete meta.value\n }\n }\n return meta\n }\n\n pushEvent(type, el, targetCtx, phxEvent, meta){\n this.pushWithReply(() => this.putRef([el], type), \"event\", {\n type: type,\n event: phxEvent,\n value: this.extractMeta(el, meta),\n cid: this.targetComponentID(el, targetCtx)\n })\n }\n\n pushKey(keyElement, targetCtx, kind, phxEvent, meta){\n this.pushWithReply(() => this.putRef([keyElement], kind), \"event\", {\n type: kind,\n event: phxEvent,\n value: this.extractMeta(keyElement, meta),\n cid: this.targetComponentID(keyElement, targetCtx)\n })\n }\n\n pushFileProgress(fileEl, entryRef, progress, onReply = function (){ }){\n this.liveSocket.withinOwners(fileEl.form, (view, targetCtx) => {\n view.pushWithReply(null, \"progress\", {\n event: fileEl.getAttribute(view.binding(PHX_PROGRESS)),\n ref: fileEl.getAttribute(PHX_UPLOAD_REF),\n entry_ref: entryRef,\n progress: progress,\n cid: view.targetComponentID(fileEl.form, targetCtx)\n }, onReply)\n })\n }\n\n pushInput(inputEl, targetCtx, forceCid, phxEvent, eventTarget, callback){\n let uploads\n let cid = isCid(forceCid) ? forceCid : this.targetComponentID(inputEl.form, targetCtx)\n let refGenerator = () => this.putRef([inputEl, inputEl.form], \"change\")\n let formData = serializeForm(inputEl.form, {_target: eventTarget.name})\n if(DOM.isUploadInput(inputEl) && inputEl.files && inputEl.files.length > 0){\n LiveUploader.trackFiles(inputEl, Array.from(inputEl.files))\n }\n uploads = LiveUploader.serializeUploads(inputEl)\n let event = {\n type: \"form\",\n event: phxEvent,\n value: formData,\n uploads: uploads,\n cid: cid\n }\n this.pushWithReply(refGenerator, \"event\", event, resp => {\n DOM.showError(inputEl, this.liveSocket.binding(PHX_FEEDBACK_FOR))\n if(DOM.isUploadInput(inputEl) && inputEl.getAttribute(\"data-phx-auto-upload\") !== null){\n if(LiveUploader.filesAwaitingPreflight(inputEl).length > 0){\n let [ref, _els] = refGenerator()\n this.uploadFiles(inputEl.form, targetCtx, ref, cid, (_uploads) => {\n callback && callback(resp)\n this.triggerAwaitingSubmit(inputEl.form)\n })\n }\n } else {\n callback && callback(resp)\n }\n })\n }\n\n triggerAwaitingSubmit(formEl){\n let awaitingSubmit = this.getScheduledSubmit(formEl)\n if(awaitingSubmit){\n let [_el, _ref, callback] = awaitingSubmit\n this.cancelSubmit(formEl)\n callback()\n }\n }\n\n getScheduledSubmit(formEl){\n return this.formSubmits.find(([el, _callback]) => el.isSameNode(formEl))\n }\n\n scheduleSubmit(formEl, ref, callback){\n if(this.getScheduledSubmit(formEl)){ return true }\n this.formSubmits.push([formEl, ref, callback])\n }\n\n cancelSubmit(formEl){\n this.formSubmits = this.formSubmits.filter(([el, ref, _callback]) => {\n if(el.isSameNode(formEl)){\n this.undoRefs(ref)\n return false\n } else {\n return true\n }\n })\n }\n\n pushFormSubmit(formEl, targetCtx, phxEvent, onReply){\n let filterIgnored = el => {\n let userIgnored = closestPhxBinding(el, `${this.binding(PHX_UPDATE)}=ignore`, el.form)\n return !(userIgnored || closestPhxBinding(el, \"data-phx-update=ignore\", el.form))\n }\n let filterDisables = el => {\n return el.hasAttribute(this.binding(PHX_DISABLE_WITH))\n }\n let filterButton = el => el.tagName == \"BUTTON\"\n\n let filterInput = el => [\"INPUT\", \"TEXTAREA\", \"SELECT\"].includes(el.tagName)\n\n let refGenerator = () => {\n let formElements = Array.from(formEl.elements)\n let disables = formElements.filter(filterDisables)\n let buttons = formElements.filter(filterButton).filter(filterIgnored)\n let inputs = formElements.filter(filterInput).filter(filterIgnored)\n\n buttons.forEach(button => {\n button.setAttribute(PHX_DISABLED, button.disabled)\n button.disabled = true\n })\n inputs.forEach(input => {\n input.setAttribute(PHX_READONLY, input.readOnly)\n input.readOnly = true\n if(input.files){\n input.setAttribute(PHX_DISABLED, input.disabled)\n input.disabled = true\n }\n })\n formEl.setAttribute(this.binding(PHX_PAGE_LOADING), \"\")\n return this.putRef([formEl].concat(disables).concat(buttons).concat(inputs), \"submit\")\n }\n\n let cid = this.targetComponentID(formEl, targetCtx)\n if(LiveUploader.hasUploadsInProgress(formEl)){\n let [ref, _els] = refGenerator()\n return this.scheduleSubmit(formEl, ref, () => this.pushFormSubmit(formEl, targetCtx, phxEvent, onReply))\n } else if(LiveUploader.inputsAwaitingPreflight(formEl).length > 0){\n let [ref, els] = refGenerator()\n let proxyRefGen = () => [ref, els]\n this.uploadFiles(formEl, targetCtx, ref, cid, (_uploads) => {\n let formData = serializeForm(formEl, {})\n this.pushWithReply(proxyRefGen, \"event\", {\n type: \"form\",\n event: phxEvent,\n value: formData,\n cid: cid\n }, onReply)\n })\n } else {\n let formData = serializeForm(formEl)\n this.pushWithReply(refGenerator, \"event\", {\n type: \"form\",\n event: phxEvent,\n value: formData,\n cid: cid\n }, onReply)\n }\n }\n\n uploadFiles(formEl, targetCtx, ref, cid, onComplete){\n let joinCountAtUpload = this.joinCount\n let inputEls = LiveUploader.activeFileInputs(formEl)\n let numFileInputsInProgress = inputEls.length\n\n // get each file input\n inputEls.forEach(inputEl => {\n let uploader = new LiveUploader(inputEl, this, () => {\n numFileInputsInProgress--\n if(numFileInputsInProgress === 0){ onComplete() }\n });\n\n this.uploaders[inputEl] = uploader\n let entries = uploader.entries().map(entry => entry.toPreflightPayload())\n\n let payload = {\n ref: inputEl.getAttribute(PHX_UPLOAD_REF),\n entries: entries,\n cid: this.targetComponentID(inputEl.form, targetCtx)\n }\n\n this.log(\"upload\", () => [\"sending preflight request\", payload])\n\n this.pushWithReply(null, \"allow_upload\", payload, resp => {\n this.log(\"upload\", () => [\"got preflight response\", resp])\n if(resp.error){\n this.undoRefs(ref)\n let [entry_ref, reason] = resp.error\n this.log(\"upload\", () => [`error for entry ${entry_ref}`, reason])\n } else {\n let onError = (callback) => {\n this.channel.onError(() => {\n if(this.joinCount === joinCountAtUpload){ callback() }\n })\n }\n uploader.initAdapterUpload(resp, onError, this.liveSocket)\n }\n })\n })\n }\n\n dispatchUploads(name, filesOrBlobs){\n let inputs = DOM.findUploadInputs(this.el).filter(el => el.name === name)\n if(inputs.length === 0){ logError(`no live file inputs found matching the name \"${name}\"`) }\n else if(inputs.length > 1){ logError(`duplicate live file inputs found matching the name \"${name}\"`) }\n else { DOM.dispatchEvent(inputs[0], PHX_TRACK_UPLOADS, {files: filesOrBlobs}) }\n }\n\n pushFormRecovery(form, newCid, callback){\n this.liveSocket.withinOwners(form, (view, targetCtx) => {\n let input = form.elements[0]\n let phxEvent = form.getAttribute(this.binding(PHX_AUTO_RECOVER)) || form.getAttribute(this.binding(\"change\"))\n view.pushInput(input, targetCtx, newCid, phxEvent, input, callback)\n })\n }\n\n pushLinkPatch(href, targetEl, callback){\n let linkRef = this.liveSocket.setPendingLink(href)\n let refGen = targetEl ? () => this.putRef([targetEl], \"click\") : null\n\n this.pushWithReply(refGen, \"live_patch\", {url: href}, resp => {\n if(resp.link_redirect){\n this.liveSocket.replaceMain(href, null, callback, linkRef)\n } else {\n if(this.liveSocket.commitPendingLink(linkRef)){\n this.href = href\n }\n this.applyPendingUpdates()\n callback && callback(linkRef)\n }\n }).receive(\"timeout\", () => this.liveSocket.redirect(window.location.href))\n }\n\n formsForRecovery(html){\n if(this.joinCount === 0){ return [] }\n\n let phxChange = this.binding(\"change\")\n let template = document.createElement(\"template\")\n template.innerHTML = html\n\n return (\n DOM.all(this.el, `form[${phxChange}]`)\n .filter(form => form.id && this.ownsElement(form))\n .filter(form => form.elements.length > 0)\n .filter(form => form.getAttribute(this.binding(PHX_AUTO_RECOVER)) !== \"ignore\")\n .map(form => {\n let newForm = template.content.querySelector(`form[id=\"${form.id}\"][${phxChange}=\"${form.getAttribute(phxChange)}\"]`)\n if(newForm){\n return [form, newForm, this.componentID(newForm)]\n } else {\n return [form, null, null]\n }\n })\n .filter(([form, newForm, newCid]) => newForm)\n )\n }\n\n maybePushComponentsDestroyed(destroyedCIDs){\n let willDestroyCIDs = destroyedCIDs.filter(cid => {\n return DOM.findComponentNodeList(this.el, cid).length === 0\n })\n if(willDestroyCIDs.length > 0){\n this.pruningCIDs.push(...willDestroyCIDs)\n\n this.pushWithReply(null, \"cids_will_destroy\", {cids: willDestroyCIDs}, () => {\n // The cids are either back on the page or they will be fully removed,\n // so we can remove them from the pruningCIDs.\n this.pruningCIDs = this.pruningCIDs.filter(cid => willDestroyCIDs.indexOf(cid) !== -1)\n\n // See if any of the cids we wanted to destroy were added back,\n // if they were added back, we don't actually destroy them.\n let completelyDestroyCIDs = willDestroyCIDs.filter(cid => {\n return DOM.findComponentNodeList(this.el, cid).length === 0\n })\n\n if(completelyDestroyCIDs.length > 0){\n this.pushWithReply(null, \"cids_destroyed\", {cids: completelyDestroyCIDs}, (resp) => {\n this.rendered.pruneCIDs(resp.cids)\n })\n }\n })\n }\n }\n\n ownsElement(el){\n return el.getAttribute(PHX_PARENT_ID) === this.id ||\n maybe(el.closest(PHX_VIEW_SELECTOR), node => node.id) === this.id\n }\n\n submitForm(form, targetCtx, phxEvent){\n DOM.putPrivate(form, PHX_HAS_SUBMITTED, true)\n this.liveSocket.blurActiveElement(this)\n this.pushFormSubmit(form, targetCtx, phxEvent, () => {\n this.liveSocket.restorePreviouslyActiveFocus()\n })\n }\n\n binding(kind){ return this.liveSocket.binding(kind) }\n}\n", "/** Initializes the LiveSocket\n *\n *\n * @param {string} endPoint - The string WebSocket endpoint, ie, `\"wss://example.com/live\"`,\n * `\"/live\"` (inherited host & protocol)\n * @param {Phoenix.Socket} socket - the required Phoenix Socket class imported from \"phoenix\". For example:\n *\n * import {Socket} from \"phoenix\"\n * import {LiveSocket} from \"phoenix_live_view\"\n * let liveSocket = new LiveSocket(\"/live\", Socket, {...})\n *\n * @param {Object} [opts] - Optional configuration. Outside of keys listed below, all\n * configuration is passed directly to the Phoenix Socket constructor.\n * @param {Object} [opts.defaults] - The optional defaults to use for various bindings,\n * such as `phx-debounce`. Supports the following keys:\n *\n * - debounce - the millisecond phx-debounce time. Defaults 300\n * - throttle - the millisecond phx-throttle time. Defaults 300\n *\n * @param {Function} [opts.params] - The optional function for passing connect params.\n * The function receives the element associated with a given LiveView. For example:\n *\n * (el) => {view: el.getAttribute(\"data-my-view-name\", token: window.myToken}\n *\n * @param {string} [opts.bindingPrefix] - The optional prefix to use for all phx DOM annotations.\n * Defaults to \"phx-\".\n * @param {Object} [opts.hooks] - The optional object for referencing LiveView hook callbacks.\n * @param {Object} [opts.uploaders] - The optional object for referencing LiveView uploader callbacks.\n * @param {integer} [opts.loaderTimeout] - The optional delay in milliseconds to wait before apply\n * loading states.\n * @param {Function} [opts.viewLogger] - The optional function to log debug information. For example:\n *\n * (view, kind, msg, obj) => console.log(`${view.id} ${kind}: ${msg} - `, obj)\n *\n * @param {Object} [opts.metadata] - The optional object mapping event names to functions for\n * populating event metadata. For example:\n *\n * metadata: {\n * click: (e, el) => {\n * return {\n * ctrlKey: e.ctrlKey,\n * metaKey: e.metaKey,\n * detail: e.detail || 1,\n * }\n * },\n * keydown: (e, el) => {\n * return {\n * key: e.key,\n * ctrlKey: e.ctrlKey,\n * metaKey: e.metaKey,\n * shiftKey: e.shiftKey\n * }\n * }\n * }\n * @param {Object} [opts.sessionStorage] - An optional Storage compatible object\n * Useful when LiveView won't have access to `sessionStorage`. For example, This could\n * happen if a site loads a cross-domain LiveView in an iframe. Example usage:\n *\n * class InMemoryStorage {\n * constructor() { this.storage = {} }\n * getItem(keyName) { return this.storage[keyName] }\n * removeItem(keyName) { delete this.storage[keyName] }\n * setItem(keyName, keyValue) { this.storage[keyName] = keyValue }\n * }\n *\n * @param {Object} [opts.localStorage] - An optional Storage compatible object\n * Useful for when LiveView won't have access to `localStorage`.\n * See `opts.sessionStorage` for examples.\n*/\n\nimport {\n BINDING_PREFIX,\n CONSECUTIVE_RELOADS,\n DEFAULTS,\n FAILSAFE_JITTER,\n LOADER_TIMEOUT,\n MAX_RELOADS,\n PHX_DEBOUNCE,\n PHX_DROP_TARGET,\n PHX_HAS_FOCUSED,\n PHX_KEY,\n PHX_LINK_STATE,\n PHX_LIVE_LINK,\n PHX_LV_DEBUG,\n PHX_LV_LATENCY_SIM,\n PHX_LV_PROFILE,\n PHX_MAIN,\n PHX_PARENT_ID,\n PHX_VIEW_SELECTOR,\n PHX_ROOT_ID,\n PHX_THROTTLE,\n PHX_TRACK_UPLOADS,\n RELOAD_JITTER\n\n} from \"./constants\"\n\nimport {\n clone,\n closestPhxBinding,\n closure,\n debug,\n maybe\n} from \"./utils\"\n\nimport Browser from \"./browser\"\nimport DOM from \"./dom\"\nimport Hooks from \"./hooks\"\nimport LiveUploader from \"./live_uploader\"\nimport View from \"./view\"\nimport {PHX_SESSION} from \"./constants\"\n\nexport default class LiveSocket {\n constructor(url, phxSocket, opts = {}){\n this.unloaded = false\n if(!phxSocket || phxSocket.constructor.name === \"Object\"){\n throw new Error(`\n a phoenix Socket must be provided as the second argument to the LiveSocket constructor. For example:\n\n import {Socket} from \"phoenix\"\n import LiveSocket from \"phoenix_live_view\"\n let liveSocket = new LiveSocket(\"/live\", Socket, {...})\n `)\n }\n this.socket = new phxSocket(url, opts)\n this.bindingPrefix = opts.bindingPrefix || BINDING_PREFIX\n this.opts = opts\n this.params = closure(opts.params || {})\n this.viewLogger = opts.viewLogger\n this.metadataCallbacks = opts.metadata || {}\n this.defaults = Object.assign(clone(DEFAULTS), opts.defaults || {})\n this.activeElement = null\n this.prevActive = null\n this.silenced = false\n this.main = null\n this.linkRef = 1\n this.roots = {}\n this.href = window.location.href\n this.pendingLink = null\n this.currentLocation = clone(window.location)\n this.hooks = opts.hooks || {}\n this.uploaders = opts.uploaders || {}\n this.loaderTimeout = opts.loaderTimeout || LOADER_TIMEOUT\n this.localStorage = opts.localStorage || window.localStorage\n this.sessionStorage = opts.sessionStorage || window.sessionStorage\n this.boundTopLevelEvents = false\n this.domCallbacks = Object.assign({onNodeAdded: closure(), onBeforeElUpdated: closure()}, opts.dom || {})\n window.addEventListener(\"pagehide\", _e => {\n this.unloaded = true\n })\n this.socket.onOpen(() => {\n if(this.isUnloaded()){\n // reload page if being restored from back/forward cache and browser does not emit \"pageshow\"\n window.location.reload()\n }\n })\n }\n\n // public\n\n isProfileEnabled(){ return this.sessionStorage.getItem(PHX_LV_PROFILE) === \"true\" }\n\n isDebugEnabled(){ return this.sessionStorage.getItem(PHX_LV_DEBUG) === \"true\" }\n\n enableDebug(){ this.sessionStorage.setItem(PHX_LV_DEBUG, \"true\") }\n\n enableProfiling(){ this.sessionStorage.setItem(PHX_LV_PROFILE, \"true\") }\n\n disableDebug(){ this.sessionStorage.removeItem(PHX_LV_DEBUG) }\n\n disableProfiling(){ this.sessionStorage.removeItem(PHX_LV_PROFILE) }\n\n enableLatencySim(upperBoundMs){\n this.enableDebug()\n console.log(\"latency simulator enabled for the duration of this browser session. Call disableLatencySim() to disable\")\n this.sessionStorage.setItem(PHX_LV_LATENCY_SIM, upperBoundMs)\n }\n\n disableLatencySim(){ this.sessionStorage.removeItem(PHX_LV_LATENCY_SIM) }\n\n getLatencySim(){\n let str = this.sessionStorage.getItem(PHX_LV_LATENCY_SIM)\n return str ? parseInt(str) : null\n }\n\n getSocket(){ return this.socket }\n\n connect(){\n let doConnect = () => {\n if(this.joinRootViews()){\n this.bindTopLevelEvents()\n this.socket.connect()\n }\n }\n if([\"complete\", \"loaded\", \"interactive\"].indexOf(document.readyState) >= 0){\n doConnect()\n } else {\n document.addEventListener(\"DOMContentLoaded\", () => doConnect())\n }\n }\n\n disconnect(callback){ this.socket.disconnect(callback) }\n\n // private\n\n triggerDOM(kind, args){ this.domCallbacks[kind](...args) }\n\n time(name, func){\n if(!this.isProfileEnabled() || !console.time){ return func() }\n console.time(name)\n let result = func()\n console.timeEnd(name)\n return result\n }\n\n log(view, kind, msgCallback){\n if(this.viewLogger){\n let [msg, obj] = msgCallback()\n this.viewLogger(view, kind, msg, obj)\n } else if(this.isDebugEnabled()){\n let [msg, obj] = msgCallback()\n debug(view, kind, msg, obj)\n }\n }\n\n onChannel(channel, event, cb){\n channel.on(event, data => {\n let latency = this.getLatencySim()\n if(!latency){\n cb(data)\n } else {\n console.log(`simulating ${latency}ms of latency from server to client`)\n setTimeout(() => cb(data), latency)\n }\n })\n }\n\n wrapPush(view, opts, push){\n let latency = this.getLatencySim()\n let oldJoinCount = view.joinCount\n if(!latency){\n if(opts.timeout){\n return push().receive(\"timeout\", () => {\n if(view.joinCount === oldJoinCount && !view.isDestroyed()){\n this.reloadWithJitter(view, () => {\n this.log(view, \"timeout\", () => [\"received timeout while communicating with server. Falling back to hard refresh for recovery\"])\n })\n }\n })\n } else {\n return push()\n }\n }\n\n console.log(`simulating ${latency}ms of latency from client to server`)\n let fakePush = {\n receives: [],\n receive(kind, cb){ this.receives.push([kind, cb]) }\n }\n setTimeout(() => {\n if(view.isDestroyed()){ return }\n fakePush.receives.reduce((acc, [kind, cb]) => acc.receive(kind, cb), push())\n }, latency)\n return fakePush\n }\n\n reloadWithJitter(view, log){\n view.destroy()\n this.disconnect()\n let [minMs, maxMs] = RELOAD_JITTER\n let afterMs = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs\n let tries = Browser.updateLocal(this.localStorage, window.location.pathname, CONSECUTIVE_RELOADS, 0, count => count + 1)\n log ? log() : this.log(view, \"join\", () => [`encountered ${tries} consecutive reloads`])\n if(tries > MAX_RELOADS){\n this.log(view, \"join\", () => [`exceeded ${MAX_RELOADS} consecutive reloads. Entering failsafe mode`])\n afterMs = FAILSAFE_JITTER\n }\n setTimeout(() => {\n if(this.hasPendingLink()){\n window.location = this.pendingLink\n } else {\n window.location.reload()\n }\n }, afterMs)\n }\n\n getHookCallbacks(name){\n return name && name.startsWith(\"Phoenix.\") ? Hooks[name.split(\".\")[1]] : this.hooks[name]\n }\n\n isUnloaded(){ return this.unloaded }\n\n isConnected(){ return this.socket.isConnected() }\n\n getBindingPrefix(){ return this.bindingPrefix }\n\n binding(kind){ return `${this.getBindingPrefix()}${kind}` }\n\n channel(topic, params){ return this.socket.channel(topic, params) }\n\n joinRootViews(){\n let rootsFound = false\n DOM.all(document, `${PHX_VIEW_SELECTOR}:not([${PHX_PARENT_ID}])`, rootEl => {\n if(!this.getRootById(rootEl.id)){\n let view = this.newRootView(rootEl)\n view.setHref(this.getHref())\n view.join()\n if(rootEl.getAttribute(PHX_MAIN)){ this.main = view }\n }\n rootsFound = true\n })\n return rootsFound\n }\n\n redirect(to, flash){\n this.disconnect()\n Browser.redirect(to, flash)\n }\n\n replaceMain(href, flash, callback = null, linkRef = this.setPendingLink(href)){\n let oldMainEl = this.main.el\n let newMainEl = DOM.cloneNode(oldMainEl, \"\")\n this.main.showLoader(this.loaderTimeout)\n this.main.destroy()\n\n this.main = this.newRootView(newMainEl, flash)\n this.main.setRedirect(href)\n this.main.join(joinCount => {\n if(joinCount === 1 && this.commitPendingLink(linkRef)){\n oldMainEl.replaceWith(newMainEl)\n callback && callback()\n }\n })\n }\n\n isPhxView(el){ return el.getAttribute && el.getAttribute(PHX_SESSION) !== null }\n\n newRootView(el, flash){\n let view = new View(el, this, null, flash)\n this.roots[view.id] = view\n return view\n }\n\n owner(childEl, callback){\n let view = maybe(childEl.closest(PHX_VIEW_SELECTOR), el => this.getViewByEl(el))\n if(view){ callback(view) }\n }\n\n withinOwners(childEl, callback){\n this.owner(childEl, view => {\n let phxTarget = childEl.getAttribute(this.binding(\"target\"))\n if(phxTarget === null){\n callback(view, childEl)\n } else {\n view.withinTargets(phxTarget, callback)\n }\n })\n }\n\n getViewByEl(el){\n let rootId = el.getAttribute(PHX_ROOT_ID)\n return maybe(this.getRootById(rootId), root => root.getDescendentByEl(el))\n }\n\n getRootById(id){ return this.roots[id] }\n\n destroyAllViews(){\n for(let id in this.roots){\n this.roots[id].destroy()\n delete this.roots[id]\n }\n }\n\n destroyViewByEl(el){\n let root = this.getRootById(el.getAttribute(PHX_ROOT_ID))\n if(root){ root.destroyDescendent(el.id) }\n }\n\n setActiveElement(target){\n if(this.activeElement === target){ return }\n this.activeElement = target\n let cancel = () => {\n if(target === this.activeElement){ this.activeElement = null }\n target.removeEventListener(\"mouseup\", this)\n target.removeEventListener(\"touchend\", this)\n }\n target.addEventListener(\"mouseup\", cancel)\n target.addEventListener(\"touchend\", cancel)\n }\n\n getActiveElement(){\n if(document.activeElement === document.body){\n return this.activeElement || document.activeElement\n } else {\n // document.activeElement can be null in Internet Explorer 11\n return document.activeElement || document.body\n }\n }\n\n dropActiveElement(view){\n if(this.prevActive && view.ownsElement(this.prevActive)){\n this.prevActive = null\n }\n }\n\n restorePreviouslyActiveFocus(){\n if(this.prevActive && this.prevActive !== document.body){\n this.prevActive.focus()\n }\n }\n\n blurActiveElement(){\n this.prevActive = this.getActiveElement()\n if(this.prevActive !== document.body){ this.prevActive.blur() }\n }\n\n bindTopLevelEvents(){\n if(this.boundTopLevelEvents){ return }\n\n this.boundTopLevelEvents = true\n document.body.addEventListener(\"click\", function (){ }) // ensure all click events bubble for mobile Safari\n window.addEventListener(\"pageshow\", e => {\n if(e.persisted){ // reload page if being restored from back/forward cache\n this.getSocket().disconnect()\n this.withPageLoading({to: window.location.href, kind: \"redirect\"})\n window.location.reload()\n }\n }, true)\n this.bindNav()\n this.bindClicks()\n this.bindForms()\n this.bind({keyup: \"keyup\", keydown: \"keydown\"}, (e, type, view, target, targetCtx, phxEvent, _phxTarget) => {\n let matchKey = target.getAttribute(this.binding(PHX_KEY))\n let pressedKey = e.key && e.key.toLowerCase() // chrome clicked autocompletes send a keydown without key\n if(matchKey && matchKey.toLowerCase() !== pressedKey){ return }\n\n view.pushKey(target, targetCtx, type, phxEvent, {key: e.key, ...this.eventMeta(type, e, target)})\n })\n this.bind({blur: \"focusout\", focus: \"focusin\"}, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {\n if(!phxTarget){\n view.pushEvent(type, targetEl, targetCtx, phxEvent, this.eventMeta(type, e, targetEl))\n }\n })\n this.bind({blur: \"blur\", focus: \"focus\"}, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {\n // blur and focus are triggered on document and window. Discard one to avoid dups\n if(phxTarget && !phxTarget !== \"window\"){\n view.pushEvent(type, targetEl, targetCtx, phxEvent, this.eventMeta(type, e, targetEl))\n }\n })\n window.addEventListener(\"dragover\", e => e.preventDefault())\n window.addEventListener(\"drop\", e => {\n e.preventDefault()\n let dropTargetId = maybe(closestPhxBinding(e.target, this.binding(PHX_DROP_TARGET)), trueTarget => {\n return trueTarget.getAttribute(this.binding(PHX_DROP_TARGET))\n })\n let dropTarget = dropTargetId && document.getElementById(dropTargetId)\n let files = Array.from(e.dataTransfer.files || [])\n if(!dropTarget || dropTarget.disabled || files.length === 0 || !(dropTarget.files instanceof FileList)){ return }\n\n LiveUploader.trackFiles(dropTarget, files)\n dropTarget.dispatchEvent(new Event(\"input\", {bubbles: true}))\n })\n this.on(PHX_TRACK_UPLOADS, e => {\n let uploadTarget = e.target\n if(!DOM.isUploadInput(uploadTarget)){ return }\n let files = Array.from(e.detail.files || []).filter(f => f instanceof File || f instanceof Blob)\n LiveUploader.trackFiles(uploadTarget, files)\n uploadTarget.dispatchEvent(new Event(\"input\", {bubbles: true}))\n })\n }\n\n eventMeta(eventName, e, targetEl){\n let callback = this.metadataCallbacks[eventName]\n return callback ? callback(e, targetEl) : {}\n }\n\n setPendingLink(href){\n this.linkRef++\n this.pendingLink = href\n return this.linkRef\n }\n\n commitPendingLink(linkRef){\n if(this.linkRef !== linkRef){\n return false\n } else {\n this.href = this.pendingLink\n this.pendingLink = null\n return true\n }\n }\n\n getHref(){ return this.href }\n\n hasPendingLink(){ return !!this.pendingLink }\n\n bind(events, callback){\n for(let event in events){\n let browserEventName = events[event]\n\n this.on(browserEventName, e => {\n let binding = this.binding(event)\n let windowBinding = this.binding(`window-${event}`)\n let targetPhxEvent = e.target.getAttribute && e.target.getAttribute(binding)\n if(targetPhxEvent){\n this.debounce(e.target, e, () => {\n this.withinOwners(e.target, (view, targetCtx) => {\n callback(e, event, view, e.target, targetCtx, targetPhxEvent, null)\n })\n })\n } else {\n DOM.all(document, `[${windowBinding}]`, el => {\n let phxEvent = el.getAttribute(windowBinding)\n this.debounce(el, e, () => {\n this.withinOwners(el, (view, targetCtx) => {\n callback(e, event, view, el, targetCtx, phxEvent, \"window\")\n })\n })\n })\n }\n })\n }\n }\n\n bindClicks(){\n this.bindClick(\"click\", \"click\", false)\n this.bindClick(\"mousedown\", \"capture-click\", true)\n }\n\n bindClick(eventName, bindingName, capture){\n let click = this.binding(bindingName)\n window.addEventListener(eventName, e => {\n if(!this.isConnected()){ return }\n let target = null\n if(capture){\n target = e.target.matches(`[${click}]`) ? e.target : e.target.querySelector(`[${click}]`)\n } else {\n target = closestPhxBinding(e.target, click)\n }\n let phxEvent = target && target.getAttribute(click)\n if(!phxEvent){ return }\n if(target.getAttribute(\"href\") === \"#\"){ e.preventDefault() }\n\n this.debounce(target, e, () => {\n this.withinOwners(target, (view, targetCtx) => {\n view.pushEvent(\"click\", target, targetCtx, phxEvent, this.eventMeta(\"click\", e, target))\n })\n })\n }, capture)\n }\n\n bindNav(){\n if(!Browser.canPushState()){ return }\n if(history.scrollRestoration){ history.scrollRestoration = \"manual\" }\n let scrollTimer = null\n window.addEventListener(\"scroll\", _e => {\n clearTimeout(scrollTimer)\n scrollTimer = setTimeout(() => {\n Browser.updateCurrentState(state => Object.assign(state, {scroll: window.scrollY}))\n }, 100)\n })\n window.addEventListener(\"popstate\", event => {\n if(!this.registerNewLocation(window.location)){ return }\n let {type, id, root, scroll} = event.state || {}\n let href = window.location.href\n\n if(this.main.isConnected() && (type === \"patch\" && id === this.main.id)){\n this.main.pushLinkPatch(href, null)\n } else {\n this.replaceMain(href, null, () => {\n if(root){ this.replaceRootHistory() }\n if(typeof(scroll) === \"number\"){\n setTimeout(() => {\n window.scrollTo(0, scroll)\n }, 0) // the body needs to render before we scroll.\n }\n })\n }\n }, false)\n window.addEventListener(\"click\", e => {\n let target = closestPhxBinding(e.target, PHX_LIVE_LINK)\n let type = target && target.getAttribute(PHX_LIVE_LINK)\n let wantsNewTab = e.metaKey || e.ctrlKey || e.button === 1\n if(!type || !this.isConnected() || !this.main || wantsNewTab){ return }\n let href = target.href\n let linkState = target.getAttribute(PHX_LINK_STATE)\n e.preventDefault()\n if(this.pendingLink === href){ return }\n\n if(type === \"patch\"){\n this.pushHistoryPatch(href, linkState, target)\n } else if(type === \"redirect\"){\n this.historyRedirect(href, linkState)\n } else {\n throw new Error(`expected ${PHX_LIVE_LINK} to be \"patch\" or \"redirect\", got: ${type}`)\n }\n }, false)\n }\n\n withPageLoading(info, callback){\n DOM.dispatchEvent(window, \"phx:page-loading-start\", info)\n let done = () => DOM.dispatchEvent(window, \"phx:page-loading-stop\", info)\n return callback ? callback(done) : done\n }\n\n pushHistoryPatch(href, linkState, targetEl){\n this.withPageLoading({to: href, kind: \"patch\"}, done => {\n this.main.pushLinkPatch(href, targetEl, linkRef => {\n this.historyPatch(href, linkState, linkRef)\n done()\n })\n })\n }\n\n historyPatch(href, linkState, linkRef = this.setPendingLink(href)){\n if(!this.commitPendingLink(linkRef)){ return }\n\n Browser.pushState(linkState, {type: \"patch\", id: this.main.id}, href)\n this.registerNewLocation(window.location)\n }\n\n historyRedirect(href, linkState, flash){\n let scroll = window.scrollY\n this.withPageLoading({to: href, kind: \"redirect\"}, done => {\n this.replaceMain(href, flash, () => {\n Browser.pushState(linkState, {type: \"redirect\", id: this.main.id, scroll: scroll}, href)\n this.registerNewLocation(window.location)\n done()\n })\n })\n }\n\n replaceRootHistory(){\n Browser.pushState(\"replace\", {root: true, type: \"patch\", id: this.main.id})\n }\n\n registerNewLocation(newLocation){\n let {pathname, search} = this.currentLocation\n if(pathname + search === newLocation.pathname + newLocation.search){\n return false\n } else {\n this.currentLocation = clone(newLocation)\n return true\n }\n }\n\n bindForms(){\n let iterations = 0\n this.on(\"submit\", e => {\n let phxEvent = e.target.getAttribute(this.binding(\"submit\"))\n if(!phxEvent){ return }\n e.preventDefault()\n e.target.disabled = true\n this.withinOwners(e.target, (view, targetCtx) => view.submitForm(e.target, targetCtx, phxEvent))\n }, false)\n\n for(let type of [\"change\", \"input\"]){\n this.on(type, e => {\n let input = e.target\n let phxEvent = input.form && input.form.getAttribute(this.binding(\"change\"))\n if(!phxEvent){ return }\n if(input.type === \"number\" && input.validity && input.validity.badInput){ return }\n let currentIterations = iterations\n iterations++\n let {at: at, type: lastType} = DOM.private(input, \"prev-iteration\") || {}\n // detect dup because some browsers dispatch both \"input\" and \"change\"\n if(at === currentIterations - 1 && type !== lastType){ return }\n\n DOM.putPrivate(input, \"prev-iteration\", {at: currentIterations, type: type})\n\n this.debounce(input, e, () => {\n this.withinOwners(input.form, (view, targetCtx) => {\n DOM.putPrivate(input, PHX_HAS_FOCUSED, true)\n if(!DOM.isTextualInput(input)){\n this.setActiveElement(input)\n }\n view.pushInput(input, targetCtx, null, phxEvent, e.target)\n })\n })\n }, false)\n }\n }\n\n debounce(el, event, callback){\n let phxDebounce = this.binding(PHX_DEBOUNCE)\n let phxThrottle = this.binding(PHX_THROTTLE)\n let defaultDebounce = this.defaults.debounce.toString()\n let defaultThrottle = this.defaults.throttle.toString()\n DOM.debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, callback)\n }\n\n silenceEvents(callback){\n this.silenced = true\n callback()\n this.silenced = false\n }\n\n on(event, callback){\n window.addEventListener(event, e => {\n if(!this.silenced){ callback(e) }\n })\n }\n}\n"],
- "mappings": "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",
+ "sources": ["../../assets/js/phoenix_live_view/constants.js", "../../assets/js/phoenix_live_view/entry_uploader.js", "../../assets/js/phoenix_live_view/utils.js", "../../assets/js/phoenix_live_view/browser.js", "../../assets/js/phoenix_live_view/dom.js", "../../assets/js/phoenix_live_view/upload_entry.js", "../../assets/js/phoenix_live_view/live_uploader.js", "../../assets/js/phoenix_live_view/aria.js", "../../assets/js/phoenix_live_view/hooks.js", "../../assets/js/phoenix_live_view/dom_post_morph_restorer.js", "../../assets/node_modules/morphdom/dist/morphdom-esm.js", "../../assets/js/phoenix_live_view/dom_patch.js", "../../assets/js/phoenix_live_view/rendered.js", "../../assets/js/phoenix_live_view/view_hook.js", "../../assets/js/phoenix_live_view/js.js", "../../assets/js/phoenix_live_view/view.js", "../../assets/js/phoenix_live_view/live_socket.js"],
+ "sourcesContent": ["export const CONSECUTIVE_RELOADS = \"consecutive-reloads\"\nexport const MAX_RELOADS = 10\nexport const RELOAD_JITTER_MIN = 5000\nexport const RELOAD_JITTER_MAX = 10000\nexport const FAILSAFE_JITTER = 30000\nexport const PHX_EVENT_CLASSES = [\n \"phx-click-loading\", \"phx-change-loading\", \"phx-submit-loading\",\n \"phx-keydown-loading\", \"phx-keyup-loading\", \"phx-blur-loading\", \"phx-focus-loading\"\n]\nexport const PHX_COMPONENT = \"data-phx-component\"\nexport const PHX_LIVE_LINK = \"data-phx-link\"\nexport const PHX_TRACK_STATIC = \"track-static\"\nexport const PHX_LINK_STATE = \"data-phx-link-state\"\nexport const PHX_REF = \"data-phx-ref\"\nexport const PHX_REF_SRC = \"data-phx-ref-src\"\nexport const PHX_TRACK_UPLOADS = \"track-uploads\"\nexport const PHX_UPLOAD_REF = \"data-phx-upload-ref\"\nexport const PHX_PREFLIGHTED_REFS = \"data-phx-preflighted-refs\"\nexport const PHX_DONE_REFS = \"data-phx-done-refs\"\nexport const PHX_DROP_TARGET = \"drop-target\"\nexport const PHX_ACTIVE_ENTRY_REFS = \"data-phx-active-refs\"\nexport const PHX_LIVE_FILE_UPDATED = \"phx:live-file:updated\"\nexport const PHX_SKIP = \"data-phx-skip\"\nexport const PHX_PRUNE = \"data-phx-prune\"\nexport const PHX_PAGE_LOADING = \"page-loading\"\nexport const PHX_CONNECTED_CLASS = \"phx-connected\"\nexport const PHX_DISCONNECTED_CLASS = \"phx-loading\"\nexport const PHX_NO_FEEDBACK_CLASS = \"phx-no-feedback\"\nexport const PHX_ERROR_CLASS = \"phx-error\"\nexport const PHX_PARENT_ID = \"data-phx-parent-id\"\nexport const PHX_MAIN = \"data-phx-main\"\nexport const PHX_ROOT_ID = \"data-phx-root-id\"\nexport const PHX_TRIGGER_ACTION = \"trigger-action\"\nexport const PHX_FEEDBACK_FOR = \"feedback-for\"\nexport const PHX_HAS_FOCUSED = \"phx-has-focused\"\nexport const FOCUSABLE_INPUTS = [\"text\", \"textarea\", \"number\", \"email\", \"password\", \"search\", \"tel\", \"url\", \"date\", \"time\", \"datetime-local\", \"color\", \"range\"]\nexport const CHECKABLE_INPUTS = [\"checkbox\", \"radio\"]\nexport const PHX_HAS_SUBMITTED = \"phx-has-submitted\"\nexport const PHX_SESSION = \"data-phx-session\"\nexport const PHX_VIEW_SELECTOR = `[${PHX_SESSION}]`\nexport const PHX_STICKY = \"data-phx-sticky\"\nexport const PHX_STATIC = \"data-phx-static\"\nexport const PHX_READONLY = \"data-phx-readonly\"\nexport const PHX_DISABLED = \"data-phx-disabled\"\nexport const PHX_DISABLE_WITH = \"disable-with\"\nexport const PHX_DISABLE_WITH_RESTORE = \"data-phx-disable-with-restore\"\nexport const PHX_HOOK = \"hook\"\nexport const PHX_DEBOUNCE = \"debounce\"\nexport const PHX_THROTTLE = \"throttle\"\nexport const PHX_UPDATE = \"update\"\nexport const PHX_STREAM = \"stream\"\nexport const PHX_KEY = \"key\"\nexport const PHX_PRIVATE = \"phxPrivate\"\nexport const PHX_AUTO_RECOVER = \"auto-recover\"\nexport const PHX_LV_DEBUG = \"phx:live-socket:debug\"\nexport const PHX_LV_PROFILE = \"phx:live-socket:profiling\"\nexport const PHX_LV_LATENCY_SIM = \"phx:live-socket:latency-sim\"\nexport const PHX_PROGRESS = \"progress\"\nexport const PHX_MOUNTED = \"mounted\"\nexport const LOADER_TIMEOUT = 1\nexport const BEFORE_UNLOAD_LOADER_TIMEOUT = 200\nexport const BINDING_PREFIX = \"phx-\"\nexport const PUSH_TIMEOUT = 30000\nexport const LINK_HEADER = \"x-requested-with\"\nexport const RESPONSE_URL_HEADER = \"x-response-url\"\nexport const DEBOUNCE_TRIGGER = \"debounce-trigger\"\nexport const THROTTLED = \"throttled\"\nexport const DEBOUNCE_PREV_KEY = \"debounce-prev-key\"\nexport const DEFAULTS = {\n debounce: 300,\n throttle: 300\n}\n\n// Rendered\nexport const DYNAMICS = \"d\"\nexport const STATIC = \"s\"\nexport const COMPONENTS = \"c\"\nexport const EVENTS = \"e\"\nexport const REPLY = \"r\"\nexport const TITLE = \"t\"\nexport const TEMPLATES = \"p\"\nexport const STREAM = \"stream\"\n", "import {\n logError\n} from \"./utils\"\n\nexport default class EntryUploader {\n constructor(entry, chunkSize, liveSocket){\n this.liveSocket = liveSocket\n this.entry = entry\n this.offset = 0\n this.chunkSize = chunkSize\n this.chunkTimer = null\n this.uploadChannel = liveSocket.channel(`lvu:${entry.ref}`, {token: entry.metadata()})\n }\n\n error(reason){\n clearTimeout(this.chunkTimer)\n this.uploadChannel.leave()\n this.entry.error(reason)\n }\n\n upload(){\n this.uploadChannel.onError(reason => this.error(reason))\n this.uploadChannel.join()\n .receive(\"ok\", _data => this.readNextChunk())\n .receive(\"error\", reason => this.error(reason))\n }\n\n isDone(){ return this.offset >= this.entry.file.size }\n\n readNextChunk(){\n let reader = new window.FileReader()\n let blob = this.entry.file.slice(this.offset, this.chunkSize + this.offset)\n reader.onload = (e) => {\n if(e.target.error === null){\n this.offset += e.target.result.byteLength\n this.pushChunk(e.target.result)\n } else {\n return logError(\"Read error: \" + e.target.error)\n }\n }\n reader.readAsArrayBuffer(blob)\n }\n\n pushChunk(chunk){\n if(!this.uploadChannel.isJoined()){ return }\n this.uploadChannel.push(\"chunk\", chunk)\n .receive(\"ok\", () => {\n this.entry.progress((this.offset / this.entry.file.size) * 100)\n if(!this.isDone()){\n this.chunkTimer = setTimeout(() => this.readNextChunk(), this.liveSocket.getLatencySim() || 0)\n }\n })\n }\n}\n", "import {\n PHX_VIEW_SELECTOR\n} from \"./constants\"\n\nimport EntryUploader from \"./entry_uploader\"\n\nexport let logError = (msg, obj) => console.error && console.error(msg, obj)\n\nexport let isCid = (cid) => {\n let type = typeof(cid)\n return type === \"number\" || (type === \"string\" && /^(0|[1-9]\\d*)$/.test(cid))\n}\n\nexport function detectDuplicateIds(){\n let ids = new Set()\n let elems = document.querySelectorAll(\"*[id]\")\n for(let i = 0, len = elems.length; i < len; i++){\n if(ids.has(elems[i].id)){\n console.error(`Multiple IDs detected: ${elems[i].id}. Ensure unique element ids.`)\n } else {\n ids.add(elems[i].id)\n }\n }\n}\n\nexport let debug = (view, kind, msg, obj) => {\n if(view.liveSocket.isDebugEnabled()){\n console.log(`${view.id} ${kind}: ${msg} - `, obj)\n }\n}\n\n// wraps value in closure or returns closure\nexport let closure = (val) => typeof val === \"function\" ? val : function (){ return val }\n\nexport let clone = (obj) => { return JSON.parse(JSON.stringify(obj)) }\n\nexport let closestPhxBinding = (el, binding, borderEl) => {\n do {\n if(el.matches(`[${binding}]`) && !el.disabled){ return el }\n el = el.parentElement || el.parentNode\n } while(el !== null && el.nodeType === 1 && !((borderEl && borderEl.isSameNode(el)) || el.matches(PHX_VIEW_SELECTOR)))\n return null\n}\n\nexport let isObject = (obj) => {\n return obj !== null && typeof obj === \"object\" && !(obj instanceof Array)\n}\n\nexport let isEqualObj = (obj1, obj2) => JSON.stringify(obj1) === JSON.stringify(obj2)\n\nexport let isEmpty = (obj) => {\n for(let x in obj){ return false }\n return true\n}\n\nexport let maybe = (el, callback) => el && callback(el)\n\nexport let channelUploader = function (entries, onError, resp, liveSocket){\n entries.forEach(entry => {\n let entryUploader = new EntryUploader(entry, resp.config.chunk_size, liveSocket)\n entryUploader.upload()\n })\n}\n", "let Browser = {\n canPushState(){ return (typeof (history.pushState) !== \"undefined\") },\n\n dropLocal(localStorage, namespace, subkey){\n return localStorage.removeItem(this.localKey(namespace, subkey))\n },\n\n updateLocal(localStorage, namespace, subkey, initial, func){\n let current = this.getLocal(localStorage, namespace, subkey)\n let key = this.localKey(namespace, subkey)\n let newVal = current === null ? initial : func(current)\n localStorage.setItem(key, JSON.stringify(newVal))\n return newVal\n },\n\n getLocal(localStorage, namespace, subkey){\n return JSON.parse(localStorage.getItem(this.localKey(namespace, subkey)))\n },\n\n updateCurrentState(callback){\n if(!this.canPushState()){ return }\n history.replaceState(callback(history.state || {}), \"\", window.location.href)\n },\n\n pushState(kind, meta, to){\n if(this.canPushState()){\n if(to !== window.location.href){\n if(meta.type == \"redirect\" && meta.scroll){\n // If we're redirecting store the current scrollY for the current history state.\n let currentState = history.state || {}\n currentState.scroll = meta.scroll\n history.replaceState(currentState, \"\", window.location.href)\n }\n\n delete meta.scroll // Only store the scroll in the redirect case.\n history[kind + \"State\"](meta, \"\", to || null) // IE will coerce undefined to string\n let hashEl = this.getHashTargetEl(window.location.hash)\n\n if(hashEl){\n hashEl.scrollIntoView()\n } else if(meta.type === \"redirect\"){\n window.scroll(0, 0)\n }\n }\n } else {\n this.redirect(to)\n }\n },\n\n setCookie(name, value){\n document.cookie = `${name}=${value}`\n },\n\n getCookie(name){\n return document.cookie.replace(new RegExp(`(?:(?:^|.*;\\s*)${name}\\s*\\=\\s*([^;]*).*$)|^.*$`), \"$1\")\n },\n\n redirect(toURL, flash){\n if(flash){ Browser.setCookie(\"__phoenix_flash__\", flash + \"; max-age=60000; path=/\") }\n window.location = toURL\n },\n\n localKey(namespace, subkey){ return `${namespace}-${subkey}` },\n\n getHashTargetEl(maybeHash){\n let hash = maybeHash.toString().substring(1)\n if(hash === \"\"){ return }\n return document.getElementById(hash) || document.querySelector(`a[name=\"${hash}\"]`)\n }\n}\n\nexport default Browser\n", "import {\n CHECKABLE_INPUTS,\n DEBOUNCE_PREV_KEY,\n DEBOUNCE_TRIGGER,\n FOCUSABLE_INPUTS,\n PHX_COMPONENT,\n PHX_EVENT_CLASSES,\n PHX_HAS_FOCUSED,\n PHX_HAS_SUBMITTED,\n PHX_MAIN,\n PHX_NO_FEEDBACK_CLASS,\n PHX_PARENT_ID,\n PHX_PRIVATE,\n PHX_REF,\n PHX_REF_SRC,\n PHX_ROOT_ID,\n PHX_SESSION,\n PHX_STATIC,\n PHX_UPLOAD_REF,\n PHX_VIEW_SELECTOR,\n PHX_STICKY,\n THROTTLED\n} from \"./constants\"\n\nimport {\n logError\n} from \"./utils\"\n\nlet DOM = {\n byId(id){ return document.getElementById(id) || logError(`no id found for ${id}`) },\n\n removeClass(el, className){\n el.classList.remove(className)\n if(el.classList.length === 0){ el.removeAttribute(\"class\") }\n },\n\n all(node, query, callback){\n if(!node){ return [] }\n let array = Array.from(node.querySelectorAll(query))\n return callback ? array.forEach(callback) : array\n },\n\n childNodeLength(html){\n let template = document.createElement(\"template\")\n template.innerHTML = html\n return template.content.childElementCount\n },\n\n isUploadInput(el){ return el.type === \"file\" && el.getAttribute(PHX_UPLOAD_REF) !== null },\n\n findUploadInputs(node){ return this.all(node, `input[type=\"file\"][${PHX_UPLOAD_REF}]`) },\n\n findComponentNodeList(node, cid){\n return this.filterWithinSameLiveView(this.all(node, `[${PHX_COMPONENT}=\"${cid}\"]`), node)\n },\n\n isPhxDestroyed(node){\n return node.id && DOM.private(node, \"destroyed\") ? true : false\n },\n\n wantsNewTab(e){\n let wantsNewTab = e.ctrlKey || e.shiftKey || e.metaKey || (e.button && e.button === 1)\n return wantsNewTab || e.target.getAttribute(\"target\") === \"_blank\"\n },\n\n isUnloadableFormSubmit(e){\n return !e.defaultPrevented && !this.wantsNewTab(e)\n },\n\n isNewPageHref(href, currentLocation){\n let url\n try {\n url = new URL(href)\n } catch(e) {\n try {\n url = new URL(href, currentLocation)\n } catch(e) {\n // bad URL, fallback to let browser try it as external\n return true\n }\n }\n\n if(url.host === currentLocation.host && url.protocol === currentLocation.protocol){\n if(url.pathname === currentLocation.pathname && url.search === currentLocation.search){\n return url.hash === \"\" && !url.href.endsWith(\"#\")\n }\n }\n return true\n },\n\n markPhxChildDestroyed(el){\n if(this.isPhxChild(el)){ el.setAttribute(PHX_SESSION, \"\") }\n this.putPrivate(el, \"destroyed\", true)\n },\n\n findPhxChildrenInFragment(html, parentId){\n let template = document.createElement(\"template\")\n template.innerHTML = html\n return this.findPhxChildren(template.content, parentId)\n },\n\n isIgnored(el, phxUpdate){\n return (el.getAttribute(phxUpdate) || el.getAttribute(\"data-phx-update\")) === \"ignore\"\n },\n\n isPhxUpdate(el, phxUpdate, updateTypes){\n return el.getAttribute && updateTypes.indexOf(el.getAttribute(phxUpdate)) >= 0\n },\n\n findPhxSticky(el){ return this.all(el, `[${PHX_STICKY}]`) },\n\n findPhxChildren(el, parentId){\n return this.all(el, `${PHX_VIEW_SELECTOR}[${PHX_PARENT_ID}=\"${parentId}\"]`)\n },\n\n findParentCIDs(node, cids){\n let initial = new Set(cids)\n let parentCids =\n cids.reduce((acc, cid) => {\n let selector = `[${PHX_COMPONENT}=\"${cid}\"] [${PHX_COMPONENT}]`\n\n this.filterWithinSameLiveView(this.all(node, selector), node)\n .map(el => parseInt(el.getAttribute(PHX_COMPONENT)))\n .forEach(childCID => acc.delete(childCID))\n\n return acc\n }, initial)\n\n return parentCids.size === 0 ? new Set(cids) : parentCids\n },\n\n filterWithinSameLiveView(nodes, parent){\n if(parent.querySelector(PHX_VIEW_SELECTOR)){\n return nodes.filter(el => this.withinSameLiveView(el, parent))\n } else {\n return nodes\n }\n },\n\n withinSameLiveView(node, parent){\n while(node = node.parentNode){\n if(node.isSameNode(parent)){ return true }\n if(node.getAttribute(PHX_SESSION) !== null){ return false }\n }\n },\n\n private(el, key){ return el[PHX_PRIVATE] && el[PHX_PRIVATE][key] },\n\n deletePrivate(el, key){ el[PHX_PRIVATE] && delete (el[PHX_PRIVATE][key]) },\n\n putPrivate(el, key, value){\n if(!el[PHX_PRIVATE]){ el[PHX_PRIVATE] = {} }\n el[PHX_PRIVATE][key] = value\n },\n\n updatePrivate(el, key, defaultVal, updateFunc){\n let existing = this.private(el, key)\n if(existing === undefined){\n this.putPrivate(el, key, updateFunc(defaultVal))\n } else {\n this.putPrivate(el, key, updateFunc(existing))\n }\n },\n\n copyPrivates(target, source){\n if(source[PHX_PRIVATE]){\n target[PHX_PRIVATE] = source[PHX_PRIVATE]\n }\n },\n\n putTitle(str){\n let titleEl = document.querySelector(\"title\")\n if(titleEl){\n let {prefix, suffix} = titleEl.dataset\n document.title = `${prefix || \"\"}${str}${suffix || \"\"}`\n } else {\n document.title = str\n }\n },\n\n debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, asyncFilter, callback){\n let debounce = el.getAttribute(phxDebounce)\n let throttle = el.getAttribute(phxThrottle)\n if(debounce === \"\"){ debounce = defaultDebounce }\n if(throttle === \"\"){ throttle = defaultThrottle }\n let value = debounce || throttle\n switch(value){\n case null: return callback()\n\n case \"blur\":\n if(this.once(el, \"debounce-blur\")){\n el.addEventListener(\"blur\", () => callback())\n }\n return\n\n default:\n let timeout = parseInt(value)\n let trigger = () => throttle ? this.deletePrivate(el, THROTTLED) : callback()\n let currentCycle = this.incCycle(el, DEBOUNCE_TRIGGER, trigger)\n if(isNaN(timeout)){ return logError(`invalid throttle/debounce value: ${value}`) }\n if(throttle){\n let newKeyDown = false\n if(event.type === \"keydown\"){\n let prevKey = this.private(el, DEBOUNCE_PREV_KEY)\n this.putPrivate(el, DEBOUNCE_PREV_KEY, event.key)\n newKeyDown = prevKey !== event.key\n }\n\n if(!newKeyDown && this.private(el, THROTTLED)){\n return false\n } else {\n callback()\n this.putPrivate(el, THROTTLED, true)\n setTimeout(() => {\n if(asyncFilter()){ this.triggerCycle(el, DEBOUNCE_TRIGGER) }\n }, timeout)\n }\n } else {\n setTimeout(() => {\n if(asyncFilter()){ this.triggerCycle(el, DEBOUNCE_TRIGGER, currentCycle) }\n }, timeout)\n }\n\n let form = el.form\n if(form && this.once(form, \"bind-debounce\")){\n form.addEventListener(\"submit\", () => {\n Array.from((new FormData(form)).entries(), ([name]) => {\n let input = form.querySelector(`[name=\"${name}\"]`)\n this.incCycle(input, DEBOUNCE_TRIGGER)\n this.deletePrivate(input, THROTTLED)\n })\n })\n }\n if(this.once(el, \"bind-debounce\")){\n el.addEventListener(\"blur\", () => this.triggerCycle(el, DEBOUNCE_TRIGGER))\n }\n }\n },\n\n triggerCycle(el, key, currentCycle){\n let [cycle, trigger] = this.private(el, key)\n if(!currentCycle){ currentCycle = cycle }\n if(currentCycle === cycle){\n this.incCycle(el, key)\n trigger()\n }\n },\n\n once(el, key){\n if(this.private(el, key) === true){ return false }\n this.putPrivate(el, key, true)\n return true\n },\n\n incCycle(el, key, trigger = function (){ }){\n let [currentCycle] = this.private(el, key) || [0, trigger]\n currentCycle++\n this.putPrivate(el, key, [currentCycle, trigger])\n return currentCycle\n },\n\n discardError(container, el, phxFeedbackFor){\n let field = el.getAttribute && el.getAttribute(phxFeedbackFor)\n // TODO: Remove id lookup after we update Phoenix to use input_name instead of input_id\n let input = field && container.querySelector(`[id=\"${field}\"], [name=\"${field}\"], [name=\"${field}[]\"]`)\n if(!input){ return }\n\n if(!(this.private(input, PHX_HAS_FOCUSED) || this.private(input, PHX_HAS_SUBMITTED))){\n el.classList.add(PHX_NO_FEEDBACK_CLASS)\n }\n },\n\n resetForm(form, phxFeedbackFor){\n Array.from(form.elements).forEach(input => {\n let query = `[${phxFeedbackFor}=\"${input.id}\"],\n [${phxFeedbackFor}=\"${input.name}\"],\n [${phxFeedbackFor}=\"${input.name.replace(/\\[\\]$/, \"\")}\"]`\n\n this.deletePrivate(input, PHX_HAS_FOCUSED)\n this.deletePrivate(input, PHX_HAS_SUBMITTED)\n this.all(document, query, feedbackEl => {\n feedbackEl.classList.add(PHX_NO_FEEDBACK_CLASS)\n })\n })\n },\n\n showError(inputEl, phxFeedbackFor){\n if(inputEl.id || inputEl.name){\n this.all(inputEl.form, `[${phxFeedbackFor}=\"${inputEl.id}\"], [${phxFeedbackFor}=\"${inputEl.name}\"]`, (el) => {\n this.removeClass(el, PHX_NO_FEEDBACK_CLASS)\n })\n }\n },\n\n isPhxChild(node){\n return node.getAttribute && node.getAttribute(PHX_PARENT_ID)\n },\n\n isPhxSticky(node){\n return node.getAttribute && node.getAttribute(PHX_STICKY) !== null\n },\n\n firstPhxChild(el){\n return this.isPhxChild(el) ? el : this.all(el, `[${PHX_PARENT_ID}]`)[0]\n },\n\n dispatchEvent(target, name, opts = {}){\n let bubbles = opts.bubbles === undefined ? true : !!opts.bubbles\n let eventOpts = {bubbles: bubbles, cancelable: true, detail: opts.detail || {}}\n let event = name === \"click\" ? new MouseEvent(\"click\", eventOpts) : new CustomEvent(name, eventOpts)\n target.dispatchEvent(event)\n },\n\n cloneNode(node, html){\n if(typeof (html) === \"undefined\"){\n return node.cloneNode(true)\n } else {\n let cloned = node.cloneNode(false)\n cloned.innerHTML = html\n return cloned\n }\n },\n\n mergeAttrs(target, source, opts = {}){\n let exclude = opts.exclude || []\n let isIgnored = opts.isIgnored\n let sourceAttrs = source.attributes\n for(let i = sourceAttrs.length - 1; i >= 0; i--){\n let name = sourceAttrs[i].name\n if(exclude.indexOf(name) < 0){ target.setAttribute(name, source.getAttribute(name)) }\n }\n\n let targetAttrs = target.attributes\n for(let i = targetAttrs.length - 1; i >= 0; i--){\n let name = targetAttrs[i].name\n if(isIgnored){\n if(name.startsWith(\"data-\") && !source.hasAttribute(name)){ target.removeAttribute(name) }\n } else {\n if(!source.hasAttribute(name)){ target.removeAttribute(name) }\n }\n }\n },\n\n mergeFocusedInput(target, source){\n // skip selects because FF will reset highlighted index for any setAttribute\n if(!(target instanceof HTMLSelectElement)){ DOM.mergeAttrs(target, source, {exclude: [\"value\"]}) }\n if(source.readOnly){\n target.setAttribute(\"readonly\", true)\n } else {\n target.removeAttribute(\"readonly\")\n }\n },\n\n hasSelectionRange(el){\n return el.setSelectionRange && (el.type === \"text\" || el.type === \"textarea\")\n },\n\n restoreFocus(focused, selectionStart, selectionEnd){\n if(!DOM.isTextualInput(focused)){ return }\n let wasFocused = focused.matches(\":focus\")\n if(focused.readOnly){ focused.blur() }\n if(!wasFocused){ focused.focus() }\n if(this.hasSelectionRange(focused)){\n focused.setSelectionRange(selectionStart, selectionEnd)\n }\n },\n\n isFormInput(el){ return /^(?:input|select|textarea)$/i.test(el.tagName) && el.type !== \"button\" },\n\n syncAttrsToProps(el){\n if(el instanceof HTMLInputElement && CHECKABLE_INPUTS.indexOf(el.type.toLocaleLowerCase()) >= 0){\n el.checked = el.getAttribute(\"checked\") !== null\n }\n },\n\n isTextualInput(el){ return FOCUSABLE_INPUTS.indexOf(el.type) >= 0 },\n\n isNowTriggerFormExternal(el, phxTriggerExternal){\n return el.getAttribute && el.getAttribute(phxTriggerExternal) !== null\n },\n\n syncPendingRef(fromEl, toEl, disableWith){\n let ref = fromEl.getAttribute(PHX_REF)\n if(ref === null){ return true }\n let refSrc = fromEl.getAttribute(PHX_REF_SRC)\n\n if(DOM.isFormInput(fromEl) || fromEl.getAttribute(disableWith) !== null){\n if(DOM.isUploadInput(fromEl)){ DOM.mergeAttrs(fromEl, toEl, {isIgnored: true}) }\n DOM.putPrivate(fromEl, PHX_REF, toEl)\n return false\n } else {\n PHX_EVENT_CLASSES.forEach(className => {\n fromEl.classList.contains(className) && toEl.classList.add(className)\n })\n toEl.setAttribute(PHX_REF, ref)\n toEl.setAttribute(PHX_REF_SRC, refSrc)\n return true\n }\n },\n\n cleanChildNodes(container, phxUpdate){\n if(DOM.isPhxUpdate(container, phxUpdate, [\"append\", \"prepend\"])){\n let toRemove = []\n container.childNodes.forEach(childNode => {\n if(!childNode.id){\n // Skip warning if it's an empty text node (e.g. a new-line)\n let isEmptyTextNode = childNode.nodeType === Node.TEXT_NODE && childNode.nodeValue.trim() === \"\"\n if(!isEmptyTextNode){\n logError(\"only HTML element tags with an id are allowed inside containers with phx-update.\\n\\n\" +\n `removing illegal node: \"${(childNode.outerHTML || childNode.nodeValue).trim()}\"\\n\\n`)\n }\n toRemove.push(childNode)\n }\n })\n toRemove.forEach(childNode => childNode.remove())\n }\n },\n\n replaceRootContainer(container, tagName, attrs){\n let retainedAttrs = new Set([\"id\", PHX_SESSION, PHX_STATIC, PHX_MAIN, PHX_ROOT_ID])\n if(container.tagName.toLowerCase() === tagName.toLowerCase()){\n Array.from(container.attributes)\n .filter(attr => !retainedAttrs.has(attr.name.toLowerCase()))\n .forEach(attr => container.removeAttribute(attr.name))\n\n Object.keys(attrs)\n .filter(name => !retainedAttrs.has(name.toLowerCase()))\n .forEach(attr => container.setAttribute(attr, attrs[attr]))\n\n return container\n\n } else {\n let newContainer = document.createElement(tagName)\n Object.keys(attrs).forEach(attr => newContainer.setAttribute(attr, attrs[attr]))\n retainedAttrs.forEach(attr => newContainer.setAttribute(attr, container.getAttribute(attr)))\n newContainer.innerHTML = container.innerHTML\n container.replaceWith(newContainer)\n return newContainer\n }\n },\n\n getSticky(el, name, defaultVal){\n let op = (DOM.private(el, \"sticky\") || []).find(([existingName, ]) => name === existingName)\n if(op){\n let [_name, _op, stashedResult] = op\n return stashedResult\n } else {\n return typeof(defaultVal) === \"function\" ? defaultVal() : defaultVal\n }\n },\n\n deleteSticky(el, name){\n this.updatePrivate(el, \"sticky\", [], ops => {\n return ops.filter(([existingName, _]) => existingName !== name)\n })\n },\n\n putSticky(el, name, op){\n let stashedResult = op(el)\n this.updatePrivate(el, \"sticky\", [], ops => {\n let existingIndex = ops.findIndex(([existingName, ]) => name === existingName)\n if(existingIndex >= 0){\n ops[existingIndex] = [name, op, stashedResult]\n } else {\n ops.push([name, op, stashedResult])\n }\n return ops\n })\n },\n\n applyStickyOperations(el){\n let ops = DOM.private(el, \"sticky\")\n if(!ops){ return }\n\n ops.forEach(([name, op, _stashed]) => this.putSticky(el, name, op))\n }\n}\n\nexport default DOM", "import {\n PHX_ACTIVE_ENTRY_REFS,\n PHX_LIVE_FILE_UPDATED,\n PHX_PREFLIGHTED_REFS\n} from \"./constants\"\n\nimport {\n channelUploader,\n logError\n} from \"./utils\"\n\nimport LiveUploader from \"./live_uploader\"\n\nexport default class UploadEntry {\n static isActive(fileEl, file){\n let isNew = file._phxRef === undefined\n let activeRefs = fileEl.getAttribute(PHX_ACTIVE_ENTRY_REFS).split(\",\")\n let isActive = activeRefs.indexOf(LiveUploader.genFileRef(file)) >= 0\n return file.size > 0 && (isNew || isActive)\n }\n\n static isPreflighted(fileEl, file){\n let preflightedRefs = fileEl.getAttribute(PHX_PREFLIGHTED_REFS).split(\",\")\n let isPreflighted = preflightedRefs.indexOf(LiveUploader.genFileRef(file)) >= 0\n return isPreflighted && this.isActive(fileEl, file)\n }\n\n constructor(fileEl, file, view){\n this.ref = LiveUploader.genFileRef(file)\n this.fileEl = fileEl\n this.file = file\n this.view = view\n this.meta = null\n this._isCancelled = false\n this._isDone = false\n this._progress = 0\n this._lastProgressSent = -1\n this._onDone = function (){ }\n this._onElUpdated = this.onElUpdated.bind(this)\n this.fileEl.addEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated)\n }\n\n metadata(){ return this.meta }\n\n progress(progress){\n this._progress = Math.floor(progress)\n if(this._progress > this._lastProgressSent){\n if(this._progress >= 100){\n this._progress = 100\n this._lastProgressSent = 100\n this._isDone = true\n this.view.pushFileProgress(this.fileEl, this.ref, 100, () => {\n LiveUploader.untrackFile(this.fileEl, this.file)\n this._onDone()\n })\n } else {\n this._lastProgressSent = this._progress\n this.view.pushFileProgress(this.fileEl, this.ref, this._progress)\n }\n }\n }\n\n cancel(){\n this._isCancelled = true\n this._isDone = true\n this._onDone()\n }\n\n isDone(){ return this._isDone }\n\n error(reason = \"failed\"){\n this.fileEl.removeEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated)\n this.view.pushFileProgress(this.fileEl, this.ref, {error: reason})\n LiveUploader.clearFiles(this.fileEl)\n }\n\n //private\n\n onDone(callback){\n this._onDone = () => {\n this.fileEl.removeEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated)\n callback()\n }\n }\n\n onElUpdated(){\n let activeRefs = this.fileEl.getAttribute(PHX_ACTIVE_ENTRY_REFS).split(\",\")\n if(activeRefs.indexOf(this.ref) === -1){ this.cancel() }\n }\n\n toPreflightPayload(){\n return {\n last_modified: this.file.lastModified,\n name: this.file.name,\n relative_path: this.file.webkitRelativePath,\n size: this.file.size,\n type: this.file.type,\n ref: this.ref\n }\n }\n\n uploader(uploaders){\n if(this.meta.uploader){\n let callback = uploaders[this.meta.uploader] || logError(`no uploader configured for ${this.meta.uploader}`)\n return {name: this.meta.uploader, callback: callback}\n } else {\n return {name: \"channel\", callback: channelUploader}\n }\n }\n\n zipPostFlight(resp){\n this.meta = resp.entries[this.ref]\n if(!this.meta){ logError(`no preflight upload response returned with ref ${this.ref}`, {input: this.fileEl, response: resp}) }\n }\n}\n", "import {\n PHX_DONE_REFS,\n PHX_PREFLIGHTED_REFS,\n PHX_UPLOAD_REF\n} from \"./constants\"\n\nimport {\n} from \"./utils\"\n\nimport DOM from \"./dom\"\nimport UploadEntry from \"./upload_entry\"\n\nlet liveUploaderFileRef = 0\n\nexport default class LiveUploader {\n static genFileRef(file){\n let ref = file._phxRef\n if(ref !== undefined){\n return ref\n } else {\n file._phxRef = (liveUploaderFileRef++).toString()\n return file._phxRef\n }\n }\n\n static getEntryDataURL(inputEl, ref, callback){\n let file = this.activeFiles(inputEl).find(file => this.genFileRef(file) === ref)\n callback(URL.createObjectURL(file))\n }\n\n static hasUploadsInProgress(formEl){\n let active = 0\n DOM.findUploadInputs(formEl).forEach(input => {\n if(input.getAttribute(PHX_PREFLIGHTED_REFS) !== input.getAttribute(PHX_DONE_REFS)){\n active++\n }\n })\n return active > 0\n }\n\n static serializeUploads(inputEl){\n let files = this.activeFiles(inputEl)\n let fileData = {}\n files.forEach(file => {\n let entry = {path: inputEl.name}\n let uploadRef = inputEl.getAttribute(PHX_UPLOAD_REF)\n fileData[uploadRef] = fileData[uploadRef] || []\n entry.ref = this.genFileRef(file)\n entry.last_modified = file.lastModified\n entry.name = file.name || entry.ref\n entry.relative_path = file.webkitRelativePath\n entry.type = file.type\n entry.size = file.size\n fileData[uploadRef].push(entry)\n })\n return fileData\n }\n\n static clearFiles(inputEl){\n inputEl.value = null\n inputEl.removeAttribute(PHX_UPLOAD_REF)\n DOM.putPrivate(inputEl, \"files\", [])\n }\n\n static untrackFile(inputEl, file){\n DOM.putPrivate(inputEl, \"files\", DOM.private(inputEl, \"files\").filter(f => !Object.is(f, file)))\n }\n\n static trackFiles(inputEl, files, dataTransfer){\n if(inputEl.getAttribute(\"multiple\") !== null){\n let newFiles = files.filter(file => !this.activeFiles(inputEl).find(f => Object.is(f, file)))\n DOM.putPrivate(inputEl, \"files\", this.activeFiles(inputEl).concat(newFiles))\n inputEl.value = null\n } else {\n // Reset inputEl files to align output with programmatic changes (i.e. drag and drop)\n if(dataTransfer && dataTransfer.files.length > 0){ inputEl.files = dataTransfer.files }\n DOM.putPrivate(inputEl, \"files\", files)\n }\n }\n\n static activeFileInputs(formEl){\n let fileInputs = DOM.findUploadInputs(formEl)\n return Array.from(fileInputs).filter(el => el.files && this.activeFiles(el).length > 0)\n }\n\n static activeFiles(input){\n return (DOM.private(input, \"files\") || []).filter(f => UploadEntry.isActive(input, f))\n }\n\n static inputsAwaitingPreflight(formEl){\n let fileInputs = DOM.findUploadInputs(formEl)\n return Array.from(fileInputs).filter(input => this.filesAwaitingPreflight(input).length > 0)\n }\n\n static filesAwaitingPreflight(input){\n return this.activeFiles(input).filter(f => !UploadEntry.isPreflighted(input, f))\n }\n\n constructor(inputEl, view, onComplete){\n this.view = view\n this.onComplete = onComplete\n this._entries =\n Array.from(LiveUploader.filesAwaitingPreflight(inputEl) || [])\n .map(file => new UploadEntry(inputEl, file, view))\n\n this.numEntriesInProgress = this._entries.length\n }\n\n entries(){ return this._entries }\n\n initAdapterUpload(resp, onError, liveSocket){\n this._entries =\n this._entries.map(entry => {\n entry.zipPostFlight(resp)\n entry.onDone(() => {\n this.numEntriesInProgress--\n if(this.numEntriesInProgress === 0){ this.onComplete() }\n })\n return entry\n })\n\n let groupedEntries = this._entries.reduce((acc, entry) => {\n let {name, callback} = entry.uploader(liveSocket.uploaders)\n acc[name] = acc[name] || {callback: callback, entries: []}\n acc[name].entries.push(entry)\n return acc\n }, {})\n\n for(let name in groupedEntries){\n let {callback, entries} = groupedEntries[name]\n callback(entries, onError, resp, liveSocket)\n }\n }\n}\n", "let ARIA = {\n focusMain(){\n let target = document.querySelector(\"main h1, main, h1\")\n if(target){\n let origTabIndex = target.tabIndex\n target.tabIndex = -1\n target.focus()\n target.tabIndex = origTabIndex\n }\n },\n\n anyOf(instance, classes){ return classes.find(name => instance instanceof name) },\n\n isFocusable(el, interactiveOnly){\n return(\n (el instanceof HTMLAnchorElement && el.rel !== \"ignore\") ||\n (el instanceof HTMLAreaElement && el.href !== undefined) ||\n (!el.disabled && (this.anyOf(el, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, HTMLButtonElement]))) ||\n (el instanceof HTMLIFrameElement) ||\n (el.tabIndex > 0 || (!interactiveOnly && el.tabIndex === 0 && el.getAttribute(\"tabindex\") !== null && el.getAttribute(\"aria-hidden\") !== \"true\"))\n )\n },\n\n attemptFocus(el, interactiveOnly){\n if(this.isFocusable(el, interactiveOnly)){ try{ el.focus() } catch(e){} }\n return !!document.activeElement && document.activeElement.isSameNode(el)\n },\n\n focusFirstInteractive(el){\n let child = el.firstElementChild\n while(child){\n if(this.attemptFocus(child, true) || this.focusFirstInteractive(child, true)){\n return true\n }\n child = child.nextElementSibling\n }\n },\n\n focusFirst(el){\n let child = el.firstElementChild\n while(child){\n if(this.attemptFocus(child) || this.focusFirst(child)){\n return true\n }\n child = child.nextElementSibling\n }\n },\n\n focusLast(el){\n let child = el.lastElementChild\n while(child){\n if(this.attemptFocus(child) || this.focusLast(child)){\n return true\n }\n child = child.previousElementSibling\n }\n }\n}\nexport default ARIA", "import {\n PHX_ACTIVE_ENTRY_REFS,\n PHX_LIVE_FILE_UPDATED,\n PHX_PREFLIGHTED_REFS,\n PHX_UPLOAD_REF\n} from \"./constants\"\n\nimport LiveUploader from \"./live_uploader\"\nimport ARIA from \"./aria\"\n\nlet Hooks = {\n LiveFileUpload: {\n activeRefs(){ return this.el.getAttribute(PHX_ACTIVE_ENTRY_REFS) },\n\n preflightedRefs(){ return this.el.getAttribute(PHX_PREFLIGHTED_REFS) },\n\n mounted(){ this.preflightedWas = this.preflightedRefs() },\n\n updated(){\n let newPreflights = this.preflightedRefs()\n if(this.preflightedWas !== newPreflights){\n this.preflightedWas = newPreflights\n if(newPreflights === \"\"){\n this.__view.cancelSubmit(this.el.form)\n }\n }\n\n if(this.activeRefs() === \"\"){ this.el.value = null }\n this.el.dispatchEvent(new CustomEvent(PHX_LIVE_FILE_UPDATED))\n }\n },\n\n LiveImgPreview: {\n mounted(){\n this.ref = this.el.getAttribute(\"data-phx-entry-ref\")\n this.inputEl = document.getElementById(this.el.getAttribute(PHX_UPLOAD_REF))\n LiveUploader.getEntryDataURL(this.inputEl, this.ref, url => {\n this.url = url\n this.el.src = url\n })\n },\n destroyed(){\n URL.revokeObjectURL(this.url)\n }\n },\n FocusWrap: {\n mounted(){\n this.focusStart = this.el.firstElementChild\n this.focusEnd = this.el.lastElementChild\n this.focusStart.addEventListener(\"focus\", () => ARIA.focusLast(this.el))\n this.focusEnd.addEventListener(\"focus\", () => ARIA.focusFirst(this.el))\n this.el.addEventListener(\"phx:show-end\", () => this.el.focus())\n if(window.getComputedStyle(this.el).display !== \"none\"){\n ARIA.focusFirst(this.el)\n }\n }\n }\n}\n\nexport default Hooks\n", "import {\n maybe\n} from \"./utils\"\n\nimport DOM from \"./dom\"\n\nexport default class DOMPostMorphRestorer {\n constructor(containerBefore, containerAfter, updateType){\n let idsBefore = new Set()\n let idsAfter = new Set([...containerAfter.children].map(child => child.id))\n\n let elementsToModify = []\n\n Array.from(containerBefore.children).forEach(child => {\n if(child.id){ // all of our children should be elements with ids\n idsBefore.add(child.id)\n if(idsAfter.has(child.id)){\n let previousElementId = child.previousElementSibling && child.previousElementSibling.id\n elementsToModify.push({elementId: child.id, previousElementId: previousElementId})\n }\n }\n })\n\n this.containerId = containerAfter.id\n this.updateType = updateType\n this.elementsToModify = elementsToModify\n this.elementIdsToAdd = [...idsAfter].filter(id => !idsBefore.has(id))\n }\n\n // We do the following to optimize append/prepend operations:\n // 1) Track ids of modified elements & of new elements\n // 2) All the modified elements are put back in the correct position in the DOM tree\n // by storing the id of their previous sibling\n // 3) New elements are going to be put in the right place by morphdom during append.\n // For prepend, we move them to the first position in the container\n perform(){\n let container = DOM.byId(this.containerId)\n this.elementsToModify.forEach(elementToModify => {\n if(elementToModify.previousElementId){\n maybe(document.getElementById(elementToModify.previousElementId), previousElem => {\n maybe(document.getElementById(elementToModify.elementId), elem => {\n let isInRightPlace = elem.previousElementSibling && elem.previousElementSibling.id == previousElem.id\n if(!isInRightPlace){\n previousElem.insertAdjacentElement(\"afterend\", elem)\n }\n })\n })\n } else {\n // This is the first element in the container\n maybe(document.getElementById(elementToModify.elementId), elem => {\n let isInRightPlace = elem.previousElementSibling == null\n if(!isInRightPlace){\n container.insertAdjacentElement(\"afterbegin\", elem)\n }\n })\n }\n })\n\n if(this.updateType == \"prepend\"){\n this.elementIdsToAdd.reverse().forEach(elemId => {\n maybe(document.getElementById(elemId), elem => container.insertAdjacentElement(\"afterbegin\", elem))\n })\n }\n }\n}\n", "var DOCUMENT_FRAGMENT_NODE = 11;\n\nfunction morphAttrs(fromNode, toNode) {\n var toNodeAttrs = toNode.attributes;\n var attr;\n var attrName;\n var attrNamespaceURI;\n var attrValue;\n var fromValue;\n\n // document-fragments dont have attributes so lets not do anything\n if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE || fromNode.nodeType === DOCUMENT_FRAGMENT_NODE) {\n return;\n }\n\n // update attributes on original DOM element\n for (var i = toNodeAttrs.length - 1; i >= 0; i--) {\n attr = toNodeAttrs[i];\n attrName = attr.name;\n attrNamespaceURI = attr.namespaceURI;\n attrValue = attr.value;\n\n if (attrNamespaceURI) {\n attrName = attr.localName || attrName;\n fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName);\n\n if (fromValue !== attrValue) {\n if (attr.prefix === 'xmlns'){\n attrName = attr.name; // It's not allowed to set an attribute with the XMLNS namespace without specifying the `xmlns` prefix\n }\n fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue);\n }\n } else {\n fromValue = fromNode.getAttribute(attrName);\n\n if (fromValue !== attrValue) {\n fromNode.setAttribute(attrName, attrValue);\n }\n }\n }\n\n // Remove any extra attributes found on the original DOM element that\n // weren't found on the target element.\n var fromNodeAttrs = fromNode.attributes;\n\n for (var d = fromNodeAttrs.length - 1; d >= 0; d--) {\n attr = fromNodeAttrs[d];\n attrName = attr.name;\n attrNamespaceURI = attr.namespaceURI;\n\n if (attrNamespaceURI) {\n attrName = attr.localName || attrName;\n\n if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) {\n fromNode.removeAttributeNS(attrNamespaceURI, attrName);\n }\n } else {\n if (!toNode.hasAttribute(attrName)) {\n fromNode.removeAttribute(attrName);\n }\n }\n }\n}\n\nvar range; // Create a range object for efficently rendering strings to elements.\nvar NS_XHTML = 'http://www.w3.org/1999/xhtml';\n\nvar doc = typeof document === 'undefined' ? undefined : document;\nvar HAS_TEMPLATE_SUPPORT = !!doc && 'content' in doc.createElement('template');\nvar HAS_RANGE_SUPPORT = !!doc && doc.createRange && 'createContextualFragment' in doc.createRange();\n\nfunction createFragmentFromTemplate(str) {\n var template = doc.createElement('template');\n template.innerHTML = str;\n return template.content.childNodes[0];\n}\n\nfunction createFragmentFromRange(str) {\n if (!range) {\n range = doc.createRange();\n range.selectNode(doc.body);\n }\n\n var fragment = range.createContextualFragment(str);\n return fragment.childNodes[0];\n}\n\nfunction createFragmentFromWrap(str) {\n var fragment = doc.createElement('body');\n fragment.innerHTML = str;\n return fragment.childNodes[0];\n}\n\n/**\n * This is about the same\n * var html = new DOMParser().parseFromString(str, 'text/html');\n * return html.body.firstChild;\n *\n * @method toElement\n * @param {String} str\n */\nfunction toElement(str) {\n str = str.trim();\n if (HAS_TEMPLATE_SUPPORT) {\n // avoid restrictions on content for things like `Hi ` which\n // createContextualFragment doesn't support\n // support not available in IE\n return createFragmentFromTemplate(str);\n } else if (HAS_RANGE_SUPPORT) {\n return createFragmentFromRange(str);\n }\n\n return createFragmentFromWrap(str);\n}\n\n/**\n * Returns true if two node's names are the same.\n *\n * NOTE: We don't bother checking `namespaceURI` because you will never find two HTML elements with the same\n * nodeName and different namespace URIs.\n *\n * @param {Element} a\n * @param {Element} b The target element\n * @return {boolean}\n */\nfunction compareNodeNames(fromEl, toEl) {\n var fromNodeName = fromEl.nodeName;\n var toNodeName = toEl.nodeName;\n var fromCodeStart, toCodeStart;\n\n if (fromNodeName === toNodeName) {\n return true;\n }\n\n fromCodeStart = fromNodeName.charCodeAt(0);\n toCodeStart = toNodeName.charCodeAt(0);\n\n // If the target element is a virtual DOM node or SVG node then we may\n // need to normalize the tag name before comparing. Normal HTML elements that are\n // in the \"http://www.w3.org/1999/xhtml\"\n // are converted to upper case\n if (fromCodeStart <= 90 && toCodeStart >= 97) { // from is upper and to is lower\n return fromNodeName === toNodeName.toUpperCase();\n } else if (toCodeStart <= 90 && fromCodeStart >= 97) { // to is upper and from is lower\n return toNodeName === fromNodeName.toUpperCase();\n } else {\n return false;\n }\n}\n\n/**\n * Create an element, optionally with a known namespace URI.\n *\n * @param {string} name the element name, e.g. 'div' or 'svg'\n * @param {string} [namespaceURI] the element's namespace URI, i.e. the value of\n * its `xmlns` attribute or its inferred namespace.\n *\n * @return {Element}\n */\nfunction createElementNS(name, namespaceURI) {\n return !namespaceURI || namespaceURI === NS_XHTML ?\n doc.createElement(name) :\n doc.createElementNS(namespaceURI, name);\n}\n\n/**\n * Copies the children of one DOM element to another DOM element\n */\nfunction moveChildren(fromEl, toEl) {\n var curChild = fromEl.firstChild;\n while (curChild) {\n var nextChild = curChild.nextSibling;\n toEl.appendChild(curChild);\n curChild = nextChild;\n }\n return toEl;\n}\n\nfunction syncBooleanAttrProp(fromEl, toEl, name) {\n if (fromEl[name] !== toEl[name]) {\n fromEl[name] = toEl[name];\n if (fromEl[name]) {\n fromEl.setAttribute(name, '');\n } else {\n fromEl.removeAttribute(name);\n }\n }\n}\n\nvar specialElHandlers = {\n OPTION: function(fromEl, toEl) {\n var parentNode = fromEl.parentNode;\n if (parentNode) {\n var parentName = parentNode.nodeName.toUpperCase();\n if (parentName === 'OPTGROUP') {\n parentNode = parentNode.parentNode;\n parentName = parentNode && parentNode.nodeName.toUpperCase();\n }\n if (parentName === 'SELECT' && !parentNode.hasAttribute('multiple')) {\n if (fromEl.hasAttribute('selected') && !toEl.selected) {\n // Workaround for MS Edge bug where the 'selected' attribute can only be\n // removed if set to a non-empty value:\n // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12087679/\n fromEl.setAttribute('selected', 'selected');\n fromEl.removeAttribute('selected');\n }\n // We have to reset select element's selectedIndex to -1, otherwise setting\n // fromEl.selected using the syncBooleanAttrProp below has no effect.\n // The correct selectedIndex will be set in the SELECT special handler below.\n parentNode.selectedIndex = -1;\n }\n }\n syncBooleanAttrProp(fromEl, toEl, 'selected');\n },\n /**\n * The \"value\" attribute is special for the element since it sets\n * the initial value. Changing the \"value\" attribute without changing the\n * \"value\" property will have no effect since it is only used to the set the\n * initial value. Similar for the \"checked\" attribute, and \"disabled\".\n */\n INPUT: function(fromEl, toEl) {\n syncBooleanAttrProp(fromEl, toEl, 'checked');\n syncBooleanAttrProp(fromEl, toEl, 'disabled');\n\n if (fromEl.value !== toEl.value) {\n fromEl.value = toEl.value;\n }\n\n if (!toEl.hasAttribute('value')) {\n fromEl.removeAttribute('value');\n }\n },\n\n TEXTAREA: function(fromEl, toEl) {\n var newValue = toEl.value;\n if (fromEl.value !== newValue) {\n fromEl.value = newValue;\n }\n\n var firstChild = fromEl.firstChild;\n if (firstChild) {\n // Needed for IE. Apparently IE sets the placeholder as the\n // node value and vise versa. This ignores an empty update.\n var oldValue = firstChild.nodeValue;\n\n if (oldValue == newValue || (!newValue && oldValue == fromEl.placeholder)) {\n return;\n }\n\n firstChild.nodeValue = newValue;\n }\n },\n SELECT: function(fromEl, toEl) {\n if (!toEl.hasAttribute('multiple')) {\n var selectedIndex = -1;\n var i = 0;\n // We have to loop through children of fromEl, not toEl since nodes can be moved\n // from toEl to fromEl directly when morphing.\n // At the time this special handler is invoked, all children have already been morphed\n // and appended to / removed from fromEl, so using fromEl here is safe and correct.\n var curChild = fromEl.firstChild;\n var optgroup;\n var nodeName;\n while(curChild) {\n nodeName = curChild.nodeName && curChild.nodeName.toUpperCase();\n if (nodeName === 'OPTGROUP') {\n optgroup = curChild;\n curChild = optgroup.firstChild;\n } else {\n if (nodeName === 'OPTION') {\n if (curChild.hasAttribute('selected')) {\n selectedIndex = i;\n break;\n }\n i++;\n }\n curChild = curChild.nextSibling;\n if (!curChild && optgroup) {\n curChild = optgroup.nextSibling;\n optgroup = null;\n }\n }\n }\n\n fromEl.selectedIndex = selectedIndex;\n }\n }\n};\n\nvar ELEMENT_NODE = 1;\nvar DOCUMENT_FRAGMENT_NODE$1 = 11;\nvar TEXT_NODE = 3;\nvar COMMENT_NODE = 8;\n\nfunction noop() {}\n\nfunction defaultGetNodeKey(node) {\n if (node) {\n return (node.getAttribute && node.getAttribute('id')) || node.id;\n }\n}\n\nfunction morphdomFactory(morphAttrs) {\n\n return function morphdom(fromNode, toNode, options) {\n if (!options) {\n options = {};\n }\n\n if (typeof toNode === 'string') {\n if (fromNode.nodeName === '#document' || fromNode.nodeName === 'HTML' || fromNode.nodeName === 'BODY') {\n var toNodeHtml = toNode;\n toNode = doc.createElement('html');\n toNode.innerHTML = toNodeHtml;\n } else {\n toNode = toElement(toNode);\n }\n } else if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE$1) {\n toNode = toNode.firstElementChild;\n }\n\n var getNodeKey = options.getNodeKey || defaultGetNodeKey;\n var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;\n var onNodeAdded = options.onNodeAdded || noop;\n var onBeforeElUpdated = options.onBeforeElUpdated || noop;\n var onElUpdated = options.onElUpdated || noop;\n var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;\n var onNodeDiscarded = options.onNodeDiscarded || noop;\n var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;\n var skipFromChildren = options.skipFromChildren || noop;\n var addChild = options.addChild || function(parent, child){ return parent.appendChild(child); };\n var childrenOnly = options.childrenOnly === true;\n\n // This object is used as a lookup to quickly find all keyed elements in the original DOM tree.\n var fromNodesLookup = Object.create(null);\n var keyedRemovalList = [];\n\n function addKeyedRemoval(key) {\n keyedRemovalList.push(key);\n }\n\n function walkDiscardedChildNodes(node, skipKeyedNodes) {\n if (node.nodeType === ELEMENT_NODE) {\n var curChild = node.firstChild;\n while (curChild) {\n\n var key = undefined;\n\n if (skipKeyedNodes && (key = getNodeKey(curChild))) {\n // If we are skipping keyed nodes then we add the key\n // to a list so that it can be handled at the very end.\n addKeyedRemoval(key);\n } else {\n // Only report the node as discarded if it is not keyed. We do this because\n // at the end we loop through all keyed elements that were unmatched\n // and then discard them in one final pass.\n onNodeDiscarded(curChild);\n if (curChild.firstChild) {\n walkDiscardedChildNodes(curChild, skipKeyedNodes);\n }\n }\n\n curChild = curChild.nextSibling;\n }\n }\n }\n\n /**\n * Removes a DOM node out of the original DOM\n *\n * @param {Node} node The node to remove\n * @param {Node} parentNode The nodes parent\n * @param {Boolean} skipKeyedNodes If true then elements with keys will be skipped and not discarded.\n * @return {undefined}\n */\n function removeNode(node, parentNode, skipKeyedNodes) {\n if (onBeforeNodeDiscarded(node) === false) {\n return;\n }\n\n if (parentNode) {\n parentNode.removeChild(node);\n }\n\n onNodeDiscarded(node);\n walkDiscardedChildNodes(node, skipKeyedNodes);\n }\n\n // // TreeWalker implementation is no faster, but keeping this around in case this changes in the future\n // function indexTree(root) {\n // var treeWalker = document.createTreeWalker(\n // root,\n // NodeFilter.SHOW_ELEMENT);\n //\n // var el;\n // while((el = treeWalker.nextNode())) {\n // var key = getNodeKey(el);\n // if (key) {\n // fromNodesLookup[key] = el;\n // }\n // }\n // }\n\n // // NodeIterator implementation is no faster, but keeping this around in case this changes in the future\n //\n // function indexTree(node) {\n // var nodeIterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT);\n // var el;\n // while((el = nodeIterator.nextNode())) {\n // var key = getNodeKey(el);\n // if (key) {\n // fromNodesLookup[key] = el;\n // }\n // }\n // }\n\n function indexTree(node) {\n if (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE$1) {\n var curChild = node.firstChild;\n while (curChild) {\n var key = getNodeKey(curChild);\n if (key) {\n fromNodesLookup[key] = curChild;\n }\n\n // Walk recursively\n indexTree(curChild);\n\n curChild = curChild.nextSibling;\n }\n }\n }\n\n indexTree(fromNode);\n\n function handleNodeAdded(el) {\n onNodeAdded(el);\n\n var curChild = el.firstChild;\n while (curChild) {\n var nextSibling = curChild.nextSibling;\n\n var key = getNodeKey(curChild);\n if (key) {\n var unmatchedFromEl = fromNodesLookup[key];\n // if we find a duplicate #id node in cache, replace `el` with cache value\n // and morph it to the child node.\n if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) {\n curChild.parentNode.replaceChild(unmatchedFromEl, curChild);\n morphEl(unmatchedFromEl, curChild);\n } else {\n handleNodeAdded(curChild);\n }\n } else {\n // recursively call for curChild and it's children to see if we find something in\n // fromNodesLookup\n handleNodeAdded(curChild);\n }\n\n curChild = nextSibling;\n }\n }\n\n function cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey) {\n // We have processed all of the \"to nodes\". If curFromNodeChild is\n // non-null then we still have some from nodes left over that need\n // to be removed\n while (curFromNodeChild) {\n var fromNextSibling = curFromNodeChild.nextSibling;\n if ((curFromNodeKey = getNodeKey(curFromNodeChild))) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n curFromNodeChild = fromNextSibling;\n }\n }\n\n function morphEl(fromEl, toEl, childrenOnly) {\n var toElKey = getNodeKey(toEl);\n\n if (toElKey) {\n // If an element with an ID is being morphed then it will be in the final\n // DOM so clear it out of the saved elements collection\n delete fromNodesLookup[toElKey];\n }\n\n if (!childrenOnly) {\n // optional\n if (onBeforeElUpdated(fromEl, toEl) === false) {\n return;\n }\n\n // update attributes on original DOM element first\n morphAttrs(fromEl, toEl);\n // optional\n onElUpdated(fromEl);\n\n if (onBeforeElChildrenUpdated(fromEl, toEl) === false) {\n return;\n }\n }\n\n if (fromEl.nodeName !== 'TEXTAREA') {\n morphChildren(fromEl, toEl);\n } else {\n specialElHandlers.TEXTAREA(fromEl, toEl);\n }\n }\n\n function morphChildren(fromEl, toEl) {\n var skipFrom = skipFromChildren(fromEl);\n var curToNodeChild = toEl.firstChild;\n var curFromNodeChild = fromEl.firstChild;\n var curToNodeKey;\n var curFromNodeKey;\n\n var fromNextSibling;\n var toNextSibling;\n var matchingFromEl;\n\n // walk the children\n outer: while (curToNodeChild) {\n toNextSibling = curToNodeChild.nextSibling;\n curToNodeKey = getNodeKey(curToNodeChild);\n\n // walk the fromNode children all the way through\n while (!skipFrom && curFromNodeChild) {\n fromNextSibling = curFromNodeChild.nextSibling;\n\n if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n continue outer;\n }\n\n curFromNodeKey = getNodeKey(curFromNodeChild);\n\n var curFromNodeType = curFromNodeChild.nodeType;\n\n // this means if the curFromNodeChild doesnt have a match with the curToNodeChild\n var isCompatible = undefined;\n\n if (curFromNodeType === curToNodeChild.nodeType) {\n if (curFromNodeType === ELEMENT_NODE) {\n // Both nodes being compared are Element nodes\n\n if (curToNodeKey) {\n // The target node has a key so we want to match it up with the correct element\n // in the original DOM tree\n if (curToNodeKey !== curFromNodeKey) {\n // The current element in the original DOM tree does not have a matching key so\n // let's check our lookup to see if there is a matching element in the original\n // DOM tree\n if ((matchingFromEl = fromNodesLookup[curToNodeKey])) {\n if (fromNextSibling === matchingFromEl) {\n // Special case for single element removals. To avoid removing the original\n // DOM node out of the tree (since that can break CSS transitions, etc.),\n // we will instead discard the current node and wait until the next\n // iteration to properly match up the keyed target element with its matching\n // element in the original tree\n isCompatible = false;\n } else {\n // We found a matching keyed element somewhere in the original DOM tree.\n // Let's move the original DOM node into the current position and morph\n // it.\n\n // NOTE: We use insertBefore instead of replaceChild because we want to go through\n // the `removeNode()` function for the node that is being discarded so that\n // all lifecycle hooks are correctly invoked\n fromEl.insertBefore(matchingFromEl, curFromNodeChild);\n\n // fromNextSibling = curFromNodeChild.nextSibling;\n\n if (curFromNodeKey) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n\n curFromNodeChild = matchingFromEl;\n }\n } else {\n // The nodes are not compatible since the \"to\" node has a key and there\n // is no matching keyed node in the source tree\n isCompatible = false;\n }\n }\n } else if (curFromNodeKey) {\n // The original has a key\n isCompatible = false;\n }\n\n isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild);\n if (isCompatible) {\n // We found compatible DOM elements so transform\n // the current \"from\" node to match the current\n // target DOM node.\n // MORPH\n morphEl(curFromNodeChild, curToNodeChild);\n }\n\n } else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) {\n // Both nodes being compared are Text or Comment nodes\n isCompatible = true;\n // Simply update nodeValue on the original node to\n // change the text value\n if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) {\n curFromNodeChild.nodeValue = curToNodeChild.nodeValue;\n }\n\n }\n }\n\n if (isCompatible) {\n // Advance both the \"to\" child and the \"from\" child since we found a match\n // Nothing else to do as we already recursively called morphChildren above\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n continue outer;\n }\n\n // No compatible match so remove the old node from the DOM and continue trying to find a\n // match in the original DOM. However, we only do this if the from node is not keyed\n // since it is possible that a keyed node might match up with a node somewhere else in the\n // target tree and we don't want to discard it just yet since it still might find a\n // home in the final DOM tree. After everything is done we will remove any keyed nodes\n // that didn't find a home\n if (curFromNodeKey) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n\n curFromNodeChild = fromNextSibling;\n } // END: while(curFromNodeChild) {}\n\n // If we got this far then we did not find a candidate match for\n // our \"to node\" and we exhausted all of the children \"from\"\n // nodes. Therefore, we will just append the current \"to\" node\n // to the end\n if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {\n // MORPH\n if(!skipFrom){ addChild(fromEl, matchingFromEl); }\n morphEl(matchingFromEl, curToNodeChild);\n } else {\n var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);\n if (onBeforeNodeAddedResult !== false) {\n if (onBeforeNodeAddedResult) {\n curToNodeChild = onBeforeNodeAddedResult;\n }\n\n if (curToNodeChild.actualize) {\n curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);\n }\n addChild(fromEl, curToNodeChild);\n handleNodeAdded(curToNodeChild);\n }\n }\n\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n }\n\n cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey);\n\n var specialElHandler = specialElHandlers[fromEl.nodeName];\n if (specialElHandler) {\n specialElHandler(fromEl, toEl);\n }\n } // END: morphChildren(...)\n\n var morphedNode = fromNode;\n var morphedNodeType = morphedNode.nodeType;\n var toNodeType = toNode.nodeType;\n\n if (!childrenOnly) {\n // Handle the case where we are given two DOM nodes that are not\n // compatible (e.g. -->
or --> TEXT)\n if (morphedNodeType === ELEMENT_NODE) {\n if (toNodeType === ELEMENT_NODE) {\n if (!compareNodeNames(fromNode, toNode)) {\n onNodeDiscarded(fromNode);\n morphedNode = moveChildren(fromNode, createElementNS(toNode.nodeName, toNode.namespaceURI));\n }\n } else {\n // Going from an element node to a text node\n morphedNode = toNode;\n }\n } else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node\n if (toNodeType === morphedNodeType) {\n if (morphedNode.nodeValue !== toNode.nodeValue) {\n morphedNode.nodeValue = toNode.nodeValue;\n }\n\n return morphedNode;\n } else {\n // Text node to something else\n morphedNode = toNode;\n }\n }\n }\n\n if (morphedNode === toNode) {\n // The \"to node\" was not compatible with the \"from node\" so we had to\n // toss out the \"from node\" and use the \"to node\"\n onNodeDiscarded(fromNode);\n } else {\n if (toNode.isSameNode && toNode.isSameNode(morphedNode)) {\n return;\n }\n\n morphEl(morphedNode, toNode, childrenOnly);\n\n // We now need to loop over any keyed nodes that might need to be\n // removed. We only do the removal if we know that the keyed node\n // never found a match. When a keyed node is matched up we remove\n // it out of fromNodesLookup and we use fromNodesLookup to determine\n // if a keyed node has been matched up or not\n if (keyedRemovalList) {\n for (var i=0, len=keyedRemovalList.length; i
{\n if(activeElement && activeElement.isSameNode(fromEl) && DOM.isFormInput(fromEl)){\n DOM.mergeFocusedInput(fromEl, toEl)\n return false\n }\n }\n })\n }\n\n constructor(view, container, id, html, streams, targetCID){\n this.view = view\n this.liveSocket = view.liveSocket\n this.container = container\n this.id = id\n this.rootID = view.root.id\n this.html = html\n this.streams = streams\n this.streamInserts = {}\n this.targetCID = targetCID\n this.cidPatch = isCid(this.targetCID)\n this.pendingRemoves = []\n this.phxRemove = this.liveSocket.binding(\"remove\")\n this.callbacks = {\n beforeadded: [], beforeupdated: [], beforephxChildAdded: [],\n afteradded: [], afterupdated: [], afterdiscarded: [], afterphxChildAdded: [],\n aftertransitionsDiscarded: []\n }\n }\n\n before(kind, callback){ this.callbacks[`before${kind}`].push(callback) }\n after(kind, callback){ this.callbacks[`after${kind}`].push(callback) }\n\n trackBefore(kind, ...args){\n this.callbacks[`before${kind}`].forEach(callback => callback(...args))\n }\n\n trackAfter(kind, ...args){\n this.callbacks[`after${kind}`].forEach(callback => callback(...args))\n }\n\n markPrunableContentForRemoval(){\n let phxUpdate = this.liveSocket.binding(PHX_UPDATE)\n DOM.all(this.container, `[${phxUpdate}=${PHX_STREAM}]`, el => el.innerHTML = \"\")\n DOM.all(this.container, `[${phxUpdate}=append] > *, [${phxUpdate}=prepend] > *`, el => {\n el.setAttribute(PHX_PRUNE, \"\")\n })\n }\n\n perform(){\n let {view, liveSocket, container, html} = this\n let targetContainer = this.isCIDPatch() ? this.targetCIDContainer(html) : container\n if(this.isCIDPatch() && !targetContainer){ return }\n\n let focused = liveSocket.getActiveElement()\n let {selectionStart, selectionEnd} = focused && DOM.hasSelectionRange(focused) ? focused : {}\n let phxUpdate = liveSocket.binding(PHX_UPDATE)\n let phxFeedbackFor = liveSocket.binding(PHX_FEEDBACK_FOR)\n let disableWith = liveSocket.binding(PHX_DISABLE_WITH)\n let phxTriggerExternal = liveSocket.binding(PHX_TRIGGER_ACTION)\n let added = []\n let updates = []\n let appendPrependUpdates = []\n\n let externalFormTriggered = null\n\n let diffHTML = liveSocket.time(\"premorph container prep\", () => {\n return this.buildDiffHTML(container, html, phxUpdate, targetContainer)\n })\n\n this.trackBefore(\"added\", container)\n this.trackBefore(\"updated\", container, container)\n\n liveSocket.time(\"morphdom\", () => {\n\n this.streams.forEach(([inserts, deleteIds]) => {\n this.streamInserts = Object.assign(this.streamInserts, inserts)\n deleteIds.forEach(id => {\n let child = container.querySelector(`[id=\"${id}\"]`)\n if(child){\n if(!this.maybePendingRemove(child)){\n child.remove()\n this.onNodeDiscarded(child)\n }\n }\n })\n })\n\n morphdom(targetContainer, diffHTML, {\n childrenOnly: targetContainer.getAttribute(PHX_COMPONENT) === null,\n getNodeKey: (node) => {\n return DOM.isPhxDestroyed(node) ? null : node.id\n },\n // skip indexing from children when container is stream\n skipFromChildren: (from) => { return from.getAttribute(phxUpdate) === PHX_STREAM },\n // tell morphdom how to add a child\n addChild: (parent, child) => {\n let streamAt = child.id ? this.streamInserts[child.id] : undefined\n if(streamAt === undefined) { return parent.appendChild(child) }\n\n //streaming\n DOM.putPrivate(child, PHX_STREAM, true)\n if(streamAt === 0){\n parent.insertAdjacentElement(\"afterbegin\", child)\n } else if(streamAt === -1){\n parent.appendChild(child)\n } else if(streamAt > 0){\n let sibling = Array.from(parent.children)[streamAt]\n parent.insertBefore(child, sibling)\n }\n },\n onBeforeNodeAdded: (el) => {\n this.trackBefore(\"added\", el)\n return el\n },\n onNodeAdded: (el) => {\n // hack to fix Safari handling of img srcset and video tags\n if(el instanceof HTMLImageElement && el.srcset){\n el.srcset = el.srcset\n } else if(el instanceof HTMLVideoElement && el.autoplay){\n el.play()\n }\n if(DOM.isNowTriggerFormExternal(el, phxTriggerExternal)){\n externalFormTriggered = el\n }\n //input handling\n DOM.discardError(targetContainer, el, phxFeedbackFor)\n // nested view handling\n if((DOM.isPhxChild(el) && view.ownsElement(el)) || DOM.isPhxSticky(el) && view.ownsElement(el.parentNode)){\n this.trackAfter(\"phxChildAdded\", el)\n }\n added.push(el)\n },\n onNodeDiscarded: (el) => this.onNodeDiscarded(el),\n onBeforeNodeDiscarded: (el) => {\n if(el.getAttribute && el.getAttribute(PHX_PRUNE) !== null){ return true }\n if(DOM.private(el, PHX_STREAM)){ return false }\n if(el.parentElement !== null && DOM.isPhxUpdate(el.parentElement, phxUpdate, [\"append\", \"prepend\"]) && el.id){ return false }\n if(this.maybePendingRemove(el)){ return false }\n if(this.skipCIDSibling(el)){ return false }\n return true\n },\n onElUpdated: (el) => {\n if(DOM.isNowTriggerFormExternal(el, phxTriggerExternal)){\n externalFormTriggered = el\n }\n updates.push(el)\n this.maybeReOrderStream(el)\n },\n onBeforeElUpdated: (fromEl, toEl) => {\n DOM.cleanChildNodes(toEl, phxUpdate)\n if(this.skipCIDSibling(toEl)){ return false }\n if(DOM.isPhxSticky(fromEl)){ return false }\n if(DOM.isIgnored(fromEl, phxUpdate) || (fromEl.form && fromEl.form.isSameNode(externalFormTriggered))){\n this.trackBefore(\"updated\", fromEl, toEl)\n DOM.mergeAttrs(fromEl, toEl, {isIgnored: true})\n updates.push(fromEl)\n DOM.applyStickyOperations(fromEl)\n return false\n }\n if(fromEl.type === \"number\" && (fromEl.validity && fromEl.validity.badInput)){ return false }\n if(!DOM.syncPendingRef(fromEl, toEl, disableWith)){\n if(DOM.isUploadInput(fromEl)){\n this.trackBefore(\"updated\", fromEl, toEl)\n updates.push(fromEl)\n }\n DOM.applyStickyOperations(fromEl)\n return false\n }\n\n // nested view handling\n if(DOM.isPhxChild(toEl)){\n let prevSession = fromEl.getAttribute(PHX_SESSION)\n DOM.mergeAttrs(fromEl, toEl, {exclude: [PHX_STATIC]})\n if(prevSession !== \"\"){ fromEl.setAttribute(PHX_SESSION, prevSession) }\n fromEl.setAttribute(PHX_ROOT_ID, this.rootID)\n DOM.applyStickyOperations(fromEl)\n return false\n }\n\n // input handling\n DOM.copyPrivates(toEl, fromEl)\n DOM.discardError(targetContainer, toEl, phxFeedbackFor)\n\n let isFocusedFormEl = focused && fromEl.isSameNode(focused) && DOM.isFormInput(fromEl)\n if(isFocusedFormEl && fromEl.type !== \"hidden\"){\n this.trackBefore(\"updated\", fromEl, toEl)\n DOM.mergeFocusedInput(fromEl, toEl)\n DOM.syncAttrsToProps(fromEl)\n updates.push(fromEl)\n DOM.applyStickyOperations(fromEl)\n return false\n } else {\n if(DOM.isPhxUpdate(toEl, phxUpdate, [\"append\", \"prepend\"])){\n appendPrependUpdates.push(new DOMPostMorphRestorer(fromEl, toEl, toEl.getAttribute(phxUpdate)))\n }\n DOM.syncAttrsToProps(toEl)\n DOM.applyStickyOperations(toEl)\n this.trackBefore(\"updated\", fromEl, toEl)\n return true\n }\n }\n })\n })\n\n if(liveSocket.isDebugEnabled()){ detectDuplicateIds() }\n\n if(appendPrependUpdates.length > 0){\n liveSocket.time(\"post-morph append/prepend restoration\", () => {\n appendPrependUpdates.forEach(update => update.perform())\n })\n }\n\n liveSocket.silenceEvents(() => DOM.restoreFocus(focused, selectionStart, selectionEnd))\n DOM.dispatchEvent(document, \"phx:update\")\n added.forEach(el => this.trackAfter(\"added\", el))\n updates.forEach(el => this.trackAfter(\"updated\", el))\n\n this.transitionPendingRemoves()\n\n if(externalFormTriggered){\n liveSocket.unload()\n externalFormTriggered.submit()\n }\n return true\n }\n\n onNodeDiscarded(el){\n // nested view handling\n if(DOM.isPhxChild(el) || DOM.isPhxSticky(el)){ this.liveSocket.destroyViewByEl(el) }\n this.trackAfter(\"discarded\", el)\n }\n\n maybePendingRemove(node){\n if(node.getAttribute && node.getAttribute(this.phxRemove) !== null){\n this.pendingRemoves.push(node)\n return true\n } else {\n return false\n }\n }\n\n maybeReOrderStream(el){\n let streamAt = el.id ? this.streamInserts[el.id] : undefined\n if(streamAt === undefined){ return }\n\n DOM.putPrivate(el, PHX_STREAM, true)\n if(streamAt === 0){\n el.parentElement.insertBefore(el, el.parentElement.firstElementChild)\n } else if(streamAt > 0){\n let children = Array.from(el.parentElement.children)\n let oldIndex = children.indexOf(el)\n if(streamAt >= children.length - 1){\n el.parentElement.appendChild(el)\n } else {\n let sibling = children[streamAt]\n if(oldIndex > streamAt){\n el.parentElement.insertBefore(el, sibling)\n } else {\n el.parentElement.insertBefore(el, sibling.nextElementSibling)\n }\n }\n }\n }\n\n transitionPendingRemoves(){\n let {pendingRemoves, liveSocket} = this\n if(pendingRemoves.length > 0){\n liveSocket.transitionRemoves(pendingRemoves)\n liveSocket.requestDOMUpdate(() => {\n pendingRemoves.forEach(el => {\n let child = DOM.firstPhxChild(el)\n if(child){ liveSocket.destroyViewByEl(child) }\n el.remove()\n })\n this.trackAfter(\"transitionsDiscarded\", pendingRemoves)\n })\n }\n }\n\n isCIDPatch(){ return this.cidPatch }\n\n skipCIDSibling(el){\n return el.nodeType === Node.ELEMENT_NODE && el.getAttribute(PHX_SKIP) !== null\n }\n\n targetCIDContainer(html){\n if(!this.isCIDPatch()){ return }\n let [first, ...rest] = DOM.findComponentNodeList(this.container, this.targetCID)\n if(rest.length === 0 && DOM.childNodeLength(html) === 1){\n return first\n } else {\n return first && first.parentNode\n }\n }\n\n // builds HTML for morphdom patch\n // - for full patches of LiveView or a component with a single\n // root node, simply returns the HTML\n // - for patches of a component with multiple root nodes, the\n // parent node becomes the target container and non-component\n // siblings are marked as skip.\n buildDiffHTML(container, html, phxUpdate, targetContainer){\n let isCIDPatch = this.isCIDPatch()\n let isCIDWithSingleRoot = isCIDPatch && targetContainer.getAttribute(PHX_COMPONENT) === this.targetCID.toString()\n if(!isCIDPatch || isCIDWithSingleRoot){\n return html\n } else {\n // component patch with multiple CID roots\n let diffContainer = null\n let template = document.createElement(\"template\")\n diffContainer = DOM.cloneNode(targetContainer)\n let [firstComponent, ...rest] = DOM.findComponentNodeList(diffContainer, this.targetCID)\n template.innerHTML = html\n rest.forEach(el => el.remove())\n Array.from(diffContainer.childNodes).forEach(child => {\n // we can only skip trackable nodes with an ID\n if(child.id && child.nodeType === Node.ELEMENT_NODE && child.getAttribute(PHX_COMPONENT) !== this.targetCID.toString()){\n child.setAttribute(PHX_SKIP, \"\")\n child.innerHTML = \"\"\n }\n })\n Array.from(template.content.childNodes).forEach(el => diffContainer.insertBefore(el, firstComponent))\n firstComponent.remove()\n return diffContainer.outerHTML\n }\n }\n\n indexOf(parent, child){ return Array.from(parent.children).indexOf(child) }\n}\n", "import {\n COMPONENTS,\n DYNAMICS,\n TEMPLATES,\n EVENTS,\n PHX_COMPONENT,\n PHX_SKIP,\n REPLY,\n STATIC,\n TITLE,\n STREAM,\n} from \"./constants\"\n\nimport {\n isObject,\n logError,\n isCid,\n} from \"./utils\"\n\nexport default class Rendered {\n static extract(diff){\n let {[REPLY]: reply, [EVENTS]: events, [TITLE]: title} = diff\n delete diff[REPLY]\n delete diff[EVENTS]\n delete diff[TITLE]\n return {diff, title, reply: reply || null, events: events || []}\n }\n\n constructor(viewId, rendered){\n this.viewId = viewId\n this.rendered = {}\n this.mergeDiff(rendered)\n }\n\n parentViewId(){ return this.viewId }\n\n toString(onlyCids){\n let [str, streams] = this.recursiveToString(this.rendered, this.rendered[COMPONENTS], onlyCids)\n return [str, streams]\n }\n\n recursiveToString(rendered, components = rendered[COMPONENTS], onlyCids){\n onlyCids = onlyCids ? new Set(onlyCids) : null\n let output = {buffer: \"\", components: components, onlyCids: onlyCids, streams: new Set()}\n this.toOutputBuffer(rendered, null, output)\n return [output.buffer, output.streams]\n }\n\n componentCIDs(diff){ return Object.keys(diff[COMPONENTS] || {}).map(i => parseInt(i)) }\n\n isComponentOnlyDiff(diff){\n if(!diff[COMPONENTS]){ return false }\n return Object.keys(diff).length === 1\n }\n\n getComponent(diff, cid){ return diff[COMPONENTS][cid] }\n\n mergeDiff(diff){\n let newc = diff[COMPONENTS]\n let cache = {}\n delete diff[COMPONENTS]\n this.rendered = this.mutableMerge(this.rendered, diff)\n this.rendered[COMPONENTS] = this.rendered[COMPONENTS] || {}\n\n if(newc){\n let oldc = this.rendered[COMPONENTS]\n\n for(let cid in newc){\n newc[cid] = this.cachedFindComponent(cid, newc[cid], oldc, newc, cache)\n }\n\n for(let cid in newc){ oldc[cid] = newc[cid] }\n diff[COMPONENTS] = newc\n }\n }\n\n cachedFindComponent(cid, cdiff, oldc, newc, cache){\n if(cache[cid]){\n return cache[cid]\n } else {\n let ndiff, stat, scid = cdiff[STATIC]\n\n if(isCid(scid)){\n let tdiff\n\n if(scid > 0){\n tdiff = this.cachedFindComponent(scid, newc[scid], oldc, newc, cache)\n } else {\n tdiff = oldc[-scid]\n }\n\n stat = tdiff[STATIC]\n ndiff = this.cloneMerge(tdiff, cdiff)\n ndiff[STATIC] = stat\n } else {\n ndiff = cdiff[STATIC] !== undefined ? cdiff : this.cloneMerge(oldc[cid] || {}, cdiff)\n }\n\n cache[cid] = ndiff\n return ndiff\n }\n }\n\n mutableMerge(target, source){\n if(source[STATIC] !== undefined){\n return source\n } else {\n this.doMutableMerge(target, source)\n return target\n }\n }\n\n doMutableMerge(target, source){\n for(let key in source){\n let val = source[key]\n let targetVal = target[key]\n let isObjVal = isObject(val)\n if(isObjVal && val[STATIC] === undefined && isObject(targetVal)){\n this.doMutableMerge(targetVal, val)\n } else {\n target[key] = val\n }\n }\n }\n\n cloneMerge(target, source){\n let merged = {...target, ...source}\n for(let key in merged){\n let val = source[key]\n let targetVal = target[key]\n if(isObject(val) && val[STATIC] === undefined && isObject(targetVal)){\n merged[key] = this.cloneMerge(targetVal, val)\n }\n }\n return merged\n }\n\n componentToString(cid){\n let [str, streams] = this.recursiveCIDToString(this.rendered[COMPONENTS], cid)\n return [str, streams]\n }\n\n pruneCIDs(cids){\n cids.forEach(cid => delete this.rendered[COMPONENTS][cid])\n }\n\n // private\n\n get(){ return this.rendered }\n\n isNewFingerprint(diff = {}){ return !!diff[STATIC] }\n\n templateStatic(part, templates){\n if(typeof (part) === \"number\") {\n return templates[part]\n } else {\n return part\n }\n }\n\n toOutputBuffer(rendered, templates, output){\n if(rendered[DYNAMICS]){ return this.comprehensionToBuffer(rendered, templates, output) }\n let {[STATIC]: statics} = rendered\n statics = this.templateStatic(statics, templates)\n\n output.buffer += statics[0]\n for(let i = 1; i < statics.length; i++){\n this.dynamicToBuffer(rendered[i - 1], templates, output)\n output.buffer += statics[i]\n }\n }\n\n comprehensionToBuffer(rendered, templates, output){\n let {[DYNAMICS]: dynamics, [STATIC]: statics, [STREAM]: stream} = rendered\n let [_inserts, deleteIds] = stream || [{}, []]\n statics = this.templateStatic(statics, templates)\n let compTemplates = templates || rendered[TEMPLATES]\n for(let d = 0; d < dynamics.length; d++){\n let dynamic = dynamics[d]\n output.buffer += statics[0]\n for(let i = 1; i < statics.length; i++){\n this.dynamicToBuffer(dynamic[i - 1], compTemplates, output)\n output.buffer += statics[i]\n }\n }\n\n if(stream !== undefined && (rendered[DYNAMICS].length > 0 || deleteIds.length > 0)){\n rendered[DYNAMICS] = []\n output.streams.add(stream)\n }\n }\n\n dynamicToBuffer(rendered, templates, output){\n if(typeof (rendered) === \"number\"){\n let [str, streams] = this.recursiveCIDToString(output.components, rendered, output.onlyCids)\n output.buffer += str\n output.streams = new Set([...output.streams, ...streams])\n } else if(isObject(rendered)){\n this.toOutputBuffer(rendered, templates, output)\n } else {\n output.buffer += rendered\n }\n }\n\n recursiveCIDToString(components, cid, onlyCids){\n let component = components[cid] || logError(`no component for CID ${cid}`, components)\n let template = document.createElement(\"template\")\n let [html, streams] = this.recursiveToString(component, components, onlyCids)\n template.innerHTML = html\n let container = template.content\n let skip = onlyCids && !onlyCids.has(cid)\n\n let [hasChildNodes, hasChildComponents] =\n Array.from(container.childNodes).reduce(([hasNodes, hasComponents], child, i) => {\n if(child.nodeType === Node.ELEMENT_NODE){\n if(child.getAttribute(PHX_COMPONENT)){\n return [hasNodes, true]\n }\n child.setAttribute(PHX_COMPONENT, cid)\n if(!child.id){ child.id = `${this.parentViewId()}-${cid}-${i}` }\n if(skip){\n child.setAttribute(PHX_SKIP, \"\")\n child.innerHTML = \"\"\n }\n return [true, hasComponents]\n } else {\n if(child.nodeValue.trim() !== \"\"){\n logError(\"only HTML element tags are allowed at the root of components.\\n\\n\" +\n `got: \"${child.nodeValue.trim()}\"\\n\\n` +\n \"within:\\n\", template.innerHTML.trim())\n child.replaceWith(this.createSpan(child.nodeValue, cid))\n return [true, hasComponents]\n } else {\n child.remove()\n return [hasNodes, hasComponents]\n }\n }\n }, [false, false])\n\n if(!hasChildNodes && !hasChildComponents){\n logError(\"expected at least one HTML element tag inside a component, but the component is empty:\\n\",\n template.innerHTML.trim())\n return [this.createSpan(\"\", cid).outerHTML, streams]\n } else if(!hasChildNodes && hasChildComponents){\n logError(\"expected at least one HTML element tag directly inside a component, but only subcomponents were found. A component must render at least one HTML tag directly inside itself.\",\n template.innerHTML.trim())\n return [template.innerHTML, streams]\n } else {\n return [template.innerHTML, streams]\n }\n }\n\n createSpan(text, cid){\n let span = document.createElement(\"span\")\n span.innerText = text\n span.setAttribute(PHX_COMPONENT, cid)\n return span\n }\n}\n", "let viewHookID = 1\nexport default class ViewHook {\n static makeID(){ return viewHookID++ }\n static elementID(el){ return el.phxHookId }\n\n constructor(view, el, callbacks){\n this.__view = view\n this.liveSocket = view.liveSocket\n this.__callbacks = callbacks\n this.__listeners = new Set()\n this.__isDisconnected = false\n this.el = el\n this.el.phxHookId = this.constructor.makeID()\n for(let key in this.__callbacks){ this[key] = this.__callbacks[key] }\n }\n\n __mounted(){ this.mounted && this.mounted() }\n __updated(){ this.updated && this.updated() }\n __beforeUpdate(){ this.beforeUpdate && this.beforeUpdate() }\n __destroyed(){ this.destroyed && this.destroyed() }\n __reconnected(){\n if(this.__isDisconnected){\n this.__isDisconnected = false\n this.reconnected && this.reconnected()\n }\n }\n __disconnected(){\n this.__isDisconnected = true\n this.disconnected && this.disconnected()\n }\n\n pushEvent(event, payload = {}, onReply = function (){ }){\n return this.__view.pushHookEvent(null, event, payload, onReply)\n }\n\n pushEventTo(phxTarget, event, payload = {}, onReply = function (){ }){\n return this.__view.withinTargets(phxTarget, (view, targetCtx) => {\n return view.pushHookEvent(targetCtx, event, payload, onReply)\n })\n }\n\n handleEvent(event, callback){\n let callbackRef = (customEvent, bypass) => bypass ? event : callback(customEvent.detail)\n window.addEventListener(`phx:${event}`, callbackRef)\n this.__listeners.add(callbackRef)\n return callbackRef\n }\n\n removeHandleEvent(callbackRef){\n let event = callbackRef(null, true)\n window.removeEventListener(`phx:${event}`, callbackRef)\n this.__listeners.delete(callbackRef)\n }\n\n upload(name, files){\n return this.__view.dispatchUploads(name, files)\n }\n\n uploadTo(phxTarget, name, files){\n return this.__view.withinTargets(phxTarget, view => view.dispatchUploads(name, files))\n }\n\n __cleanup__(){\n this.__listeners.forEach(callbackRef => this.removeHandleEvent(callbackRef))\n }\n}\n", "import DOM from \"./dom\"\nimport ARIA from \"./aria\"\n\nlet focusStack = null\n\nlet JS = {\n exec(eventType, phxEvent, view, sourceEl, defaults){\n let [defaultKind, defaultArgs] = defaults || [null, {}]\n let commands = phxEvent.charAt(0) === \"[\" ?\n JSON.parse(phxEvent) : [[defaultKind, defaultArgs]]\n\n commands.forEach(([kind, args]) => {\n if(kind === defaultKind && defaultArgs.data){\n args.data = Object.assign(args.data || {}, defaultArgs.data)\n }\n this.filterToEls(sourceEl, args).forEach(el => {\n this[`exec_${kind}`](eventType, phxEvent, view, sourceEl, el, args)\n })\n })\n },\n\n isVisible(el){\n return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length > 0)\n },\n\n // private\n\n // commands\n\n exec_dispatch(eventType, phxEvent, view, sourceEl, el, {to, event, detail, bubbles}){\n detail = detail || {}\n detail.dispatcher = sourceEl\n DOM.dispatchEvent(el, event, {detail, bubbles})\n },\n\n exec_push(eventType, phxEvent, view, sourceEl, el, args){\n if(!view.isConnected()){ return }\n\n let {event, data, target, page_loading, loading, value, dispatcher} = args\n let pushOpts = {loading, value, target, page_loading: !!page_loading}\n let targetSrc = eventType === \"change\" && dispatcher ? dispatcher : sourceEl\n let phxTarget = target || targetSrc.getAttribute(view.binding(\"target\")) || targetSrc\n view.withinTargets(phxTarget, (targetView, targetCtx) => {\n if(eventType === \"change\"){\n let {newCid, _target, callback} = args\n _target = _target || (DOM.isFormInput(sourceEl) ? sourceEl.name : undefined)\n if(_target){ pushOpts._target = _target }\n targetView.pushInput(sourceEl, targetCtx, newCid, event || phxEvent, pushOpts, callback)\n } else if(eventType === \"submit\"){\n targetView.submitForm(sourceEl, targetCtx, event || phxEvent, pushOpts)\n } else {\n targetView.pushEvent(eventType, sourceEl, targetCtx, event || phxEvent, data, pushOpts)\n }\n })\n },\n\n exec_navigate(eventType, phxEvent, view, sourceEl, el, {href, replace}){\n view.liveSocket.historyRedirect(href, replace ? \"replace\" : \"push\")\n },\n\n exec_patch(eventType, phxEvent, view, sourceEl, el, {href, replace}){\n view.liveSocket.pushHistoryPatch(href, replace ? \"replace\" : \"push\", sourceEl)\n },\n\n exec_focus(eventType, phxEvent, view, sourceEl, el){\n window.requestAnimationFrame(() => ARIA.attemptFocus(el))\n },\n\n exec_focus_first(eventType, phxEvent, view, sourceEl, el){\n window.requestAnimationFrame(() => ARIA.focusFirstInteractive(el) || ARIA.focusFirst(el))\n },\n\n exec_push_focus(eventType, phxEvent, view, sourceEl, el){\n window.requestAnimationFrame(() => focusStack = el || sourceEl)\n },\n\n exec_pop_focus(eventType, phxEvent, view, sourceEl, el){\n window.requestAnimationFrame(() => {\n if(focusStack){ focusStack.focus() }\n focusStack = null\n })\n },\n\n exec_add_class(eventType, phxEvent, view, sourceEl, el, {names, transition, time}){\n this.addOrRemoveClasses(el, names, [], transition, time, view)\n },\n\n exec_remove_class(eventType, phxEvent, view, sourceEl, el, {names, transition, time}){\n this.addOrRemoveClasses(el, [], names, transition, time, view)\n },\n\n exec_transition(eventType, phxEvent, view, sourceEl, el, {time, transition}){\n this.addOrRemoveClasses(el, [], [], transition, time, view)\n },\n\n exec_toggle(eventType, phxEvent, view, sourceEl, el, {display, ins, outs, time}){\n this.toggle(eventType, view, el, display, ins, outs, time)\n },\n\n exec_show(eventType, phxEvent, view, sourceEl, el, {display, transition, time}){\n this.show(eventType, view, el, display, transition, time)\n },\n\n exec_hide(eventType, phxEvent, view, sourceEl, el, {display, transition, time}){\n this.hide(eventType, view, el, display, transition, time)\n },\n\n exec_set_attr(eventType, phxEvent, view, sourceEl, el, {attr: [attr, val]}){\n this.setOrRemoveAttrs(el, [[attr, val]], [])\n },\n\n exec_remove_attr(eventType, phxEvent, view, sourceEl, el, {attr}){\n this.setOrRemoveAttrs(el, [], [attr])\n },\n\n // utils for commands\n\n show(eventType, view, el, display, transition, time){\n if(!this.isVisible(el)){\n this.toggle(eventType, view, el, display, transition, null, time)\n }\n },\n\n hide(eventType, view, el, display, transition, time){\n if(this.isVisible(el)){\n this.toggle(eventType, view, el, display, null, transition, time)\n }\n },\n\n toggle(eventType, view, el, display, ins, outs, time){\n let [inClasses, inStartClasses, inEndClasses] = ins || [[], [], []]\n let [outClasses, outStartClasses, outEndClasses] = outs || [[], [], []]\n if(inClasses.length > 0 || outClasses.length > 0){\n if(this.isVisible(el)){\n let onStart = () => {\n this.addOrRemoveClasses(el, outStartClasses, inClasses.concat(inStartClasses).concat(inEndClasses))\n window.requestAnimationFrame(() => {\n this.addOrRemoveClasses(el, outClasses, [])\n window.requestAnimationFrame(() => this.addOrRemoveClasses(el, outEndClasses, outStartClasses))\n })\n }\n el.dispatchEvent(new Event(\"phx:hide-start\"))\n view.transition(time, onStart, () => {\n this.addOrRemoveClasses(el, [], outClasses.concat(outEndClasses))\n DOM.putSticky(el, \"toggle\", currentEl => currentEl.style.display = \"none\")\n el.dispatchEvent(new Event(\"phx:hide-end\"))\n })\n } else {\n if(eventType === \"remove\"){ return }\n let onStart = () => {\n this.addOrRemoveClasses(el, inStartClasses, outClasses.concat(outStartClasses).concat(outEndClasses))\n let stickyDisplay = display || this.defaultDisplay(el)\n DOM.putSticky(el, \"toggle\", currentEl => currentEl.style.display = stickyDisplay)\n window.requestAnimationFrame(() => {\n this.addOrRemoveClasses(el, inClasses, [])\n window.requestAnimationFrame(() => this.addOrRemoveClasses(el, inEndClasses, inStartClasses))\n })\n }\n el.dispatchEvent(new Event(\"phx:show-start\"))\n view.transition(time, onStart, () => {\n this.addOrRemoveClasses(el, [], inClasses.concat(inEndClasses))\n el.dispatchEvent(new Event(\"phx:show-end\"))\n })\n }\n } else {\n if(this.isVisible(el)){\n window.requestAnimationFrame(() => {\n el.dispatchEvent(new Event(\"phx:hide-start\"))\n DOM.putSticky(el, \"toggle\", currentEl => currentEl.style.display = \"none\")\n el.dispatchEvent(new Event(\"phx:hide-end\"))\n })\n } else {\n window.requestAnimationFrame(() => {\n el.dispatchEvent(new Event(\"phx:show-start\"))\n let stickyDisplay = display || this.defaultDisplay(el)\n DOM.putSticky(el, \"toggle\", currentEl => currentEl.style.display = stickyDisplay)\n el.dispatchEvent(new Event(\"phx:show-end\"))\n })\n }\n }\n },\n\n addOrRemoveClasses(el, adds, removes, transition, time, view){\n let [transition_run, transition_start, transition_end] = transition || [[], [], []]\n if(transition_run.length > 0){\n let onStart = () => this.addOrRemoveClasses(el, transition_start.concat(transition_run), [])\n let onDone = () => this.addOrRemoveClasses(el, adds.concat(transition_end), removes.concat(transition_run).concat(transition_start))\n return view.transition(time, onStart, onDone)\n }\n window.requestAnimationFrame(() => {\n let [prevAdds, prevRemoves] = DOM.getSticky(el, \"classes\", [[], []])\n let keepAdds = adds.filter(name => prevAdds.indexOf(name) < 0 && !el.classList.contains(name))\n let keepRemoves = removes.filter(name => prevRemoves.indexOf(name) < 0 && el.classList.contains(name))\n let newAdds = prevAdds.filter(name => removes.indexOf(name) < 0).concat(keepAdds)\n let newRemoves = prevRemoves.filter(name => adds.indexOf(name) < 0).concat(keepRemoves)\n\n DOM.putSticky(el, \"classes\", currentEl => {\n currentEl.classList.remove(...newRemoves)\n currentEl.classList.add(...newAdds)\n return [newAdds, newRemoves]\n })\n })\n },\n\n setOrRemoveAttrs(el, sets, removes){\n let [prevSets, prevRemoves] = DOM.getSticky(el, \"attrs\", [[], []])\n\n let alteredAttrs = sets.map(([attr, _val]) => attr).concat(removes);\n let newSets = prevSets.filter(([attr, _val]) => !alteredAttrs.includes(attr)).concat(sets);\n let newRemoves = prevRemoves.filter((attr) => !alteredAttrs.includes(attr)).concat(removes);\n\n DOM.putSticky(el, \"attrs\", currentEl => {\n newRemoves.forEach(attr => currentEl.removeAttribute(attr))\n newSets.forEach(([attr, val]) => currentEl.setAttribute(attr, val))\n return [newSets, newRemoves]\n })\n },\n\n hasAllClasses(el, classes){ return classes.every(name => el.classList.contains(name)) },\n\n isToggledOut(el, outClasses){\n return !this.isVisible(el) || this.hasAllClasses(el, outClasses)\n },\n\n filterToEls(sourceEl, {to}){\n return to ? DOM.all(document, to) : [sourceEl]\n },\n\n defaultDisplay(el){\n return {tr: \"table-row\", td: \"table-cell\"}[el.tagName.toLowerCase()] || \"block\"\n }\n}\n\nexport default JS\n", "import {\n BEFORE_UNLOAD_LOADER_TIMEOUT,\n CHECKABLE_INPUTS,\n CONSECUTIVE_RELOADS,\n PHX_AUTO_RECOVER,\n PHX_COMPONENT,\n PHX_CONNECTED_CLASS,\n PHX_DISABLE_WITH,\n PHX_DISABLE_WITH_RESTORE,\n PHX_DISABLED,\n PHX_DISCONNECTED_CLASS,\n PHX_EVENT_CLASSES,\n PHX_ERROR_CLASS,\n PHX_FEEDBACK_FOR,\n PHX_HAS_SUBMITTED,\n PHX_HOOK,\n PHX_PAGE_LOADING,\n PHX_PARENT_ID,\n PHX_PROGRESS,\n PHX_READONLY,\n PHX_REF,\n PHX_REF_SRC,\n PHX_ROOT_ID,\n PHX_SESSION,\n PHX_STATIC,\n PHX_TRACK_STATIC,\n PHX_TRACK_UPLOADS,\n PHX_UPDATE,\n PHX_UPLOAD_REF,\n PHX_VIEW_SELECTOR,\n PHX_MAIN,\n PHX_MOUNTED,\n PUSH_TIMEOUT,\n} from \"./constants\"\n\nimport {\n clone,\n closestPhxBinding,\n isEmpty,\n isEqualObj,\n logError,\n maybe,\n isCid,\n} from \"./utils\"\n\nimport Browser from \"./browser\"\nimport DOM from \"./dom\"\nimport DOMPatch from \"./dom_patch\"\nimport LiveUploader from \"./live_uploader\"\nimport Rendered from \"./rendered\"\nimport ViewHook from \"./view_hook\"\nimport JS from \"./js\"\n\nlet serializeForm = (form, meta, onlyNames = []) => {\n let formData = new FormData(form)\n let toRemove = []\n\n formData.forEach((val, key, _index) => {\n if(val instanceof File){ toRemove.push(key) }\n })\n\n // Cleanup after building fileData\n toRemove.forEach(key => formData.delete(key))\n\n let params = new URLSearchParams()\n for(let [key, val] of formData.entries()){\n if(onlyNames.length === 0 || onlyNames.indexOf(key) >= 0){\n params.append(key, val)\n }\n }\n for(let metaKey in meta){ params.append(metaKey, meta[metaKey]) }\n\n return params.toString()\n}\n\nexport default class View {\n constructor(el, liveSocket, parentView, flash, liveReferer){\n this.isDead = false\n this.liveSocket = liveSocket\n this.flash = flash\n this.parent = parentView\n this.root = parentView ? parentView.root : this\n this.el = el\n this.id = this.el.id\n this.ref = 0\n this.childJoins = 0\n this.loaderTimer = null\n this.pendingDiffs = []\n this.pruningCIDs = []\n this.redirect = false\n this.href = null\n this.joinCount = this.parent ? this.parent.joinCount - 1 : 0\n this.joinPending = true\n this.destroyed = false\n this.joinCallback = function(onDone){ onDone && onDone() }\n this.stopCallback = function(){ }\n this.pendingJoinOps = this.parent ? null : []\n this.viewHooks = {}\n this.uploaders = {}\n this.formSubmits = []\n this.children = this.parent ? null : {}\n this.root.children[this.id] = {}\n this.channel = this.liveSocket.channel(`lv:${this.id}`, () => {\n return {\n redirect: this.redirect ? this.href : undefined,\n url: this.redirect ? undefined : this.href || undefined,\n params: this.connectParams(liveReferer),\n session: this.getSession(),\n static: this.getStatic(),\n flash: this.flash,\n }\n })\n }\n\n setHref(href){ this.href = href }\n\n setRedirect(href){\n this.redirect = true\n this.href = href\n }\n\n isMain(){ return this.el.hasAttribute(PHX_MAIN) }\n\n connectParams(liveReferer){\n let params = this.liveSocket.params(this.el)\n let manifest =\n DOM.all(document, `[${this.binding(PHX_TRACK_STATIC)}]`)\n .map(node => node.src || node.href).filter(url => typeof (url) === \"string\")\n\n if(manifest.length > 0){ params[\"_track_static\"] = manifest }\n params[\"_mounts\"] = this.joinCount\n params[\"_live_referer\"] = liveReferer\n\n return params\n }\n\n isConnected(){ return this.channel.canPush() }\n\n getSession(){ return this.el.getAttribute(PHX_SESSION) }\n\n getStatic(){\n let val = this.el.getAttribute(PHX_STATIC)\n return val === \"\" ? null : val\n }\n\n destroy(callback = function (){ }){\n this.destroyAllChildren()\n this.destroyed = true\n delete this.root.children[this.id]\n if(this.parent){ delete this.root.children[this.parent.id][this.id] }\n clearTimeout(this.loaderTimer)\n let onFinished = () => {\n callback()\n for(let id in this.viewHooks){\n this.destroyHook(this.viewHooks[id])\n }\n }\n\n DOM.markPhxChildDestroyed(this.el)\n\n this.log(\"destroyed\", () => [\"the child has been removed from the parent\"])\n this.channel.leave()\n .receive(\"ok\", onFinished)\n .receive(\"error\", onFinished)\n .receive(\"timeout\", onFinished)\n }\n\n setContainerClasses(...classes){\n this.el.classList.remove(\n PHX_CONNECTED_CLASS,\n PHX_DISCONNECTED_CLASS,\n PHX_ERROR_CLASS\n )\n this.el.classList.add(...classes)\n }\n\n showLoader(timeout){\n clearTimeout(this.loaderTimer)\n if(timeout){\n this.loaderTimer = setTimeout(() => this.showLoader(), timeout)\n } else {\n for(let id in this.viewHooks){ this.viewHooks[id].__disconnected() }\n this.setContainerClasses(PHX_DISCONNECTED_CLASS)\n }\n }\n\n execAll(binding){\n DOM.all(this.el, `[${binding}]`, el => this.liveSocket.execJS(el, el.getAttribute(binding)))\n }\n\n hideLoader(){\n clearTimeout(this.loaderTimer)\n this.setContainerClasses(PHX_CONNECTED_CLASS)\n this.execAll(this.binding(\"connected\"))\n }\n\n triggerReconnected(){\n for(let id in this.viewHooks){ this.viewHooks[id].__reconnected() }\n }\n\n log(kind, msgCallback){\n this.liveSocket.log(this, kind, msgCallback)\n }\n\n transition(time, onStart, onDone = function(){}){\n this.liveSocket.transition(time, onStart, onDone)\n }\n\n withinTargets(phxTarget, callback){\n if(phxTarget instanceof HTMLElement || phxTarget instanceof SVGElement){\n return this.liveSocket.owner(phxTarget, view => callback(view, phxTarget))\n }\n\n if(isCid(phxTarget)){\n let targets = DOM.findComponentNodeList(this.el, phxTarget)\n if(targets.length === 0){\n logError(`no component found matching phx-target of ${phxTarget}`)\n } else {\n callback(this, parseInt(phxTarget))\n }\n } else {\n let targets = Array.from(document.querySelectorAll(phxTarget))\n if(targets.length === 0){ logError(`nothing found matching the phx-target selector \"${phxTarget}\"`) }\n targets.forEach(target => this.liveSocket.owner(target, view => callback(view, target)))\n }\n }\n\n applyDiff(type, rawDiff, callback){\n this.log(type, () => [\"\", clone(rawDiff)])\n let {diff, reply, events, title} = Rendered.extract(rawDiff)\n callback({diff, reply, events})\n if(title){ window.requestAnimationFrame(() => DOM.putTitle(title)) }\n }\n\n onJoin(resp){\n let {rendered, container} = resp\n if(container){\n let [tag, attrs] = container\n this.el = DOM.replaceRootContainer(this.el, tag, attrs)\n }\n this.childJoins = 0\n this.joinPending = true\n this.flash = null\n\n Browser.dropLocal(this.liveSocket.localStorage, window.location.pathname, CONSECUTIVE_RELOADS)\n this.applyDiff(\"mount\", rendered, ({diff, events}) => {\n this.rendered = new Rendered(this.id, diff)\n let [html, streams] = this.renderContainer(null, \"join\")\n this.dropPendingRefs()\n let forms = this.formsForRecovery(html)\n this.joinCount++\n\n if(forms.length > 0){\n forms.forEach(([form, newForm, newCid], i) => {\n this.pushFormRecovery(form, newCid, resp => {\n if(i === forms.length - 1){\n this.onJoinComplete(resp, html, streams, events)\n }\n })\n })\n } else {\n this.onJoinComplete(resp, html, streams, events)\n }\n })\n }\n\n dropPendingRefs(){\n DOM.all(document, `[${PHX_REF_SRC}=\"${this.id}\"][${PHX_REF}]`, el => {\n el.removeAttribute(PHX_REF)\n el.removeAttribute(PHX_REF_SRC)\n })\n }\n\n onJoinComplete({live_patch}, html, streams, events){\n // In order to provide a better experience, we want to join\n // all LiveViews first and only then apply their patches.\n if(this.joinCount > 1 || (this.parent && !this.parent.isJoinPending())){\n return this.applyJoinPatch(live_patch, html, streams, events)\n }\n\n // One downside of this approach is that we need to find phxChildren\n // in the html fragment, instead of directly on the DOM. The fragment\n // also does not include PHX_STATIC, so we need to copy it over from\n // the DOM.\n let newChildren = DOM.findPhxChildrenInFragment(html, this.id).filter(toEl => {\n let fromEl = toEl.id && this.el.querySelector(`[id=\"${toEl.id}\"]`)\n let phxStatic = fromEl && fromEl.getAttribute(PHX_STATIC)\n if(phxStatic){ toEl.setAttribute(PHX_STATIC, phxStatic) }\n return this.joinChild(toEl)\n })\n\n if(newChildren.length === 0){\n if(this.parent){\n this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, streams, events)])\n this.parent.ackJoin(this)\n } else {\n this.onAllChildJoinsComplete()\n this.applyJoinPatch(live_patch, html, streams, events)\n }\n } else {\n this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, streams, events)])\n }\n }\n\n attachTrueDocEl(){\n this.el = DOM.byId(this.id)\n this.el.setAttribute(PHX_ROOT_ID, this.root.id)\n }\n\n execNewMounted(){\n DOM.all(this.el, `[${this.binding(PHX_HOOK)}], [data-phx-${PHX_HOOK}]`, hookEl => {\n this.maybeAddNewHook(hookEl)\n })\n DOM.all(this.el, `[${this.binding(PHX_MOUNTED)}]`, el => this.maybeMounted(el))\n }\n\n applyJoinPatch(live_patch, html, streams, events){\n this.attachTrueDocEl()\n let patch = new DOMPatch(this, this.el, this.id, html, streams, null)\n patch.markPrunableContentForRemoval()\n this.performPatch(patch, false)\n this.joinNewChildren()\n this.execNewMounted()\n\n this.joinPending = false\n this.liveSocket.dispatchEvents(events)\n this.applyPendingUpdates()\n\n if(live_patch){\n let {kind, to} = live_patch\n this.liveSocket.historyPatch(to, kind)\n }\n this.hideLoader()\n if(this.joinCount > 1){ this.triggerReconnected() }\n this.stopCallback()\n }\n\n triggerBeforeUpdateHook(fromEl, toEl){\n this.liveSocket.triggerDOM(\"onBeforeElUpdated\", [fromEl, toEl])\n let hook = this.getHook(fromEl)\n let isIgnored = hook && DOM.isIgnored(fromEl, this.binding(PHX_UPDATE))\n if(hook && !fromEl.isEqualNode(toEl) && !(isIgnored && isEqualObj(fromEl.dataset, toEl.dataset))){\n hook.__beforeUpdate()\n return hook\n }\n }\n\n maybeMounted(el){\n let phxMounted = el.getAttribute(this.binding(PHX_MOUNTED))\n let hasBeenInvoked = phxMounted && DOM.private(el, \"mounted\")\n if(phxMounted && !hasBeenInvoked){\n this.liveSocket.execJS(el, phxMounted)\n DOM.putPrivate(el, \"mounted\", true)\n }\n }\n\n maybeAddNewHook(el, force){\n let newHook = this.addHook(el)\n if(newHook){ newHook.__mounted() }\n }\n\n performPatch(patch, pruneCids){\n let removedEls = []\n let phxChildrenAdded = false\n let updatedHookIds = new Set()\n\n patch.after(\"added\", el => {\n this.liveSocket.triggerDOM(\"onNodeAdded\", [el])\n this.maybeAddNewHook(el)\n if(el.getAttribute){ this.maybeMounted(el) }\n })\n\n patch.after(\"phxChildAdded\", el => {\n if(DOM.isPhxSticky(el)){\n this.liveSocket.joinRootViews()\n } else {\n phxChildrenAdded = true\n }\n })\n\n patch.before(\"updated\", (fromEl, toEl) => {\n let hook = this.triggerBeforeUpdateHook(fromEl, toEl)\n if(hook){ updatedHookIds.add(fromEl.id) }\n })\n\n patch.after(\"updated\", el => {\n if(updatedHookIds.has(el.id)){ this.getHook(el).__updated() }\n })\n\n patch.after(\"discarded\", (el) => {\n if(el.nodeType === Node.ELEMENT_NODE){ removedEls.push(el) }\n })\n\n patch.after(\"transitionsDiscarded\", els => this.afterElementsRemoved(els, pruneCids))\n patch.perform()\n this.afterElementsRemoved(removedEls, pruneCids)\n\n return phxChildrenAdded\n }\n\n afterElementsRemoved(elements, pruneCids){\n let destroyedCIDs = []\n elements.forEach(parent => {\n let components = DOM.all(parent, `[${PHX_COMPONENT}]`)\n let hooks = DOM.all(parent, `[${this.binding(PHX_HOOK)}]`)\n components.concat(parent).forEach(el => {\n let cid = this.componentID(el)\n if(isCid(cid) && destroyedCIDs.indexOf(cid) === -1){ destroyedCIDs.push(cid) }\n })\n hooks.concat(parent).forEach(hookEl => {\n let hook = this.getHook(hookEl)\n hook && this.destroyHook(hook)\n })\n })\n // We should not pruneCids on joins. Otherwise, in case of\n // rejoins, we may notify cids that no longer belong to the\n // current LiveView to be removed.\n if(pruneCids){\n this.maybePushComponentsDestroyed(destroyedCIDs)\n }\n }\n\n joinNewChildren(){\n DOM.findPhxChildren(this.el, this.id).forEach(el => this.joinChild(el))\n }\n\n getChildById(id){ return this.root.children[this.id][id] }\n\n getDescendentByEl(el){\n if(el.id === this.id){\n return this\n } else {\n return this.children[el.getAttribute(PHX_PARENT_ID)][el.id]\n }\n }\n\n destroyDescendent(id){\n for(let parentId in this.root.children){\n for(let childId in this.root.children[parentId]){\n if(childId === id){ return this.root.children[parentId][childId].destroy() }\n }\n }\n }\n\n joinChild(el){\n let child = this.getChildById(el.id)\n if(!child){\n let view = new View(el, this.liveSocket, this)\n this.root.children[this.id][view.id] = view\n view.join()\n this.childJoins++\n return true\n }\n }\n\n isJoinPending(){ return this.joinPending }\n\n ackJoin(_child){\n this.childJoins--\n\n if(this.childJoins === 0){\n if(this.parent){\n this.parent.ackJoin(this)\n } else {\n this.onAllChildJoinsComplete()\n }\n }\n }\n\n onAllChildJoinsComplete(){\n this.joinCallback(() => {\n this.pendingJoinOps.forEach(([view, op]) => {\n if(!view.isDestroyed()){ op() }\n })\n this.pendingJoinOps = []\n })\n }\n\n update(diff, events){\n if(this.isJoinPending() || (this.liveSocket.hasPendingLink() && this.root.isMain())){\n return this.pendingDiffs.push({diff, events})\n }\n\n this.rendered.mergeDiff(diff)\n let phxChildrenAdded = false\n\n // When the diff only contains component diffs, then walk components\n // and patch only the parent component containers found in the diff.\n // Otherwise, patch entire LV container.\n if(this.rendered.isComponentOnlyDiff(diff)){\n this.liveSocket.time(\"component patch complete\", () => {\n let parentCids = DOM.findParentCIDs(this.el, this.rendered.componentCIDs(diff))\n parentCids.forEach(parentCID => {\n if(this.componentPatch(this.rendered.getComponent(diff, parentCID), parentCID)){ phxChildrenAdded = true }\n })\n })\n } else if(!isEmpty(diff)){\n this.liveSocket.time(\"full patch complete\", () => {\n let [html, streams] = this.renderContainer(diff, \"update\")\n let patch = new DOMPatch(this, this.el, this.id, html, streams, null)\n phxChildrenAdded = this.performPatch(patch, true)\n })\n }\n\n this.liveSocket.dispatchEvents(events)\n if(phxChildrenAdded){ this.joinNewChildren() }\n }\n\n renderContainer(diff, kind){\n return this.liveSocket.time(`toString diff (${kind})`, () => {\n let tag = this.el.tagName\n // Don't skip any component in the diff nor any marked as pruned\n // (as they may have been added back)\n let cids = diff ? this.rendered.componentCIDs(diff).concat(this.pruningCIDs) : null\n let [html, streams] = this.rendered.toString(cids)\n return [`<${tag}>${html}${tag}>`, streams]\n })\n }\n\n componentPatch(diff, cid){\n if(isEmpty(diff)) return false\n let [html, streams] = this.rendered.componentToString(cid)\n let patch = new DOMPatch(this, this.el, this.id, html, streams, cid)\n let childrenAdded = this.performPatch(patch, true)\n return childrenAdded\n }\n\n getHook(el){ return this.viewHooks[ViewHook.elementID(el)] }\n\n addHook(el){\n if(ViewHook.elementID(el) || !el.getAttribute){ return }\n let hookName = el.getAttribute(`data-phx-${PHX_HOOK}`) || el.getAttribute(this.binding(PHX_HOOK))\n if(hookName && !this.ownsElement(el)){ return }\n let callbacks = this.liveSocket.getHookCallbacks(hookName)\n\n if(callbacks){\n if(!el.id){ logError(`no DOM ID for hook \"${hookName}\". Hooks require a unique ID on each element.`, el) }\n let hook = new ViewHook(this, el, callbacks)\n this.viewHooks[ViewHook.elementID(hook.el)] = hook\n return hook\n } else if(hookName !== null){\n logError(`unknown hook found for \"${hookName}\"`, el)\n }\n }\n\n destroyHook(hook){\n hook.__destroyed()\n hook.__cleanup__()\n delete this.viewHooks[ViewHook.elementID(hook.el)]\n }\n\n applyPendingUpdates(){\n this.pendingDiffs.forEach(({diff, events}) => this.update(diff, events))\n this.pendingDiffs = []\n this.eachChild(child => child.applyPendingUpdates())\n }\n\n eachChild(callback){\n let children = this.root.children[this.id] || {}\n for(let id in children){ callback(this.getChildById(id)) }\n }\n\n onChannel(event, cb){\n this.liveSocket.onChannel(this.channel, event, resp => {\n if(this.isJoinPending()){\n this.root.pendingJoinOps.push([this, () => cb(resp)])\n } else {\n this.liveSocket.requestDOMUpdate(() => cb(resp))\n }\n })\n }\n\n bindChannel(){\n // The diff event should be handled by the regular update operations.\n // All other operations are queued to be applied only after join.\n this.liveSocket.onChannel(this.channel, \"diff\", (rawDiff) => {\n this.liveSocket.requestDOMUpdate(() => {\n this.applyDiff(\"update\", rawDiff, ({diff, events}) => this.update(diff, events))\n })\n })\n this.onChannel(\"redirect\", ({to, flash}) => this.onRedirect({to, flash}))\n this.onChannel(\"live_patch\", (redir) => this.onLivePatch(redir))\n this.onChannel(\"live_redirect\", (redir) => this.onLiveRedirect(redir))\n this.channel.onError(reason => this.onError(reason))\n this.channel.onClose(reason => this.onClose(reason))\n }\n\n destroyAllChildren(){ this.eachChild(child => child.destroy()) }\n\n onLiveRedirect(redir){\n let {to, kind, flash} = redir\n let url = this.expandURL(to)\n this.liveSocket.historyRedirect(url, kind, flash)\n }\n\n onLivePatch(redir){\n let {to, kind} = redir\n this.href = this.expandURL(to)\n this.liveSocket.historyPatch(to, kind)\n }\n\n expandURL(to){\n return to.startsWith(\"/\") ? `${window.location.protocol}//${window.location.host}${to}` : to\n }\n\n onRedirect({to, flash}){ this.liveSocket.redirect(to, flash) }\n\n isDestroyed(){ return this.destroyed }\n\n joinDead(){ this.isDead = true }\n\n join(callback){\n this.showLoader(this.liveSocket.loaderTimeout)\n this.bindChannel()\n if(this.isMain()){\n this.stopCallback = this.liveSocket.withPageLoading({to: this.href, kind: \"initial\"})\n }\n this.joinCallback = (onDone) => {\n onDone = onDone || function(){}\n callback ? callback(this.joinCount, onDone) : onDone()\n }\n this.liveSocket.wrapPush(this, {timeout: false}, () => {\n return this.channel.join()\n .receive(\"ok\", data => {\n if(!this.isDestroyed()){\n this.liveSocket.requestDOMUpdate(() => this.onJoin(data))\n }\n })\n .receive(\"error\", resp => !this.isDestroyed() && this.onJoinError(resp))\n .receive(\"timeout\", () => !this.isDestroyed() && this.onJoinError({reason: \"timeout\"}))\n })\n }\n\n onJoinError(resp){\n if(resp.reason === \"reload\"){\n this.log(\"error\", () => [`failed mount with ${resp.status}. Falling back to page request`, resp])\n return this.onRedirect({to: this.href})\n } else if(resp.reason === \"unauthorized\" || resp.reason === \"stale\"){\n this.log(\"error\", () => [\"unauthorized live_redirect. Falling back to page request\", resp])\n return this.onRedirect({to: this.href})\n }\n if(resp.redirect || resp.live_redirect){\n this.joinPending = false\n this.channel.leave()\n }\n if(resp.redirect){ return this.onRedirect(resp.redirect) }\n if(resp.live_redirect){ return this.onLiveRedirect(resp.live_redirect) }\n this.log(\"error\", () => [\"unable to join\", resp])\n if(this.liveSocket.isConnected()){ this.liveSocket.reloadWithJitter(this) }\n }\n\n onClose(reason){\n if(this.isDestroyed()){ return }\n if(this.liveSocket.hasPendingLink() && reason !== \"leave\"){\n return this.liveSocket.reloadWithJitter(this)\n }\n this.destroyAllChildren()\n this.liveSocket.dropActiveElement(this)\n // document.activeElement can be null in Internet Explorer 11\n if(document.activeElement){ document.activeElement.blur() }\n if(this.liveSocket.isUnloaded()){\n this.showLoader(BEFORE_UNLOAD_LOADER_TIMEOUT)\n }\n }\n\n onError(reason){\n this.onClose(reason)\n if(this.liveSocket.isConnected()){ this.log(\"error\", () => [\"view crashed\", reason]) }\n if(!this.liveSocket.isUnloaded()){ this.displayError() }\n }\n\n displayError(){\n if(this.isMain()){ DOM.dispatchEvent(window, \"phx:page-loading-start\", {detail: {to: this.href, kind: \"error\"}}) }\n this.showLoader()\n this.setContainerClasses(PHX_DISCONNECTED_CLASS, PHX_ERROR_CLASS)\n this.execAll(this.binding(\"disconnected\"))\n }\n\n pushWithReply(refGenerator, event, payload, onReply = function (){ }){\n if(!this.isConnected()){ return }\n\n let [ref, [el], opts] = refGenerator ? refGenerator() : [null, [], {}]\n let onLoadingDone = function(){ }\n if(opts.page_loading || (el && (el.getAttribute(this.binding(PHX_PAGE_LOADING)) !== null))){\n onLoadingDone = this.liveSocket.withPageLoading({kind: \"element\", target: el})\n }\n\n if(typeof (payload.cid) !== \"number\"){ delete payload.cid }\n return (\n this.liveSocket.wrapPush(this, {timeout: true}, () => {\n return this.channel.push(event, payload, PUSH_TIMEOUT).receive(\"ok\", resp => {\n let finish = (hookReply) => {\n if(resp.redirect){ this.onRedirect(resp.redirect) }\n if(resp.live_patch){ this.onLivePatch(resp.live_patch) }\n if(resp.live_redirect){ this.onLiveRedirect(resp.live_redirect) }\n if(ref !== null){ this.undoRefs(ref) }\n onLoadingDone()\n onReply(resp, hookReply)\n }\n if(resp.diff){\n this.liveSocket.requestDOMUpdate(() => {\n this.applyDiff(\"update\", resp.diff, ({diff, reply, events}) => {\n this.update(diff, events)\n finish(reply)\n })\n })\n } else {\n finish(null)\n }\n })\n })\n )\n }\n\n undoRefs(ref){\n if(!this.isConnected()){ return } // exit if external form triggered\n\n DOM.all(document, `[${PHX_REF_SRC}=\"${this.id}\"][${PHX_REF}=\"${ref}\"]`, el => {\n let disabledVal = el.getAttribute(PHX_DISABLED)\n // remove refs\n el.removeAttribute(PHX_REF)\n el.removeAttribute(PHX_REF_SRC)\n // restore inputs\n if(el.getAttribute(PHX_READONLY) !== null){\n el.readOnly = false\n el.removeAttribute(PHX_READONLY)\n }\n if(disabledVal !== null){\n el.disabled = disabledVal === \"true\" ? true : false\n el.removeAttribute(PHX_DISABLED)\n }\n // remove classes\n PHX_EVENT_CLASSES.forEach(className => DOM.removeClass(el, className))\n // restore disables\n let disableRestore = el.getAttribute(PHX_DISABLE_WITH_RESTORE)\n if(disableRestore !== null){\n el.innerText = disableRestore\n el.removeAttribute(PHX_DISABLE_WITH_RESTORE)\n }\n let toEl = DOM.private(el, PHX_REF)\n if(toEl){\n let hook = this.triggerBeforeUpdateHook(el, toEl)\n DOMPatch.patchEl(el, toEl, this.liveSocket.getActiveElement())\n if(hook){ hook.__updated() }\n DOM.deletePrivate(el, PHX_REF)\n }\n })\n }\n\n putRef(elements, event, opts = {}){\n let newRef = this.ref++\n let disableWith = this.binding(PHX_DISABLE_WITH)\n if(opts.loading){ elements = elements.concat(DOM.all(document, opts.loading))}\n\n elements.forEach(el => {\n el.classList.add(`phx-${event}-loading`)\n el.setAttribute(PHX_REF, newRef)\n el.setAttribute(PHX_REF_SRC, this.el.id)\n let disableText = el.getAttribute(disableWith)\n if(disableText !== null){\n if(!el.getAttribute(PHX_DISABLE_WITH_RESTORE)){\n el.setAttribute(PHX_DISABLE_WITH_RESTORE, el.innerText)\n }\n if(disableText !== \"\"){ el.innerText = disableText }\n el.setAttribute(\"disabled\", \"\")\n }\n })\n return [newRef, elements, opts]\n }\n\n componentID(el){\n let cid = el.getAttribute && el.getAttribute(PHX_COMPONENT)\n return cid ? parseInt(cid) : null\n }\n\n targetComponentID(target, targetCtx, opts = {}){\n if(isCid(targetCtx)){ return targetCtx }\n\n let cidOrSelector = target.getAttribute(this.binding(\"target\"))\n if(isCid(cidOrSelector)){\n return parseInt(cidOrSelector)\n } else if(targetCtx && (cidOrSelector !== null || opts.target)){\n return this.closestComponentID(targetCtx)\n } else {\n return null\n }\n }\n\n closestComponentID(targetCtx){\n if(isCid(targetCtx)){\n return targetCtx\n } else if(targetCtx){\n return maybe(targetCtx.closest(`[${PHX_COMPONENT}]`), el => this.ownsElement(el) && this.componentID(el))\n } else {\n return null\n }\n }\n\n pushHookEvent(targetCtx, event, payload, onReply){\n if(!this.isConnected()){\n this.log(\"hook\", () => [\"unable to push hook event. LiveView not connected\", event, payload])\n return false\n }\n let [ref, els, opts] = this.putRef([], \"hook\")\n this.pushWithReply(() => [ref, els, opts], \"event\", {\n type: \"hook\",\n event: event,\n value: payload,\n cid: this.closestComponentID(targetCtx)\n }, (resp, reply) => onReply(reply, ref))\n\n return ref\n }\n\n extractMeta(el, meta, value){\n let prefix = this.binding(\"value-\")\n for(let i = 0; i < el.attributes.length; i++){\n if(!meta){ meta = {} }\n let name = el.attributes[i].name\n if(name.startsWith(prefix)){ meta[name.replace(prefix, \"\")] = el.getAttribute(name) }\n }\n if(el.value !== undefined){\n if(!meta){ meta = {} }\n meta.value = el.value\n\n if(el.tagName === \"INPUT\" && CHECKABLE_INPUTS.indexOf(el.type) >= 0 && !el.checked){\n delete meta.value\n }\n }\n if(value){\n if(!meta){ meta = {} }\n for(let key in value){ meta[key] = value[key] }\n }\n return meta\n }\n\n pushEvent(type, el, targetCtx, phxEvent, meta, opts = {}){\n this.pushWithReply(() => this.putRef([el], type, opts), \"event\", {\n type: type,\n event: phxEvent,\n value: this.extractMeta(el, meta, opts.value),\n cid: this.targetComponentID(el, targetCtx, opts)\n })\n }\n\n pushFileProgress(fileEl, entryRef, progress, onReply = function (){ }){\n this.liveSocket.withinOwners(fileEl.form, (view, targetCtx) => {\n view.pushWithReply(null, \"progress\", {\n event: fileEl.getAttribute(view.binding(PHX_PROGRESS)),\n ref: fileEl.getAttribute(PHX_UPLOAD_REF),\n entry_ref: entryRef,\n progress: progress,\n cid: view.targetComponentID(fileEl.form, targetCtx)\n }, onReply)\n })\n }\n\n pushInput(inputEl, targetCtx, forceCid, phxEvent, opts, callback){\n let uploads\n let cid = isCid(forceCid) ? forceCid : this.targetComponentID(inputEl.form, targetCtx)\n let refGenerator = () => this.putRef([inputEl, inputEl.form], \"change\", opts)\n let formData\n if(inputEl.getAttribute(this.binding(\"change\"))){\n formData = serializeForm(inputEl.form, {_target: opts._target}, [inputEl.name])\n } else {\n formData = serializeForm(inputEl.form, {_target: opts._target})\n }\n if(DOM.isUploadInput(inputEl) && inputEl.files && inputEl.files.length > 0){\n LiveUploader.trackFiles(inputEl, Array.from(inputEl.files))\n }\n uploads = LiveUploader.serializeUploads(inputEl)\n let event = {\n type: \"form\",\n event: phxEvent,\n value: formData,\n uploads: uploads,\n cid: cid\n }\n this.pushWithReply(refGenerator, \"event\", event, resp => {\n DOM.showError(inputEl, this.liveSocket.binding(PHX_FEEDBACK_FOR))\n if(DOM.isUploadInput(inputEl) && inputEl.getAttribute(\"data-phx-auto-upload\") !== null){\n if(LiveUploader.filesAwaitingPreflight(inputEl).length > 0){\n let [ref, _els] = refGenerator()\n this.uploadFiles(inputEl.form, targetCtx, ref, cid, (_uploads) => {\n callback && callback(resp)\n this.triggerAwaitingSubmit(inputEl.form)\n })\n }\n } else {\n callback && callback(resp)\n }\n })\n }\n\n triggerAwaitingSubmit(formEl){\n let awaitingSubmit = this.getScheduledSubmit(formEl)\n if(awaitingSubmit){\n let [_el, _ref, _opts, callback] = awaitingSubmit\n this.cancelSubmit(formEl)\n callback()\n }\n }\n\n getScheduledSubmit(formEl){\n return this.formSubmits.find(([el, _ref, _opts, _callback]) => el.isSameNode(formEl))\n }\n\n scheduleSubmit(formEl, ref, opts, callback){\n if(this.getScheduledSubmit(formEl)){ return true }\n this.formSubmits.push([formEl, ref, opts, callback])\n }\n\n cancelSubmit(formEl){\n this.formSubmits = this.formSubmits.filter(([el, ref, _callback]) => {\n if(el.isSameNode(formEl)){\n this.undoRefs(ref)\n return false\n } else {\n return true\n }\n })\n }\n\n disableForm(formEl, opts = {}){\n let filterIgnored = el => {\n let userIgnored = closestPhxBinding(el, `${this.binding(PHX_UPDATE)}=ignore`, el.form)\n return !(userIgnored || closestPhxBinding(el, \"data-phx-update=ignore\", el.form))\n }\n let filterDisables = el => {\n return el.hasAttribute(this.binding(PHX_DISABLE_WITH))\n }\n let filterButton = el => el.tagName == \"BUTTON\"\n\n let filterInput = el => [\"INPUT\", \"TEXTAREA\", \"SELECT\"].includes(el.tagName)\n\n let formElements = Array.from(formEl.elements)\n let disables = formElements.filter(filterDisables)\n let buttons = formElements.filter(filterButton).filter(filterIgnored)\n let inputs = formElements.filter(filterInput).filter(filterIgnored)\n\n buttons.forEach(button => {\n button.setAttribute(PHX_DISABLED, button.disabled)\n button.disabled = true\n })\n inputs.forEach(input => {\n input.setAttribute(PHX_READONLY, input.readOnly)\n input.readOnly = true\n if(input.files){\n input.setAttribute(PHX_DISABLED, input.disabled)\n input.disabled = true\n }\n })\n formEl.setAttribute(this.binding(PHX_PAGE_LOADING), \"\")\n return this.putRef([formEl].concat(disables).concat(buttons).concat(inputs), \"submit\", opts)\n }\n\n pushFormSubmit(formEl, targetCtx, phxEvent, opts, onReply){\n let refGenerator = () => this.disableForm(formEl, opts)\n let cid = this.targetComponentID(formEl, targetCtx)\n if(LiveUploader.hasUploadsInProgress(formEl)){\n let [ref, _els] = refGenerator()\n let push = () => this.pushFormSubmit(formEl, targetCtx, phxEvent, opts, onReply)\n return this.scheduleSubmit(formEl, ref, opts, push)\n } else if(LiveUploader.inputsAwaitingPreflight(formEl).length > 0){\n let [ref, els] = refGenerator()\n let proxyRefGen = () => [ref, els, opts]\n this.uploadFiles(formEl, targetCtx, ref, cid, (_uploads) => {\n let formData = serializeForm(formEl, {})\n this.pushWithReply(proxyRefGen, \"event\", {\n type: \"form\",\n event: phxEvent,\n value: formData,\n cid: cid\n }, onReply)\n })\n } else {\n let formData = serializeForm(formEl, {})\n this.pushWithReply(refGenerator, \"event\", {\n type: \"form\",\n event: phxEvent,\n value: formData,\n cid: cid\n }, onReply)\n }\n }\n\n uploadFiles(formEl, targetCtx, ref, cid, onComplete){\n let joinCountAtUpload = this.joinCount\n let inputEls = LiveUploader.activeFileInputs(formEl)\n let numFileInputsInProgress = inputEls.length\n\n // get each file input\n inputEls.forEach(inputEl => {\n let uploader = new LiveUploader(inputEl, this, () => {\n numFileInputsInProgress--\n if(numFileInputsInProgress === 0){ onComplete() }\n });\n\n this.uploaders[inputEl] = uploader\n let entries = uploader.entries().map(entry => entry.toPreflightPayload())\n\n let payload = {\n ref: inputEl.getAttribute(PHX_UPLOAD_REF),\n entries: entries,\n cid: this.targetComponentID(inputEl.form, targetCtx)\n }\n\n this.log(\"upload\", () => [\"sending preflight request\", payload])\n\n this.pushWithReply(null, \"allow_upload\", payload, resp => {\n this.log(\"upload\", () => [\"got preflight response\", resp])\n if(resp.error){\n this.undoRefs(ref)\n let [entry_ref, reason] = resp.error\n this.log(\"upload\", () => [`error for entry ${entry_ref}`, reason])\n } else {\n let onError = (callback) => {\n this.channel.onError(() => {\n if(this.joinCount === joinCountAtUpload){ callback() }\n })\n }\n uploader.initAdapterUpload(resp, onError, this.liveSocket)\n }\n })\n })\n }\n\n dispatchUploads(name, filesOrBlobs){\n let inputs = DOM.findUploadInputs(this.el).filter(el => el.name === name)\n if(inputs.length === 0){ logError(`no live file inputs found matching the name \"${name}\"`) }\n else if(inputs.length > 1){ logError(`duplicate live file inputs found matching the name \"${name}\"`) }\n else { DOM.dispatchEvent(inputs[0], PHX_TRACK_UPLOADS, {detail: {files: filesOrBlobs}}) }\n }\n\n pushFormRecovery(form, newCid, callback){\n this.liveSocket.withinOwners(form, (view, targetCtx) => {\n let input = Array.from(form.elements).find(el => {\n return DOM.isFormInput(el) && el.type !== \"hidden\" && !el.hasAttribute(this.binding(\"change\"))\n })\n let phxEvent = form.getAttribute(this.binding(PHX_AUTO_RECOVER)) || form.getAttribute(this.binding(\"change\"))\n\n JS.exec(\"change\", phxEvent, view, input, [\"push\", {_target: input.name, newCid: newCid, callback: callback}])\n })\n }\n\n pushLinkPatch(href, targetEl, callback){\n let linkRef = this.liveSocket.setPendingLink(href)\n let refGen = targetEl ? () => this.putRef([targetEl], \"click\") : null\n let fallback = () => this.liveSocket.redirect(window.location.href)\n\n let push = this.pushWithReply(refGen, \"live_patch\", {url: href}, resp => {\n this.liveSocket.requestDOMUpdate(() => {\n if(resp.link_redirect){\n this.liveSocket.replaceMain(href, null, callback, linkRef)\n } else {\n if(this.liveSocket.commitPendingLink(linkRef)){\n this.href = href\n }\n this.applyPendingUpdates()\n callback && callback(linkRef)\n }\n })\n })\n\n if(push){\n push.receive(\"timeout\", fallback)\n } else {\n fallback()\n }\n }\n\n formsForRecovery(html){\n if(this.joinCount === 0){ return [] }\n\n let phxChange = this.binding(\"change\")\n let template = document.createElement(\"template\")\n template.innerHTML = html\n\n return (\n DOM.all(this.el, `form[${phxChange}]`)\n .filter(form => form.id && this.ownsElement(form))\n .filter(form => form.elements.length > 0)\n .filter(form => form.getAttribute(this.binding(PHX_AUTO_RECOVER)) !== \"ignore\")\n .map(form => {\n let newForm = template.content.querySelector(`form[id=\"${form.id}\"][${phxChange}=\"${form.getAttribute(phxChange)}\"]`)\n if(newForm){\n return [form, newForm, this.targetComponentID(newForm)]\n } else {\n return [form, null, null]\n }\n })\n .filter(([form, newForm, newCid]) => newForm)\n )\n }\n\n maybePushComponentsDestroyed(destroyedCIDs){\n let willDestroyCIDs = destroyedCIDs.filter(cid => {\n return DOM.findComponentNodeList(this.el, cid).length === 0\n })\n if(willDestroyCIDs.length > 0){\n this.pruningCIDs.push(...willDestroyCIDs)\n\n this.pushWithReply(null, \"cids_will_destroy\", {cids: willDestroyCIDs}, () => {\n // The cids are either back on the page or they will be fully removed,\n // so we can remove them from the pruningCIDs.\n this.pruningCIDs = this.pruningCIDs.filter(cid => willDestroyCIDs.indexOf(cid) !== -1)\n\n // See if any of the cids we wanted to destroy were added back,\n // if they were added back, we don't actually destroy them.\n let completelyDestroyCIDs = willDestroyCIDs.filter(cid => {\n return DOM.findComponentNodeList(this.el, cid).length === 0\n })\n\n if(completelyDestroyCIDs.length > 0){\n this.pushWithReply(null, \"cids_destroyed\", {cids: completelyDestroyCIDs}, (resp) => {\n this.rendered.pruneCIDs(resp.cids)\n })\n }\n })\n }\n }\n\n ownsElement(el){\n let parentViewEl = el.closest(PHX_VIEW_SELECTOR)\n return el.getAttribute(PHX_PARENT_ID) === this.id ||\n (parentViewEl && parentViewEl.id === this.id) ||\n (!parentViewEl && this.isDead)\n }\n\n submitForm(form, targetCtx, phxEvent, opts = {}){\n DOM.putPrivate(form, PHX_HAS_SUBMITTED, true)\n let phxFeedback = this.liveSocket.binding(PHX_FEEDBACK_FOR)\n let inputs = Array.from(form.elements)\n inputs.forEach(input => DOM.putPrivate(input, PHX_HAS_SUBMITTED, true))\n this.liveSocket.blurActiveElement(this)\n this.pushFormSubmit(form, targetCtx, phxEvent, opts, () => {\n inputs.forEach(input => DOM.showError(input, phxFeedback))\n this.liveSocket.restorePreviouslyActiveFocus()\n })\n }\n\n binding(kind){ return this.liveSocket.binding(kind) }\n}\n", "/** Initializes the LiveSocket\n *\n *\n * @param {string} endPoint - The string WebSocket endpoint, ie, `\"wss://example.com/live\"`,\n * `\"/live\"` (inherited host & protocol)\n * @param {Phoenix.Socket} socket - the required Phoenix Socket class imported from \"phoenix\". For example:\n *\n * import {Socket} from \"phoenix\"\n * import {LiveSocket} from \"phoenix_live_view\"\n * let liveSocket = new LiveSocket(\"/live\", Socket, {...})\n *\n * @param {Object} [opts] - Optional configuration. Outside of keys listed below, all\n * configuration is passed directly to the Phoenix Socket constructor.\n * @param {Object} [opts.defaults] - The optional defaults to use for various bindings,\n * such as `phx-debounce`. Supports the following keys:\n *\n * - debounce - the millisecond phx-debounce time. Defaults 300\n * - throttle - the millisecond phx-throttle time. Defaults 300\n *\n * @param {Function} [opts.params] - The optional function for passing connect params.\n * The function receives the element associated with a given LiveView. For example:\n *\n * (el) => {view: el.getAttribute(\"data-my-view-name\", token: window.myToken}\n *\n * @param {string} [opts.bindingPrefix] - The optional prefix to use for all phx DOM annotations.\n * Defaults to \"phx-\".\n * @param {Object} [opts.hooks] - The optional object for referencing LiveView hook callbacks.\n * @param {Object} [opts.uploaders] - The optional object for referencing LiveView uploader callbacks.\n * @param {integer} [opts.loaderTimeout] - The optional delay in milliseconds to wait before apply\n * loading states.\n * @param {integer} [opts.maxReloads] - The maximum reloads before entering failsafe mode.\n * @param {integer} [opts.reloadJitterMin] - The minimum time between normal reload attempts.\n * @param {integer} [opts.reloadJitterMax] - The maximum time between normal reload attempts.\n * @param {integer} [opts.failsafeJitter] - The time between reload attempts in failsafe mode.\n * @param {Function} [opts.viewLogger] - The optional function to log debug information. For example:\n *\n * (view, kind, msg, obj) => console.log(`${view.id} ${kind}: ${msg} - `, obj)\n *\n * @param {Object} [opts.metadata] - The optional object mapping event names to functions for\n * populating event metadata. For example:\n *\n * metadata: {\n * click: (e, el) => {\n * return {\n * ctrlKey: e.ctrlKey,\n * metaKey: e.metaKey,\n * detail: e.detail || 1,\n * }\n * },\n * keydown: (e, el) => {\n * return {\n * key: e.key,\n * ctrlKey: e.ctrlKey,\n * metaKey: e.metaKey,\n * shiftKey: e.shiftKey\n * }\n * }\n * }\n * @param {Object} [opts.sessionStorage] - An optional Storage compatible object\n * Useful when LiveView won't have access to `sessionStorage`. For example, This could\n * happen if a site loads a cross-domain LiveView in an iframe. Example usage:\n *\n * class InMemoryStorage {\n * constructor() { this.storage = {} }\n * getItem(keyName) { return this.storage[keyName] || null }\n * removeItem(keyName) { delete this.storage[keyName] }\n * setItem(keyName, keyValue) { this.storage[keyName] = keyValue }\n * }\n *\n * @param {Object} [opts.localStorage] - An optional Storage compatible object\n * Useful for when LiveView won't have access to `localStorage`.\n * See `opts.sessionStorage` for examples.\n*/\n\nimport {\n BINDING_PREFIX,\n CONSECUTIVE_RELOADS,\n DEFAULTS,\n FAILSAFE_JITTER,\n LOADER_TIMEOUT,\n MAX_RELOADS,\n PHX_DEBOUNCE,\n PHX_DROP_TARGET,\n PHX_HAS_FOCUSED,\n PHX_KEY,\n PHX_LINK_STATE,\n PHX_LIVE_LINK,\n PHX_LV_DEBUG,\n PHX_LV_LATENCY_SIM,\n PHX_LV_PROFILE,\n PHX_MAIN,\n PHX_PARENT_ID,\n PHX_VIEW_SELECTOR,\n PHX_ROOT_ID,\n PHX_THROTTLE,\n PHX_TRACK_UPLOADS,\n PHX_SESSION,\n PHX_FEEDBACK_FOR,\n RELOAD_JITTER_MIN,\n RELOAD_JITTER_MAX,\n} from \"./constants\"\n\nimport {\n clone,\n closestPhxBinding,\n closure,\n debug,\n isObject,\n maybe\n} from \"./utils\"\n\nimport Browser from \"./browser\"\nimport DOM from \"./dom\"\nimport Hooks from \"./hooks\"\nimport LiveUploader from \"./live_uploader\"\nimport View from \"./view\"\nimport JS from \"./js\"\n\nexport default class LiveSocket {\n constructor(url, phxSocket, opts = {}){\n this.unloaded = false\n if(!phxSocket || phxSocket.constructor.name === \"Object\"){\n throw new Error(`\n a phoenix Socket must be provided as the second argument to the LiveSocket constructor. For example:\n\n import {Socket} from \"phoenix\"\n import {LiveSocket} from \"phoenix_live_view\"\n let liveSocket = new LiveSocket(\"/live\", Socket, {...})\n `)\n }\n this.socket = new phxSocket(url, opts)\n this.bindingPrefix = opts.bindingPrefix || BINDING_PREFIX\n this.opts = opts\n this.params = closure(opts.params || {})\n this.viewLogger = opts.viewLogger\n this.metadataCallbacks = opts.metadata || {}\n this.defaults = Object.assign(clone(DEFAULTS), opts.defaults || {})\n this.activeElement = null\n this.prevActive = null\n this.silenced = false\n this.main = null\n this.outgoingMainEl = null\n this.clickStartedAtTarget = null\n this.linkRef = 1\n this.roots = {}\n this.href = window.location.href\n this.pendingLink = null\n this.currentLocation = clone(window.location)\n this.hooks = opts.hooks || {}\n this.uploaders = opts.uploaders || {}\n this.loaderTimeout = opts.loaderTimeout || LOADER_TIMEOUT\n this.reloadWithJitterTimer = null\n this.maxReloads = opts.maxReloads || MAX_RELOADS\n this.reloadJitterMin = opts.reloadJitterMin || RELOAD_JITTER_MIN\n this.reloadJitterMax = opts.reloadJitterMax || RELOAD_JITTER_MAX\n this.failsafeJitter = opts.failsafeJitter || FAILSAFE_JITTER\n this.localStorage = opts.localStorage || window.localStorage\n this.sessionStorage = opts.sessionStorage || window.sessionStorage\n this.boundTopLevelEvents = false\n this.domCallbacks = Object.assign({onNodeAdded: closure(), onBeforeElUpdated: closure()}, opts.dom || {})\n this.transitions = new TransitionSet()\n window.addEventListener(\"pagehide\", _e => {\n this.unloaded = true\n })\n this.socket.onOpen(() => {\n if(this.isUnloaded()){\n // reload page if being restored from back/forward cache and browser does not emit \"pageshow\"\n window.location.reload()\n }\n })\n }\n\n // public\n\n isProfileEnabled(){ return this.sessionStorage.getItem(PHX_LV_PROFILE) === \"true\" }\n\n isDebugEnabled(){ return this.sessionStorage.getItem(PHX_LV_DEBUG) === \"true\" }\n\n isDebugDisabled(){ return this.sessionStorage.getItem(PHX_LV_DEBUG) === \"false\" }\n\n enableDebug(){ this.sessionStorage.setItem(PHX_LV_DEBUG, \"true\") }\n\n enableProfiling(){ this.sessionStorage.setItem(PHX_LV_PROFILE, \"true\") }\n\n disableDebug(){ this.sessionStorage.setItem(PHX_LV_DEBUG, \"false\") }\n\n disableProfiling(){ this.sessionStorage.removeItem(PHX_LV_PROFILE) }\n\n enableLatencySim(upperBoundMs){\n this.enableDebug()\n console.log(\"latency simulator enabled for the duration of this browser session. Call disableLatencySim() to disable\")\n this.sessionStorage.setItem(PHX_LV_LATENCY_SIM, upperBoundMs)\n }\n\n disableLatencySim(){ this.sessionStorage.removeItem(PHX_LV_LATENCY_SIM) }\n\n getLatencySim(){\n let str = this.sessionStorage.getItem(PHX_LV_LATENCY_SIM)\n return str ? parseInt(str) : null\n }\n\n getSocket(){ return this.socket }\n\n connect(){\n // enable debug by default if on localhost and not explicitly disabled\n if(window.location.hostname === \"localhost\" && !this.isDebugDisabled()){ this.enableDebug() }\n let doConnect = () => {\n if(this.joinRootViews()){\n this.bindTopLevelEvents()\n this.socket.connect()\n } else if(this.main){\n this.socket.connect()\n } else {\n this.bindTopLevelEvents({dead: true})\n }\n this.joinDeadView()\n }\n if([\"complete\", \"loaded\", \"interactive\"].indexOf(document.readyState) >= 0){\n doConnect()\n } else {\n document.addEventListener(\"DOMContentLoaded\", () => doConnect())\n }\n }\n\n disconnect(callback){\n clearTimeout(this.reloadWithJitterTimer)\n this.socket.disconnect(callback)\n }\n\n replaceTransport(transport){\n clearTimeout(this.reloadWithJitterTimer)\n this.socket.replaceTransport(transport)\n this.connect()\n }\n\n execJS(el, encodedJS, eventType = null){\n this.owner(el, view => JS.exec(eventType, encodedJS, view, el))\n }\n\n // private\n\n unload(){\n if(this.unloaded){ return }\n if(this.main && this.isConnected()){ this.log(this.main, \"socket\", () => [\"disconnect for page nav\"]) }\n this.unloaded = true\n this.destroyAllViews()\n this.disconnect()\n }\n\n triggerDOM(kind, args){ this.domCallbacks[kind](...args) }\n\n time(name, func){\n if(!this.isProfileEnabled() || !console.time){ return func() }\n console.time(name)\n let result = func()\n console.timeEnd(name)\n return result\n }\n\n log(view, kind, msgCallback){\n if(this.viewLogger){\n let [msg, obj] = msgCallback()\n this.viewLogger(view, kind, msg, obj)\n } else if(this.isDebugEnabled()){\n let [msg, obj] = msgCallback()\n debug(view, kind, msg, obj)\n }\n }\n\n requestDOMUpdate(callback){\n this.transitions.after(callback)\n }\n\n transition(time, onStart, onDone = function(){}){\n this.transitions.addTransition(time, onStart, onDone)\n }\n\n onChannel(channel, event, cb){\n channel.on(event, data => {\n let latency = this.getLatencySim()\n if(!latency){\n cb(data)\n } else {\n setTimeout(() => cb(data), latency)\n }\n })\n }\n\n wrapPush(view, opts, push){\n let latency = this.getLatencySim()\n let oldJoinCount = view.joinCount\n if(!latency){\n if(this.isConnected() && opts.timeout){\n return push().receive(\"timeout\", () => {\n if(view.joinCount === oldJoinCount && !view.isDestroyed()){\n this.reloadWithJitter(view, () => {\n this.log(view, \"timeout\", () => [\"received timeout while communicating with server. Falling back to hard refresh for recovery\"])\n })\n }\n })\n } else {\n return push()\n }\n }\n\n let fakePush = {\n receives: [],\n receive(kind, cb){ this.receives.push([kind, cb]) }\n }\n setTimeout(() => {\n if(view.isDestroyed()){ return }\n fakePush.receives.reduce((acc, [kind, cb]) => acc.receive(kind, cb), push())\n }, latency)\n return fakePush\n }\n\n reloadWithJitter(view, log){\n clearTimeout(this.reloadWithJitterTimer)\n this.disconnect()\n let minMs = this.reloadJitterMin\n let maxMs = this.reloadJitterMax\n let afterMs = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs\n let tries = Browser.updateLocal(this.localStorage, window.location.pathname, CONSECUTIVE_RELOADS, 0, count => count + 1)\n if(tries > this.maxReloads){\n afterMs = this.failsafeJitter\n }\n this.reloadWithJitterTimer = setTimeout(() => {\n // if view has recovered, such as transport replaced, then cancel\n if(view.isDestroyed() || view.isConnected()){ return }\n view.destroy()\n log ? log() : this.log(view, \"join\", () => [`encountered ${tries} consecutive reloads`])\n if(tries > this.maxReloads){\n this.log(view, \"join\", () => [`exceeded ${this.maxReloads} consecutive reloads. Entering failsafe mode`])\n }\n if(this.hasPendingLink()){\n window.location = this.pendingLink\n } else {\n window.location.reload()\n }\n }, afterMs)\n }\n\n getHookCallbacks(name){\n return name && name.startsWith(\"Phoenix.\") ? Hooks[name.split(\".\")[1]] : this.hooks[name]\n }\n\n isUnloaded(){ return this.unloaded }\n\n isConnected(){ return this.socket.isConnected() }\n\n getBindingPrefix(){ return this.bindingPrefix }\n\n binding(kind){ return `${this.getBindingPrefix()}${kind}` }\n\n channel(topic, params){ return this.socket.channel(topic, params) }\n\n joinDeadView(){\n let body = document.body\n if(body && !this.isPhxView(body) && !this.isPhxView(document.firstElementChild)){\n let view = this.newRootView(body)\n view.setHref(this.getHref())\n view.joinDead()\n if(!this.main){ this.main = view }\n window.requestAnimationFrame(() => view.execNewMounted())\n }\n }\n\n joinRootViews(){\n let rootsFound = false\n DOM.all(document, `${PHX_VIEW_SELECTOR}:not([${PHX_PARENT_ID}])`, rootEl => {\n if(!this.getRootById(rootEl.id)){\n let view = this.newRootView(rootEl)\n view.setHref(this.getHref())\n view.join()\n if(rootEl.hasAttribute(PHX_MAIN)){ this.main = view }\n }\n rootsFound = true\n })\n return rootsFound\n }\n\n redirect(to, flash){\n this.unload()\n Browser.redirect(to, flash)\n }\n\n replaceMain(href, flash, callback = null, linkRef = this.setPendingLink(href)){\n let liveReferer = this.currentLocation.href\n this.outgoingMainEl = this.outgoingMainEl || this.main.el\n let newMainEl = DOM.cloneNode(this.outgoingMainEl, \"\")\n this.main.showLoader(this.loaderTimeout)\n this.main.destroy()\n\n this.main = this.newRootView(newMainEl, flash, liveReferer)\n this.main.setRedirect(href)\n this.transitionRemoves()\n this.main.join((joinCount, onDone) => {\n if(joinCount === 1 && this.commitPendingLink(linkRef)){\n this.requestDOMUpdate(() => {\n DOM.findPhxSticky(document).forEach(el => newMainEl.appendChild(el))\n this.outgoingMainEl.replaceWith(newMainEl)\n this.outgoingMainEl = null\n callback && requestAnimationFrame(callback)\n onDone()\n })\n }\n })\n }\n\n transitionRemoves(elements){\n let removeAttr = this.binding(\"remove\")\n elements = elements || DOM.all(document, `[${removeAttr}]`)\n elements.forEach(el => {\n if(document.body.contains(el)){ // skip children already removed\n this.execJS(el, el.getAttribute(removeAttr), \"remove\")\n }\n })\n }\n\n isPhxView(el){ return el.getAttribute && el.getAttribute(PHX_SESSION) !== null }\n\n newRootView(el, flash, liveReferer){\n let view = new View(el, this, null, flash, liveReferer)\n this.roots[view.id] = view\n return view\n }\n\n owner(childEl, callback){\n let view = maybe(childEl.closest(PHX_VIEW_SELECTOR), el => this.getViewByEl(el)) || this.main\n if(view){ callback(view) }\n }\n\n withinOwners(childEl, callback){\n this.owner(childEl, view => callback(view, childEl))\n }\n\n getViewByEl(el){\n let rootId = el.getAttribute(PHX_ROOT_ID)\n return maybe(this.getRootById(rootId), root => root.getDescendentByEl(el))\n }\n\n getRootById(id){ return this.roots[id] }\n\n destroyAllViews(){\n for(let id in this.roots){\n this.roots[id].destroy()\n delete this.roots[id]\n }\n this.main = null\n }\n\n destroyViewByEl(el){\n let root = this.getRootById(el.getAttribute(PHX_ROOT_ID))\n if(root && root.id === el.id){\n root.destroy()\n delete this.roots[root.id]\n } else if(root){\n root.destroyDescendent(el.id)\n }\n }\n\n setActiveElement(target){\n if(this.activeElement === target){ return }\n this.activeElement = target\n let cancel = () => {\n if(target === this.activeElement){ this.activeElement = null }\n target.removeEventListener(\"mouseup\", this)\n target.removeEventListener(\"touchend\", this)\n }\n target.addEventListener(\"mouseup\", cancel)\n target.addEventListener(\"touchend\", cancel)\n }\n\n getActiveElement(){\n if(document.activeElement === document.body){\n return this.activeElement || document.activeElement\n } else {\n // document.activeElement can be null in Internet Explorer 11\n return document.activeElement || document.body\n }\n }\n\n dropActiveElement(view){\n if(this.prevActive && view.ownsElement(this.prevActive)){\n this.prevActive = null\n }\n }\n\n restorePreviouslyActiveFocus(){\n if(this.prevActive && this.prevActive !== document.body){\n this.prevActive.focus()\n }\n }\n\n blurActiveElement(){\n this.prevActive = this.getActiveElement()\n if(this.prevActive !== document.body){ this.prevActive.blur() }\n }\n\n bindTopLevelEvents({dead} = {}){\n if(this.boundTopLevelEvents){ return }\n\n this.boundTopLevelEvents = true\n // enter failsafe reload if server has gone away intentionally, such as \"disconnect\" broadcast\n this.socket.onClose(event => {\n // unload when navigating href or form submit (such as for firefox)\n if(event && event.code === 1001){ return this.unload() }\n // failsafe reload if normal closure and we still have a main LV\n if(event && event.code === 1000 && this.main){ return this.reloadWithJitter(this.main) }\n })\n document.body.addEventListener(\"click\", function (){ }) // ensure all click events bubble for mobile Safari\n window.addEventListener(\"pageshow\", e => {\n if(e.persisted){ // reload page if being restored from back/forward cache\n this.getSocket().disconnect()\n this.withPageLoading({to: window.location.href, kind: \"redirect\"})\n window.location.reload()\n }\n }, true)\n if(!dead){ this.bindNav() }\n this.bindClicks()\n if(!dead){ this.bindForms() }\n this.bind({keyup: \"keyup\", keydown: \"keydown\"}, (e, type, view, targetEl, phxEvent, eventTarget) => {\n let matchKey = targetEl.getAttribute(this.binding(PHX_KEY))\n let pressedKey = e.key && e.key.toLowerCase() // chrome clicked autocompletes send a keydown without key\n if(matchKey && matchKey.toLowerCase() !== pressedKey){ return }\n\n let data = {key: e.key, ...this.eventMeta(type, e, targetEl)}\n JS.exec(type, phxEvent, view, targetEl, [\"push\", {data}])\n })\n this.bind({blur: \"focusout\", focus: \"focusin\"}, (e, type, view, targetEl, phxEvent, eventTarget) => {\n if(!eventTarget){\n let data = {key: e.key, ...this.eventMeta(type, e, targetEl)}\n JS.exec(type, phxEvent, view, targetEl, [\"push\", {data}])\n }\n })\n this.bind({blur: \"blur\", focus: \"focus\"}, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {\n // blur and focus are triggered on document and window. Discard one to avoid dups\n if(phxTarget === \"window\"){\n let data = this.eventMeta(type, e, targetEl)\n JS.exec(type, phxEvent, view, targetEl, [\"push\", {data}])\n }\n })\n window.addEventListener(\"dragover\", e => e.preventDefault())\n window.addEventListener(\"drop\", e => {\n e.preventDefault()\n let dropTargetId = maybe(closestPhxBinding(e.target, this.binding(PHX_DROP_TARGET)), trueTarget => {\n return trueTarget.getAttribute(this.binding(PHX_DROP_TARGET))\n })\n let dropTarget = dropTargetId && document.getElementById(dropTargetId)\n let files = Array.from(e.dataTransfer.files || [])\n if(!dropTarget || dropTarget.disabled || files.length === 0 || !(dropTarget.files instanceof FileList)){ return }\n\n LiveUploader.trackFiles(dropTarget, files, e.dataTransfer)\n dropTarget.dispatchEvent(new Event(\"input\", {bubbles: true}))\n })\n this.on(PHX_TRACK_UPLOADS, e => {\n let uploadTarget = e.target\n if(!DOM.isUploadInput(uploadTarget)){ return }\n let files = Array.from(e.detail.files || []).filter(f => f instanceof File || f instanceof Blob)\n LiveUploader.trackFiles(uploadTarget, files)\n uploadTarget.dispatchEvent(new Event(\"input\", {bubbles: true}))\n })\n }\n\n eventMeta(eventName, e, targetEl){\n let callback = this.metadataCallbacks[eventName]\n return callback ? callback(e, targetEl) : {}\n }\n\n setPendingLink(href){\n this.linkRef++\n this.pendingLink = href\n return this.linkRef\n }\n\n commitPendingLink(linkRef){\n if(this.linkRef !== linkRef){\n return false\n } else {\n this.href = this.pendingLink\n this.pendingLink = null\n return true\n }\n }\n\n getHref(){ return this.href }\n\n hasPendingLink(){ return !!this.pendingLink }\n\n bind(events, callback){\n for(let event in events){\n let browserEventName = events[event]\n\n this.on(browserEventName, e => {\n let binding = this.binding(event)\n let windowBinding = this.binding(`window-${event}`)\n let targetPhxEvent = e.target.getAttribute && e.target.getAttribute(binding)\n if(targetPhxEvent){\n this.debounce(e.target, e, browserEventName, () => {\n this.withinOwners(e.target, view => {\n callback(e, event, view, e.target, targetPhxEvent, null)\n })\n })\n } else {\n DOM.all(document, `[${windowBinding}]`, el => {\n let phxEvent = el.getAttribute(windowBinding)\n this.debounce(el, e, browserEventName, () => {\n this.withinOwners(el, view => {\n callback(e, event, view, el, phxEvent, \"window\")\n })\n })\n })\n }\n })\n }\n }\n\n bindClicks(){\n window.addEventListener(\"click\", e => this.clickStartedAtTarget = e.target)\n this.bindClick(\"click\", \"click\", false)\n this.bindClick(\"mousedown\", \"capture-click\", true)\n }\n\n bindClick(eventName, bindingName, capture){\n let click = this.binding(bindingName)\n window.addEventListener(eventName, e => {\n let target = null\n if(capture){\n target = e.target.matches(`[${click}]`) ? e.target : e.target.querySelector(`[${click}]`)\n } else {\n let clickStartedAtTarget = this.clickStartedAtTarget || e.target\n target = closestPhxBinding(clickStartedAtTarget, click)\n this.dispatchClickAway(e, clickStartedAtTarget)\n this.clickStartedAtTarget = null\n }\n let phxEvent = target && target.getAttribute(click)\n if(!phxEvent){\n let href = e.target instanceof HTMLAnchorElement ? e.target.getAttribute(\"href\") : null\n if(!capture && href !== null && !DOM.wantsNewTab(e) && DOM.isNewPageHref(href, window.location)){\n this.unload()\n }\n return\n }\n if(target.getAttribute(\"href\") === \"#\"){ e.preventDefault() }\n\n this.debounce(target, e, \"click\", () => {\n this.withinOwners(target, view => {\n JS.exec(\"click\", phxEvent, view, target, [\"push\", {data: this.eventMeta(\"click\", e, target)}])\n })\n })\n }, capture)\n }\n\n dispatchClickAway(e, clickStartedAt){\n let phxClickAway = this.binding(\"click-away\")\n DOM.all(document, `[${phxClickAway}]`, el => {\n if(!(el.isSameNode(clickStartedAt) || el.contains(clickStartedAt))){\n this.withinOwners(e.target, view => {\n let phxEvent = el.getAttribute(phxClickAway)\n if(JS.isVisible(el)){\n JS.exec(\"click\", phxEvent, view, el, [\"push\", {data: this.eventMeta(\"click\", e, e.target)}])\n }\n })\n }\n })\n }\n\n bindNav(){\n if(!Browser.canPushState()){ return }\n if(history.scrollRestoration){ history.scrollRestoration = \"manual\" }\n let scrollTimer = null\n window.addEventListener(\"scroll\", _e => {\n clearTimeout(scrollTimer)\n scrollTimer = setTimeout(() => {\n Browser.updateCurrentState(state => Object.assign(state, {scroll: window.scrollY}))\n }, 100)\n })\n window.addEventListener(\"popstate\", event => {\n if(!this.registerNewLocation(window.location)){ return }\n let {type, id, root, scroll} = event.state || {}\n let href = window.location.href\n\n this.requestDOMUpdate(() => {\n if(this.main.isConnected() && (type === \"patch\" && id === this.main.id)){\n this.main.pushLinkPatch(href, null, () => {\n this.maybeScroll(scroll)\n })\n } else {\n this.replaceMain(href, null, () => {\n if(root){ this.replaceRootHistory() }\n this.maybeScroll(scroll)\n })\n }\n })\n }, false)\n window.addEventListener(\"click\", e => {\n let target = closestPhxBinding(e.target, PHX_LIVE_LINK)\n let type = target && target.getAttribute(PHX_LIVE_LINK)\n if(!type || !this.isConnected() || !this.main || DOM.wantsNewTab(e)){ return }\n\n let href = target.href\n let linkState = target.getAttribute(PHX_LINK_STATE)\n e.preventDefault()\n e.stopImmediatePropagation() // do not bubble click to regular phx-click bindings\n if(this.pendingLink === href){ return }\n\n this.requestDOMUpdate(() => {\n if(type === \"patch\"){\n this.pushHistoryPatch(href, linkState, target)\n } else if(type === \"redirect\"){\n this.historyRedirect(href, linkState)\n } else {\n throw new Error(`expected ${PHX_LIVE_LINK} to be \"patch\" or \"redirect\", got: ${type}`)\n }\n let phxClick = target.getAttribute(this.binding(\"click\"))\n if(phxClick){\n this.requestDOMUpdate(() => this.execJS(target, phxClick, \"click\"))\n }\n })\n }, false)\n }\n\n maybeScroll(scroll) {\n if(typeof(scroll) === \"number\"){\n requestAnimationFrame(() => {\n window.scrollTo(0, scroll)\n }) // the body needs to render before we scroll.\n }\n }\n\n dispatchEvent(event, payload = {}){\n DOM.dispatchEvent(window, `phx:${event}`, {detail: payload})\n }\n\n dispatchEvents(events){\n events.forEach(([event, payload]) => this.dispatchEvent(event, payload))\n }\n\n withPageLoading(info, callback){\n DOM.dispatchEvent(window, \"phx:page-loading-start\", {detail: info})\n let done = () => DOM.dispatchEvent(window, \"phx:page-loading-stop\", {detail: info})\n return callback ? callback(done) : done\n }\n\n pushHistoryPatch(href, linkState, targetEl){\n if(!this.isConnected()){ return Browser.redirect(href) }\n\n this.withPageLoading({to: href, kind: \"patch\"}, done => {\n this.main.pushLinkPatch(href, targetEl, linkRef => {\n this.historyPatch(href, linkState, linkRef)\n done()\n })\n })\n }\n\n historyPatch(href, linkState, linkRef = this.setPendingLink(href)){\n if(!this.commitPendingLink(linkRef)){ return }\n\n Browser.pushState(linkState, {type: \"patch\", id: this.main.id}, href)\n this.registerNewLocation(window.location)\n }\n\n historyRedirect(href, linkState, flash){\n // convert to full href if only path prefix\n if(!this.isConnected()){ return Browser.redirect(href, flash) }\n if(/^\\/$|^\\/[^\\/]+.*$/.test(href)){\n let {protocol, host} = window.location\n href = `${protocol}//${host}${href}`\n }\n let scroll = window.scrollY\n this.withPageLoading({to: href, kind: \"redirect\"}, done => {\n this.replaceMain(href, flash, () => {\n Browser.pushState(linkState, {type: \"redirect\", id: this.main.id, scroll: scroll}, href)\n this.registerNewLocation(window.location)\n done()\n })\n })\n }\n\n replaceRootHistory(){\n Browser.pushState(\"replace\", {root: true, type: \"patch\", id: this.main.id})\n }\n\n registerNewLocation(newLocation){\n let {pathname, search} = this.currentLocation\n if(pathname + search === newLocation.pathname + newLocation.search){\n return false\n } else {\n this.currentLocation = clone(newLocation)\n return true\n }\n }\n\n bindForms(){\n let iterations = 0\n let externalFormSubmitted = false\n\n // disable forms on submit that track phx-change but perform external submit\n this.on(\"submit\", e => {\n let phxSubmit = e.target.getAttribute(this.binding(\"submit\"))\n let phxChange = e.target.getAttribute(this.binding(\"change\"))\n if(!externalFormSubmitted && phxChange && !phxSubmit){\n externalFormSubmitted = true\n e.preventDefault()\n this.withinOwners(e.target, view => {\n view.disableForm(e.target)\n // safari needs next tick\n window.requestAnimationFrame(() => {\n if(DOM.isUnloadableFormSubmit(e)){ this.unload() }\n e.target.submit()\n })\n })\n }\n }, true)\n\n this.on(\"submit\", e => {\n let phxEvent = e.target.getAttribute(this.binding(\"submit\"))\n if(!phxEvent){\n if(DOM.isUnloadableFormSubmit(e)){ this.unload() }\n return\n }\n e.preventDefault()\n e.target.disabled = true\n this.withinOwners(e.target, view => {\n JS.exec(\"submit\", phxEvent, view, e.target, [\"push\", {}])\n })\n }, false)\n\n for(let type of [\"change\", \"input\"]){\n this.on(type, e => {\n let phxChange = this.binding(\"change\")\n let input = e.target\n let inputEvent = input.getAttribute(phxChange)\n let formEvent = input.form && input.form.getAttribute(phxChange)\n let phxEvent = inputEvent || formEvent\n if(!phxEvent){ return }\n if(input.type === \"number\" && input.validity && input.validity.badInput){ return }\n\n let dispatcher = inputEvent ? input : input.form\n let currentIterations = iterations\n iterations++\n let {at: at, type: lastType} = DOM.private(input, \"prev-iteration\") || {}\n // detect dup because some browsers dispatch both \"input\" and \"change\"\n if(at === currentIterations - 1 && type !== lastType){ return }\n\n DOM.putPrivate(input, \"prev-iteration\", {at: currentIterations, type: type})\n\n this.debounce(input, e, type, () => {\n this.withinOwners(dispatcher, view => {\n DOM.putPrivate(input, PHX_HAS_FOCUSED, true)\n if(!DOM.isTextualInput(input)){\n this.setActiveElement(input)\n }\n JS.exec(\"change\", phxEvent, view, input, [\"push\", {_target: e.target.name, dispatcher: dispatcher}])\n })\n })\n }, false)\n }\n this.on(\"reset\", (e) => {\n let form = e.target\n DOM.resetForm(form, this.binding(PHX_FEEDBACK_FOR))\n let input = Array.from(form.elements).find(el => el.type === \"reset\")\n // wait until next tick to get updated input value\n window.requestAnimationFrame(() => {\n input.dispatchEvent(new Event(\"input\", {bubbles: true, cancelable: false}))\n })\n })\n }\n\n debounce(el, event, eventType, callback){\n if(eventType === \"blur\" || eventType === \"focusout\"){ return callback() }\n\n let phxDebounce = this.binding(PHX_DEBOUNCE)\n let phxThrottle = this.binding(PHX_THROTTLE)\n let defaultDebounce = this.defaults.debounce.toString()\n let defaultThrottle = this.defaults.throttle.toString()\n\n this.withinOwners(el, view => {\n let asyncFilter = () => !view.isDestroyed() && document.body.contains(el)\n DOM.debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, asyncFilter, () => {\n callback()\n })\n })\n }\n\n silenceEvents(callback){\n this.silenced = true\n callback()\n this.silenced = false\n }\n\n on(event, callback){\n window.addEventListener(event, e => {\n if(!this.silenced){ callback(e) }\n })\n }\n}\n\nclass TransitionSet {\n constructor(){\n this.transitions = new Set()\n this.pendingOps = []\n }\n\n reset(){\n this.transitions.forEach(timer => {\n clearTimeout(timer)\n this.transitions.delete(timer)\n })\n this.flushPendingOps()\n }\n\n after(callback){\n if(this.size() === 0){\n callback()\n } else {\n this.pushPendingOp(callback)\n }\n }\n\n addTransition(time, onStart, onDone){\n onStart()\n let timer = setTimeout(() => {\n this.transitions.delete(timer)\n onDone()\n this.flushPendingOps()\n }, time)\n this.transitions.add(timer)\n }\n\n pushPendingOp(op){ this.pendingOps.push(op) }\n\n size(){ return this.transitions.size }\n\n flushPendingOps(){\n if(this.size() > 0){ return }\n let op = this.pendingOps.shift()\n if(op){\n op()\n this.flushPendingOps()\n }\n }\n}\n"],
+ "mappings": "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",
"names": []
}
diff --git a/priv/static/phoenix_live_view.js b/priv/static/phoenix_live_view.js
index 80447f89cb..409bb8ef29 100644
--- a/priv/static/phoenix_live_view.js
+++ b/priv/static/phoenix_live_view.js
@@ -31,7 +31,8 @@ var LiveView = (() => {
// js/phoenix_live_view/constants.js
var CONSECUTIVE_RELOADS = "consecutive-reloads";
var MAX_RELOADS = 10;
- var RELOAD_JITTER = [1e3, 3e3];
+ var RELOAD_JITTER_MIN = 5e3;
+ var RELOAD_JITTER_MAX = 1e4;
var FAILSAFE_JITTER = 3e4;
var PHX_EVENT_CLASSES = [
"phx-click-loading",
@@ -47,6 +48,7 @@ var LiveView = (() => {
var PHX_TRACK_STATIC = "track-static";
var PHX_LINK_STATE = "data-phx-link-state";
var PHX_REF = "data-phx-ref";
+ var PHX_REF_SRC = "data-phx-ref-src";
var PHX_TRACK_UPLOADS = "track-uploads";
var PHX_UPLOAD_REF = "data-phx-upload-ref";
var PHX_PREFLIGHTED_REFS = "data-phx-preflighted-refs";
@@ -55,10 +57,10 @@ var LiveView = (() => {
var PHX_ACTIVE_ENTRY_REFS = "data-phx-active-refs";
var PHX_LIVE_FILE_UPDATED = "phx:live-file:updated";
var PHX_SKIP = "data-phx-skip";
- var PHX_REMOVE = "data-phx-remove";
+ var PHX_PRUNE = "data-phx-prune";
var PHX_PAGE_LOADING = "page-loading";
var PHX_CONNECTED_CLASS = "phx-connected";
- var PHX_DISCONNECTED_CLASS = "phx-disconnected";
+ var PHX_DISCONNECTED_CLASS = "phx-loading";
var PHX_NO_FEEDBACK_CLASS = "phx-no-feedback";
var PHX_ERROR_CLASS = "phx-error";
var PHX_PARENT_ID = "data-phx-parent-id";
@@ -67,11 +69,12 @@ var LiveView = (() => {
var PHX_TRIGGER_ACTION = "trigger-action";
var PHX_FEEDBACK_FOR = "feedback-for";
var PHX_HAS_FOCUSED = "phx-has-focused";
- var FOCUSABLE_INPUTS = ["text", "textarea", "number", "email", "password", "search", "tel", "url", "date", "time"];
+ var FOCUSABLE_INPUTS = ["text", "textarea", "number", "email", "password", "search", "tel", "url", "date", "time", "datetime-local", "color", "range"];
var CHECKABLE_INPUTS = ["checkbox", "radio"];
var PHX_HAS_SUBMITTED = "phx-has-submitted";
var PHX_SESSION = "data-phx-session";
var PHX_VIEW_SELECTOR = `[${PHX_SESSION}]`;
+ var PHX_STICKY = "data-phx-sticky";
var PHX_STATIC = "data-phx-static";
var PHX_READONLY = "data-phx-readonly";
var PHX_DISABLED = "data-phx-disabled";
@@ -81,6 +84,7 @@ var LiveView = (() => {
var PHX_DEBOUNCE = "debounce";
var PHX_THROTTLE = "throttle";
var PHX_UPDATE = "update";
+ var PHX_STREAM = "stream";
var PHX_KEY = "key";
var PHX_PRIVATE = "phxPrivate";
var PHX_AUTO_RECOVER = "auto-recover";
@@ -88,6 +92,7 @@ var LiveView = (() => {
var PHX_LV_PROFILE = "phx:live-socket:profiling";
var PHX_LV_LATENCY_SIM = "phx:live-socket:latency-sim";
var PHX_PROGRESS = "progress";
+ var PHX_MOUNTED = "mounted";
var LOADER_TIMEOUT = 1;
var BEFORE_UNLOAD_LOADER_TIMEOUT = 200;
var BINDING_PREFIX = "phx-";
@@ -105,6 +110,8 @@ var LiveView = (() => {
var EVENTS = "e";
var REPLY = "r";
var TITLE = "t";
+ var TEMPLATES = "p";
+ var STREAM = "stream";
// js/phoenix_live_view/entry_uploader.js
var EntryUploader = class {
@@ -156,7 +163,10 @@ var LiveView = (() => {
// js/phoenix_live_view/utils.js
var logError = (msg, obj) => console.error && console.error(msg, obj);
- var isCid = (cid) => typeof cid === "number";
+ var isCid = (cid) => {
+ let type = typeof cid;
+ return type === "number" || type === "string" && /^(0|[1-9]\d*)$/.test(cid);
+ };
function detectDuplicateIds() {
let ids = new Set();
let elems = document.querySelectorAll("*[id]");
@@ -181,7 +191,7 @@ var LiveView = (() => {
};
var closestPhxBinding = (el, binding, borderEl) => {
do {
- if (el.matches(`[${binding}]`)) {
+ if (el.matches(`[${binding}]`) && !el.disabled) {
return el;
}
el = el.parentElement || el.parentNode;
@@ -311,8 +321,35 @@ var LiveView = (() => {
isPhxDestroyed(node) {
return node.id && DOM.private(node, "destroyed") ? true : false;
},
+ wantsNewTab(e) {
+ let wantsNewTab = e.ctrlKey || e.shiftKey || e.metaKey || e.button && e.button === 1;
+ return wantsNewTab || e.target.getAttribute("target") === "_blank";
+ },
+ isUnloadableFormSubmit(e) {
+ return !e.defaultPrevented && !this.wantsNewTab(e);
+ },
+ isNewPageHref(href, currentLocation) {
+ let url;
+ try {
+ url = new URL(href);
+ } catch (e) {
+ try {
+ url = new URL(href, currentLocation);
+ } catch (e2) {
+ return true;
+ }
+ }
+ if (url.host === currentLocation.host && url.protocol === currentLocation.protocol) {
+ if (url.pathname === currentLocation.pathname && url.search === currentLocation.search) {
+ return url.hash === "" && !url.href.endsWith("#");
+ }
+ }
+ return true;
+ },
markPhxChildDestroyed(el) {
- el.setAttribute(PHX_SESSION, "");
+ if (this.isPhxChild(el)) {
+ el.setAttribute(PHX_SESSION, "");
+ }
this.putPrivate(el, "destroyed", true);
},
findPhxChildrenInFragment(html, parentId) {
@@ -326,16 +363,20 @@ var LiveView = (() => {
isPhxUpdate(el, phxUpdate, updateTypes) {
return el.getAttribute && updateTypes.indexOf(el.getAttribute(phxUpdate)) >= 0;
},
+ findPhxSticky(el) {
+ return this.all(el, `[${PHX_STICKY}]`);
+ },
findPhxChildren(el, parentId) {
return this.all(el, `${PHX_VIEW_SELECTOR}[${PHX_PARENT_ID}="${parentId}"]`);
},
findParentCIDs(node, cids) {
let initial = new Set(cids);
- return cids.reduce((acc, cid) => {
+ let parentCids = cids.reduce((acc, cid) => {
let selector = `[${PHX_COMPONENT}="${cid}"] [${PHX_COMPONENT}]`;
this.filterWithinSameLiveView(this.all(node, selector), node).map((el) => parseInt(el.getAttribute(PHX_COMPONENT))).forEach((childCID) => acc.delete(childCID));
return acc;
}, initial);
+ return parentCids.size === 0 ? new Set(cids) : parentCids;
},
filterWithinSameLiveView(nodes, parent) {
if (parent.querySelector(PHX_VIEW_SELECTOR)) {
@@ -366,17 +407,29 @@ var LiveView = (() => {
}
el[PHX_PRIVATE][key] = value;
},
+ updatePrivate(el, key, defaultVal, updateFunc) {
+ let existing = this.private(el, key);
+ if (existing === void 0) {
+ this.putPrivate(el, key, updateFunc(defaultVal));
+ } else {
+ this.putPrivate(el, key, updateFunc(existing));
+ }
+ },
copyPrivates(target, source) {
if (source[PHX_PRIVATE]) {
- target[PHX_PRIVATE] = clone(source[PHX_PRIVATE]);
+ target[PHX_PRIVATE] = source[PHX_PRIVATE];
}
},
putTitle(str) {
let titleEl = document.querySelector("title");
- let { prefix, suffix } = titleEl.dataset;
- document.title = `${prefix || ""}${str}${suffix || ""}`;
+ if (titleEl) {
+ let { prefix, suffix } = titleEl.dataset;
+ document.title = `${prefix || ""}${str}${suffix || ""}`;
+ } else {
+ document.title = str;
+ }
},
- debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, callback) {
+ debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, asyncFilter, callback) {
let debounce = el.getAttribute(phxDebounce);
let throttle = el.getAttribute(phxThrottle);
if (debounce === "") {
@@ -413,10 +466,18 @@ var LiveView = (() => {
} else {
callback();
this.putPrivate(el, THROTTLED, true);
- setTimeout(() => this.triggerCycle(el, DEBOUNCE_TRIGGER), timeout);
+ setTimeout(() => {
+ if (asyncFilter()) {
+ this.triggerCycle(el, DEBOUNCE_TRIGGER);
+ }
+ }, timeout);
}
} else {
- setTimeout(() => this.triggerCycle(el, DEBOUNCE_TRIGGER, currentCycle), timeout);
+ setTimeout(() => {
+ if (asyncFilter()) {
+ this.triggerCycle(el, DEBOUNCE_TRIGGER, currentCycle);
+ }
+ }, timeout);
}
let form = el.form;
if (form && this.once(form, "bind-debounce")) {
@@ -459,14 +520,26 @@ var LiveView = (() => {
},
discardError(container, el, phxFeedbackFor) {
let field = el.getAttribute && el.getAttribute(phxFeedbackFor);
- let input = field && container.querySelector(`[id="${field}"], [name="${field}"]`);
+ let input = field && container.querySelector(`[id="${field}"], [name="${field}"], [name="${field}[]"]`);
if (!input) {
return;
}
- if (!(this.private(input, PHX_HAS_FOCUSED) || this.private(input.form, PHX_HAS_SUBMITTED))) {
+ if (!(this.private(input, PHX_HAS_FOCUSED) || this.private(input, PHX_HAS_SUBMITTED))) {
el.classList.add(PHX_NO_FEEDBACK_CLASS);
}
},
+ resetForm(form, phxFeedbackFor) {
+ Array.from(form.elements).forEach((input) => {
+ let query = `[${phxFeedbackFor}="${input.id}"],
+ [${phxFeedbackFor}="${input.name}"],
+ [${phxFeedbackFor}="${input.name.replace(/\[\]$/, "")}"]`;
+ this.deletePrivate(input, PHX_HAS_FOCUSED);
+ this.deletePrivate(input, PHX_HAS_SUBMITTED);
+ this.all(document, query, (feedbackEl) => {
+ feedbackEl.classList.add(PHX_NO_FEEDBACK_CLASS);
+ });
+ });
+ },
showError(inputEl, phxFeedbackFor) {
if (inputEl.id || inputEl.name) {
this.all(inputEl.form, `[${phxFeedbackFor}="${inputEl.id}"], [${phxFeedbackFor}="${inputEl.name}"]`, (el) => {
@@ -477,8 +550,16 @@ var LiveView = (() => {
isPhxChild(node) {
return node.getAttribute && node.getAttribute(PHX_PARENT_ID);
},
- dispatchEvent(target, eventString, detail = {}) {
- let event = new CustomEvent(eventString, { bubbles: true, cancelable: true, detail });
+ isPhxSticky(node) {
+ return node.getAttribute && node.getAttribute(PHX_STICKY) !== null;
+ },
+ firstPhxChild(el) {
+ return this.isPhxChild(el) ? el : this.all(el, `[${PHX_PARENT_ID}]`)[0];
+ },
+ dispatchEvent(target, name, opts = {}) {
+ let bubbles = opts.bubbles === void 0 ? true : !!opts.bubbles;
+ let eventOpts = { bubbles, cancelable: true, detail: opts.detail || {} };
+ let event = name === "click" ? new MouseEvent("click", eventOpts) : new CustomEvent(name, eventOpts);
target.dispatchEvent(event);
},
cloneNode(node, html) {
@@ -516,7 +597,7 @@ var LiveView = (() => {
},
mergeFocusedInput(target, source) {
if (!(target instanceof HTMLSelectElement)) {
- DOM.mergeAttrs(target, source, { except: ["value"] });
+ DOM.mergeAttrs(target, source, { exclude: ["value"] });
}
if (source.readOnly) {
target.setAttribute("readonly", true);
@@ -550,14 +631,6 @@ var LiveView = (() => {
el.checked = el.getAttribute("checked") !== null;
}
},
- syncPropsToAttrs(el) {
- if (el instanceof HTMLSelectElement) {
- let selectedItem = el.options.item(el.selectedIndex);
- if (selectedItem && selectedItem.getAttribute("selected") === null) {
- selectedItem.setAttribute("selected", "");
- }
- }
- },
isTextualInput(el) {
return FOCUSABLE_INPUTS.indexOf(el.type) >= 0;
},
@@ -569,6 +642,7 @@ var LiveView = (() => {
if (ref === null) {
return true;
}
+ let refSrc = fromEl.getAttribute(PHX_REF_SRC);
if (DOM.isFormInput(fromEl) || fromEl.getAttribute(disableWith) !== null) {
if (DOM.isUploadInput(fromEl)) {
DOM.mergeAttrs(fromEl, toEl, { isIgnored: true });
@@ -580,6 +654,7 @@ var LiveView = (() => {
fromEl.classList.contains(className) && toEl.classList.add(className);
});
toEl.setAttribute(PHX_REF, ref);
+ toEl.setAttribute(PHX_REF_SRC, refSrc);
return true;
}
},
@@ -603,7 +678,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
}
},
replaceRootContainer(container, tagName, attrs) {
- let retainedAttrs = new Set(["id", PHX_SESSION, PHX_STATIC, PHX_MAIN]);
+ let retainedAttrs = new Set(["id", PHX_SESSION, PHX_STATIC, PHX_MAIN, PHX_ROOT_ID]);
if (container.tagName.toLowerCase() === tagName.toLowerCase()) {
Array.from(container.attributes).filter((attr) => !retainedAttrs.has(attr.name.toLowerCase())).forEach((attr) => container.removeAttribute(attr.name));
Object.keys(attrs).filter((name) => !retainedAttrs.has(name.toLowerCase())).forEach((attr) => container.setAttribute(attr, attrs[attr]));
@@ -616,6 +691,39 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
container.replaceWith(newContainer);
return newContainer;
}
+ },
+ getSticky(el, name, defaultVal) {
+ let op = (DOM.private(el, "sticky") || []).find(([existingName]) => name === existingName);
+ if (op) {
+ let [_name, _op, stashedResult] = op;
+ return stashedResult;
+ } else {
+ return typeof defaultVal === "function" ? defaultVal() : defaultVal;
+ }
+ },
+ deleteSticky(el, name) {
+ this.updatePrivate(el, "sticky", [], (ops) => {
+ return ops.filter(([existingName, _]) => existingName !== name);
+ });
+ },
+ putSticky(el, name, op) {
+ let stashedResult = op(el);
+ this.updatePrivate(el, "sticky", [], (ops) => {
+ let existingIndex = ops.findIndex(([existingName]) => name === existingName);
+ if (existingIndex >= 0) {
+ ops[existingIndex] = [name, op, stashedResult];
+ } else {
+ ops.push([name, op, stashedResult]);
+ }
+ return ops;
+ });
+ },
+ applyStickyOperations(el) {
+ let ops = DOM.private(el, "sticky");
+ if (!ops) {
+ return;
+ }
+ ops.forEach(([name, op, _stashed]) => this.putSticky(el, name, op));
}
};
var dom_default = DOM;
@@ -677,6 +785,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
return this._isDone;
}
error(reason = "failed") {
+ this.fileEl.removeEventListener(PHX_LIVE_FILE_UPDATED, this._onElUpdated);
this.view.pushFileProgress(this.fileEl, this.ref, { error: reason });
LiveUploader.clearFiles(this.fileEl);
}
@@ -696,6 +805,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
return {
last_modified: this.file.lastModified,
name: this.file.name,
+ relative_path: this.file.webkitRelativePath,
size: this.file.size,
type: this.file.type,
ref: this.ref
@@ -750,7 +860,9 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
let uploadRef = inputEl.getAttribute(PHX_UPLOAD_REF);
fileData[uploadRef] = fileData[uploadRef] || [];
entry.ref = this.genFileRef(file);
+ entry.last_modified = file.lastModified;
entry.name = file.name || entry.ref;
+ entry.relative_path = file.webkitRelativePath;
entry.type = file.type;
entry.size = file.size;
fileData[uploadRef].push(entry);
@@ -765,12 +877,15 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
static untrackFile(inputEl, file) {
dom_default.putPrivate(inputEl, "files", dom_default.private(inputEl, "files").filter((f) => !Object.is(f, file)));
}
- static trackFiles(inputEl, files) {
+ static trackFiles(inputEl, files, dataTransfer) {
if (inputEl.getAttribute("multiple") !== null) {
let newFiles = files.filter((file) => !this.activeFiles(inputEl).find((f) => Object.is(f, file)));
dom_default.putPrivate(inputEl, "files", this.activeFiles(inputEl).concat(newFiles));
inputEl.value = null;
} else {
+ if (dataTransfer && dataTransfer.files.length > 0) {
+ inputEl.files = dataTransfer.files;
+ }
dom_default.putPrivate(inputEl, "files", files);
}
}
@@ -821,6 +936,62 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
}
};
+ // js/phoenix_live_view/aria.js
+ var ARIA = {
+ focusMain() {
+ let target = document.querySelector("main h1, main, h1");
+ if (target) {
+ let origTabIndex = target.tabIndex;
+ target.tabIndex = -1;
+ target.focus();
+ target.tabIndex = origTabIndex;
+ }
+ },
+ anyOf(instance, classes) {
+ return classes.find((name) => instance instanceof name);
+ },
+ isFocusable(el, interactiveOnly) {
+ return el instanceof HTMLAnchorElement && el.rel !== "ignore" || el instanceof HTMLAreaElement && el.href !== void 0 || !el.disabled && this.anyOf(el, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, HTMLButtonElement]) || el instanceof HTMLIFrameElement || (el.tabIndex > 0 || !interactiveOnly && el.tabIndex === 0 && el.getAttribute("tabindex") !== null && el.getAttribute("aria-hidden") !== "true");
+ },
+ attemptFocus(el, interactiveOnly) {
+ if (this.isFocusable(el, interactiveOnly)) {
+ try {
+ el.focus();
+ } catch (e) {
+ }
+ }
+ return !!document.activeElement && document.activeElement.isSameNode(el);
+ },
+ focusFirstInteractive(el) {
+ let child = el.firstElementChild;
+ while (child) {
+ if (this.attemptFocus(child, true) || this.focusFirstInteractive(child, true)) {
+ return true;
+ }
+ child = child.nextElementSibling;
+ }
+ },
+ focusFirst(el) {
+ let child = el.firstElementChild;
+ while (child) {
+ if (this.attemptFocus(child) || this.focusFirst(child)) {
+ return true;
+ }
+ child = child.nextElementSibling;
+ }
+ },
+ focusLast(el) {
+ let child = el.lastElementChild;
+ while (child) {
+ if (this.attemptFocus(child) || this.focusLast(child)) {
+ return true;
+ }
+ child = child.previousElementSibling;
+ }
+ }
+ };
+ var aria_default = ARIA;
+
// js/phoenix_live_view/hooks.js
var Hooks = {
LiveFileUpload: {
@@ -859,6 +1030,18 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
destroyed() {
URL.revokeObjectURL(this.url);
}
+ },
+ FocusWrap: {
+ mounted() {
+ this.focusStart = this.el.firstElementChild;
+ this.focusEnd = this.el.lastElementChild;
+ this.focusStart.addEventListener("focus", () => aria_default.focusLast(this.el));
+ this.focusEnd.addEventListener("focus", () => aria_default.focusFirst(this.el));
+ this.el.addEventListener("phx:show-end", () => this.el.focus());
+ if (window.getComputedStyle(this.el).display !== "none") {
+ aria_default.focusFirst(this.el);
+ }
+ }
}
};
var hooks_default = Hooks;
@@ -1131,6 +1314,8 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
} else {
toNode = toElement(toNode);
}
+ } else if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE$1) {
+ toNode = toNode.firstElementChild;
}
var getNodeKey = options.getNodeKey || defaultGetNodeKey;
var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;
@@ -1140,6 +1325,10 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;
var onNodeDiscarded = options.onNodeDiscarded || noop;
var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;
+ var skipFromChildren = options.skipFromChildren || noop;
+ var addChild = options.addChild || function(parent, child) {
+ return parent.appendChild(child);
+ };
var childrenOnly = options.childrenOnly === true;
var fromNodesLookup = Object.create(null);
var keyedRemovalList = [];
@@ -1240,6 +1429,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
}
}
function morphChildren(fromEl, toEl) {
+ var skipFrom = skipFromChildren(fromEl);
var curToNodeChild = toEl.firstChild;
var curFromNodeChild = fromEl.firstChild;
var curToNodeKey;
@@ -1251,7 +1441,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
while (curToNodeChild) {
toNextSibling = curToNodeChild.nextSibling;
curToNodeKey = getNodeKey(curToNodeChild);
- while (curFromNodeChild) {
+ while (!skipFrom && curFromNodeChild) {
fromNextSibling = curFromNodeChild.nextSibling;
if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {
curToNodeChild = toNextSibling;
@@ -1308,7 +1498,9 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
curFromNodeChild = fromNextSibling;
}
if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {
- fromEl.appendChild(matchingFromEl);
+ if (!skipFrom) {
+ addChild(fromEl, matchingFromEl);
+ }
morphEl(matchingFromEl, curToNodeChild);
} else {
var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);
@@ -1319,7 +1511,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
if (curToNodeChild.actualize) {
curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);
}
- fromEl.appendChild(curToNodeChild);
+ addChild(fromEl, curToNodeChild);
handleNodeAdded(curToNodeChild);
}
}
@@ -1397,15 +1589,19 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
}
});
}
- constructor(view, container, id, html, targetCID) {
+ constructor(view, container, id, html, streams, targetCID) {
this.view = view;
this.liveSocket = view.liveSocket;
this.container = container;
this.id = id;
this.rootID = view.root.id;
this.html = html;
+ this.streams = streams;
+ this.streamInserts = {};
this.targetCID = targetCID;
this.cidPatch = isCid(this.targetCID);
+ this.pendingRemoves = [];
+ this.phxRemove = this.liveSocket.binding("remove");
this.callbacks = {
beforeadded: [],
beforeupdated: [],
@@ -1413,7 +1609,8 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
afteradded: [],
afterupdated: [],
afterdiscarded: [],
- afterphxChildAdded: []
+ afterphxChildAdded: [],
+ aftertransitionsDiscarded: []
};
}
before(kind, callback) {
@@ -1429,8 +1626,10 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
this.callbacks[`after${kind}`].forEach((callback) => callback(...args));
}
markPrunableContentForRemoval() {
- dom_default.all(this.container, "[phx-update=append] > *, [phx-update=prepend] > *", (el) => {
- el.setAttribute(PHX_REMOVE, "");
+ let phxUpdate = this.liveSocket.binding(PHX_UPDATE);
+ dom_default.all(this.container, `[${phxUpdate}=${PHX_STREAM}]`, (el) => el.innerHTML = "");
+ dom_default.all(this.container, `[${phxUpdate}=append] > *, [${phxUpdate}=prepend] > *`, (el) => {
+ el.setAttribute(PHX_PRUNE, "");
});
}
perform() {
@@ -1455,11 +1654,41 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
this.trackBefore("added", container);
this.trackBefore("updated", container, container);
liveSocket.time("morphdom", () => {
+ this.streams.forEach(([inserts, deleteIds]) => {
+ this.streamInserts = Object.assign(this.streamInserts, inserts);
+ deleteIds.forEach((id) => {
+ let child = container.querySelector(`[id="${id}"]`);
+ if (child) {
+ if (!this.maybePendingRemove(child)) {
+ child.remove();
+ this.onNodeDiscarded(child);
+ }
+ }
+ });
+ });
morphdom_esm_default(targetContainer, diffHTML, {
childrenOnly: targetContainer.getAttribute(PHX_COMPONENT) === null,
getNodeKey: (node) => {
return dom_default.isPhxDestroyed(node) ? null : node.id;
},
+ skipFromChildren: (from) => {
+ return from.getAttribute(phxUpdate) === PHX_STREAM;
+ },
+ addChild: (parent, child) => {
+ let streamAt = child.id ? this.streamInserts[child.id] : void 0;
+ if (streamAt === void 0) {
+ return parent.appendChild(child);
+ }
+ dom_default.putPrivate(child, PHX_STREAM, true);
+ if (streamAt === 0) {
+ parent.insertAdjacentElement("afterbegin", child);
+ } else if (streamAt === -1) {
+ parent.appendChild(child);
+ } else if (streamAt > 0) {
+ let sibling = Array.from(parent.children)[streamAt];
+ parent.insertBefore(child, sibling);
+ }
+ },
onBeforeNodeAdded: (el) => {
this.trackBefore("added", el);
return el;
@@ -1474,22 +1703,23 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
externalFormTriggered = el;
}
dom_default.discardError(targetContainer, el, phxFeedbackFor);
- if (dom_default.isPhxChild(el) && view.ownsElement(el)) {
+ if (dom_default.isPhxChild(el) && view.ownsElement(el) || dom_default.isPhxSticky(el) && view.ownsElement(el.parentNode)) {
this.trackAfter("phxChildAdded", el);
}
added.push(el);
},
- onNodeDiscarded: (el) => {
- if (dom_default.isPhxChild(el)) {
- liveSocket.destroyViewByEl(el);
- }
- this.trackAfter("discarded", el);
- },
+ onNodeDiscarded: (el) => this.onNodeDiscarded(el),
onBeforeNodeDiscarded: (el) => {
- if (el.getAttribute && el.getAttribute(PHX_REMOVE) !== null) {
+ if (el.getAttribute && el.getAttribute(PHX_PRUNE) !== null) {
return true;
}
- if (el.parentNode !== null && dom_default.isPhxUpdate(el.parentNode, phxUpdate, ["append", "prepend"]) && el.id) {
+ if (dom_default.private(el, PHX_STREAM)) {
+ return false;
+ }
+ if (el.parentElement !== null && dom_default.isPhxUpdate(el.parentElement, phxUpdate, ["append", "prepend"]) && el.id) {
+ return false;
+ }
+ if (this.maybePendingRemove(el)) {
return false;
}
if (this.skipCIDSibling(el)) {
@@ -1502,16 +1732,21 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
externalFormTriggered = el;
}
updates.push(el);
+ this.maybeReOrderStream(el);
},
onBeforeElUpdated: (fromEl, toEl) => {
dom_default.cleanChildNodes(toEl, phxUpdate);
if (this.skipCIDSibling(toEl)) {
return false;
}
- if (dom_default.isIgnored(fromEl, phxUpdate)) {
+ if (dom_default.isPhxSticky(fromEl)) {
+ return false;
+ }
+ if (dom_default.isIgnored(fromEl, phxUpdate) || fromEl.form && fromEl.form.isSameNode(externalFormTriggered)) {
this.trackBefore("updated", fromEl, toEl);
dom_default.mergeAttrs(fromEl, toEl, { isIgnored: true });
updates.push(fromEl);
+ dom_default.applyStickyOperations(fromEl);
return false;
}
if (fromEl.type === "number" && (fromEl.validity && fromEl.validity.badInput)) {
@@ -1522,6 +1757,7 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
this.trackBefore("updated", fromEl, toEl);
updates.push(fromEl);
}
+ dom_default.applyStickyOperations(fromEl);
return false;
}
if (dom_default.isPhxChild(toEl)) {
@@ -1531,23 +1767,25 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
fromEl.setAttribute(PHX_SESSION, prevSession);
}
fromEl.setAttribute(PHX_ROOT_ID, this.rootID);
+ dom_default.applyStickyOperations(fromEl);
return false;
}
dom_default.copyPrivates(toEl, fromEl);
dom_default.discardError(targetContainer, toEl, phxFeedbackFor);
- dom_default.syncPropsToAttrs(toEl);
let isFocusedFormEl = focused && fromEl.isSameNode(focused) && dom_default.isFormInput(fromEl);
- if (isFocusedFormEl && !this.forceFocusedSelectUpdate(fromEl, toEl)) {
+ if (isFocusedFormEl && fromEl.type !== "hidden") {
this.trackBefore("updated", fromEl, toEl);
dom_default.mergeFocusedInput(fromEl, toEl);
dom_default.syncAttrsToProps(fromEl);
updates.push(fromEl);
+ dom_default.applyStickyOperations(fromEl);
return false;
} else {
if (dom_default.isPhxUpdate(toEl, phxUpdate, ["append", "prepend"])) {
appendPrependUpdates.push(new DOMPostMorphRestorer(fromEl, toEl, toEl.getAttribute(phxUpdate)));
}
dom_default.syncAttrsToProps(toEl);
+ dom_default.applyStickyOperations(toEl);
this.trackBefore("updated", fromEl, toEl);
return true;
}
@@ -1566,15 +1804,65 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
dom_default.dispatchEvent(document, "phx:update");
added.forEach((el) => this.trackAfter("added", el));
updates.forEach((el) => this.trackAfter("updated", el));
+ this.transitionPendingRemoves();
if (externalFormTriggered) {
- liveSocket.disconnect();
+ liveSocket.unload();
externalFormTriggered.submit();
}
return true;
}
- forceFocusedSelectUpdate(fromEl, toEl) {
- let isSelect = ["select", "select-one", "select-multiple"].find((t) => t === fromEl.type);
- return fromEl.multiple === true || isSelect && fromEl.innerHTML != toEl.innerHTML;
+ onNodeDiscarded(el) {
+ if (dom_default.isPhxChild(el) || dom_default.isPhxSticky(el)) {
+ this.liveSocket.destroyViewByEl(el);
+ }
+ this.trackAfter("discarded", el);
+ }
+ maybePendingRemove(node) {
+ if (node.getAttribute && node.getAttribute(this.phxRemove) !== null) {
+ this.pendingRemoves.push(node);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ maybeReOrderStream(el) {
+ let streamAt = el.id ? this.streamInserts[el.id] : void 0;
+ if (streamAt === void 0) {
+ return;
+ }
+ dom_default.putPrivate(el, PHX_STREAM, true);
+ if (streamAt === 0) {
+ el.parentElement.insertBefore(el, el.parentElement.firstElementChild);
+ } else if (streamAt > 0) {
+ let children = Array.from(el.parentElement.children);
+ let oldIndex = children.indexOf(el);
+ if (streamAt >= children.length - 1) {
+ el.parentElement.appendChild(el);
+ } else {
+ let sibling = children[streamAt];
+ if (oldIndex > streamAt) {
+ el.parentElement.insertBefore(el, sibling);
+ } else {
+ el.parentElement.insertBefore(el, sibling.nextElementSibling);
+ }
+ }
+ }
+ }
+ transitionPendingRemoves() {
+ let { pendingRemoves, liveSocket } = this;
+ if (pendingRemoves.length > 0) {
+ liveSocket.transitionRemoves(pendingRemoves);
+ liveSocket.requestDOMUpdate(() => {
+ pendingRemoves.forEach((el) => {
+ let child = dom_default.firstPhxChild(el);
+ if (child) {
+ liveSocket.destroyViewByEl(child);
+ }
+ el.remove();
+ });
+ this.trackAfter("transitionsDiscarded", pendingRemoves);
+ });
+ }
}
isCIDPatch() {
return this.cidPatch;
@@ -1616,6 +1904,9 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
return diffContainer.outerHTML;
}
}
+ indexOf(parent, child) {
+ return Array.from(parent.children).indexOf(child);
+ }
};
// js/phoenix_live_view/rendered.js
@@ -1636,13 +1927,14 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
return this.viewId;
}
toString(onlyCids) {
- return this.recursiveToString(this.rendered, this.rendered[COMPONENTS], onlyCids);
+ let [str, streams] = this.recursiveToString(this.rendered, this.rendered[COMPONENTS], onlyCids);
+ return [str, streams];
}
recursiveToString(rendered, components = rendered[COMPONENTS], onlyCids) {
onlyCids = onlyCids ? new Set(onlyCids) : null;
- let output = { buffer: "", components, onlyCids };
- this.toOutputBuffer(rendered, output);
- return output.buffer;
+ let output = { buffer: "", components, onlyCids, streams: new Set() };
+ this.toOutputBuffer(rendered, null, output);
+ return [output.buffer, output.streams];
}
componentCIDs(diff) {
return Object.keys(diff[COMPONENTS] || {}).map((i) => parseInt(i));
@@ -1667,8 +1959,8 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
for (let cid in newc) {
newc[cid] = this.cachedFindComponent(cid, newc[cid], oldc, newc, cache);
}
- for (var key in newc) {
- oldc[key] = newc[key];
+ for (let cid in newc) {
+ oldc[cid] = newc[cid];
}
diff[COMPONENTS] = newc;
}
@@ -1707,7 +1999,8 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
for (let key in source) {
let val = source[key];
let targetVal = target[key];
- if (isObject(val) && val[STATIC] === void 0 && isObject(targetVal)) {
+ let isObjVal = isObject(val);
+ if (isObjVal && val[STATIC] === void 0 && isObject(targetVal)) {
this.doMutableMerge(targetVal, val);
} else {
target[key] = val;
@@ -1726,7 +2019,8 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
return merged;
}
componentToString(cid) {
- return this.recursiveCIDToString(this.rendered[COMPONENTS], cid);
+ let [str, streams] = this.recursiveCIDToString(this.rendered[COMPONENTS], cid);
+ return [str, streams];
}
pruneCIDs(cids) {
cids.forEach((cid) => delete this.rendered[COMPONENTS][cid]);
@@ -1737,33 +2031,50 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
isNewFingerprint(diff = {}) {
return !!diff[STATIC];
}
- toOutputBuffer(rendered, output) {
+ templateStatic(part, templates) {
+ if (typeof part === "number") {
+ return templates[part];
+ } else {
+ return part;
+ }
+ }
+ toOutputBuffer(rendered, templates, output) {
if (rendered[DYNAMICS]) {
- return this.comprehensionToBuffer(rendered, output);
+ return this.comprehensionToBuffer(rendered, templates, output);
}
let { [STATIC]: statics } = rendered;
+ statics = this.templateStatic(statics, templates);
output.buffer += statics[0];
for (let i = 1; i < statics.length; i++) {
- this.dynamicToBuffer(rendered[i - 1], output);
+ this.dynamicToBuffer(rendered[i - 1], templates, output);
output.buffer += statics[i];
}
}
- comprehensionToBuffer(rendered, output) {
- let { [DYNAMICS]: dynamics, [STATIC]: statics } = rendered;
+ comprehensionToBuffer(rendered, templates, output) {
+ let { [DYNAMICS]: dynamics, [STATIC]: statics, [STREAM]: stream } = rendered;
+ let [_inserts, deleteIds] = stream || [{}, []];
+ statics = this.templateStatic(statics, templates);
+ let compTemplates = templates || rendered[TEMPLATES];
for (let d = 0; d < dynamics.length; d++) {
let dynamic = dynamics[d];
output.buffer += statics[0];
for (let i = 1; i < statics.length; i++) {
- this.dynamicToBuffer(dynamic[i - 1], output);
+ this.dynamicToBuffer(dynamic[i - 1], compTemplates, output);
output.buffer += statics[i];
}
}
+ if (stream !== void 0 && (rendered[DYNAMICS].length > 0 || deleteIds.length > 0)) {
+ rendered[DYNAMICS] = [];
+ output.streams.add(stream);
+ }
}
- dynamicToBuffer(rendered, output) {
+ dynamicToBuffer(rendered, templates, output) {
if (typeof rendered === "number") {
- output.buffer += this.recursiveCIDToString(output.components, rendered, output.onlyCids);
+ let [str, streams] = this.recursiveCIDToString(output.components, rendered, output.onlyCids);
+ output.buffer += str;
+ output.streams = new Set([...output.streams, ...streams]);
} else if (isObject(rendered)) {
- this.toOutputBuffer(rendered, output);
+ this.toOutputBuffer(rendered, templates, output);
} else {
output.buffer += rendered;
}
@@ -1771,7 +2082,8 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
recursiveCIDToString(components, cid, onlyCids) {
let component = components[cid] || logError(`no component for CID ${cid}`, components);
let template = document.createElement("template");
- template.innerHTML = this.recursiveToString(component, components, onlyCids);
+ let [html, streams] = this.recursiveToString(component, components, onlyCids);
+ template.innerHTML = html;
let container = template.content;
let skip = onlyCids && !onlyCids.has(cid);
let [hasChildNodes, hasChildComponents] = Array.from(container.childNodes).reduce(([hasNodes, hasComponents], child, i) => {
@@ -1806,12 +2118,12 @@ within:
}, [false, false]);
if (!hasChildNodes && !hasChildComponents) {
logError("expected at least one HTML element tag inside a component, but the component is empty:\n", template.innerHTML.trim());
- return this.createSpan("", cid).outerHTML;
+ return [this.createSpan("", cid).outerHTML, streams];
} else if (!hasChildNodes && hasChildComponents) {
logError("expected at least one HTML element tag directly inside a component, but only subcomponents were found. A component must render at least one HTML tag directly inside itself.", template.innerHTML.trim());
- return template.innerHTML;
+ return [template.innerHTML, streams];
} else {
- return template.innerHTML;
+ return [template.innerHTML, streams];
}
}
createSpan(text, cid) {
@@ -1833,7 +2145,7 @@ within:
}
constructor(view, el, callbacks) {
this.__view = view;
- this.__liveSocket = view.liveSocket;
+ this.liveSocket = view.liveSocket;
this.__callbacks = callbacks;
this.__listeners = new Set();
this.__isDisconnected = false;
@@ -1877,13 +2189,13 @@ within:
}
handleEvent(event, callback) {
let callbackRef = (customEvent, bypass) => bypass ? event : callback(customEvent.detail);
- window.addEventListener(`phx:hook:${event}`, callbackRef);
+ window.addEventListener(`phx:${event}`, callbackRef);
this.__listeners.add(callbackRef);
return callbackRef;
}
removeHandleEvent(callbackRef) {
let event = callbackRef(null, true);
- window.removeEventListener(`phx:hook:${event}`, callbackRef);
+ window.removeEventListener(`phx:${event}`, callbackRef);
this.__listeners.delete(callbackRef);
}
upload(name, files) {
@@ -1897,8 +2209,211 @@ within:
}
};
+ // js/phoenix_live_view/js.js
+ var focusStack = null;
+ var JS = {
+ exec(eventType, phxEvent, view, sourceEl, defaults) {
+ let [defaultKind, defaultArgs] = defaults || [null, {}];
+ let commands = phxEvent.charAt(0) === "[" ? JSON.parse(phxEvent) : [[defaultKind, defaultArgs]];
+ commands.forEach(([kind, args]) => {
+ if (kind === defaultKind && defaultArgs.data) {
+ args.data = Object.assign(args.data || {}, defaultArgs.data);
+ }
+ this.filterToEls(sourceEl, args).forEach((el) => {
+ this[`exec_${kind}`](eventType, phxEvent, view, sourceEl, el, args);
+ });
+ });
+ },
+ isVisible(el) {
+ return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length > 0);
+ },
+ exec_dispatch(eventType, phxEvent, view, sourceEl, el, { to, event, detail, bubbles }) {
+ detail = detail || {};
+ detail.dispatcher = sourceEl;
+ dom_default.dispatchEvent(el, event, { detail, bubbles });
+ },
+ exec_push(eventType, phxEvent, view, sourceEl, el, args) {
+ if (!view.isConnected()) {
+ return;
+ }
+ let { event, data, target, page_loading, loading, value, dispatcher } = args;
+ let pushOpts = { loading, value, target, page_loading: !!page_loading };
+ let targetSrc = eventType === "change" && dispatcher ? dispatcher : sourceEl;
+ let phxTarget = target || targetSrc.getAttribute(view.binding("target")) || targetSrc;
+ view.withinTargets(phxTarget, (targetView, targetCtx) => {
+ if (eventType === "change") {
+ let { newCid, _target, callback } = args;
+ _target = _target || (dom_default.isFormInput(sourceEl) ? sourceEl.name : void 0);
+ if (_target) {
+ pushOpts._target = _target;
+ }
+ targetView.pushInput(sourceEl, targetCtx, newCid, event || phxEvent, pushOpts, callback);
+ } else if (eventType === "submit") {
+ targetView.submitForm(sourceEl, targetCtx, event || phxEvent, pushOpts);
+ } else {
+ targetView.pushEvent(eventType, sourceEl, targetCtx, event || phxEvent, data, pushOpts);
+ }
+ });
+ },
+ exec_navigate(eventType, phxEvent, view, sourceEl, el, { href, replace }) {
+ view.liveSocket.historyRedirect(href, replace ? "replace" : "push");
+ },
+ exec_patch(eventType, phxEvent, view, sourceEl, el, { href, replace }) {
+ view.liveSocket.pushHistoryPatch(href, replace ? "replace" : "push", sourceEl);
+ },
+ exec_focus(eventType, phxEvent, view, sourceEl, el) {
+ window.requestAnimationFrame(() => aria_default.attemptFocus(el));
+ },
+ exec_focus_first(eventType, phxEvent, view, sourceEl, el) {
+ window.requestAnimationFrame(() => aria_default.focusFirstInteractive(el) || aria_default.focusFirst(el));
+ },
+ exec_push_focus(eventType, phxEvent, view, sourceEl, el) {
+ window.requestAnimationFrame(() => focusStack = el || sourceEl);
+ },
+ exec_pop_focus(eventType, phxEvent, view, sourceEl, el) {
+ window.requestAnimationFrame(() => {
+ if (focusStack) {
+ focusStack.focus();
+ }
+ focusStack = null;
+ });
+ },
+ exec_add_class(eventType, phxEvent, view, sourceEl, el, { names, transition, time }) {
+ this.addOrRemoveClasses(el, names, [], transition, time, view);
+ },
+ exec_remove_class(eventType, phxEvent, view, sourceEl, el, { names, transition, time }) {
+ this.addOrRemoveClasses(el, [], names, transition, time, view);
+ },
+ exec_transition(eventType, phxEvent, view, sourceEl, el, { time, transition }) {
+ this.addOrRemoveClasses(el, [], [], transition, time, view);
+ },
+ exec_toggle(eventType, phxEvent, view, sourceEl, el, { display, ins, outs, time }) {
+ this.toggle(eventType, view, el, display, ins, outs, time);
+ },
+ exec_show(eventType, phxEvent, view, sourceEl, el, { display, transition, time }) {
+ this.show(eventType, view, el, display, transition, time);
+ },
+ exec_hide(eventType, phxEvent, view, sourceEl, el, { display, transition, time }) {
+ this.hide(eventType, view, el, display, transition, time);
+ },
+ exec_set_attr(eventType, phxEvent, view, sourceEl, el, { attr: [attr, val] }) {
+ this.setOrRemoveAttrs(el, [[attr, val]], []);
+ },
+ exec_remove_attr(eventType, phxEvent, view, sourceEl, el, { attr }) {
+ this.setOrRemoveAttrs(el, [], [attr]);
+ },
+ show(eventType, view, el, display, transition, time) {
+ if (!this.isVisible(el)) {
+ this.toggle(eventType, view, el, display, transition, null, time);
+ }
+ },
+ hide(eventType, view, el, display, transition, time) {
+ if (this.isVisible(el)) {
+ this.toggle(eventType, view, el, display, null, transition, time);
+ }
+ },
+ toggle(eventType, view, el, display, ins, outs, time) {
+ let [inClasses, inStartClasses, inEndClasses] = ins || [[], [], []];
+ let [outClasses, outStartClasses, outEndClasses] = outs || [[], [], []];
+ if (inClasses.length > 0 || outClasses.length > 0) {
+ if (this.isVisible(el)) {
+ let onStart = () => {
+ this.addOrRemoveClasses(el, outStartClasses, inClasses.concat(inStartClasses).concat(inEndClasses));
+ window.requestAnimationFrame(() => {
+ this.addOrRemoveClasses(el, outClasses, []);
+ window.requestAnimationFrame(() => this.addOrRemoveClasses(el, outEndClasses, outStartClasses));
+ });
+ };
+ el.dispatchEvent(new Event("phx:hide-start"));
+ view.transition(time, onStart, () => {
+ this.addOrRemoveClasses(el, [], outClasses.concat(outEndClasses));
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = "none");
+ el.dispatchEvent(new Event("phx:hide-end"));
+ });
+ } else {
+ if (eventType === "remove") {
+ return;
+ }
+ let onStart = () => {
+ this.addOrRemoveClasses(el, inStartClasses, outClasses.concat(outStartClasses).concat(outEndClasses));
+ let stickyDisplay = display || this.defaultDisplay(el);
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = stickyDisplay);
+ window.requestAnimationFrame(() => {
+ this.addOrRemoveClasses(el, inClasses, []);
+ window.requestAnimationFrame(() => this.addOrRemoveClasses(el, inEndClasses, inStartClasses));
+ });
+ };
+ el.dispatchEvent(new Event("phx:show-start"));
+ view.transition(time, onStart, () => {
+ this.addOrRemoveClasses(el, [], inClasses.concat(inEndClasses));
+ el.dispatchEvent(new Event("phx:show-end"));
+ });
+ }
+ } else {
+ if (this.isVisible(el)) {
+ window.requestAnimationFrame(() => {
+ el.dispatchEvent(new Event("phx:hide-start"));
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = "none");
+ el.dispatchEvent(new Event("phx:hide-end"));
+ });
+ } else {
+ window.requestAnimationFrame(() => {
+ el.dispatchEvent(new Event("phx:show-start"));
+ let stickyDisplay = display || this.defaultDisplay(el);
+ dom_default.putSticky(el, "toggle", (currentEl) => currentEl.style.display = stickyDisplay);
+ el.dispatchEvent(new Event("phx:show-end"));
+ });
+ }
+ }
+ },
+ addOrRemoveClasses(el, adds, removes, transition, time, view) {
+ let [transition_run, transition_start, transition_end] = transition || [[], [], []];
+ if (transition_run.length > 0) {
+ let onStart = () => this.addOrRemoveClasses(el, transition_start.concat(transition_run), []);
+ let onDone = () => this.addOrRemoveClasses(el, adds.concat(transition_end), removes.concat(transition_run).concat(transition_start));
+ return view.transition(time, onStart, onDone);
+ }
+ window.requestAnimationFrame(() => {
+ let [prevAdds, prevRemoves] = dom_default.getSticky(el, "classes", [[], []]);
+ let keepAdds = adds.filter((name) => prevAdds.indexOf(name) < 0 && !el.classList.contains(name));
+ let keepRemoves = removes.filter((name) => prevRemoves.indexOf(name) < 0 && el.classList.contains(name));
+ let newAdds = prevAdds.filter((name) => removes.indexOf(name) < 0).concat(keepAdds);
+ let newRemoves = prevRemoves.filter((name) => adds.indexOf(name) < 0).concat(keepRemoves);
+ dom_default.putSticky(el, "classes", (currentEl) => {
+ currentEl.classList.remove(...newRemoves);
+ currentEl.classList.add(...newAdds);
+ return [newAdds, newRemoves];
+ });
+ });
+ },
+ setOrRemoveAttrs(el, sets, removes) {
+ let [prevSets, prevRemoves] = dom_default.getSticky(el, "attrs", [[], []]);
+ let alteredAttrs = sets.map(([attr, _val]) => attr).concat(removes);
+ let newSets = prevSets.filter(([attr, _val]) => !alteredAttrs.includes(attr)).concat(sets);
+ let newRemoves = prevRemoves.filter((attr) => !alteredAttrs.includes(attr)).concat(removes);
+ dom_default.putSticky(el, "attrs", (currentEl) => {
+ newRemoves.forEach((attr) => currentEl.removeAttribute(attr));
+ newSets.forEach(([attr, val]) => currentEl.setAttribute(attr, val));
+ return [newSets, newRemoves];
+ });
+ },
+ hasAllClasses(el, classes) {
+ return classes.every((name) => el.classList.contains(name));
+ },
+ isToggledOut(el, outClasses) {
+ return !this.isVisible(el) || this.hasAllClasses(el, outClasses);
+ },
+ filterToEls(sourceEl, { to }) {
+ return to ? dom_default.all(document, to) : [sourceEl];
+ },
+ defaultDisplay(el) {
+ return { tr: "table-row", td: "table-cell" }[el.tagName.toLowerCase()] || "block";
+ }
+ };
+ var js_default = JS;
+
// js/phoenix_live_view/view.js
- var serializeForm = (form, meta = {}) => {
+ var serializeForm = (form, meta, onlyNames = []) => {
let formData = new FormData(form);
let toRemove = [];
formData.forEach((val, key, _index) => {
@@ -1909,7 +2424,9 @@ within:
toRemove.forEach((key) => formData.delete(key));
let params = new URLSearchParams();
for (let [key, val] of formData.entries()) {
- params.append(key, val);
+ if (onlyNames.length === 0 || onlyNames.indexOf(key) >= 0) {
+ params.append(key, val);
+ }
}
for (let metaKey in meta) {
params.append(metaKey, meta[metaKey]);
@@ -1917,7 +2434,8 @@ within:
return params.toString();
};
var View = class {
- constructor(el, liveSocket, parentView, flash) {
+ constructor(el, liveSocket, parentView, flash, liveReferer) {
+ this.isDead = false;
this.liveSocket = liveSocket;
this.flash = flash;
this.parent = parentView;
@@ -1934,7 +2452,8 @@ within:
this.joinCount = this.parent ? this.parent.joinCount - 1 : 0;
this.joinPending = true;
this.destroyed = false;
- this.joinCallback = function() {
+ this.joinCallback = function(onDone) {
+ onDone && onDone();
};
this.stopCallback = function() {
};
@@ -1948,14 +2467,12 @@ within:
return {
redirect: this.redirect ? this.href : void 0,
url: this.redirect ? void 0 : this.href || void 0,
- params: this.connectParams(),
+ params: this.connectParams(liveReferer),
session: this.getSession(),
static: this.getStatic(),
flash: this.flash
};
});
- this.showLoader(this.liveSocket.loaderTimeout);
- this.bindChannel();
}
setHref(href) {
this.href = href;
@@ -1965,15 +2482,16 @@ within:
this.href = href;
}
isMain() {
- return this.liveSocket.main === this;
+ return this.el.hasAttribute(PHX_MAIN);
}
- connectParams() {
+ connectParams(liveReferer) {
let params = this.liveSocket.params(this.el);
let manifest = dom_default.all(document, `[${this.binding(PHX_TRACK_STATIC)}]`).map((node) => node.src || node.href).filter((url) => typeof url === "string");
if (manifest.length > 0) {
params["_track_static"] = manifest;
}
params["_mounts"] = this.joinCount;
+ params["_live_referer"] = liveReferer;
return params;
}
isConnected() {
@@ -2009,9 +2527,6 @@ within:
this.el.classList.remove(PHX_CONNECTED_CLASS, PHX_DISCONNECTED_CLASS, PHX_ERROR_CLASS);
this.el.classList.add(...classes);
}
- isLoading() {
- return this.el.classList.contains(PHX_DISCONNECTED_CLASS);
- }
showLoader(timeout) {
clearTimeout(this.loaderTimer);
if (timeout) {
@@ -2023,9 +2538,13 @@ within:
this.setContainerClasses(PHX_DISCONNECTED_CLASS);
}
}
+ execAll(binding) {
+ dom_default.all(this.el, `[${binding}]`, (el) => this.liveSocket.execJS(el, el.getAttribute(binding)));
+ }
hideLoader() {
clearTimeout(this.loaderTimer);
this.setContainerClasses(PHX_CONNECTED_CLASS);
+ this.execAll(this.binding("connected"));
}
triggerReconnected() {
for (let id in this.viewHooks) {
@@ -2035,16 +2554,20 @@ within:
log(kind, msgCallback) {
this.liveSocket.log(this, kind, msgCallback);
}
+ transition(time, onStart, onDone = function() {
+ }) {
+ this.liveSocket.transition(time, onStart, onDone);
+ }
withinTargets(phxTarget, callback) {
- if (phxTarget instanceof HTMLElement) {
+ if (phxTarget instanceof HTMLElement || phxTarget instanceof SVGElement) {
return this.liveSocket.owner(phxTarget, (view) => callback(view, phxTarget));
}
- if (/^(0|[1-9]\d*)$/.test(phxTarget)) {
+ if (isCid(phxTarget)) {
let targets = dom_default.findComponentNodeList(this.el, phxTarget);
if (targets.length === 0) {
logError(`no component found matching phx-target of ${phxTarget}`);
} else {
- callback(this, targets[0]);
+ callback(this, parseInt(phxTarget));
}
} else {
let targets = Array.from(document.querySelectorAll(phxTarget));
@@ -2057,11 +2580,10 @@ within:
applyDiff(type, rawDiff, callback) {
this.log(type, () => ["", clone(rawDiff)]);
let { diff, reply, events, title } = Rendered.extract(rawDiff);
+ callback({ diff, reply, events });
if (title) {
- dom_default.putTitle(title);
+ window.requestAnimationFrame(() => dom_default.putTitle(title));
}
- callback({ diff, reply, events });
- return reply;
}
onJoin(resp) {
let { rendered, container } = resp;
@@ -2075,7 +2597,7 @@ within:
browser_default.dropLocal(this.liveSocket.localStorage, window.location.pathname, CONSECUTIVE_RELOADS);
this.applyDiff("mount", rendered, ({ diff, events }) => {
this.rendered = new Rendered(this.id, diff);
- let html = this.renderContainer(null, "join");
+ let [html, streams] = this.renderContainer(null, "join");
this.dropPendingRefs();
let forms = this.formsForRecovery(html);
this.joinCount++;
@@ -2083,21 +2605,24 @@ within:
forms.forEach(([form, newForm, newCid], i) => {
this.pushFormRecovery(form, newCid, (resp2) => {
if (i === forms.length - 1) {
- this.onJoinComplete(resp2, html, events);
+ this.onJoinComplete(resp2, html, streams, events);
}
});
});
} else {
- this.onJoinComplete(resp, html, events);
+ this.onJoinComplete(resp, html, streams, events);
}
});
}
dropPendingRefs() {
- dom_default.all(this.el, `[${PHX_REF}]`, (el) => el.removeAttribute(PHX_REF));
+ dom_default.all(document, `[${PHX_REF_SRC}="${this.id}"][${PHX_REF}]`, (el) => {
+ el.removeAttribute(PHX_REF);
+ el.removeAttribute(PHX_REF_SRC);
+ });
}
- onJoinComplete({ live_patch }, html, events) {
+ onJoinComplete({ live_patch }, html, streams, events) {
if (this.joinCount > 1 || this.parent && !this.parent.isJoinPending()) {
- return this.applyJoinPatch(live_patch, html, events);
+ return this.applyJoinPatch(live_patch, html, streams, events);
}
let newChildren = dom_default.findPhxChildrenInFragment(html, this.id).filter((toEl) => {
let fromEl = toEl.id && this.el.querySelector(`[id="${toEl.id}"]`);
@@ -2109,39 +2634,35 @@ within:
});
if (newChildren.length === 0) {
if (this.parent) {
- this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, events)]);
+ this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, streams, events)]);
this.parent.ackJoin(this);
} else {
this.onAllChildJoinsComplete();
- this.applyJoinPatch(live_patch, html, events);
+ this.applyJoinPatch(live_patch, html, streams, events);
}
} else {
- this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, events)]);
+ this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, streams, events)]);
}
}
attachTrueDocEl() {
this.el = dom_default.byId(this.id);
this.el.setAttribute(PHX_ROOT_ID, this.root.id);
}
- dispatchEvents(events) {
- events.forEach(([event, payload]) => {
- window.dispatchEvent(new CustomEvent(`phx:hook:${event}`, { detail: payload }));
+ execNewMounted() {
+ dom_default.all(this.el, `[${this.binding(PHX_HOOK)}], [data-phx-${PHX_HOOK}]`, (hookEl) => {
+ this.maybeAddNewHook(hookEl);
});
+ dom_default.all(this.el, `[${this.binding(PHX_MOUNTED)}]`, (el) => this.maybeMounted(el));
}
- applyJoinPatch(live_patch, html, events) {
+ applyJoinPatch(live_patch, html, streams, events) {
this.attachTrueDocEl();
- let patch = new DOMPatch(this, this.el, this.id, html, null);
+ let patch = new DOMPatch(this, this.el, this.id, html, streams, null);
patch.markPrunableContentForRemoval();
this.performPatch(patch, false);
this.joinNewChildren();
- dom_default.all(this.el, `[${this.binding(PHX_HOOK)}], [data-phx-${PHX_HOOK}]`, (hookEl) => {
- let hook = this.addHook(hookEl);
- if (hook) {
- hook.__mounted();
- }
- });
+ this.execNewMounted();
this.joinPending = false;
- this.dispatchEvents(events);
+ this.liveSocket.dispatchEvents(events);
this.applyPendingUpdates();
if (live_patch) {
let { kind, to } = live_patch;
@@ -2162,18 +2683,38 @@ within:
return hook;
}
}
+ maybeMounted(el) {
+ let phxMounted = el.getAttribute(this.binding(PHX_MOUNTED));
+ let hasBeenInvoked = phxMounted && dom_default.private(el, "mounted");
+ if (phxMounted && !hasBeenInvoked) {
+ this.liveSocket.execJS(el, phxMounted);
+ dom_default.putPrivate(el, "mounted", true);
+ }
+ }
+ maybeAddNewHook(el, force) {
+ let newHook = this.addHook(el);
+ if (newHook) {
+ newHook.__mounted();
+ }
+ }
performPatch(patch, pruneCids) {
- let destroyedCIDs = [];
+ let removedEls = [];
let phxChildrenAdded = false;
let updatedHookIds = new Set();
patch.after("added", (el) => {
this.liveSocket.triggerDOM("onNodeAdded", [el]);
- let newHook = this.addHook(el);
- if (newHook) {
- newHook.__mounted();
+ this.maybeAddNewHook(el);
+ if (el.getAttribute) {
+ this.maybeMounted(el);
+ }
+ });
+ patch.after("phxChildAdded", (el) => {
+ if (dom_default.isPhxSticky(el)) {
+ this.liveSocket.joinRootViews();
+ } else {
+ phxChildrenAdded = true;
}
});
- patch.after("phxChildAdded", (_el) => phxChildrenAdded = true);
patch.before("updated", (fromEl, toEl) => {
let hook = this.triggerBeforeUpdateHook(fromEl, toEl);
if (hook) {
@@ -2186,18 +2727,34 @@ within:
}
});
patch.after("discarded", (el) => {
- let cid = this.componentID(el);
- if (isCid(cid) && destroyedCIDs.indexOf(cid) === -1) {
- destroyedCIDs.push(cid);
+ if (el.nodeType === Node.ELEMENT_NODE) {
+ removedEls.push(el);
}
- let hook = this.getHook(el);
- hook && this.destroyHook(hook);
});
+ patch.after("transitionsDiscarded", (els) => this.afterElementsRemoved(els, pruneCids));
patch.perform();
+ this.afterElementsRemoved(removedEls, pruneCids);
+ return phxChildrenAdded;
+ }
+ afterElementsRemoved(elements, pruneCids) {
+ let destroyedCIDs = [];
+ elements.forEach((parent) => {
+ let components = dom_default.all(parent, `[${PHX_COMPONENT}]`);
+ let hooks = dom_default.all(parent, `[${this.binding(PHX_HOOK)}]`);
+ components.concat(parent).forEach((el) => {
+ let cid = this.componentID(el);
+ if (isCid(cid) && destroyedCIDs.indexOf(cid) === -1) {
+ destroyedCIDs.push(cid);
+ }
+ });
+ hooks.concat(parent).forEach((hookEl) => {
+ let hook = this.getHook(hookEl);
+ hook && this.destroyHook(hook);
+ });
+ });
if (pruneCids) {
this.maybePushComponentsDestroyed(destroyedCIDs);
}
- return phxChildrenAdded;
}
joinNewChildren() {
dom_default.findPhxChildren(this.el, this.id).forEach((el) => this.joinChild(el));
@@ -2245,16 +2802,17 @@ within:
}
}
onAllChildJoinsComplete() {
- this.joinCallback();
- this.pendingJoinOps.forEach(([view, op]) => {
- if (!view.isDestroyed()) {
- op();
- }
+ this.joinCallback(() => {
+ this.pendingJoinOps.forEach(([view, op]) => {
+ if (!view.isDestroyed()) {
+ op();
+ }
+ });
+ this.pendingJoinOps = [];
});
- this.pendingJoinOps = [];
}
update(diff, events) {
- if (this.isJoinPending() || this.liveSocket.hasPendingLink()) {
+ if (this.isJoinPending() || this.liveSocket.hasPendingLink() && this.root.isMain()) {
return this.pendingDiffs.push({ diff, events });
}
this.rendered.mergeDiff(diff);
@@ -2270,12 +2828,12 @@ within:
});
} else if (!isEmpty(diff)) {
this.liveSocket.time("full patch complete", () => {
- let html = this.renderContainer(diff, "update");
- let patch = new DOMPatch(this, this.el, this.id, html, null);
+ let [html, streams] = this.renderContainer(diff, "update");
+ let patch = new DOMPatch(this, this.el, this.id, html, streams, null);
phxChildrenAdded = this.performPatch(patch, true);
});
}
- this.dispatchEvents(events);
+ this.liveSocket.dispatchEvents(events);
if (phxChildrenAdded) {
this.joinNewChildren();
}
@@ -2284,15 +2842,15 @@ within:
return this.liveSocket.time(`toString diff (${kind})`, () => {
let tag = this.el.tagName;
let cids = diff ? this.rendered.componentCIDs(diff).concat(this.pruningCIDs) : null;
- let html = this.rendered.toString(cids);
- return `<${tag}>${html}${tag}>`;
+ let [html, streams] = this.rendered.toString(cids);
+ return [`<${tag}>${html}${tag}>`, streams];
});
}
componentPatch(diff, cid) {
if (isEmpty(diff))
return false;
- let html = this.rendered.componentToString(cid);
- let patch = new DOMPatch(this, this.el, this.id, html, cid);
+ let [html, streams] = this.rendered.componentToString(cid);
+ let patch = new DOMPatch(this, this.el, this.id, html, streams, cid);
let childrenAdded = this.performPatch(patch, true);
return childrenAdded;
}
@@ -2327,19 +2885,28 @@ within:
applyPendingUpdates() {
this.pendingDiffs.forEach(({ diff, events }) => this.update(diff, events));
this.pendingDiffs = [];
+ this.eachChild((child) => child.applyPendingUpdates());
+ }
+ eachChild(callback) {
+ let children = this.root.children[this.id] || {};
+ for (let id in children) {
+ callback(this.getChildById(id));
+ }
}
onChannel(event, cb) {
this.liveSocket.onChannel(this.channel, event, (resp) => {
if (this.isJoinPending()) {
this.root.pendingJoinOps.push([this, () => cb(resp)]);
} else {
- cb(resp);
+ this.liveSocket.requestDOMUpdate(() => cb(resp));
}
});
}
bindChannel() {
this.liveSocket.onChannel(this.channel, "diff", (rawDiff) => {
- this.applyDiff("update", rawDiff, ({ diff, events }) => this.update(diff, events));
+ this.liveSocket.requestDOMUpdate(() => {
+ this.applyDiff("update", rawDiff, ({ diff, events }) => this.update(diff, events));
+ });
});
this.onChannel("redirect", ({ to, flash }) => this.onRedirect({ to, flash }));
this.onChannel("live_patch", (redir) => this.onLivePatch(redir));
@@ -2348,9 +2915,7 @@ within:
this.channel.onClose((reason) => this.onClose(reason));
}
destroyAllChildren() {
- for (let id in this.root.children[this.id]) {
- this.getChildById(id).destroy();
- }
+ this.eachChild((child) => child.destroy());
}
onLiveRedirect(redir) {
let { to, kind, flash } = redir;
@@ -2371,17 +2936,33 @@ within:
isDestroyed() {
return this.destroyed;
}
+ joinDead() {
+ this.isDead = true;
+ }
join(callback) {
- if (!this.parent) {
+ this.showLoader(this.liveSocket.loaderTimeout);
+ this.bindChannel();
+ if (this.isMain()) {
this.stopCallback = this.liveSocket.withPageLoading({ to: this.href, kind: "initial" });
}
- this.joinCallback = () => callback && callback(this.joinCount);
+ this.joinCallback = (onDone) => {
+ onDone = onDone || function() {
+ };
+ callback ? callback(this.joinCount, onDone) : onDone();
+ };
this.liveSocket.wrapPush(this, { timeout: false }, () => {
- return this.channel.join().receive("ok", (data) => !this.isDestroyed() && this.onJoin(data)).receive("error", (resp) => !this.isDestroyed() && this.onJoinError(resp)).receive("timeout", () => !this.isDestroyed() && this.onJoinError({ reason: "timeout" }));
+ return this.channel.join().receive("ok", (data) => {
+ if (!this.isDestroyed()) {
+ this.liveSocket.requestDOMUpdate(() => this.onJoin(data));
+ }
+ }).receive("error", (resp) => !this.isDestroyed() && this.onJoinError(resp)).receive("timeout", () => !this.isDestroyed() && this.onJoinError({ reason: "timeout" }));
});
}
onJoinError(resp) {
- if (resp.reason === "unauthorized" || resp.reason === "stale") {
+ if (resp.reason === "reload") {
+ this.log("error", () => [`failed mount with ${resp.status}. Falling back to page request`, resp]);
+ return this.onRedirect({ to: this.href });
+ } else if (resp.reason === "unauthorized" || resp.reason === "stale") {
this.log("error", () => ["unauthorized live_redirect. Falling back to page request", resp]);
return this.onRedirect({ to: this.href });
}
@@ -2396,13 +2977,15 @@ within:
return this.onLiveRedirect(resp.live_redirect);
}
this.log("error", () => ["unable to join", resp]);
- return this.liveSocket.reloadWithJitter(this);
+ if (this.liveSocket.isConnected()) {
+ this.liveSocket.reloadWithJitter(this);
+ }
}
onClose(reason) {
if (this.isDestroyed()) {
return;
}
- if (this.isJoinPending() && document.visibilityState !== "hidden" || this.liveSocket.hasPendingLink() && reason !== "leave") {
+ if (this.liveSocket.hasPendingLink() && reason !== "leave") {
return this.liveSocket.reloadWithJitter(this);
}
this.destroyAllChildren();
@@ -2416,27 +2999,30 @@ within:
}
onError(reason) {
this.onClose(reason);
- this.log("error", () => ["view crashed", reason]);
+ if (this.liveSocket.isConnected()) {
+ this.log("error", () => ["view crashed", reason]);
+ }
if (!this.liveSocket.isUnloaded()) {
this.displayError();
}
}
displayError() {
if (this.isMain()) {
- dom_default.dispatchEvent(window, "phx:page-loading-start", { to: this.href, kind: "error" });
+ dom_default.dispatchEvent(window, "phx:page-loading-start", { detail: { to: this.href, kind: "error" } });
}
this.showLoader();
this.setContainerClasses(PHX_DISCONNECTED_CLASS, PHX_ERROR_CLASS);
+ this.execAll(this.binding("disconnected"));
}
pushWithReply(refGenerator, event, payload, onReply = function() {
}) {
if (!this.isConnected()) {
return;
}
- let [ref, [el]] = refGenerator ? refGenerator() : [null, []];
+ let [ref, [el], opts] = refGenerator ? refGenerator() : [null, [], {}];
let onLoadingDone = function() {
};
- if (el && el.getAttribute(this.binding(PHX_PAGE_LOADING)) !== null) {
+ if (opts.page_loading || el && el.getAttribute(this.binding(PHX_PAGE_LOADING)) !== null) {
onLoadingDone = this.liveSocket.withPageLoading({ kind: "element", target: el });
}
if (typeof payload.cid !== "number") {
@@ -2444,33 +3030,43 @@ within:
}
return this.liveSocket.wrapPush(this, { timeout: true }, () => {
return this.channel.push(event, payload, PUSH_TIMEOUT).receive("ok", (resp) => {
- let hookReply = null;
- if (ref !== null) {
- this.undoRefs(ref);
- }
+ let finish = (hookReply) => {
+ if (resp.redirect) {
+ this.onRedirect(resp.redirect);
+ }
+ if (resp.live_patch) {
+ this.onLivePatch(resp.live_patch);
+ }
+ if (resp.live_redirect) {
+ this.onLiveRedirect(resp.live_redirect);
+ }
+ if (ref !== null) {
+ this.undoRefs(ref);
+ }
+ onLoadingDone();
+ onReply(resp, hookReply);
+ };
if (resp.diff) {
- hookReply = this.applyDiff("update", resp.diff, ({ diff, events }) => {
- this.update(diff, events);
+ this.liveSocket.requestDOMUpdate(() => {
+ this.applyDiff("update", resp.diff, ({ diff, reply, events }) => {
+ this.update(diff, events);
+ finish(reply);
+ });
});
+ } else {
+ finish(null);
}
- if (resp.redirect) {
- this.onRedirect(resp.redirect);
- }
- if (resp.live_patch) {
- this.onLivePatch(resp.live_patch);
- }
- if (resp.live_redirect) {
- this.onLiveRedirect(resp.live_redirect);
- }
- onLoadingDone();
- onReply(resp, hookReply);
});
});
}
undoRefs(ref) {
- dom_default.all(this.el, `[${PHX_REF}="${ref}"]`, (el) => {
+ if (!this.isConnected()) {
+ return;
+ }
+ dom_default.all(document, `[${PHX_REF_SRC}="${this.id}"][${PHX_REF}="${ref}"]`, (el) => {
let disabledVal = el.getAttribute(PHX_DISABLED);
el.removeAttribute(PHX_REF);
+ el.removeAttribute(PHX_REF_SRC);
if (el.getAttribute(PHX_READONLY) !== null) {
el.readOnly = false;
el.removeAttribute(PHX_READONLY);
@@ -2496,35 +3092,50 @@ within:
}
});
}
- putRef(elements, event) {
+ putRef(elements, event, opts = {}) {
let newRef = this.ref++;
let disableWith = this.binding(PHX_DISABLE_WITH);
+ if (opts.loading) {
+ elements = elements.concat(dom_default.all(document, opts.loading));
+ }
elements.forEach((el) => {
el.classList.add(`phx-${event}-loading`);
el.setAttribute(PHX_REF, newRef);
+ el.setAttribute(PHX_REF_SRC, this.el.id);
let disableText = el.getAttribute(disableWith);
if (disableText !== null) {
if (!el.getAttribute(PHX_DISABLE_WITH_RESTORE)) {
el.setAttribute(PHX_DISABLE_WITH_RESTORE, el.innerText);
}
- el.innerText = disableText;
+ if (disableText !== "") {
+ el.innerText = disableText;
+ }
+ el.setAttribute("disabled", "");
}
});
- return [newRef, elements];
+ return [newRef, elements, opts];
}
componentID(el) {
let cid = el.getAttribute && el.getAttribute(PHX_COMPONENT);
return cid ? parseInt(cid) : null;
}
- targetComponentID(target, targetCtx) {
- if (target.getAttribute(this.binding("target"))) {
+ targetComponentID(target, targetCtx, opts = {}) {
+ if (isCid(targetCtx)) {
+ return targetCtx;
+ }
+ let cidOrSelector = target.getAttribute(this.binding("target"));
+ if (isCid(cidOrSelector)) {
+ return parseInt(cidOrSelector);
+ } else if (targetCtx && (cidOrSelector !== null || opts.target)) {
return this.closestComponentID(targetCtx);
} else {
return null;
}
}
closestComponentID(targetCtx) {
- if (targetCtx) {
+ if (isCid(targetCtx)) {
+ return targetCtx;
+ } else if (targetCtx) {
return maybe(targetCtx.closest(`[${PHX_COMPONENT}]`), (el) => this.ownsElement(el) && this.componentID(el));
} else {
return null;
@@ -2535,8 +3146,8 @@ within:
this.log("hook", () => ["unable to push hook event. LiveView not connected", event, payload]);
return false;
}
- let [ref, els] = this.putRef([], "hook");
- this.pushWithReply(() => [ref, els], "event", {
+ let [ref, els, opts] = this.putRef([], "hook");
+ this.pushWithReply(() => [ref, els, opts], "event", {
type: "hook",
event,
value: payload,
@@ -2544,36 +3155,42 @@ within:
}, (resp, reply) => onReply(reply, ref));
return ref;
}
- extractMeta(el, meta) {
+ extractMeta(el, meta, value) {
let prefix = this.binding("value-");
for (let i = 0; i < el.attributes.length; i++) {
+ if (!meta) {
+ meta = {};
+ }
let name = el.attributes[i].name;
if (name.startsWith(prefix)) {
meta[name.replace(prefix, "")] = el.getAttribute(name);
}
}
if (el.value !== void 0) {
+ if (!meta) {
+ meta = {};
+ }
meta.value = el.value;
if (el.tagName === "INPUT" && CHECKABLE_INPUTS.indexOf(el.type) >= 0 && !el.checked) {
delete meta.value;
}
}
+ if (value) {
+ if (!meta) {
+ meta = {};
+ }
+ for (let key in value) {
+ meta[key] = value[key];
+ }
+ }
return meta;
}
- pushEvent(type, el, targetCtx, phxEvent, meta) {
- this.pushWithReply(() => this.putRef([el], type), "event", {
+ pushEvent(type, el, targetCtx, phxEvent, meta, opts = {}) {
+ this.pushWithReply(() => this.putRef([el], type, opts), "event", {
type,
event: phxEvent,
- value: this.extractMeta(el, meta),
- cid: this.targetComponentID(el, targetCtx)
- });
- }
- pushKey(keyElement, targetCtx, kind, phxEvent, meta) {
- this.pushWithReply(() => this.putRef([keyElement], kind), "event", {
- type: kind,
- event: phxEvent,
- value: this.extractMeta(keyElement, meta),
- cid: this.targetComponentID(keyElement, targetCtx)
+ value: this.extractMeta(el, meta, opts.value),
+ cid: this.targetComponentID(el, targetCtx, opts)
});
}
pushFileProgress(fileEl, entryRef, progress, onReply = function() {
@@ -2588,11 +3205,16 @@ within:
}, onReply);
});
}
- pushInput(inputEl, targetCtx, forceCid, phxEvent, eventTarget, callback) {
+ pushInput(inputEl, targetCtx, forceCid, phxEvent, opts, callback) {
let uploads;
let cid = isCid(forceCid) ? forceCid : this.targetComponentID(inputEl.form, targetCtx);
- let refGenerator = () => this.putRef([inputEl, inputEl.form], "change");
- let formData = serializeForm(inputEl.form, { _target: eventTarget.name });
+ let refGenerator = () => this.putRef([inputEl, inputEl.form], "change", opts);
+ let formData;
+ if (inputEl.getAttribute(this.binding("change"))) {
+ formData = serializeForm(inputEl.form, { _target: opts._target }, [inputEl.name]);
+ } else {
+ formData = serializeForm(inputEl.form, { _target: opts._target });
+ }
if (dom_default.isUploadInput(inputEl) && inputEl.files && inputEl.files.length > 0) {
LiveUploader.trackFiles(inputEl, Array.from(inputEl.files));
}
@@ -2622,19 +3244,19 @@ within:
triggerAwaitingSubmit(formEl) {
let awaitingSubmit = this.getScheduledSubmit(formEl);
if (awaitingSubmit) {
- let [_el, _ref, callback] = awaitingSubmit;
+ let [_el, _ref, _opts, callback] = awaitingSubmit;
this.cancelSubmit(formEl);
callback();
}
}
getScheduledSubmit(formEl) {
- return this.formSubmits.find(([el, _callback]) => el.isSameNode(formEl));
+ return this.formSubmits.find(([el, _ref, _opts, _callback]) => el.isSameNode(formEl));
}
- scheduleSubmit(formEl, ref, callback) {
+ scheduleSubmit(formEl, ref, opts, callback) {
if (this.getScheduledSubmit(formEl)) {
return true;
}
- this.formSubmits.push([formEl, ref, callback]);
+ this.formSubmits.push([formEl, ref, opts, callback]);
}
cancelSubmit(formEl) {
this.formSubmits = this.formSubmits.filter(([el, ref, _callback]) => {
@@ -2646,7 +3268,7 @@ within:
}
});
}
- pushFormSubmit(formEl, targetCtx, phxEvent, onReply) {
+ disableForm(formEl, opts = {}) {
let filterIgnored = (el) => {
let userIgnored = closestPhxBinding(el, `${this.binding(PHX_UPDATE)}=ignore`, el.form);
return !(userIgnored || closestPhxBinding(el, "data-phx-update=ignore", el.form));
@@ -2656,33 +3278,35 @@ within:
};
let filterButton = (el) => el.tagName == "BUTTON";
let filterInput = (el) => ["INPUT", "TEXTAREA", "SELECT"].includes(el.tagName);
- let refGenerator = () => {
- let formElements = Array.from(formEl.elements);
- let disables = formElements.filter(filterDisables);
- let buttons = formElements.filter(filterButton).filter(filterIgnored);
- let inputs = formElements.filter(filterInput).filter(filterIgnored);
- buttons.forEach((button) => {
- button.setAttribute(PHX_DISABLED, button.disabled);
- button.disabled = true;
- });
- inputs.forEach((input) => {
- input.setAttribute(PHX_READONLY, input.readOnly);
- input.readOnly = true;
- if (input.files) {
- input.setAttribute(PHX_DISABLED, input.disabled);
- input.disabled = true;
- }
- });
- formEl.setAttribute(this.binding(PHX_PAGE_LOADING), "");
- return this.putRef([formEl].concat(disables).concat(buttons).concat(inputs), "submit");
- };
+ let formElements = Array.from(formEl.elements);
+ let disables = formElements.filter(filterDisables);
+ let buttons = formElements.filter(filterButton).filter(filterIgnored);
+ let inputs = formElements.filter(filterInput).filter(filterIgnored);
+ buttons.forEach((button) => {
+ button.setAttribute(PHX_DISABLED, button.disabled);
+ button.disabled = true;
+ });
+ inputs.forEach((input) => {
+ input.setAttribute(PHX_READONLY, input.readOnly);
+ input.readOnly = true;
+ if (input.files) {
+ input.setAttribute(PHX_DISABLED, input.disabled);
+ input.disabled = true;
+ }
+ });
+ formEl.setAttribute(this.binding(PHX_PAGE_LOADING), "");
+ return this.putRef([formEl].concat(disables).concat(buttons).concat(inputs), "submit", opts);
+ }
+ pushFormSubmit(formEl, targetCtx, phxEvent, opts, onReply) {
+ let refGenerator = () => this.disableForm(formEl, opts);
let cid = this.targetComponentID(formEl, targetCtx);
if (LiveUploader.hasUploadsInProgress(formEl)) {
let [ref, _els] = refGenerator();
- return this.scheduleSubmit(formEl, ref, () => this.pushFormSubmit(formEl, targetCtx, phxEvent, onReply));
+ let push = () => this.pushFormSubmit(formEl, targetCtx, phxEvent, opts, onReply);
+ return this.scheduleSubmit(formEl, ref, opts, push);
} else if (LiveUploader.inputsAwaitingPreflight(formEl).length > 0) {
let [ref, els] = refGenerator();
- let proxyRefGen = () => [ref, els];
+ let proxyRefGen = () => [ref, els, opts];
this.uploadFiles(formEl, targetCtx, ref, cid, (_uploads) => {
let formData = serializeForm(formEl, {});
this.pushWithReply(proxyRefGen, "event", {
@@ -2693,7 +3317,7 @@ within:
}, onReply);
});
} else {
- let formData = serializeForm(formEl);
+ let formData = serializeForm(formEl, {});
this.pushWithReply(refGenerator, "event", {
type: "form",
event: phxEvent,
@@ -2747,30 +3371,40 @@ within:
} else if (inputs.length > 1) {
logError(`duplicate live file inputs found matching the name "${name}"`);
} else {
- dom_default.dispatchEvent(inputs[0], PHX_TRACK_UPLOADS, { files: filesOrBlobs });
+ dom_default.dispatchEvent(inputs[0], PHX_TRACK_UPLOADS, { detail: { files: filesOrBlobs } });
}
}
pushFormRecovery(form, newCid, callback) {
this.liveSocket.withinOwners(form, (view, targetCtx) => {
- let input = form.elements[0];
+ let input = Array.from(form.elements).find((el) => {
+ return dom_default.isFormInput(el) && el.type !== "hidden" && !el.hasAttribute(this.binding("change"));
+ });
let phxEvent = form.getAttribute(this.binding(PHX_AUTO_RECOVER)) || form.getAttribute(this.binding("change"));
- view.pushInput(input, targetCtx, newCid, phxEvent, input, callback);
+ js_default.exec("change", phxEvent, view, input, ["push", { _target: input.name, newCid, callback }]);
});
}
pushLinkPatch(href, targetEl, callback) {
let linkRef = this.liveSocket.setPendingLink(href);
let refGen = targetEl ? () => this.putRef([targetEl], "click") : null;
- this.pushWithReply(refGen, "live_patch", { url: href }, (resp) => {
- if (resp.link_redirect) {
- this.liveSocket.replaceMain(href, null, callback, linkRef);
- } else {
- if (this.liveSocket.commitPendingLink(linkRef)) {
- this.href = href;
+ let fallback = () => this.liveSocket.redirect(window.location.href);
+ let push = this.pushWithReply(refGen, "live_patch", { url: href }, (resp) => {
+ this.liveSocket.requestDOMUpdate(() => {
+ if (resp.link_redirect) {
+ this.liveSocket.replaceMain(href, null, callback, linkRef);
+ } else {
+ if (this.liveSocket.commitPendingLink(linkRef)) {
+ this.href = href;
+ }
+ this.applyPendingUpdates();
+ callback && callback(linkRef);
}
- this.applyPendingUpdates();
- callback && callback(linkRef);
- }
- }).receive("timeout", () => this.liveSocket.redirect(window.location.href));
+ });
+ });
+ if (push) {
+ push.receive("timeout", fallback);
+ } else {
+ fallback();
+ }
}
formsForRecovery(html) {
if (this.joinCount === 0) {
@@ -2782,7 +3416,7 @@ within:
return dom_default.all(this.el, `form[${phxChange}]`).filter((form) => form.id && this.ownsElement(form)).filter((form) => form.elements.length > 0).filter((form) => form.getAttribute(this.binding(PHX_AUTO_RECOVER)) !== "ignore").map((form) => {
let newForm = template.content.querySelector(`form[id="${form.id}"][${phxChange}="${form.getAttribute(phxChange)}"]`);
if (newForm) {
- return [form, newForm, this.componentID(newForm)];
+ return [form, newForm, this.targetComponentID(newForm)];
} else {
return [form, null, null];
}
@@ -2808,12 +3442,17 @@ within:
}
}
ownsElement(el) {
- return el.getAttribute(PHX_PARENT_ID) === this.id || maybe(el.closest(PHX_VIEW_SELECTOR), (node) => node.id) === this.id;
+ let parentViewEl = el.closest(PHX_VIEW_SELECTOR);
+ return el.getAttribute(PHX_PARENT_ID) === this.id || parentViewEl && parentViewEl.id === this.id || !parentViewEl && this.isDead;
}
- submitForm(form, targetCtx, phxEvent) {
+ submitForm(form, targetCtx, phxEvent, opts = {}) {
dom_default.putPrivate(form, PHX_HAS_SUBMITTED, true);
+ let phxFeedback = this.liveSocket.binding(PHX_FEEDBACK_FOR);
+ let inputs = Array.from(form.elements);
+ inputs.forEach((input) => dom_default.putPrivate(input, PHX_HAS_SUBMITTED, true));
this.liveSocket.blurActiveElement(this);
- this.pushFormSubmit(form, targetCtx, phxEvent, () => {
+ this.pushFormSubmit(form, targetCtx, phxEvent, opts, () => {
+ inputs.forEach((input) => dom_default.showError(input, phxFeedback));
this.liveSocket.restorePreviouslyActiveFocus();
});
}
@@ -2831,7 +3470,7 @@ within:
a phoenix Socket must be provided as the second argument to the LiveSocket constructor. For example:
import {Socket} from "phoenix"
- import LiveSocket from "phoenix_live_view"
+ import {LiveSocket} from "phoenix_live_view"
let liveSocket = new LiveSocket("/live", Socket, {...})
`);
}
@@ -2846,6 +3485,8 @@ within:
this.prevActive = null;
this.silenced = false;
this.main = null;
+ this.outgoingMainEl = null;
+ this.clickStartedAtTarget = null;
this.linkRef = 1;
this.roots = {};
this.href = window.location.href;
@@ -2854,10 +3495,16 @@ within:
this.hooks = opts.hooks || {};
this.uploaders = opts.uploaders || {};
this.loaderTimeout = opts.loaderTimeout || LOADER_TIMEOUT;
+ this.reloadWithJitterTimer = null;
+ this.maxReloads = opts.maxReloads || MAX_RELOADS;
+ this.reloadJitterMin = opts.reloadJitterMin || RELOAD_JITTER_MIN;
+ this.reloadJitterMax = opts.reloadJitterMax || RELOAD_JITTER_MAX;
+ this.failsafeJitter = opts.failsafeJitter || FAILSAFE_JITTER;
this.localStorage = opts.localStorage || window.localStorage;
this.sessionStorage = opts.sessionStorage || window.sessionStorage;
this.boundTopLevelEvents = false;
this.domCallbacks = Object.assign({ onNodeAdded: closure(), onBeforeElUpdated: closure() }, opts.dom || {});
+ this.transitions = new TransitionSet();
window.addEventListener("pagehide", (_e) => {
this.unloaded = true;
});
@@ -2873,6 +3520,9 @@ within:
isDebugEnabled() {
return this.sessionStorage.getItem(PHX_LV_DEBUG) === "true";
}
+ isDebugDisabled() {
+ return this.sessionStorage.getItem(PHX_LV_DEBUG) === "false";
+ }
enableDebug() {
this.sessionStorage.setItem(PHX_LV_DEBUG, "true");
}
@@ -2880,7 +3530,7 @@ within:
this.sessionStorage.setItem(PHX_LV_PROFILE, "true");
}
disableDebug() {
- this.sessionStorage.removeItem(PHX_LV_DEBUG);
+ this.sessionStorage.setItem(PHX_LV_DEBUG, "false");
}
disableProfiling() {
this.sessionStorage.removeItem(PHX_LV_PROFILE);
@@ -2901,11 +3551,19 @@ within:
return this.socket;
}
connect() {
+ if (window.location.hostname === "localhost" && !this.isDebugDisabled()) {
+ this.enableDebug();
+ }
let doConnect = () => {
if (this.joinRootViews()) {
this.bindTopLevelEvents();
this.socket.connect();
+ } else if (this.main) {
+ this.socket.connect();
+ } else {
+ this.bindTopLevelEvents({ dead: true });
}
+ this.joinDeadView();
};
if (["complete", "loaded", "interactive"].indexOf(document.readyState) >= 0) {
doConnect();
@@ -2914,8 +3572,28 @@ within:
}
}
disconnect(callback) {
+ clearTimeout(this.reloadWithJitterTimer);
this.socket.disconnect(callback);
}
+ replaceTransport(transport) {
+ clearTimeout(this.reloadWithJitterTimer);
+ this.socket.replaceTransport(transport);
+ this.connect();
+ }
+ execJS(el, encodedJS, eventType = null) {
+ this.owner(el, (view) => js_default.exec(eventType, encodedJS, view, el));
+ }
+ unload() {
+ if (this.unloaded) {
+ return;
+ }
+ if (this.main && this.isConnected()) {
+ this.log(this.main, "socket", () => ["disconnect for page nav"]);
+ }
+ this.unloaded = true;
+ this.destroyAllViews();
+ this.disconnect();
+ }
triggerDOM(kind, args) {
this.domCallbacks[kind](...args);
}
@@ -2937,13 +3615,19 @@ within:
debug(view, kind, msg, obj);
}
}
+ requestDOMUpdate(callback) {
+ this.transitions.after(callback);
+ }
+ transition(time, onStart, onDone = function() {
+ }) {
+ this.transitions.addTransition(time, onStart, onDone);
+ }
onChannel(channel, event, cb) {
channel.on(event, (data) => {
let latency = this.getLatencySim();
if (!latency) {
cb(data);
} else {
- console.log(`simulating ${latency}ms of latency from server to client`);
setTimeout(() => cb(data), latency);
}
});
@@ -2952,7 +3636,7 @@ within:
let latency = this.getLatencySim();
let oldJoinCount = view.joinCount;
if (!latency) {
- if (opts.timeout) {
+ if (this.isConnected() && opts.timeout) {
return push().receive("timeout", () => {
if (view.joinCount === oldJoinCount && !view.isDestroyed()) {
this.reloadWithJitter(view, () => {
@@ -2964,7 +3648,6 @@ within:
return push();
}
}
- console.log(`simulating ${latency}ms of latency from client to server`);
let fakePush = {
receives: [],
receive(kind, cb) {
@@ -2980,17 +3663,24 @@ within:
return fakePush;
}
reloadWithJitter(view, log) {
- view.destroy();
+ clearTimeout(this.reloadWithJitterTimer);
this.disconnect();
- let [minMs, maxMs] = RELOAD_JITTER;
+ let minMs = this.reloadJitterMin;
+ let maxMs = this.reloadJitterMax;
let afterMs = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs;
let tries = browser_default.updateLocal(this.localStorage, window.location.pathname, CONSECUTIVE_RELOADS, 0, (count) => count + 1);
- log ? log() : this.log(view, "join", () => [`encountered ${tries} consecutive reloads`]);
- if (tries > MAX_RELOADS) {
- this.log(view, "join", () => [`exceeded ${MAX_RELOADS} consecutive reloads. Entering failsafe mode`]);
- afterMs = FAILSAFE_JITTER;
+ if (tries > this.maxReloads) {
+ afterMs = this.failsafeJitter;
}
- setTimeout(() => {
+ this.reloadWithJitterTimer = setTimeout(() => {
+ if (view.isDestroyed() || view.isConnected()) {
+ return;
+ }
+ view.destroy();
+ log ? log() : this.log(view, "join", () => [`encountered ${tries} consecutive reloads`]);
+ if (tries > this.maxReloads) {
+ this.log(view, "join", () => [`exceeded ${this.maxReloads} consecutive reloads. Entering failsafe mode`]);
+ }
if (this.hasPendingLink()) {
window.location = this.pendingLink;
} else {
@@ -3016,6 +3706,18 @@ within:
channel(topic, params) {
return this.socket.channel(topic, params);
}
+ joinDeadView() {
+ let body = document.body;
+ if (body && !this.isPhxView(body) && !this.isPhxView(document.firstElementChild)) {
+ let view = this.newRootView(body);
+ view.setHref(this.getHref());
+ view.joinDead();
+ if (!this.main) {
+ this.main = view;
+ }
+ window.requestAnimationFrame(() => view.execNewMounted());
+ }
+ }
joinRootViews() {
let rootsFound = false;
dom_default.all(document, `${PHX_VIEW_SELECTOR}:not([${PHX_PARENT_ID}])`, (rootEl) => {
@@ -3023,7 +3725,7 @@ within:
let view = this.newRootView(rootEl);
view.setHref(this.getHref());
view.join();
- if (rootEl.getAttribute(PHX_MAIN)) {
+ if (rootEl.hasAttribute(PHX_MAIN)) {
this.main = view;
}
}
@@ -3032,46 +3734,55 @@ within:
return rootsFound;
}
redirect(to, flash) {
- this.disconnect();
+ this.unload();
browser_default.redirect(to, flash);
}
replaceMain(href, flash, callback = null, linkRef = this.setPendingLink(href)) {
- let oldMainEl = this.main.el;
- let newMainEl = dom_default.cloneNode(oldMainEl, "");
+ let liveReferer = this.currentLocation.href;
+ this.outgoingMainEl = this.outgoingMainEl || this.main.el;
+ let newMainEl = dom_default.cloneNode(this.outgoingMainEl, "");
this.main.showLoader(this.loaderTimeout);
this.main.destroy();
- this.main = this.newRootView(newMainEl, flash);
+ this.main = this.newRootView(newMainEl, flash, liveReferer);
this.main.setRedirect(href);
- this.main.join((joinCount) => {
+ this.transitionRemoves();
+ this.main.join((joinCount, onDone) => {
if (joinCount === 1 && this.commitPendingLink(linkRef)) {
- oldMainEl.replaceWith(newMainEl);
- callback && callback();
+ this.requestDOMUpdate(() => {
+ dom_default.findPhxSticky(document).forEach((el) => newMainEl.appendChild(el));
+ this.outgoingMainEl.replaceWith(newMainEl);
+ this.outgoingMainEl = null;
+ callback && requestAnimationFrame(callback);
+ onDone();
+ });
+ }
+ });
+ }
+ transitionRemoves(elements) {
+ let removeAttr = this.binding("remove");
+ elements = elements || dom_default.all(document, `[${removeAttr}]`);
+ elements.forEach((el) => {
+ if (document.body.contains(el)) {
+ this.execJS(el, el.getAttribute(removeAttr), "remove");
}
});
}
isPhxView(el) {
return el.getAttribute && el.getAttribute(PHX_SESSION) !== null;
}
- newRootView(el, flash) {
- let view = new View(el, this, null, flash);
+ newRootView(el, flash, liveReferer) {
+ let view = new View(el, this, null, flash, liveReferer);
this.roots[view.id] = view;
return view;
}
owner(childEl, callback) {
- let view = maybe(childEl.closest(PHX_VIEW_SELECTOR), (el) => this.getViewByEl(el));
+ let view = maybe(childEl.closest(PHX_VIEW_SELECTOR), (el) => this.getViewByEl(el)) || this.main;
if (view) {
callback(view);
}
}
withinOwners(childEl, callback) {
- this.owner(childEl, (view) => {
- let phxTarget = childEl.getAttribute(this.binding("target"));
- if (phxTarget === null) {
- callback(view, childEl);
- } else {
- view.withinTargets(phxTarget, callback);
- }
- });
+ this.owner(childEl, (view) => callback(view, childEl));
}
getViewByEl(el) {
let rootId = el.getAttribute(PHX_ROOT_ID);
@@ -3085,10 +3796,14 @@ within:
this.roots[id].destroy();
delete this.roots[id];
}
+ this.main = null;
}
destroyViewByEl(el) {
let root = this.getRootById(el.getAttribute(PHX_ROOT_ID));
- if (root) {
+ if (root && root.id === el.id) {
+ root.destroy();
+ delete this.roots[root.id];
+ } else if (root) {
root.destroyDescendent(el.id);
}
}
@@ -3130,11 +3845,19 @@ within:
this.prevActive.blur();
}
}
- bindTopLevelEvents() {
+ bindTopLevelEvents({ dead } = {}) {
if (this.boundTopLevelEvents) {
return;
}
this.boundTopLevelEvents = true;
+ this.socket.onClose((event) => {
+ if (event && event.code === 1001) {
+ return this.unload();
+ }
+ if (event && event.code === 1e3 && this.main) {
+ return this.reloadWithJitter(this.main);
+ }
+ });
document.body.addEventListener("click", function() {
});
window.addEventListener("pageshow", (e) => {
@@ -3144,25 +3867,32 @@ within:
window.location.reload();
}
}, true);
- this.bindNav();
+ if (!dead) {
+ this.bindNav();
+ }
this.bindClicks();
- this.bindForms();
- this.bind({ keyup: "keyup", keydown: "keydown" }, (e, type, view, target, targetCtx, phxEvent, _phxTarget) => {
- let matchKey = target.getAttribute(this.binding(PHX_KEY));
+ if (!dead) {
+ this.bindForms();
+ }
+ this.bind({ keyup: "keyup", keydown: "keydown" }, (e, type, view, targetEl, phxEvent, eventTarget) => {
+ let matchKey = targetEl.getAttribute(this.binding(PHX_KEY));
let pressedKey = e.key && e.key.toLowerCase();
if (matchKey && matchKey.toLowerCase() !== pressedKey) {
return;
}
- view.pushKey(target, targetCtx, type, phxEvent, __spreadValues({ key: e.key }, this.eventMeta(type, e, target)));
+ let data = __spreadValues({ key: e.key }, this.eventMeta(type, e, targetEl));
+ js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
});
- this.bind({ blur: "focusout", focus: "focusin" }, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {
- if (!phxTarget) {
- view.pushEvent(type, targetEl, targetCtx, phxEvent, this.eventMeta(type, e, targetEl));
+ this.bind({ blur: "focusout", focus: "focusin" }, (e, type, view, targetEl, phxEvent, eventTarget) => {
+ if (!eventTarget) {
+ let data = __spreadValues({ key: e.key }, this.eventMeta(type, e, targetEl));
+ js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
}
});
this.bind({ blur: "blur", focus: "focus" }, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {
- if (phxTarget && !phxTarget !== "window") {
- view.pushEvent(type, targetEl, targetCtx, phxEvent, this.eventMeta(type, e, targetEl));
+ if (phxTarget === "window") {
+ let data = this.eventMeta(type, e, targetEl);
+ js_default.exec(type, phxEvent, view, targetEl, ["push", { data }]);
}
});
window.addEventListener("dragover", (e) => e.preventDefault());
@@ -3176,7 +3906,7 @@ within:
if (!dropTarget || dropTarget.disabled || files.length === 0 || !(dropTarget.files instanceof FileList)) {
return;
}
- LiveUploader.trackFiles(dropTarget, files);
+ LiveUploader.trackFiles(dropTarget, files, e.dataTransfer);
dropTarget.dispatchEvent(new Event("input", { bubbles: true }));
});
this.on(PHX_TRACK_UPLOADS, (e) => {
@@ -3221,17 +3951,17 @@ within:
let windowBinding = this.binding(`window-${event}`);
let targetPhxEvent = e.target.getAttribute && e.target.getAttribute(binding);
if (targetPhxEvent) {
- this.debounce(e.target, e, () => {
- this.withinOwners(e.target, (view, targetCtx) => {
- callback(e, event, view, e.target, targetCtx, targetPhxEvent, null);
+ this.debounce(e.target, e, browserEventName, () => {
+ this.withinOwners(e.target, (view) => {
+ callback(e, event, view, e.target, targetPhxEvent, null);
});
});
} else {
dom_default.all(document, `[${windowBinding}]`, (el) => {
let phxEvent = el.getAttribute(windowBinding);
- this.debounce(el, e, () => {
- this.withinOwners(el, (view, targetCtx) => {
- callback(e, event, view, el, targetCtx, phxEvent, "window");
+ this.debounce(el, e, browserEventName, () => {
+ this.withinOwners(el, (view) => {
+ callback(e, event, view, el, phxEvent, "window");
});
});
});
@@ -3240,35 +3970,53 @@ within:
}
}
bindClicks() {
+ window.addEventListener("click", (e) => this.clickStartedAtTarget = e.target);
this.bindClick("click", "click", false);
this.bindClick("mousedown", "capture-click", true);
}
bindClick(eventName, bindingName, capture) {
let click = this.binding(bindingName);
window.addEventListener(eventName, (e) => {
- if (!this.isConnected()) {
- return;
- }
let target = null;
if (capture) {
target = e.target.matches(`[${click}]`) ? e.target : e.target.querySelector(`[${click}]`);
} else {
- target = closestPhxBinding(e.target, click);
+ let clickStartedAtTarget = this.clickStartedAtTarget || e.target;
+ target = closestPhxBinding(clickStartedAtTarget, click);
+ this.dispatchClickAway(e, clickStartedAtTarget);
+ this.clickStartedAtTarget = null;
}
let phxEvent = target && target.getAttribute(click);
if (!phxEvent) {
+ let href = e.target instanceof HTMLAnchorElement ? e.target.getAttribute("href") : null;
+ if (!capture && href !== null && !dom_default.wantsNewTab(e) && dom_default.isNewPageHref(href, window.location)) {
+ this.unload();
+ }
return;
}
if (target.getAttribute("href") === "#") {
e.preventDefault();
}
- this.debounce(target, e, () => {
- this.withinOwners(target, (view, targetCtx) => {
- view.pushEvent("click", target, targetCtx, phxEvent, this.eventMeta("click", e, target));
+ this.debounce(target, e, "click", () => {
+ this.withinOwners(target, (view) => {
+ js_default.exec("click", phxEvent, view, target, ["push", { data: this.eventMeta("click", e, target) }]);
});
});
}, capture);
}
+ dispatchClickAway(e, clickStartedAt) {
+ let phxClickAway = this.binding("click-away");
+ dom_default.all(document, `[${phxClickAway}]`, (el) => {
+ if (!(el.isSameNode(clickStartedAt) || el.contains(clickStartedAt))) {
+ this.withinOwners(e.target, (view) => {
+ let phxEvent = el.getAttribute(phxClickAway);
+ if (js_default.isVisible(el)) {
+ js_default.exec("click", phxEvent, view, el, ["push", { data: this.eventMeta("click", e, e.target) }]);
+ }
+ });
+ }
+ });
+ }
bindNav() {
if (!browser_default.canPushState()) {
return;
@@ -3289,49 +4037,71 @@ within:
}
let { type, id, root, scroll } = event.state || {};
let href = window.location.href;
- if (this.main.isConnected() && (type === "patch" && id === this.main.id)) {
- this.main.pushLinkPatch(href, null);
- } else {
- this.replaceMain(href, null, () => {
- if (root) {
- this.replaceRootHistory();
- }
- if (typeof scroll === "number") {
- setTimeout(() => {
- window.scrollTo(0, scroll);
- }, 0);
- }
- });
- }
+ this.requestDOMUpdate(() => {
+ if (this.main.isConnected() && (type === "patch" && id === this.main.id)) {
+ this.main.pushLinkPatch(href, null, () => {
+ this.maybeScroll(scroll);
+ });
+ } else {
+ this.replaceMain(href, null, () => {
+ if (root) {
+ this.replaceRootHistory();
+ }
+ this.maybeScroll(scroll);
+ });
+ }
+ });
}, false);
window.addEventListener("click", (e) => {
let target = closestPhxBinding(e.target, PHX_LIVE_LINK);
let type = target && target.getAttribute(PHX_LIVE_LINK);
- let wantsNewTab = e.metaKey || e.ctrlKey || e.button === 1;
- if (!type || !this.isConnected() || !this.main || wantsNewTab) {
+ if (!type || !this.isConnected() || !this.main || dom_default.wantsNewTab(e)) {
return;
}
let href = target.href;
let linkState = target.getAttribute(PHX_LINK_STATE);
e.preventDefault();
+ e.stopImmediatePropagation();
if (this.pendingLink === href) {
return;
}
- if (type === "patch") {
- this.pushHistoryPatch(href, linkState, target);
- } else if (type === "redirect") {
- this.historyRedirect(href, linkState);
- } else {
- throw new Error(`expected ${PHX_LIVE_LINK} to be "patch" or "redirect", got: ${type}`);
- }
+ this.requestDOMUpdate(() => {
+ if (type === "patch") {
+ this.pushHistoryPatch(href, linkState, target);
+ } else if (type === "redirect") {
+ this.historyRedirect(href, linkState);
+ } else {
+ throw new Error(`expected ${PHX_LIVE_LINK} to be "patch" or "redirect", got: ${type}`);
+ }
+ let phxClick = target.getAttribute(this.binding("click"));
+ if (phxClick) {
+ this.requestDOMUpdate(() => this.execJS(target, phxClick, "click"));
+ }
+ });
}, false);
}
+ maybeScroll(scroll) {
+ if (typeof scroll === "number") {
+ requestAnimationFrame(() => {
+ window.scrollTo(0, scroll);
+ });
+ }
+ }
+ dispatchEvent(event, payload = {}) {
+ dom_default.dispatchEvent(window, `phx:${event}`, { detail: payload });
+ }
+ dispatchEvents(events) {
+ events.forEach(([event, payload]) => this.dispatchEvent(event, payload));
+ }
withPageLoading(info, callback) {
- dom_default.dispatchEvent(window, "phx:page-loading-start", info);
- let done = () => dom_default.dispatchEvent(window, "phx:page-loading-stop", info);
+ dom_default.dispatchEvent(window, "phx:page-loading-start", { detail: info });
+ let done = () => dom_default.dispatchEvent(window, "phx:page-loading-stop", { detail: info });
return callback ? callback(done) : done;
}
pushHistoryPatch(href, linkState, targetEl) {
+ if (!this.isConnected()) {
+ return browser_default.redirect(href);
+ }
this.withPageLoading({ to: href, kind: "patch" }, (done) => {
this.main.pushLinkPatch(href, targetEl, (linkRef) => {
this.historyPatch(href, linkState, linkRef);
@@ -3347,6 +4117,13 @@ within:
this.registerNewLocation(window.location);
}
historyRedirect(href, linkState, flash) {
+ if (!this.isConnected()) {
+ return browser_default.redirect(href, flash);
+ }
+ if (/^\/$|^\/[^\/]+.*$/.test(href)) {
+ let { protocol, host } = window.location;
+ href = `${protocol}//${host}${href}`;
+ }
let scroll = window.scrollY;
this.withPageLoading({ to: href, kind: "redirect" }, (done) => {
this.replaceMain(href, flash, () => {
@@ -3370,25 +4147,52 @@ within:
}
bindForms() {
let iterations = 0;
+ let externalFormSubmitted = false;
+ this.on("submit", (e) => {
+ let phxSubmit = e.target.getAttribute(this.binding("submit"));
+ let phxChange = e.target.getAttribute(this.binding("change"));
+ if (!externalFormSubmitted && phxChange && !phxSubmit) {
+ externalFormSubmitted = true;
+ e.preventDefault();
+ this.withinOwners(e.target, (view) => {
+ view.disableForm(e.target);
+ window.requestAnimationFrame(() => {
+ if (dom_default.isUnloadableFormSubmit(e)) {
+ this.unload();
+ }
+ e.target.submit();
+ });
+ });
+ }
+ }, true);
this.on("submit", (e) => {
let phxEvent = e.target.getAttribute(this.binding("submit"));
if (!phxEvent) {
+ if (dom_default.isUnloadableFormSubmit(e)) {
+ this.unload();
+ }
return;
}
e.preventDefault();
e.target.disabled = true;
- this.withinOwners(e.target, (view, targetCtx) => view.submitForm(e.target, targetCtx, phxEvent));
+ this.withinOwners(e.target, (view) => {
+ js_default.exec("submit", phxEvent, view, e.target, ["push", {}]);
+ });
}, false);
for (let type of ["change", "input"]) {
this.on(type, (e) => {
+ let phxChange = this.binding("change");
let input = e.target;
- let phxEvent = input.form && input.form.getAttribute(this.binding("change"));
+ let inputEvent = input.getAttribute(phxChange);
+ let formEvent = input.form && input.form.getAttribute(phxChange);
+ let phxEvent = inputEvent || formEvent;
if (!phxEvent) {
return;
}
if (input.type === "number" && input.validity && input.validity.badInput) {
return;
}
+ let dispatcher = inputEvent ? input : input.form;
let currentIterations = iterations;
iterations++;
let { at, type: lastType } = dom_default.private(input, "prev-iteration") || {};
@@ -3396,24 +4200,40 @@ within:
return;
}
dom_default.putPrivate(input, "prev-iteration", { at: currentIterations, type });
- this.debounce(input, e, () => {
- this.withinOwners(input.form, (view, targetCtx) => {
+ this.debounce(input, e, type, () => {
+ this.withinOwners(dispatcher, (view) => {
dom_default.putPrivate(input, PHX_HAS_FOCUSED, true);
if (!dom_default.isTextualInput(input)) {
this.setActiveElement(input);
}
- view.pushInput(input, targetCtx, null, phxEvent, e.target);
+ js_default.exec("change", phxEvent, view, input, ["push", { _target: e.target.name, dispatcher }]);
});
});
}, false);
}
+ this.on("reset", (e) => {
+ let form = e.target;
+ dom_default.resetForm(form, this.binding(PHX_FEEDBACK_FOR));
+ let input = Array.from(form.elements).find((el) => el.type === "reset");
+ window.requestAnimationFrame(() => {
+ input.dispatchEvent(new Event("input", { bubbles: true, cancelable: false }));
+ });
+ });
}
- debounce(el, event, callback) {
+ debounce(el, event, eventType, callback) {
+ if (eventType === "blur" || eventType === "focusout") {
+ return callback();
+ }
let phxDebounce = this.binding(PHX_DEBOUNCE);
let phxThrottle = this.binding(PHX_THROTTLE);
let defaultDebounce = this.defaults.debounce.toString();
let defaultThrottle = this.defaults.throttle.toString();
- dom_default.debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, callback);
+ this.withinOwners(el, (view) => {
+ let asyncFilter = () => !view.isDestroyed() && document.body.contains(el);
+ dom_default.debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, asyncFilter, () => {
+ callback();
+ });
+ });
}
silenceEvents(callback) {
this.silenced = true;
@@ -3428,5 +4248,50 @@ within:
});
}
};
+ var TransitionSet = class {
+ constructor() {
+ this.transitions = new Set();
+ this.pendingOps = [];
+ }
+ reset() {
+ this.transitions.forEach((timer) => {
+ clearTimeout(timer);
+ this.transitions.delete(timer);
+ });
+ this.flushPendingOps();
+ }
+ after(callback) {
+ if (this.size() === 0) {
+ callback();
+ } else {
+ this.pushPendingOp(callback);
+ }
+ }
+ addTransition(time, onStart, onDone) {
+ onStart();
+ let timer = setTimeout(() => {
+ this.transitions.delete(timer);
+ onDone();
+ this.flushPendingOps();
+ }, time);
+ this.transitions.add(timer);
+ }
+ pushPendingOp(op) {
+ this.pendingOps.push(op);
+ }
+ size() {
+ return this.transitions.size;
+ }
+ flushPendingOps() {
+ if (this.size() > 0) {
+ return;
+ }
+ let op = this.pendingOps.shift();
+ if (op) {
+ op();
+ this.flushPendingOps();
+ }
+ }
+ };
return phoenix_live_view_exports;
})();
diff --git a/priv/static/phoenix_live_view.js.map b/priv/static/phoenix_live_view.js.map
deleted file mode 100644
index cdcf69bb7b..0000000000
--- a/priv/static/phoenix_live_view.js.map
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "version": 3,
- "sources": ["../../assets/js/phoenix_live_view/constants.js", "../../assets/js/phoenix_live_view/entry_uploader.js", "../../assets/js/phoenix_live_view/utils.js", "../../assets/js/phoenix_live_view/browser.js", "../../assets/js/phoenix_live_view/dom.js", "../../assets/js/phoenix_live_view/upload_entry.js", "../../assets/js/phoenix_live_view/live_uploader.js", "../../assets/js/phoenix_live_view/hooks.js", "../../assets/js/phoenix_live_view/dom_post_morph_restorer.js", "../../assets/node_modules/morphdom/dist/morphdom-esm.js", "../../assets/js/phoenix_live_view/dom_patch.js", "../../assets/js/phoenix_live_view/rendered.js", "../../assets/js/phoenix_live_view/view_hook.js", "../../assets/js/phoenix_live_view/view.js", "../../assets/js/phoenix_live_view/live_socket.js"],
- "sourcesContent": ["\nexport const CONSECUTIVE_RELOADS = \"consecutive-reloads\"\nexport const MAX_RELOADS = 10\nexport const RELOAD_JITTER = [1000, 3000]\nexport const FAILSAFE_JITTER = 30000\nexport const PHX_EVENT_CLASSES = [\n \"phx-click-loading\", \"phx-change-loading\", \"phx-submit-loading\",\n \"phx-keydown-loading\", \"phx-keyup-loading\", \"phx-blur-loading\", \"phx-focus-loading\"\n]\nexport const PHX_COMPONENT = \"data-phx-component\"\nexport const PHX_LIVE_LINK = \"data-phx-link\"\nexport const PHX_TRACK_STATIC = \"track-static\"\nexport const PHX_LINK_STATE = \"data-phx-link-state\"\nexport const PHX_REF = \"data-phx-ref\"\nexport const PHX_UPLOAD_REF = \"data-phx-upload-ref\"\nexport const PHX_PREFLIGHTED_REFS = \"data-phx-preflighted-refs\"\nexport const PHX_DONE_REFS = \"data-phx-done-refs\"\nexport const PHX_DROP_TARGET = \"drop-target\"\nexport const PHX_ACTIVE_ENTRY_REFS = \"data-phx-active-refs\"\nexport const PHX_SKIP = \"data-phx-skip\"\nexport const PHX_REMOVE = \"data-phx-remove\"\nexport const PHX_PAGE_LOADING = \"page-loading\"\nexport const PHX_CONNECTED_CLASS = \"phx-connected\"\nexport const PHX_DISCONNECTED_CLASS = \"phx-disconnected\"\nexport const PHX_NO_FEEDBACK_CLASS = \"phx-no-feedback\"\nexport const PHX_ERROR_CLASS = \"phx-error\"\nexport const PHX_PARENT_ID = \"data-phx-parent-id\"\nexport const PHX_MAIN = \"data-phx-main\"\nexport const PHX_ROOT_ID = \"data-phx-root-id\"\nexport const PHX_TRIGGER_ACTION = \"trigger-action\"\nexport const PHX_FEEDBACK_FOR = \"feedback-for\"\nexport const PHX_HAS_FOCUSED = \"phx-has-focused\"\nexport const FOCUSABLE_INPUTS = [\"text\", \"textarea\", \"number\", \"email\", \"password\", \"search\", \"tel\", \"url\", \"date\", \"time\"]\nexport const CHECKABLE_INPUTS = [\"checkbox\", \"radio\"]\nexport const PHX_HAS_SUBMITTED = \"phx-has-submitted\"\nexport const PHX_SESSION = \"data-phx-session\"\nexport const PHX_VIEW_SELECTOR = `[${PHX_SESSION}]`\nexport const PHX_STATIC = \"data-phx-static\"\nexport const PHX_READONLY = \"data-phx-readonly\"\nexport const PHX_DISABLED = \"data-phx-disabled\"\nexport const PHX_DISABLE_WITH = \"disable-with\"\nexport const PHX_DISABLE_WITH_RESTORE = \"data-phx-disable-with-restore\"\nexport const PHX_HOOK = \"hook\"\nexport const PHX_DEBOUNCE = \"debounce\"\nexport const PHX_THROTTLE = \"throttle\"\nexport const PHX_UPDATE = \"update\"\nexport const PHX_KEY = \"key\"\nexport const PHX_PRIVATE = \"phxPrivate\"\nexport const PHX_AUTO_RECOVER = \"auto-recover\"\nexport const PHX_LV_DEBUG = \"phx:live-socket:debug\"\nexport const PHX_LV_PROFILE = \"phx:live-socket:profiling\"\nexport const PHX_LV_LATENCY_SIM = \"phx:live-socket:latency-sim\"\nexport const PHX_PROGRESS = \"progress\"\nexport const LOADER_TIMEOUT = 1\nexport const BEFORE_UNLOAD_LOADER_TIMEOUT = 200\nexport const BINDING_PREFIX = \"phx-\"\nexport const PUSH_TIMEOUT = 30000\nexport const LINK_HEADER = \"x-requested-with\"\nexport const RESPONSE_URL_HEADER = \"x-response-url\"\nexport const DEBOUNCE_TRIGGER = \"debounce-trigger\"\nexport const THROTTLED = \"throttled\"\nexport const DEBOUNCE_PREV_KEY = \"debounce-prev-key\"\nexport const DEFAULTS = {\n debounce: 300,\n throttle: 300\n}\n\n// Rendered\nexport const DYNAMICS = \"d\"\nexport const STATIC = \"s\"\nexport const COMPONENTS = \"c\"\nexport const EVENTS = \"e\"\nexport const REPLY = \"r\"\nexport const TITLE = \"t\"\n", "import {\n logError\n} from \"./utils\"\n\nexport default class EntryUploader {\n constructor(entry, chunkSize, liveSocket){\n this.liveSocket = liveSocket\n this.entry = entry\n this.offset = 0\n this.chunkSize = chunkSize\n this.chunkTimer = null\n this.uploadChannel = liveSocket.channel(`lvu:${entry.ref}`, {token: entry.metadata()})\n }\n\n error(reason){\n clearTimeout(this.chunkTimer)\n this.uploadChannel.leave()\n this.entry.error(reason)\n }\n\n upload(){\n this.uploadChannel.onError(reason => this.error(reason))\n this.uploadChannel.join()\n .receive(\"ok\", _data => this.readNextChunk())\n .receive(\"error\", reason => this.error(reason))\n }\n\n isDone(){ return this.offset >= this.entry.file.size }\n\n readNextChunk(){\n let reader = new window.FileReader()\n let blob = this.entry.file.slice(this.offset, this.chunkSize + this.offset)\n reader.onload = (e) => {\n if(e.target.error === null){\n this.offset += e.target.result.byteLength\n this.pushChunk(e.target.result)\n } else {\n return logError(\"Read error: \" + e.target.error)\n }\n }\n reader.readAsArrayBuffer(blob)\n }\n\n pushChunk(chunk){\n if(!this.uploadChannel.isJoined()){ return }\n this.uploadChannel.push(\"chunk\", chunk)\n .receive(\"ok\", () => {\n this.entry.progress((this.offset / this.entry.file.size) * 100)\n if(!this.isDone()){\n this.chunkTimer = setTimeout(() => this.readNextChunk(), this.liveSocket.getLatencySim() || 0)\n }\n })\n }\n}\n", "import {\n PHX_VIEW_SELECTOR\n} from \"./constants\"\n\nimport EntryUploader from \"./entry_uploader\"\n\nexport let logError = (msg, obj) => console.error && console.error(msg, obj)\n\nexport function detectDuplicateIds(){\n let ids = new Set()\n let elems = document.querySelectorAll(\"*[id]\")\n for(let i = 0, len = elems.length; i < len; i++){\n if(ids.has(elems[i].id)){\n console.error(`Multiple IDs detected: ${elems[i].id}. Ensure unique element ids.`)\n } else {\n ids.add(elems[i].id)\n }\n }\n}\n\nexport let debug = (view, kind, msg, obj) => {\n if(view.liveSocket.isDebugEnabled()){\n console.log(`${view.id} ${kind}: ${msg} - `, obj)\n }\n}\n\n// wraps value in closure or returns closure\nexport let closure = (val) => typeof val === \"function\" ? val : function (){ return val }\n\nexport let clone = (obj) => { return JSON.parse(JSON.stringify(obj)) }\n\nexport let closestPhxBinding = (el, binding, borderEl) => {\n do {\n if(el.matches(`[${binding}]`)){ return el }\n el = el.parentElement || el.parentNode\n } while(el !== null && el.nodeType === 1 && !((borderEl && borderEl.isSameNode(el)) || el.matches(PHX_VIEW_SELECTOR)))\n return null\n}\n\nexport let isObject = (obj) => {\n return obj !== null && typeof obj === \"object\" && !(obj instanceof Array)\n}\n\nexport let isEqualObj = (obj1, obj2) => JSON.stringify(obj1) === JSON.stringify(obj2)\n\nexport let isEmpty = (obj) => {\n for(let x in obj){ return false }\n return true\n}\n\nexport let maybe = (el, callback) => el && callback(el)\n\nexport let channelUploader = function (entries, onError, resp, liveSocket){\n entries.forEach(entry => {\n let entryUploader = new EntryUploader(entry, resp.config.chunk_size, liveSocket)\n entryUploader.upload()\n })\n}\n", "let Browser = {\n canPushState(){ return (typeof (history.pushState) !== \"undefined\") },\n\n dropLocal(localStorage, namespace, subkey){\n return localStorage.removeItem(this.localKey(namespace, subkey))\n },\n\n updateLocal(localStorage, namespace, subkey, initial, func){\n let current = this.getLocal(localStorage, namespace, subkey)\n let key = this.localKey(namespace, subkey)\n let newVal = current === null ? initial : func(current)\n localStorage.setItem(key, JSON.stringify(newVal))\n return newVal\n },\n\n getLocal(localStorage, namespace, subkey){\n return JSON.parse(localStorage.getItem(this.localKey(namespace, subkey)))\n },\n\n updateCurrentState(callback){\n if(!this.canPushState()){ return }\n history.replaceState(callback(history.state || {}), \"\", window.location.href)\n },\n\n pushState(kind, meta, to){\n if(this.canPushState()){\n if(to !== window.location.href){\n if(meta.type == \"redirect\" && meta.scroll){\n // If we're redirecting store the current scrollY for the current history state.\n let currentState = history.state || {}\n currentState.scroll = meta.scroll\n history.replaceState(currentState, \"\", window.location.href)\n }\n\n delete meta.scroll // Only store the scroll in the redirect case.\n history[kind + \"State\"](meta, \"\", to || null) // IE will coerce undefined to string\n let hashEl = this.getHashTargetEl(window.location.hash)\n\n if(hashEl){\n hashEl.scrollIntoView()\n } else if(meta.type === \"redirect\"){\n window.scroll(0, 0)\n }\n }\n } else {\n this.redirect(to)\n }\n },\n\n setCookie(name, value){\n document.cookie = `${name}=${value}`\n },\n\n getCookie(name){\n return document.cookie.replace(new RegExp(`(?:(?:^|.*;\\s*)${name}\\s*\\=\\s*([^;]*).*$)|^.*$`), \"$1\")\n },\n\n redirect(toURL, flash){\n if(flash){ Browser.setCookie(\"__phoenix_flash__\", flash + \"; max-age=60000; path=/\") }\n window.location = toURL\n },\n\n localKey(namespace, subkey){ return `${namespace}-${subkey}` },\n\n getHashTargetEl(maybeHash){\n let hash = maybeHash.toString().substring(1)\n if(hash === \"\"){ return }\n return document.getElementById(hash) || document.querySelector(`a[name=\"${hash}\"]`)\n }\n}\n\nexport default Browser\n", "import {\n CHECKABLE_INPUTS,\n DEBOUNCE_PREV_KEY,\n DEBOUNCE_TRIGGER,\n FOCUSABLE_INPUTS,\n PHX_COMPONENT,\n PHX_EVENT_CLASSES,\n PHX_HAS_FOCUSED,\n PHX_HAS_SUBMITTED,\n PHX_MAIN,\n PHX_NO_FEEDBACK_CLASS,\n PHX_PARENT_ID,\n PHX_PRIVATE,\n PHX_REF,\n PHX_SESSION,\n PHX_STATIC,\n PHX_UPLOAD_REF,\n PHX_VIEW_SELECTOR,\n THROTTLED\n} from \"./constants\"\n\nimport {\n clone,\n logError\n} from \"./utils\"\n\nlet DOM = {\n byId(id){ return document.getElementById(id) || logError(`no id found for ${id}`) },\n\n removeClass(el, className){\n el.classList.remove(className)\n if(el.classList.length === 0){ el.removeAttribute(\"class\") }\n },\n\n all(node, query, callback){\n let array = Array.from(node.querySelectorAll(query))\n return callback ? array.forEach(callback) : array\n },\n\n childNodeLength(html){\n let template = document.createElement(\"template\")\n template.innerHTML = html\n return template.content.childElementCount\n },\n\n isUploadInput(el){ return el.type === \"file\" && el.getAttribute(PHX_UPLOAD_REF) !== null },\n\n findUploadInputs(node){ return this.all(node, `input[type=\"file\"][${PHX_UPLOAD_REF}]`) },\n\n findComponentNodeList(node, cid){\n return this.filterWithinSameLiveView(this.all(node, `[${PHX_COMPONENT}=\"${cid}\"]`), node)\n },\n\n isPhxDestroyed(node){\n return node.id && DOM.private(node, \"destroyed\") ? true : false\n },\n\n markPhxChildDestroyed(el){\n el.setAttribute(PHX_SESSION, \"\")\n this.putPrivate(el, \"destroyed\", true)\n },\n\n findPhxChildrenInFragment(html, parentId){\n let template = document.createElement(\"template\")\n template.innerHTML = html\n return this.findPhxChildren(template.content, parentId)\n },\n\n isIgnored(el, phxUpdate){\n return (el.getAttribute(phxUpdate) || el.getAttribute(\"data-phx-update\")) === \"ignore\"\n },\n\n isPhxUpdate(el, phxUpdate, updateTypes){\n return el.getAttribute && updateTypes.indexOf(el.getAttribute(phxUpdate)) >= 0\n },\n\n findPhxChildren(el, parentId){\n return this.all(el, `${PHX_VIEW_SELECTOR}[${PHX_PARENT_ID}=\"${parentId}\"]`)\n },\n\n findParentCIDs(node, cids){\n let initial = new Set(cids)\n return cids.reduce((acc, cid) => {\n let selector = `[${PHX_COMPONENT}=\"${cid}\"] [${PHX_COMPONENT}]`\n\n this.filterWithinSameLiveView(this.all(node, selector), node)\n .map(el => parseInt(el.getAttribute(PHX_COMPONENT)))\n .forEach(childCID => acc.delete(childCID))\n\n return acc\n }, initial)\n },\n\n filterWithinSameLiveView(nodes, parent){\n if(parent.querySelector(PHX_VIEW_SELECTOR)){\n return nodes.filter(el => this.withinSameLiveView(el, parent))\n } else {\n return nodes\n }\n },\n\n withinSameLiveView(node, parent){\n while(node = node.parentNode){\n if(node.isSameNode(parent)){ return true }\n if(node.getAttribute(PHX_SESSION) !== null){ return false }\n }\n },\n\n private(el, key){ return el[PHX_PRIVATE] && el[PHX_PRIVATE][key] },\n\n deletePrivate(el, key){ el[PHX_PRIVATE] && delete (el[PHX_PRIVATE][key]) },\n\n putPrivate(el, key, value){\n if(!el[PHX_PRIVATE]){ el[PHX_PRIVATE] = {} }\n el[PHX_PRIVATE][key] = value\n },\n\n copyPrivates(target, source){\n if(source[PHX_PRIVATE]){\n target[PHX_PRIVATE] = clone(source[PHX_PRIVATE])\n }\n },\n\n putTitle(str){\n let titleEl = document.querySelector(\"title\")\n let {prefix, suffix} = titleEl.dataset\n document.title = `${prefix || \"\"}${str}${suffix || \"\"}`\n },\n\n debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, callback){\n let debounce = el.getAttribute(phxDebounce)\n let throttle = el.getAttribute(phxThrottle)\n if(debounce === \"\"){ debounce = defaultDebounce }\n if(throttle === \"\"){ throttle = defaultThrottle }\n let value = debounce || throttle\n switch(value){\n case null: return callback()\n\n case \"blur\":\n if(this.once(el, \"debounce-blur\")){\n el.addEventListener(\"blur\", () => callback())\n }\n return\n\n default:\n let timeout = parseInt(value)\n let trigger = () => throttle ? this.deletePrivate(el, THROTTLED) : callback()\n let currentCycle = this.incCycle(el, DEBOUNCE_TRIGGER, trigger)\n if(isNaN(timeout)){ return logError(`invalid throttle/debounce value: ${value}`) }\n if(throttle){\n let newKeyDown = false\n if(event.type === \"keydown\"){\n let prevKey = this.private(el, DEBOUNCE_PREV_KEY)\n this.putPrivate(el, DEBOUNCE_PREV_KEY, event.key)\n newKeyDown = prevKey !== event.key\n }\n\n if(!newKeyDown && this.private(el, THROTTLED)){\n return false\n } else {\n callback()\n this.putPrivate(el, THROTTLED, true)\n setTimeout(() => this.triggerCycle(el, DEBOUNCE_TRIGGER), timeout)\n }\n } else {\n setTimeout(() => this.triggerCycle(el, DEBOUNCE_TRIGGER, currentCycle), timeout)\n }\n\n\n let form = el.form\n if(form && this.once(form, \"bind-debounce\")){\n form.addEventListener(\"submit\", () => {\n Array.from((new FormData(form)).entries(), ([name]) => {\n let input = form.querySelector(`[name=\"${name}\"]`)\n this.incCycle(input, DEBOUNCE_TRIGGER)\n this.deletePrivate(input, THROTTLED)\n })\n })\n }\n if(this.once(el, \"bind-debounce\")){\n el.addEventListener(\"blur\", () => this.triggerCycle(el, DEBOUNCE_TRIGGER))\n }\n }\n },\n\n triggerCycle(el, key, currentCycle){\n let [cycle, trigger] = this.private(el, key)\n if(!currentCycle){ currentCycle = cycle }\n if(currentCycle === cycle){\n this.incCycle(el, key)\n trigger()\n }\n },\n\n once(el, key){\n if(this.private(el, key) === true){ return false }\n this.putPrivate(el, key, true)\n return true\n },\n\n incCycle(el, key, trigger = function (){ }){\n let [currentCycle] = this.private(el, key) || [0, trigger]\n currentCycle++\n this.putPrivate(el, key, [currentCycle, trigger])\n return currentCycle\n },\n\n discardError(container, el, phxFeedbackFor){\n let field = el.getAttribute && el.getAttribute(phxFeedbackFor)\n // TODO: Remove id lookup after we update Phoenix to use input_name instead of input_id\n let input = field && container.querySelector(`[id=\"${field}\"], [name=\"${field}\"]`)\n if(!input){ return }\n\n if(!(this.private(input, PHX_HAS_FOCUSED) || this.private(input.form, PHX_HAS_SUBMITTED))){\n el.classList.add(PHX_NO_FEEDBACK_CLASS)\n }\n },\n\n showError(inputEl, phxFeedbackFor){\n if(inputEl.id || inputEl.name){\n this.all(inputEl.form, `[${phxFeedbackFor}=\"${inputEl.id}\"], [${phxFeedbackFor}=\"${inputEl.name}\"]`, (el) => {\n this.removeClass(el, PHX_NO_FEEDBACK_CLASS)\n })\n }\n },\n\n isPhxChild(node){\n return node.getAttribute && node.getAttribute(PHX_PARENT_ID)\n },\n\n dispatchEvent(target, eventString, detail = {}){\n let event = new CustomEvent(eventString, {bubbles: true, cancelable: true, detail: detail})\n target.dispatchEvent(event)\n },\n\n cloneNode(node, html){\n if(typeof (html) === \"undefined\"){\n return node.cloneNode(true)\n } else {\n let cloned = node.cloneNode(false)\n cloned.innerHTML = html\n return cloned\n }\n },\n\n mergeAttrs(target, source, opts = {}){\n let exclude = opts.exclude || []\n let isIgnored = opts.isIgnored\n let sourceAttrs = source.attributes\n for(let i = sourceAttrs.length - 1; i >= 0; i--){\n let name = sourceAttrs[i].name\n if(exclude.indexOf(name) < 0){ target.setAttribute(name, source.getAttribute(name)) }\n }\n\n let targetAttrs = target.attributes\n for(let i = targetAttrs.length - 1; i >= 0; i--){\n let name = targetAttrs[i].name\n if(isIgnored){\n if(name.startsWith(\"data-\") && !source.hasAttribute(name)){ target.removeAttribute(name) }\n } else {\n if(!source.hasAttribute(name)){ target.removeAttribute(name) }\n }\n }\n },\n\n mergeFocusedInput(target, source){\n // skip selects because FF will reset highlighted index for any setAttribute\n if(!(target instanceof HTMLSelectElement)){ DOM.mergeAttrs(target, source, {except: [\"value\"]}) }\n if(source.readOnly){\n target.setAttribute(\"readonly\", true)\n } else {\n target.removeAttribute(\"readonly\")\n }\n },\n\n hasSelectionRange(el){\n return el.setSelectionRange && (el.type === \"text\" || el.type === \"textarea\")\n },\n\n restoreFocus(focused, selectionStart, selectionEnd){\n if(!DOM.isTextualInput(focused)){ return }\n let wasFocused = focused.matches(\":focus\")\n if(focused.readOnly){ focused.blur() }\n if(!wasFocused){ focused.focus() }\n if(this.hasSelectionRange(focused)){\n focused.setSelectionRange(selectionStart, selectionEnd)\n }\n },\n\n isFormInput(el){ return /^(?:input|select|textarea)$/i.test(el.tagName) && el.type !== \"button\" },\n\n syncAttrsToProps(el){\n if(el instanceof HTMLInputElement && CHECKABLE_INPUTS.indexOf(el.type.toLocaleLowerCase()) >= 0){\n el.checked = el.getAttribute(\"checked\") !== null\n }\n },\n\n isTextualInput(el){ return FOCUSABLE_INPUTS.indexOf(el.type) >= 0 },\n\n isNowTriggerFormExternal(el, phxTriggerExternal){\n return el.getAttribute && el.getAttribute(phxTriggerExternal) !== null\n },\n\n syncPendingRef(fromEl, toEl, disableWith){\n let ref = fromEl.getAttribute(PHX_REF)\n if(ref === null){ return true }\n\n if(DOM.isFormInput(fromEl) || fromEl.getAttribute(disableWith) !== null){\n if(DOM.isUploadInput(fromEl)){ DOM.mergeAttrs(fromEl, toEl, {isIgnored: true}) }\n DOM.putPrivate(fromEl, PHX_REF, toEl)\n return false\n } else {\n PHX_EVENT_CLASSES.forEach(className => {\n fromEl.classList.contains(className) && toEl.classList.add(className)\n })\n toEl.setAttribute(PHX_REF, ref)\n return true\n }\n },\n\n cleanChildNodes(container, phxUpdate){\n if(DOM.isPhxUpdate(container, phxUpdate, [\"append\", \"prepend\"])){\n let toRemove = []\n container.childNodes.forEach(childNode => {\n if(!childNode.id){\n // Skip warning if it's an empty text node (e.g. a new-line)\n let isEmptyTextNode = childNode.nodeType === Node.TEXT_NODE && childNode.nodeValue.trim() === \"\"\n if(!isEmptyTextNode){\n logError(\"only HTML element tags with an id are allowed inside containers with phx-update.\\n\\n\" +\n `removing illegal node: \"${(childNode.outerHTML || childNode.nodeValue).trim()}\"\\n\\n`)\n }\n toRemove.push(childNode)\n }\n })\n toRemove.forEach(childNode => childNode.remove())\n }\n },\n\n replaceRootContainer(container, tagName, attrs){\n let retainedAttrs = new Set([\"id\", PHX_SESSION, PHX_STATIC, PHX_MAIN])\n let notRetained = (attr) => !retainedAttrs.has(attr.name.toLowerCase())\n if(container.tagName.toLowerCase() === tagName.toLowerCase()){\n Array.from(container.attributes)\n .filter(notRetained)\n .forEach(attr => container.removeAttribute(attr.name))\n\n Object.keys(attrs)\n .filter(notRetained)\n .forEach(attr => container.setAttribute(attr, attrs[attr]))\n\n return container\n\n } else {\n let newContainer = document.createElement(tagName)\n Object.keys(attrs).forEach(attr => newContainer.setAttribute(attr, attrs[attr]))\n retainedAttrs.forEach(attr => newContainer.setAttribute(attr, container.getAttribute(attr)))\n newContainer.innerHTML = container.innerHTML\n container.replaceWith(newContainer)\n return newContainer\n }\n }\n}\n\nexport default DOM\n", "import {\n PHX_ACTIVE_ENTRY_REFS,\n PHX_PREFLIGHTED_REFS\n} from \"./constants\"\n\nimport {\n channelUploader,\n logError\n} from \"./utils\"\n\nimport LiveUploader from \"./live_uploader\"\n\nexport default class UploadEntry {\n static isActive(fileEl, file){\n let isNew = file._phxRef === undefined\n let activeRefs = fileEl.getAttribute(PHX_ACTIVE_ENTRY_REFS).split(\",\")\n let isActive = activeRefs.indexOf(LiveUploader.genFileRef(file)) >= 0\n return file.size > 0 && (isNew || isActive)\n }\n\n static isPreflighted(fileEl, file){\n let preflightedRefs = fileEl.getAttribute(PHX_PREFLIGHTED_REFS).split(\",\")\n let isPreflighted = preflightedRefs.indexOf(LiveUploader.genFileRef(file)) >= 0\n return isPreflighted && this.isActive(fileEl, file)\n }\n\n constructor(fileEl, file, view){\n this.ref = LiveUploader.genFileRef(file)\n this.fileEl = fileEl\n this.file = file\n this.view = view\n this.meta = null\n this._isCancelled = false\n this._isDone = false\n this._progress = 0\n this._lastProgressSent = -1\n this._onDone = function (){ }\n }\n\n metadata(){ return this.meta }\n\n progress(progress){\n this._progress = Math.floor(progress)\n if(this._progress > this._lastProgressSent){\n if(this._progress >= 100){\n this._progress = 100\n this._lastProgressSent = 100\n this._isDone = true\n this.view.pushFileProgress(this.fileEl, this.ref, 100, () => {\n LiveUploader.untrackFile(this.fileEl, this.file)\n this._onDone()\n })\n } else {\n this._lastProgressSent = this._progress\n this.view.pushFileProgress(this.fileEl, this.ref, this._progress)\n }\n }\n }\n\n cancel(){\n this._isCancelled = true\n this._isDone = true\n this._onDone()\n }\n\n isDone(){ return this._isDone }\n\n error(reason = \"failed\"){\n this.view.pushFileProgress(this.fileEl, this.ref, {error: reason})\n LiveUploader.clearFiles(this.fileEl)\n }\n\n //private\n\n onDone(callback){ this._onDone = callback }\n\n toPreflightPayload(){\n return {\n last_modified: this.file.lastModified,\n name: this.file.name,\n size: this.file.size,\n type: this.file.type,\n ref: this.ref\n }\n }\n\n uploader(uploaders){\n if(this.meta.uploader){\n let callback = uploaders[this.meta.uploader] || logError(`no uploader configured for ${this.meta.uploader}`)\n return {name: this.meta.uploader, callback: callback}\n } else {\n return {name: \"channel\", callback: channelUploader}\n }\n }\n\n zipPostFlight(resp){\n this.meta = resp.entries[this.ref]\n if(!this.meta){ logError(`no preflight upload response returned with ref ${this.ref}`, {input: this.fileEl, response: resp}) }\n }\n}\n", "import {\n PHX_DONE_REFS,\n PHX_PREFLIGHTED_REFS,\n PHX_UPLOAD_REF\n} from \"./constants\"\n\nimport {\n} from \"./utils\"\n\nimport DOM from \"./dom\"\nimport UploadEntry from \"./upload_entry\"\n\nlet liveUploaderFileRef = 0\n\nexport default class LiveUploader {\n static genFileRef(file){\n let ref = file._phxRef\n if(ref !== undefined){\n return ref\n } else {\n file._phxRef = (liveUploaderFileRef++).toString()\n return file._phxRef\n }\n }\n\n static getEntryDataURL(inputEl, ref, callback){\n let file = this.activeFiles(inputEl).find(file => this.genFileRef(file) === ref)\n let reader = new FileReader()\n reader.onload = (e) => callback(e.target.result)\n reader.readAsDataURL(file)\n }\n\n static hasUploadsInProgress(formEl){\n let active = 0\n DOM.findUploadInputs(formEl).forEach(input => {\n if(input.getAttribute(PHX_PREFLIGHTED_REFS) !== input.getAttribute(PHX_DONE_REFS)){\n active++\n }\n })\n return active > 0\n }\n\n static serializeUploads(inputEl){\n let files = this.activeFiles(inputEl, \"serialize\")\n let fileData = {}\n files.forEach(file => {\n let entry = {path: inputEl.name}\n let uploadRef = inputEl.getAttribute(PHX_UPLOAD_REF)\n fileData[uploadRef] = fileData[uploadRef] || []\n entry.ref = this.genFileRef(file)\n entry.name = file.name\n entry.type = file.type\n entry.size = file.size\n fileData[uploadRef].push(entry)\n })\n return fileData\n }\n\n static clearFiles(inputEl){\n inputEl.value = null\n inputEl.removeAttribute(PHX_UPLOAD_REF)\n DOM.putPrivate(inputEl, \"files\", [])\n }\n\n static untrackFile(inputEl, file){\n DOM.putPrivate(inputEl, \"files\", DOM.private(inputEl, \"files\").filter(f => !Object.is(f, file)))\n }\n\n static trackFiles(inputEl, files){\n if(inputEl.getAttribute(\"multiple\") !== null){\n let newFiles = files.filter(file => !this.activeFiles(inputEl).find(f => Object.is(f, file)))\n DOM.putPrivate(inputEl, \"files\", this.activeFiles(inputEl).concat(newFiles))\n inputEl.value = null\n } else {\n DOM.putPrivate(inputEl, \"files\", files)\n }\n }\n\n static activeFileInputs(formEl){\n let fileInputs = DOM.findUploadInputs(formEl)\n return Array.from(fileInputs).filter(el => el.files && this.activeFiles(el).length > 0)\n }\n\n static activeFiles(input){\n return (DOM.private(input, \"files\") || []).filter(f => UploadEntry.isActive(input, f))\n }\n\n static inputsAwaitingPreflight(formEl){\n let fileInputs = DOM.findUploadInputs(formEl)\n return Array.from(fileInputs).filter(input => this.filesAwaitingPreflight(input).length > 0)\n }\n\n static filesAwaitingPreflight(input){\n return this.activeFiles(input).filter(f => !UploadEntry.isPreflighted(input, f))\n }\n\n constructor(inputEl, view, onComplete){\n this.view = view\n this.onComplete = onComplete\n this._entries =\n Array.from(LiveUploader.filesAwaitingPreflight(inputEl) || [])\n .map(file => new UploadEntry(inputEl, file, view))\n\n this.numEntriesInProgress = this._entries.length\n }\n\n entries(){ return this._entries }\n\n initAdapterUpload(resp, onError, liveSocket){\n this._entries =\n this._entries.map(entry => {\n entry.zipPostFlight(resp)\n entry.onDone(() => {\n this.numEntriesInProgress--\n if(this.numEntriesInProgress === 0){ this.onComplete() }\n })\n return entry\n })\n\n let groupedEntries = this._entries.reduce((acc, entry) => {\n let {name, callback} = entry.uploader(liveSocket.uploaders)\n acc[name] = acc[name] || {callback: callback, entries: []}\n acc[name].entries.push(entry)\n return acc\n }, {})\n\n for(let name in groupedEntries){\n let {callback, entries} = groupedEntries[name]\n callback(entries, onError, resp, liveSocket)\n }\n }\n}\n", "import {\n PHX_PREFLIGHTED_REFS,\n PHX_UPLOAD_REF\n} from \"./constants\"\n\nimport LiveUploader from \"./live_uploader\"\n\nlet Hooks = {\n LiveFileUpload: {\n preflightedRefs(){ return this.el.getAttribute(PHX_PREFLIGHTED_REFS) },\n\n mounted(){ this.preflightedWas = this.preflightedRefs() },\n\n updated(){\n let newPreflights = this.preflightedRefs()\n if(this.preflightedWas !== newPreflights){\n this.preflightedWas = newPreflights\n if(newPreflights === \"\"){\n this.__view.cancelSubmit(this.el.form)\n }\n }\n }\n },\n\n LiveImgPreview: {\n mounted(){\n this.ref = this.el.getAttribute(\"data-phx-entry-ref\")\n this.inputEl = document.getElementById(this.el.getAttribute(PHX_UPLOAD_REF))\n LiveUploader.getEntryDataURL(this.inputEl, this.ref, url => this.el.src = url)\n }\n }\n}\n\nexport default Hooks\n", "import {\n maybe\n} from \"./utils\"\n\nimport DOM from \"./dom\"\n\nexport default class DOMPostMorphRestorer {\n constructor(containerBefore, containerAfter, updateType){\n let idsBefore = new Set()\n let idsAfter = new Set([...containerAfter.children].map(child => child.id))\n\n let elementsToModify = []\n\n Array.from(containerBefore.children).forEach(child => {\n if(child.id){ // all of our children should be elements with ids\n idsBefore.add(child.id)\n if(idsAfter.has(child.id)){\n let previousElementId = child.previousElementSibling && child.previousElementSibling.id\n elementsToModify.push({elementId: child.id, previousElementId: previousElementId})\n }\n }\n })\n\n this.containerId = containerAfter.id\n this.updateType = updateType\n this.elementsToModify = elementsToModify\n this.elementIdsToAdd = [...idsAfter].filter(id => !idsBefore.has(id))\n }\n\n // We do the following to optimize append/prepend operations:\n // 1) Track ids of modified elements & of new elements\n // 2) All the modified elements are put back in the correct position in the DOM tree\n // by storing the id of their previous sibling\n // 3) New elements are going to be put in the right place by morphdom during append.\n // For prepend, we move them to the first position in the container\n perform(){\n let container = DOM.byId(this.containerId)\n this.elementsToModify.forEach(elementToModify => {\n if(elementToModify.previousElementId){\n maybe(document.getElementById(elementToModify.previousElementId), previousElem => {\n maybe(document.getElementById(elementToModify.elementId), elem => {\n let isInRightPlace = elem.previousElementSibling && elem.previousElementSibling.id == previousElem.id\n if(!isInRightPlace){\n previousElem.insertAdjacentElement(\"afterend\", elem)\n }\n })\n })\n } else {\n // This is the first element in the container\n maybe(document.getElementById(elementToModify.elementId), elem => {\n let isInRightPlace = elem.previousElementSibling == null\n if(!isInRightPlace){\n container.insertAdjacentElement(\"afterbegin\", elem)\n }\n })\n }\n })\n\n if(this.updateType == \"prepend\"){\n this.elementIdsToAdd.reverse().forEach(elemId => {\n maybe(document.getElementById(elemId), elem => container.insertAdjacentElement(\"afterbegin\", elem))\n })\n }\n }\n}\n", "var DOCUMENT_FRAGMENT_NODE = 11;\n\nfunction morphAttrs(fromNode, toNode) {\n var toNodeAttrs = toNode.attributes;\n var attr;\n var attrName;\n var attrNamespaceURI;\n var attrValue;\n var fromValue;\n\n // document-fragments dont have attributes so lets not do anything\n if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE || fromNode.nodeType === DOCUMENT_FRAGMENT_NODE) {\n return;\n }\n\n // update attributes on original DOM element\n for (var i = toNodeAttrs.length - 1; i >= 0; i--) {\n attr = toNodeAttrs[i];\n attrName = attr.name;\n attrNamespaceURI = attr.namespaceURI;\n attrValue = attr.value;\n\n if (attrNamespaceURI) {\n attrName = attr.localName || attrName;\n fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName);\n\n if (fromValue !== attrValue) {\n if (attr.prefix === 'xmlns'){\n attrName = attr.name; // It's not allowed to set an attribute with the XMLNS namespace without specifying the `xmlns` prefix\n }\n fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue);\n }\n } else {\n fromValue = fromNode.getAttribute(attrName);\n\n if (fromValue !== attrValue) {\n fromNode.setAttribute(attrName, attrValue);\n }\n }\n }\n\n // Remove any extra attributes found on the original DOM element that\n // weren't found on the target element.\n var fromNodeAttrs = fromNode.attributes;\n\n for (var d = fromNodeAttrs.length - 1; d >= 0; d--) {\n attr = fromNodeAttrs[d];\n attrName = attr.name;\n attrNamespaceURI = attr.namespaceURI;\n\n if (attrNamespaceURI) {\n attrName = attr.localName || attrName;\n\n if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) {\n fromNode.removeAttributeNS(attrNamespaceURI, attrName);\n }\n } else {\n if (!toNode.hasAttribute(attrName)) {\n fromNode.removeAttribute(attrName);\n }\n }\n }\n}\n\nvar range; // Create a range object for efficently rendering strings to elements.\nvar NS_XHTML = 'http://www.w3.org/1999/xhtml';\n\nvar doc = typeof document === 'undefined' ? undefined : document;\nvar HAS_TEMPLATE_SUPPORT = !!doc && 'content' in doc.createElement('template');\nvar HAS_RANGE_SUPPORT = !!doc && doc.createRange && 'createContextualFragment' in doc.createRange();\n\nfunction createFragmentFromTemplate(str) {\n var template = doc.createElement('template');\n template.innerHTML = str;\n return template.content.childNodes[0];\n}\n\nfunction createFragmentFromRange(str) {\n if (!range) {\n range = doc.createRange();\n range.selectNode(doc.body);\n }\n\n var fragment = range.createContextualFragment(str);\n return fragment.childNodes[0];\n}\n\nfunction createFragmentFromWrap(str) {\n var fragment = doc.createElement('body');\n fragment.innerHTML = str;\n return fragment.childNodes[0];\n}\n\n/**\n * This is about the same\n * var html = new DOMParser().parseFromString(str, 'text/html');\n * return html.body.firstChild;\n *\n * @method toElement\n * @param {String} str\n */\nfunction toElement(str) {\n str = str.trim();\n if (HAS_TEMPLATE_SUPPORT) {\n // avoid restrictions on content for things like `Hi ` which\n // createContextualFragment doesn't support\n // support not available in IE\n return createFragmentFromTemplate(str);\n } else if (HAS_RANGE_SUPPORT) {\n return createFragmentFromRange(str);\n }\n\n return createFragmentFromWrap(str);\n}\n\n/**\n * Returns true if two node's names are the same.\n *\n * NOTE: We don't bother checking `namespaceURI` because you will never find two HTML elements with the same\n * nodeName and different namespace URIs.\n *\n * @param {Element} a\n * @param {Element} b The target element\n * @return {boolean}\n */\nfunction compareNodeNames(fromEl, toEl) {\n var fromNodeName = fromEl.nodeName;\n var toNodeName = toEl.nodeName;\n var fromCodeStart, toCodeStart;\n\n if (fromNodeName === toNodeName) {\n return true;\n }\n\n fromCodeStart = fromNodeName.charCodeAt(0);\n toCodeStart = toNodeName.charCodeAt(0);\n\n // If the target element is a virtual DOM node or SVG node then we may\n // need to normalize the tag name before comparing. Normal HTML elements that are\n // in the \"http://www.w3.org/1999/xhtml\"\n // are converted to upper case\n if (fromCodeStart <= 90 && toCodeStart >= 97) { // from is upper and to is lower\n return fromNodeName === toNodeName.toUpperCase();\n } else if (toCodeStart <= 90 && fromCodeStart >= 97) { // to is upper and from is lower\n return toNodeName === fromNodeName.toUpperCase();\n } else {\n return false;\n }\n}\n\n/**\n * Create an element, optionally with a known namespace URI.\n *\n * @param {string} name the element name, e.g. 'div' or 'svg'\n * @param {string} [namespaceURI] the element's namespace URI, i.e. the value of\n * its `xmlns` attribute or its inferred namespace.\n *\n * @return {Element}\n */\nfunction createElementNS(name, namespaceURI) {\n return !namespaceURI || namespaceURI === NS_XHTML ?\n doc.createElement(name) :\n doc.createElementNS(namespaceURI, name);\n}\n\n/**\n * Copies the children of one DOM element to another DOM element\n */\nfunction moveChildren(fromEl, toEl) {\n var curChild = fromEl.firstChild;\n while (curChild) {\n var nextChild = curChild.nextSibling;\n toEl.appendChild(curChild);\n curChild = nextChild;\n }\n return toEl;\n}\n\nfunction syncBooleanAttrProp(fromEl, toEl, name) {\n if (fromEl[name] !== toEl[name]) {\n fromEl[name] = toEl[name];\n if (fromEl[name]) {\n fromEl.setAttribute(name, '');\n } else {\n fromEl.removeAttribute(name);\n }\n }\n}\n\nvar specialElHandlers = {\n OPTION: function(fromEl, toEl) {\n var parentNode = fromEl.parentNode;\n if (parentNode) {\n var parentName = parentNode.nodeName.toUpperCase();\n if (parentName === 'OPTGROUP') {\n parentNode = parentNode.parentNode;\n parentName = parentNode && parentNode.nodeName.toUpperCase();\n }\n if (parentName === 'SELECT' && !parentNode.hasAttribute('multiple')) {\n if (fromEl.hasAttribute('selected') && !toEl.selected) {\n // Workaround for MS Edge bug where the 'selected' attribute can only be\n // removed if set to a non-empty value:\n // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12087679/\n fromEl.setAttribute('selected', 'selected');\n fromEl.removeAttribute('selected');\n }\n // We have to reset select element's selectedIndex to -1, otherwise setting\n // fromEl.selected using the syncBooleanAttrProp below has no effect.\n // The correct selectedIndex will be set in the SELECT special handler below.\n parentNode.selectedIndex = -1;\n }\n }\n syncBooleanAttrProp(fromEl, toEl, 'selected');\n },\n /**\n * The \"value\" attribute is special for the element since it sets\n * the initial value. Changing the \"value\" attribute without changing the\n * \"value\" property will have no effect since it is only used to the set the\n * initial value. Similar for the \"checked\" attribute, and \"disabled\".\n */\n INPUT: function(fromEl, toEl) {\n syncBooleanAttrProp(fromEl, toEl, 'checked');\n syncBooleanAttrProp(fromEl, toEl, 'disabled');\n\n if (fromEl.value !== toEl.value) {\n fromEl.value = toEl.value;\n }\n\n if (!toEl.hasAttribute('value')) {\n fromEl.removeAttribute('value');\n }\n },\n\n TEXTAREA: function(fromEl, toEl) {\n var newValue = toEl.value;\n if (fromEl.value !== newValue) {\n fromEl.value = newValue;\n }\n\n var firstChild = fromEl.firstChild;\n if (firstChild) {\n // Needed for IE. Apparently IE sets the placeholder as the\n // node value and vise versa. This ignores an empty update.\n var oldValue = firstChild.nodeValue;\n\n if (oldValue == newValue || (!newValue && oldValue == fromEl.placeholder)) {\n return;\n }\n\n firstChild.nodeValue = newValue;\n }\n },\n SELECT: function(fromEl, toEl) {\n if (!toEl.hasAttribute('multiple')) {\n var selectedIndex = -1;\n var i = 0;\n // We have to loop through children of fromEl, not toEl since nodes can be moved\n // from toEl to fromEl directly when morphing.\n // At the time this special handler is invoked, all children have already been morphed\n // and appended to / removed from fromEl, so using fromEl here is safe and correct.\n var curChild = fromEl.firstChild;\n var optgroup;\n var nodeName;\n while(curChild) {\n nodeName = curChild.nodeName && curChild.nodeName.toUpperCase();\n if (nodeName === 'OPTGROUP') {\n optgroup = curChild;\n curChild = optgroup.firstChild;\n } else {\n if (nodeName === 'OPTION') {\n if (curChild.hasAttribute('selected')) {\n selectedIndex = i;\n break;\n }\n i++;\n }\n curChild = curChild.nextSibling;\n if (!curChild && optgroup) {\n curChild = optgroup.nextSibling;\n optgroup = null;\n }\n }\n }\n\n fromEl.selectedIndex = selectedIndex;\n }\n }\n};\n\nvar ELEMENT_NODE = 1;\nvar DOCUMENT_FRAGMENT_NODE$1 = 11;\nvar TEXT_NODE = 3;\nvar COMMENT_NODE = 8;\n\nfunction noop() {}\n\nfunction defaultGetNodeKey(node) {\n if (node) {\n return (node.getAttribute && node.getAttribute('id')) || node.id;\n }\n}\n\nfunction morphdomFactory(morphAttrs) {\n\n return function morphdom(fromNode, toNode, options) {\n if (!options) {\n options = {};\n }\n\n if (typeof toNode === 'string') {\n if (fromNode.nodeName === '#document' || fromNode.nodeName === 'HTML' || fromNode.nodeName === 'BODY') {\n var toNodeHtml = toNode;\n toNode = doc.createElement('html');\n toNode.innerHTML = toNodeHtml;\n } else {\n toNode = toElement(toNode);\n }\n }\n\n var getNodeKey = options.getNodeKey || defaultGetNodeKey;\n var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;\n var onNodeAdded = options.onNodeAdded || noop;\n var onBeforeElUpdated = options.onBeforeElUpdated || noop;\n var onElUpdated = options.onElUpdated || noop;\n var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;\n var onNodeDiscarded = options.onNodeDiscarded || noop;\n var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;\n var childrenOnly = options.childrenOnly === true;\n\n // This object is used as a lookup to quickly find all keyed elements in the original DOM tree.\n var fromNodesLookup = Object.create(null);\n var keyedRemovalList = [];\n\n function addKeyedRemoval(key) {\n keyedRemovalList.push(key);\n }\n\n function walkDiscardedChildNodes(node, skipKeyedNodes) {\n if (node.nodeType === ELEMENT_NODE) {\n var curChild = node.firstChild;\n while (curChild) {\n\n var key = undefined;\n\n if (skipKeyedNodes && (key = getNodeKey(curChild))) {\n // If we are skipping keyed nodes then we add the key\n // to a list so that it can be handled at the very end.\n addKeyedRemoval(key);\n } else {\n // Only report the node as discarded if it is not keyed. We do this because\n // at the end we loop through all keyed elements that were unmatched\n // and then discard them in one final pass.\n onNodeDiscarded(curChild);\n if (curChild.firstChild) {\n walkDiscardedChildNodes(curChild, skipKeyedNodes);\n }\n }\n\n curChild = curChild.nextSibling;\n }\n }\n }\n\n /**\n * Removes a DOM node out of the original DOM\n *\n * @param {Node} node The node to remove\n * @param {Node} parentNode The nodes parent\n * @param {Boolean} skipKeyedNodes If true then elements with keys will be skipped and not discarded.\n * @return {undefined}\n */\n function removeNode(node, parentNode, skipKeyedNodes) {\n if (onBeforeNodeDiscarded(node) === false) {\n return;\n }\n\n if (parentNode) {\n parentNode.removeChild(node);\n }\n\n onNodeDiscarded(node);\n walkDiscardedChildNodes(node, skipKeyedNodes);\n }\n\n // // TreeWalker implementation is no faster, but keeping this around in case this changes in the future\n // function indexTree(root) {\n // var treeWalker = document.createTreeWalker(\n // root,\n // NodeFilter.SHOW_ELEMENT);\n //\n // var el;\n // while((el = treeWalker.nextNode())) {\n // var key = getNodeKey(el);\n // if (key) {\n // fromNodesLookup[key] = el;\n // }\n // }\n // }\n\n // // NodeIterator implementation is no faster, but keeping this around in case this changes in the future\n //\n // function indexTree(node) {\n // var nodeIterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT);\n // var el;\n // while((el = nodeIterator.nextNode())) {\n // var key = getNodeKey(el);\n // if (key) {\n // fromNodesLookup[key] = el;\n // }\n // }\n // }\n\n function indexTree(node) {\n if (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE$1) {\n var curChild = node.firstChild;\n while (curChild) {\n var key = getNodeKey(curChild);\n if (key) {\n fromNodesLookup[key] = curChild;\n }\n\n // Walk recursively\n indexTree(curChild);\n\n curChild = curChild.nextSibling;\n }\n }\n }\n\n indexTree(fromNode);\n\n function handleNodeAdded(el) {\n onNodeAdded(el);\n\n var curChild = el.firstChild;\n while (curChild) {\n var nextSibling = curChild.nextSibling;\n\n var key = getNodeKey(curChild);\n if (key) {\n var unmatchedFromEl = fromNodesLookup[key];\n // if we find a duplicate #id node in cache, replace `el` with cache value\n // and morph it to the child node.\n if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) {\n curChild.parentNode.replaceChild(unmatchedFromEl, curChild);\n morphEl(unmatchedFromEl, curChild);\n } else {\n handleNodeAdded(curChild);\n }\n } else {\n // recursively call for curChild and it's children to see if we find something in\n // fromNodesLookup\n handleNodeAdded(curChild);\n }\n\n curChild = nextSibling;\n }\n }\n\n function cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey) {\n // We have processed all of the \"to nodes\". If curFromNodeChild is\n // non-null then we still have some from nodes left over that need\n // to be removed\n while (curFromNodeChild) {\n var fromNextSibling = curFromNodeChild.nextSibling;\n if ((curFromNodeKey = getNodeKey(curFromNodeChild))) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n curFromNodeChild = fromNextSibling;\n }\n }\n\n function morphEl(fromEl, toEl, childrenOnly) {\n var toElKey = getNodeKey(toEl);\n\n if (toElKey) {\n // If an element with an ID is being morphed then it will be in the final\n // DOM so clear it out of the saved elements collection\n delete fromNodesLookup[toElKey];\n }\n\n if (!childrenOnly) {\n // optional\n if (onBeforeElUpdated(fromEl, toEl) === false) {\n return;\n }\n\n // update attributes on original DOM element first\n morphAttrs(fromEl, toEl);\n // optional\n onElUpdated(fromEl);\n\n if (onBeforeElChildrenUpdated(fromEl, toEl) === false) {\n return;\n }\n }\n\n if (fromEl.nodeName !== 'TEXTAREA') {\n morphChildren(fromEl, toEl);\n } else {\n specialElHandlers.TEXTAREA(fromEl, toEl);\n }\n }\n\n function morphChildren(fromEl, toEl) {\n var curToNodeChild = toEl.firstChild;\n var curFromNodeChild = fromEl.firstChild;\n var curToNodeKey;\n var curFromNodeKey;\n\n var fromNextSibling;\n var toNextSibling;\n var matchingFromEl;\n\n // walk the children\n outer: while (curToNodeChild) {\n toNextSibling = curToNodeChild.nextSibling;\n curToNodeKey = getNodeKey(curToNodeChild);\n\n // walk the fromNode children all the way through\n while (curFromNodeChild) {\n fromNextSibling = curFromNodeChild.nextSibling;\n\n if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n continue outer;\n }\n\n curFromNodeKey = getNodeKey(curFromNodeChild);\n\n var curFromNodeType = curFromNodeChild.nodeType;\n\n // this means if the curFromNodeChild doesnt have a match with the curToNodeChild\n var isCompatible = undefined;\n\n if (curFromNodeType === curToNodeChild.nodeType) {\n if (curFromNodeType === ELEMENT_NODE) {\n // Both nodes being compared are Element nodes\n\n if (curToNodeKey) {\n // The target node has a key so we want to match it up with the correct element\n // in the original DOM tree\n if (curToNodeKey !== curFromNodeKey) {\n // The current element in the original DOM tree does not have a matching key so\n // let's check our lookup to see if there is a matching element in the original\n // DOM tree\n if ((matchingFromEl = fromNodesLookup[curToNodeKey])) {\n if (fromNextSibling === matchingFromEl) {\n // Special case for single element removals. To avoid removing the original\n // DOM node out of the tree (since that can break CSS transitions, etc.),\n // we will instead discard the current node and wait until the next\n // iteration to properly match up the keyed target element with its matching\n // element in the original tree\n isCompatible = false;\n } else {\n // We found a matching keyed element somewhere in the original DOM tree.\n // Let's move the original DOM node into the current position and morph\n // it.\n\n // NOTE: We use insertBefore instead of replaceChild because we want to go through\n // the `removeNode()` function for the node that is being discarded so that\n // all lifecycle hooks are correctly invoked\n fromEl.insertBefore(matchingFromEl, curFromNodeChild);\n\n // fromNextSibling = curFromNodeChild.nextSibling;\n\n if (curFromNodeKey) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n\n curFromNodeChild = matchingFromEl;\n }\n } else {\n // The nodes are not compatible since the \"to\" node has a key and there\n // is no matching keyed node in the source tree\n isCompatible = false;\n }\n }\n } else if (curFromNodeKey) {\n // The original has a key\n isCompatible = false;\n }\n\n isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild);\n if (isCompatible) {\n // We found compatible DOM elements so transform\n // the current \"from\" node to match the current\n // target DOM node.\n // MORPH\n morphEl(curFromNodeChild, curToNodeChild);\n }\n\n } else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) {\n // Both nodes being compared are Text or Comment nodes\n isCompatible = true;\n // Simply update nodeValue on the original node to\n // change the text value\n if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) {\n curFromNodeChild.nodeValue = curToNodeChild.nodeValue;\n }\n\n }\n }\n\n if (isCompatible) {\n // Advance both the \"to\" child and the \"from\" child since we found a match\n // Nothing else to do as we already recursively called morphChildren above\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n continue outer;\n }\n\n // No compatible match so remove the old node from the DOM and continue trying to find a\n // match in the original DOM. However, we only do this if the from node is not keyed\n // since it is possible that a keyed node might match up with a node somewhere else in the\n // target tree and we don't want to discard it just yet since it still might find a\n // home in the final DOM tree. After everything is done we will remove any keyed nodes\n // that didn't find a home\n if (curFromNodeKey) {\n // Since the node is keyed it might be matched up later so we defer\n // the actual removal to later\n addKeyedRemoval(curFromNodeKey);\n } else {\n // NOTE: we skip nested keyed nodes from being removed since there is\n // still a chance they will be matched up later\n removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);\n }\n\n curFromNodeChild = fromNextSibling;\n } // END: while(curFromNodeChild) {}\n\n // If we got this far then we did not find a candidate match for\n // our \"to node\" and we exhausted all of the children \"from\"\n // nodes. Therefore, we will just append the current \"to\" node\n // to the end\n if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {\n fromEl.appendChild(matchingFromEl);\n // MORPH\n morphEl(matchingFromEl, curToNodeChild);\n } else {\n var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);\n if (onBeforeNodeAddedResult !== false) {\n if (onBeforeNodeAddedResult) {\n curToNodeChild = onBeforeNodeAddedResult;\n }\n\n if (curToNodeChild.actualize) {\n curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);\n }\n fromEl.appendChild(curToNodeChild);\n handleNodeAdded(curToNodeChild);\n }\n }\n\n curToNodeChild = toNextSibling;\n curFromNodeChild = fromNextSibling;\n }\n\n cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey);\n\n var specialElHandler = specialElHandlers[fromEl.nodeName];\n if (specialElHandler) {\n specialElHandler(fromEl, toEl);\n }\n } // END: morphChildren(...)\n\n var morphedNode = fromNode;\n var morphedNodeType = morphedNode.nodeType;\n var toNodeType = toNode.nodeType;\n\n if (!childrenOnly) {\n // Handle the case where we are given two DOM nodes that are not\n // compatible (e.g. -->
or --> TEXT)\n if (morphedNodeType === ELEMENT_NODE) {\n if (toNodeType === ELEMENT_NODE) {\n if (!compareNodeNames(fromNode, toNode)) {\n onNodeDiscarded(fromNode);\n morphedNode = moveChildren(fromNode, createElementNS(toNode.nodeName, toNode.namespaceURI));\n }\n } else {\n // Going from an element node to a text node\n morphedNode = toNode;\n }\n } else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node\n if (toNodeType === morphedNodeType) {\n if (morphedNode.nodeValue !== toNode.nodeValue) {\n morphedNode.nodeValue = toNode.nodeValue;\n }\n\n return morphedNode;\n } else {\n // Text node to something else\n morphedNode = toNode;\n }\n }\n }\n\n if (morphedNode === toNode) {\n // The \"to node\" was not compatible with the \"from node\" so we had to\n // toss out the \"from node\" and use the \"to node\"\n onNodeDiscarded(fromNode);\n } else {\n if (toNode.isSameNode && toNode.isSameNode(morphedNode)) {\n return;\n }\n\n morphEl(morphedNode, toNode, childrenOnly);\n\n // We now need to loop over any keyed nodes that might need to be\n // removed. We only do the removal if we know that the keyed node\n // never found a match. When a keyed node is matched up we remove\n // it out of fromNodesLookup and we use fromNodesLookup to determine\n // if a keyed node has been matched up or not\n if (keyedRemovalList) {\n for (var i=0, len=keyedRemovalList.length; i
{\n if(activeElement && activeElement.isSameNode(fromEl) && DOM.isFormInput(fromEl)){\n DOM.mergeFocusedInput(fromEl, toEl)\n return false\n }\n }\n })\n }\n\n constructor(view, container, id, html, targetCID){\n this.view = view\n this.liveSocket = view.liveSocket\n this.container = container\n this.id = id\n this.rootID = view.root.id\n this.html = html\n this.targetCID = targetCID\n this.cidPatch = typeof (this.targetCID) === \"number\"\n this.callbacks = {\n beforeadded: [], beforeupdated: [], beforephxChildAdded: [],\n afteradded: [], afterupdated: [], afterdiscarded: [], afterphxChildAdded: []\n }\n }\n\n before(kind, callback){ this.callbacks[`before${kind}`].push(callback) }\n after(kind, callback){ this.callbacks[`after${kind}`].push(callback) }\n\n trackBefore(kind, ...args){\n this.callbacks[`before${kind}`].forEach(callback => callback(...args))\n }\n\n trackAfter(kind, ...args){\n this.callbacks[`after${kind}`].forEach(callback => callback(...args))\n }\n\n markPrunableContentForRemoval(){\n DOM.all(this.container, \"[phx-update=append] > *, [phx-update=prepend] > *\", el => {\n el.setAttribute(PHX_REMOVE, \"\")\n })\n }\n\n perform(){\n let {view, liveSocket, container, html} = this\n let targetContainer = this.isCIDPatch() ? this.targetCIDContainer(html) : container\n if(this.isCIDPatch() && !targetContainer){ return }\n\n let focused = liveSocket.getActiveElement()\n let {selectionStart, selectionEnd} = focused && DOM.hasSelectionRange(focused) ? focused : {}\n let phxUpdate = liveSocket.binding(PHX_UPDATE)\n let phxFeedbackFor = liveSocket.binding(PHX_FEEDBACK_FOR)\n let disableWith = liveSocket.binding(PHX_DISABLE_WITH)\n let phxTriggerExternal = liveSocket.binding(PHX_TRIGGER_ACTION)\n let added = []\n let updates = []\n let appendPrependUpdates = []\n let externalFormTriggered = null\n\n let diffHTML = liveSocket.time(\"premorph container prep\", () => {\n return this.buildDiffHTML(container, html, phxUpdate, targetContainer)\n })\n\n this.trackBefore(\"added\", container)\n this.trackBefore(\"updated\", container, container)\n\n liveSocket.time(\"morphdom\", () => {\n morphdom(targetContainer, diffHTML, {\n childrenOnly: targetContainer.getAttribute(PHX_COMPONENT) === null,\n getNodeKey: (node) => {\n return DOM.isPhxDestroyed(node) ? null : node.id\n },\n onBeforeNodeAdded: (el) => {\n //input handling\n DOM.discardError(targetContainer, el, phxFeedbackFor)\n this.trackBefore(\"added\", el)\n return el\n },\n onNodeAdded: (el) => {\n if(DOM.isNowTriggerFormExternal(el, phxTriggerExternal)){\n externalFormTriggered = el\n }\n // nested view handling\n if(DOM.isPhxChild(el) && view.ownsElement(el)){\n this.trackAfter(\"phxChildAdded\", el)\n }\n added.push(el)\n },\n onNodeDiscarded: (el) => {\n // nested view handling\n if(DOM.isPhxChild(el)){ liveSocket.destroyViewByEl(el) }\n this.trackAfter(\"discarded\", el)\n },\n onBeforeNodeDiscarded: (el) => {\n if(el.getAttribute && el.getAttribute(PHX_REMOVE) !== null){ return true }\n if(el.parentNode !== null && DOM.isPhxUpdate(el.parentNode, phxUpdate, [\"append\", \"prepend\"]) && el.id){ return false }\n if(this.skipCIDSibling(el)){ return false }\n return true\n },\n onElUpdated: (el) => {\n if(DOM.isNowTriggerFormExternal(el, phxTriggerExternal)){\n externalFormTriggered = el\n }\n updates.push(el)\n },\n onBeforeElUpdated: (fromEl, toEl) => {\n DOM.cleanChildNodes(toEl, phxUpdate)\n if(this.skipCIDSibling(toEl)){ return false }\n if(DOM.isIgnored(fromEl, phxUpdate)){\n this.trackBefore(\"updated\", fromEl, toEl)\n DOM.mergeAttrs(fromEl, toEl, {isIgnored: true})\n updates.push(fromEl)\n return false\n }\n if(fromEl.type === \"number\" && (fromEl.validity && fromEl.validity.badInput)){ return false }\n if(!DOM.syncPendingRef(fromEl, toEl, disableWith)){\n if(DOM.isUploadInput(fromEl)){\n this.trackBefore(\"updated\", fromEl, toEl)\n updates.push(fromEl)\n }\n return false\n }\n\n // nested view handling\n if(DOM.isPhxChild(toEl)){\n let prevSession = fromEl.getAttribute(PHX_SESSION)\n DOM.mergeAttrs(fromEl, toEl, {exclude: [PHX_STATIC]})\n if(prevSession !== \"\"){ fromEl.setAttribute(PHX_SESSION, prevSession) }\n fromEl.setAttribute(PHX_ROOT_ID, this.rootID)\n return false\n }\n\n // input handling\n DOM.copyPrivates(toEl, fromEl)\n DOM.discardError(targetContainer, toEl, phxFeedbackFor)\n\n let isFocusedFormEl = focused && fromEl.isSameNode(focused) && DOM.isFormInput(fromEl)\n if(isFocusedFormEl && !this.forceFocusedSelectUpdate(fromEl, toEl)){\n this.trackBefore(\"updated\", fromEl, toEl)\n DOM.mergeFocusedInput(fromEl, toEl)\n DOM.syncAttrsToProps(fromEl)\n updates.push(fromEl)\n return false\n } else {\n if(DOM.isPhxUpdate(toEl, phxUpdate, [\"append\", \"prepend\"])){\n appendPrependUpdates.push(new DOMPostMorphRestorer(fromEl, toEl, toEl.getAttribute(phxUpdate)))\n }\n DOM.syncAttrsToProps(toEl)\n this.trackBefore(\"updated\", fromEl, toEl)\n return true\n }\n }\n })\n })\n\n if(liveSocket.isDebugEnabled()){ detectDuplicateIds() }\n\n if(appendPrependUpdates.length > 0){\n liveSocket.time(\"post-morph append/prepend restoration\", () => {\n appendPrependUpdates.forEach(update => update.perform())\n })\n }\n\n liveSocket.silenceEvents(() => DOM.restoreFocus(focused, selectionStart, selectionEnd))\n DOM.dispatchEvent(document, \"phx:update\")\n added.forEach(el => this.trackAfter(\"added\", el))\n updates.forEach(el => this.trackAfter(\"updated\", el))\n\n if(externalFormTriggered){\n liveSocket.disconnect()\n externalFormTriggered.submit()\n }\n return true\n }\n\n forceFocusedSelectUpdate(fromEl, toEl){\n let isSelect = [\"select\", \"select-one\", \"select-multiple\"].find((t) => t === fromEl.type)\n return fromEl.multiple === true || (isSelect && fromEl.innerHTML != toEl.innerHTML)\n }\n\n isCIDPatch(){ return this.cidPatch }\n\n skipCIDSibling(el){\n return el.nodeType === Node.ELEMENT_NODE && el.getAttribute(PHX_SKIP) !== null\n }\n\n targetCIDContainer(html){\n if(!this.isCIDPatch()){ return }\n let [first, ...rest] = DOM.findComponentNodeList(this.container, this.targetCID)\n if(rest.length === 0 && DOM.childNodeLength(html) === 1){\n return first\n } else {\n return first && first.parentNode\n }\n }\n\n // builds HTML for morphdom patch\n // - for full patches of LiveView or a component with a single\n // root node, simply returns the HTML\n // - for patches of a component with multiple root nodes, the\n // parent node becomes the target container and non-component\n // siblings are marked as skip.\n buildDiffHTML(container, html, phxUpdate, targetContainer){\n let isCIDPatch = this.isCIDPatch()\n let isCIDWithSingleRoot = isCIDPatch && targetContainer.getAttribute(PHX_COMPONENT) === this.targetCID.toString()\n if(!isCIDPatch || isCIDWithSingleRoot){\n return html\n } else {\n // component patch with multiple CID roots\n let diffContainer = null\n let template = document.createElement(\"template\")\n diffContainer = DOM.cloneNode(targetContainer)\n let [firstComponent, ...rest] = DOM.findComponentNodeList(diffContainer, this.targetCID)\n template.innerHTML = html\n rest.forEach(el => el.remove())\n Array.from(diffContainer.childNodes).forEach(child => {\n // we can only skip trackable nodes with an ID\n if(child.id && child.nodeType === Node.ELEMENT_NODE && child.getAttribute(PHX_COMPONENT) !== this.targetCID.toString()){\n child.setAttribute(PHX_SKIP, \"\")\n child.innerHTML = \"\"\n }\n })\n Array.from(template.content.childNodes).forEach(el => diffContainer.insertBefore(el, firstComponent))\n firstComponent.remove()\n return diffContainer.outerHTML\n }\n }\n}\n", "import {\n COMPONENTS,\n DYNAMICS,\n EVENTS,\n PHX_COMPONENT,\n PHX_SKIP,\n REPLY,\n STATIC,\n TITLE\n} from \"./constants\"\n\nimport {\n isObject,\n logError\n} from \"./utils\"\n\nexport default class Rendered {\n static extract(diff){\n let {[REPLY]: reply, [EVENTS]: events, [TITLE]: title} = diff\n delete diff[REPLY]\n delete diff[EVENTS]\n delete diff[TITLE]\n return {diff, title, reply: reply || null, events: events || []}\n }\n\n constructor(viewId, rendered){\n this.viewId = viewId\n this.rendered = {}\n this.mergeDiff(rendered)\n }\n\n parentViewId(){ return this.viewId }\n\n toString(onlyCids){\n return this.recursiveToString(this.rendered, this.rendered[COMPONENTS], onlyCids)\n }\n\n recursiveToString(rendered, components = rendered[COMPONENTS], onlyCids){\n onlyCids = onlyCids ? new Set(onlyCids) : null\n let output = {buffer: \"\", components: components, onlyCids: onlyCids}\n this.toOutputBuffer(rendered, output)\n return output.buffer\n }\n\n componentCIDs(diff){ return Object.keys(diff[COMPONENTS] || {}).map(i => parseInt(i)) }\n\n isComponentOnlyDiff(diff){\n if(!diff[COMPONENTS]){ return false }\n return Object.keys(diff).length === 1\n }\n\n getComponent(diff, cid){ return diff[COMPONENTS][cid] }\n\n mergeDiff(diff){\n let newc = diff[COMPONENTS]\n let cache = {}\n delete diff[COMPONENTS]\n this.rendered = this.mutableMerge(this.rendered, diff)\n this.rendered[COMPONENTS] = this.rendered[COMPONENTS] || {}\n\n if(newc){\n let oldc = this.rendered[COMPONENTS]\n\n for(let cid in newc){\n newc[cid] = this.cachedFindComponent(cid, newc[cid], oldc, newc, cache)\n }\n\n for(var key in newc){ oldc[key] = newc[key] }\n diff[COMPONENTS] = newc\n }\n }\n\n cachedFindComponent(cid, cdiff, oldc, newc, cache){\n if(cache[cid]){\n return cache[cid]\n } else {\n let ndiff, stat, scid = cdiff[STATIC]\n\n if(typeof (scid) === \"number\"){\n let tdiff\n\n if(scid > 0){\n tdiff = this.cachedFindComponent(scid, newc[scid], oldc, newc, cache)\n } else {\n tdiff = oldc[-scid]\n }\n\n stat = tdiff[STATIC]\n ndiff = this.cloneMerge(tdiff, cdiff)\n ndiff[STATIC] = stat\n } else {\n ndiff = cdiff[STATIC] !== undefined ? cdiff : this.cloneMerge(oldc[cid] || {}, cdiff)\n }\n\n cache[cid] = ndiff\n return ndiff\n }\n }\n\n mutableMerge(target, source){\n if(source[STATIC] !== undefined){\n return source\n } else {\n this.doMutableMerge(target, source)\n return target\n }\n }\n\n doMutableMerge(target, source){\n for(let key in source){\n let val = source[key]\n let targetVal = target[key]\n if(isObject(val) && val[STATIC] === undefined && isObject(targetVal)){\n this.doMutableMerge(targetVal, val)\n } else {\n target[key] = val\n }\n }\n }\n\n cloneMerge(target, source){\n let merged = {...target, ...source}\n for(let key in merged){\n let val = source[key]\n let targetVal = target[key]\n if(isObject(val) && val[STATIC] === undefined && isObject(targetVal)){\n merged[key] = this.cloneMerge(targetVal, val)\n }\n }\n return merged\n }\n\n componentToString(cid){ return this.recursiveCIDToString(this.rendered[COMPONENTS], cid) }\n\n pruneCIDs(cids){\n cids.forEach(cid => delete this.rendered[COMPONENTS][cid])\n }\n\n // private\n\n get(){ return this.rendered }\n\n isNewFingerprint(diff = {}){ return !!diff[STATIC] }\n\n toOutputBuffer(rendered, output){\n if(rendered[DYNAMICS]){ return this.comprehensionToBuffer(rendered, output) }\n let {[STATIC]: statics} = rendered\n\n output.buffer += statics[0]\n for(let i = 1; i < statics.length; i++){\n this.dynamicToBuffer(rendered[i - 1], output)\n output.buffer += statics[i]\n }\n }\n\n comprehensionToBuffer(rendered, output){\n let {[DYNAMICS]: dynamics, [STATIC]: statics} = rendered\n\n for(let d = 0; d < dynamics.length; d++){\n let dynamic = dynamics[d]\n output.buffer += statics[0]\n for(let i = 1; i < statics.length; i++){\n this.dynamicToBuffer(dynamic[i - 1], output)\n output.buffer += statics[i]\n }\n }\n }\n\n dynamicToBuffer(rendered, output){\n if(typeof (rendered) === \"number\"){\n output.buffer += this.recursiveCIDToString(output.components, rendered, output.onlyCids)\n } else if(isObject(rendered)){\n this.toOutputBuffer(rendered, output)\n } else {\n output.buffer += rendered\n }\n }\n\n recursiveCIDToString(components, cid, onlyCids){\n let component = components[cid] || logError(`no component for CID ${cid}`, components)\n let template = document.createElement(\"template\")\n template.innerHTML = this.recursiveToString(component, components, onlyCids)\n let container = template.content\n let skip = onlyCids && !onlyCids.has(cid)\n\n let [hasChildNodes, hasChildComponents] =\n Array.from(container.childNodes).reduce(([hasNodes, hasComponents], child, i) => {\n if(child.nodeType === Node.ELEMENT_NODE){\n if(child.getAttribute(PHX_COMPONENT)){\n return [hasNodes, true]\n }\n child.setAttribute(PHX_COMPONENT, cid)\n if(!child.id){ child.id = `${this.parentViewId()}-${cid}-${i}` }\n if(skip){\n child.setAttribute(PHX_SKIP, \"\")\n child.innerHTML = \"\"\n }\n return [true, hasComponents]\n } else {\n if(child.nodeValue.trim() !== \"\"){\n logError(\"only HTML element tags are allowed at the root of components.\\n\\n\" +\n `got: \"${child.nodeValue.trim()}\"\\n\\n` +\n \"within:\\n\", template.innerHTML.trim())\n child.replaceWith(this.createSpan(child.nodeValue, cid))\n return [true, hasComponents]\n } else {\n child.remove()\n return [hasNodes, hasComponents]\n }\n }\n }, [false, false])\n\n if(!hasChildNodes && !hasChildComponents){\n logError(\"expected at least one HTML element tag inside a component, but the component is empty:\\n\",\n template.innerHTML.trim())\n return this.createSpan(\"\", cid).outerHTML\n } else if(!hasChildNodes && hasChildComponents){\n logError(\"expected at least one HTML element tag directly inside a component, but only subcomponents were found. A component must render at least one HTML tag directly inside itself.\",\n template.innerHTML.trim())\n return template.innerHTML\n } else {\n return template.innerHTML\n }\n }\n\n createSpan(text, cid){\n let span = document.createElement(\"span\")\n span.innerText = text\n span.setAttribute(PHX_COMPONENT, cid)\n return span\n }\n}\n", "let viewHookID = 1\nexport default class ViewHook {\n static makeID(){ return viewHookID++ }\n static elementID(el){ return el.phxHookId }\n\n constructor(view, el, callbacks){\n this.__view = view\n this.__liveSocket = view.liveSocket\n this.__callbacks = callbacks\n this.__listeners = new Set()\n this.__isDisconnected = false\n this.el = el\n this.el.phxHookId = this.constructor.makeID()\n for(let key in this.__callbacks){ this[key] = this.__callbacks[key] }\n }\n\n __mounted(){ this.mounted && this.mounted() }\n __updated(){ this.updated && this.updated() }\n __beforeUpdate(){ this.beforeUpdate && this.beforeUpdate() }\n __destroyed(){ this.destroyed && this.destroyed() }\n __reconnected(){\n if(this.__isDisconnected){\n this.__isDisconnected = false\n this.reconnected && this.reconnected()\n }\n }\n __disconnected(){\n this.__isDisconnected = true\n this.disconnected && this.disconnected()\n }\n\n pushEvent(event, payload = {}, onReply = function (){ }){\n return this.__view.pushHookEvent(null, event, payload, onReply)\n }\n\n pushEventTo(phxTarget, event, payload = {}, onReply = function (){ }){\n return this.__view.withinTargets(phxTarget, (view, targetCtx) => {\n return view.pushHookEvent(targetCtx, event, payload, onReply)\n })\n }\n\n handleEvent(event, callback){\n let callbackRef = (customEvent, bypass) => bypass ? event : callback(customEvent.detail)\n window.addEventListener(`phx:hook:${event}`, callbackRef)\n this.__listeners.add(callbackRef)\n return callbackRef\n }\n\n removeHandleEvent(callbackRef){\n let event = callbackRef(null, true)\n window.removeEventListener(`phx:hook:${event}`, callbackRef)\n this.__listeners.delete(callbackRef)\n }\n\n __cleanup__(){\n this.__listeners.forEach(callbackRef => this.removeHandleEvent(callbackRef))\n }\n}\n", "import {\n BEFORE_UNLOAD_LOADER_TIMEOUT,\n CHECKABLE_INPUTS,\n CONSECUTIVE_RELOADS,\n PHX_AUTO_RECOVER,\n PHX_COMPONENT,\n PHX_CONNECTED_CLASS,\n PHX_DISABLE_WITH,\n PHX_DISABLE_WITH_RESTORE,\n PHX_DISABLED,\n PHX_DISCONNECTED_CLASS,\n PHX_EVENT_CLASSES,\n PHX_ERROR_CLASS,\n PHX_FEEDBACK_FOR,\n PHX_HAS_SUBMITTED,\n PHX_HOOK,\n PHX_PAGE_LOADING,\n PHX_PARENT_ID,\n PHX_PROGRESS,\n PHX_READONLY,\n PHX_REF,\n PHX_ROOT_ID,\n PHX_SESSION,\n PHX_STATIC,\n PHX_TRACK_STATIC,\n PHX_UPDATE,\n PHX_UPLOAD_REF,\n PHX_VIEW_SELECTOR,\n PUSH_TIMEOUT,\n} from \"./constants\"\n\nimport {\n clone,\n closestPhxBinding,\n isEmpty,\n isEqualObj,\n logError,\n maybe\n} from \"./utils\"\n\nimport Browser from \"./browser\"\nimport DOM from \"./dom\"\nimport DOMPatch from \"./dom_patch\"\nimport LiveUploader from \"./live_uploader\"\nimport Rendered from \"./rendered\"\nimport ViewHook from \"./view_hook\"\n\nlet serializeForm = (form, meta = {}) => {\n let formData = new FormData(form)\n let toRemove = []\n\n formData.forEach((val, key, _index) => {\n if(val instanceof File){ toRemove.push(key) }\n })\n\n // Cleanup after building fileData\n toRemove.forEach(key => formData.delete(key))\n\n let params = new URLSearchParams()\n for(let [key, val] of formData.entries()){ params.append(key, val) }\n for(let metaKey in meta){ params.append(metaKey, meta[metaKey]) }\n\n return params.toString()\n}\n\nexport default class View {\n constructor(el, liveSocket, parentView, flash){\n this.liveSocket = liveSocket\n this.flash = flash\n this.parent = parentView\n this.root = parentView ? parentView.root : this\n this.el = el\n this.id = this.el.id\n this.ref = 0\n this.childJoins = 0\n this.loaderTimer = null\n this.pendingDiffs = []\n this.pruningCIDs = []\n this.redirect = false\n this.href = null\n this.joinCount = this.parent ? this.parent.joinCount - 1 : 0\n this.joinPending = true\n this.destroyed = false\n this.joinCallback = function (){ }\n this.stopCallback = function (){ }\n this.pendingJoinOps = this.parent ? null : []\n this.viewHooks = {}\n this.uploaders = {}\n this.formSubmits = []\n this.children = this.parent ? null : {}\n this.root.children[this.id] = {}\n this.channel = this.liveSocket.channel(`lv:${this.id}`, () => {\n return {\n redirect: this.redirect ? this.href : undefined,\n url: this.redirect ? undefined : this.href || undefined,\n params: this.connectParams(),\n session: this.getSession(),\n static: this.getStatic(),\n flash: this.flash\n }\n })\n this.showLoader(this.liveSocket.loaderTimeout)\n this.bindChannel()\n }\n\n setHref(href){ this.href = href }\n\n setRedirect(href){\n this.redirect = true\n this.href = href\n }\n\n isMain(){ return this.liveSocket.main === this }\n\n connectParams(){\n let params = this.liveSocket.params(this.el)\n let manifest =\n DOM.all(document, `[${this.binding(PHX_TRACK_STATIC)}]`)\n .map(node => node.src || node.href).filter(url => typeof (url) === \"string\")\n\n if(manifest.length > 0){ params[\"_track_static\"] = manifest }\n params[\"_mounts\"] = this.joinCount\n\n return params\n }\n\n isConnected(){ return this.channel.canPush() }\n\n getSession(){ return this.el.getAttribute(PHX_SESSION) }\n\n getStatic(){\n let val = this.el.getAttribute(PHX_STATIC)\n return val === \"\" ? null : val\n }\n\n destroy(callback = function (){ }){\n this.destroyAllChildren()\n this.destroyed = true\n delete this.root.children[this.id]\n if(this.parent){ delete this.root.children[this.parent.id][this.id] }\n clearTimeout(this.loaderTimer)\n let onFinished = () => {\n callback()\n for(let id in this.viewHooks){\n this.destroyHook(this.viewHooks[id])\n }\n }\n\n DOM.markPhxChildDestroyed(this.el)\n\n this.log(\"destroyed\", () => [\"the child has been removed from the parent\"])\n this.channel.leave()\n .receive(\"ok\", onFinished)\n .receive(\"error\", onFinished)\n .receive(\"timeout\", onFinished)\n }\n\n setContainerClasses(...classes){\n this.el.classList.remove(\n PHX_CONNECTED_CLASS,\n PHX_DISCONNECTED_CLASS,\n PHX_ERROR_CLASS\n )\n this.el.classList.add(...classes)\n }\n\n isLoading(){ return this.el.classList.contains(PHX_DISCONNECTED_CLASS) }\n\n showLoader(timeout){\n clearTimeout(this.loaderTimer)\n if(timeout){\n this.loaderTimer = setTimeout(() => this.showLoader(), timeout)\n } else {\n for(let id in this.viewHooks){ this.viewHooks[id].__disconnected() }\n this.setContainerClasses(PHX_DISCONNECTED_CLASS)\n }\n }\n\n hideLoader(){\n clearTimeout(this.loaderTimer)\n this.setContainerClasses(PHX_CONNECTED_CLASS)\n }\n\n triggerReconnected(){\n for(let id in this.viewHooks){ this.viewHooks[id].__reconnected() }\n }\n\n log(kind, msgCallback){\n this.liveSocket.log(this, kind, msgCallback)\n }\n\n withinTargets(phxTarget, callback){\n if(phxTarget instanceof HTMLElement){\n return this.liveSocket.owner(phxTarget, view => callback(view, phxTarget))\n }\n\n if(/^(0|[1-9]\\d*)$/.test(phxTarget)){\n let targets = DOM.findComponentNodeList(this.el, phxTarget)\n if(targets.length === 0){\n logError(`no component found matching phx-target of ${phxTarget}`)\n } else {\n callback(this, targets[0])\n }\n } else {\n let targets = Array.from(document.querySelectorAll(phxTarget))\n if(targets.length === 0){ logError(`nothing found matching the phx-target selector \"${phxTarget}\"`) }\n targets.forEach(target => this.liveSocket.owner(target, view => callback(view, target)))\n }\n }\n\n applyDiff(type, rawDiff, callback){\n this.log(type, () => [\"\", clone(rawDiff)])\n let {diff, reply, events, title} = Rendered.extract(rawDiff)\n if(title){ DOM.putTitle(title) }\n\n callback({diff, reply, events})\n return reply\n }\n\n onJoin(resp){\n let {rendered, container} = resp\n if(container){\n let [tag, attrs] = container\n this.el = DOM.replaceRootContainer(this.el, tag, attrs)\n }\n this.childJoins = 0\n this.joinPending = true\n this.flash = null\n\n Browser.dropLocal(this.liveSocket.localStorage, window.location.pathname, CONSECUTIVE_RELOADS)\n this.applyDiff(\"mount\", rendered, ({diff, events}) => {\n this.rendered = new Rendered(this.id, diff)\n let html = this.renderContainer(null, \"join\")\n this.dropPendingRefs()\n let forms = this.formsForRecovery(html)\n this.joinCount++\n\n if(forms.length > 0){\n forms.forEach((form, i) => {\n this.pushFormRecovery(form, resp => {\n if(i === forms.length - 1){\n this.onJoinComplete(resp, html, events)\n }\n })\n })\n } else {\n this.onJoinComplete(resp, html, events)\n }\n })\n }\n\n dropPendingRefs(){ DOM.all(this.el, `[${PHX_REF}]`, el => el.removeAttribute(PHX_REF)) }\n\n onJoinComplete({live_patch}, html, events){\n // In order to provide a better experience, we want to join\n // all LiveViews first and only then apply their patches.\n if(this.joinCount > 1 || (this.parent && !this.parent.isJoinPending())){\n return this.applyJoinPatch(live_patch, html, events)\n }\n\n // One downside of this approach is that we need to find phxChildren\n // in the html fragment, instead of directly on the DOM. The fragment\n // also does not include PHX_STATIC, so we need to copy it over from\n // the DOM.\n let newChildren = DOM.findPhxChildrenInFragment(html, this.id).filter(toEl => {\n let fromEl = toEl.id && this.el.querySelector(`#${toEl.id}`)\n let phxStatic = fromEl && fromEl.getAttribute(PHX_STATIC)\n if(phxStatic){ toEl.setAttribute(PHX_STATIC, phxStatic) }\n return this.joinChild(toEl)\n })\n\n if(newChildren.length === 0){\n if(this.parent){\n this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, events)])\n this.parent.ackJoin(this)\n } else {\n this.onAllChildJoinsComplete()\n this.applyJoinPatch(live_patch, html, events)\n }\n } else {\n this.root.pendingJoinOps.push([this, () => this.applyJoinPatch(live_patch, html, events)])\n }\n }\n\n attachTrueDocEl(){\n this.el = DOM.byId(this.id)\n this.el.setAttribute(PHX_ROOT_ID, this.root.id)\n }\n\n dispatchEvents(events){\n events.forEach(([event, payload]) => {\n window.dispatchEvent(new CustomEvent(`phx:hook:${event}`, {detail: payload}))\n })\n }\n\n applyJoinPatch(live_patch, html, events){\n this.attachTrueDocEl()\n let patch = new DOMPatch(this, this.el, this.id, html, null)\n patch.markPrunableContentForRemoval()\n this.performPatch(patch, false)\n this.joinNewChildren()\n DOM.all(this.el, `[${this.binding(PHX_HOOK)}], [data-phx-${PHX_HOOK}]`, hookEl => {\n let hook = this.addHook(hookEl)\n if(hook){ hook.__mounted() }\n })\n\n this.joinPending = false\n this.dispatchEvents(events)\n this.applyPendingUpdates()\n\n if(live_patch){\n let {kind, to} = live_patch\n this.liveSocket.historyPatch(to, kind)\n }\n this.hideLoader()\n if(this.joinCount > 1){ this.triggerReconnected() }\n this.stopCallback()\n }\n\n triggerBeforeUpdateHook(fromEl, toEl){\n this.liveSocket.triggerDOM(\"onBeforeElUpdated\", [fromEl, toEl])\n let hook = this.getHook(fromEl)\n let isIgnored = hook && DOM.isIgnored(fromEl, this.binding(PHX_UPDATE))\n if(hook && !fromEl.isEqualNode(toEl) && !(isIgnored && isEqualObj(fromEl.dataset, toEl.dataset))){\n hook.__beforeUpdate()\n return hook\n }\n }\n\n performPatch(patch, pruneCids){\n let destroyedCIDs = []\n let phxChildrenAdded = false\n let updatedHookIds = new Set()\n\n patch.after(\"added\", el => {\n this.liveSocket.triggerDOM(\"onNodeAdded\", [el])\n\n let newHook = this.addHook(el)\n if(newHook){ newHook.__mounted() }\n })\n\n patch.after(\"phxChildAdded\", _el => phxChildrenAdded = true)\n\n patch.before(\"updated\", (fromEl, toEl) => {\n let hook = this.triggerBeforeUpdateHook(fromEl, toEl)\n if(hook){ updatedHookIds.add(fromEl.id) }\n })\n\n patch.after(\"updated\", el => {\n if(updatedHookIds.has(el.id)){ this.getHook(el).__updated() }\n })\n\n patch.after(\"discarded\", (el) => {\n let cid = this.componentID(el)\n if(typeof (cid) === \"number\" && destroyedCIDs.indexOf(cid) === -1){ destroyedCIDs.push(cid) }\n let hook = this.getHook(el)\n hook && this.destroyHook(hook)\n })\n\n patch.perform()\n\n // We should not pruneCids on joins. Otherwise, in case of\n // rejoins, we may notify cids that no longer belong to the\n // current LiveView to be removed.\n if(pruneCids){\n this.maybePushComponentsDestroyed(destroyedCIDs)\n }\n\n return phxChildrenAdded\n }\n\n joinNewChildren(){\n DOM.findPhxChildren(this.el, this.id).forEach(el => this.joinChild(el))\n }\n\n getChildById(id){ return this.root.children[this.id][id] }\n\n getDescendentByEl(el){\n if(el.id === this.id){\n return this\n } else {\n return this.children[el.getAttribute(PHX_PARENT_ID)][el.id]\n }\n }\n\n destroyDescendent(id){\n for(let parentId in this.root.children){\n for(let childId in this.root.children[parentId]){\n if(childId === id){ return this.root.children[parentId][childId].destroy() }\n }\n }\n }\n\n joinChild(el){\n let child = this.getChildById(el.id)\n if(!child){\n let view = new View(el, this.liveSocket, this)\n this.root.children[this.id][view.id] = view\n view.join()\n this.childJoins++\n return true\n }\n }\n\n isJoinPending(){ return this.joinPending }\n\n ackJoin(_child){\n this.childJoins--\n\n if(this.childJoins === 0){\n if(this.parent){\n this.parent.ackJoin(this)\n } else {\n this.onAllChildJoinsComplete()\n }\n }\n }\n\n onAllChildJoinsComplete(){\n this.joinCallback()\n this.pendingJoinOps.forEach(([view, op]) => {\n if(!view.isDestroyed()){ op() }\n })\n this.pendingJoinOps = []\n }\n\n update(diff, events){\n if(this.isJoinPending() || this.liveSocket.hasPendingLink()){\n return this.pendingDiffs.push({diff, events})\n }\n\n this.rendered.mergeDiff(diff)\n let phxChildrenAdded = false\n\n // When the diff only contains component diffs, then walk components\n // and patch only the parent component containers found in the diff.\n // Otherwise, patch entire LV container.\n if(this.rendered.isComponentOnlyDiff(diff)){\n this.liveSocket.time(\"component patch complete\", () => {\n let parentCids = DOM.findParentCIDs(this.el, this.rendered.componentCIDs(diff))\n parentCids.forEach(parentCID => {\n if(this.componentPatch(this.rendered.getComponent(diff, parentCID), parentCID)){ phxChildrenAdded = true }\n })\n })\n } else if(!isEmpty(diff)){\n this.liveSocket.time(\"full patch complete\", () => {\n let html = this.renderContainer(diff, \"update\")\n let patch = new DOMPatch(this, this.el, this.id, html, null)\n phxChildrenAdded = this.performPatch(patch, true)\n })\n }\n\n this.dispatchEvents(events)\n if(phxChildrenAdded){ this.joinNewChildren() }\n }\n\n renderContainer(diff, kind){\n return this.liveSocket.time(`toString diff (${kind})`, () => {\n let tag = this.el.tagName\n // Don't skip any component in the diff nor any marked as pruned\n // (as they may have been added back)\n let cids = diff ? this.rendered.componentCIDs(diff).concat(this.pruningCIDs) : null\n let html = this.rendered.toString(cids)\n return `<${tag}>${html}${tag}>`\n })\n }\n\n componentPatch(diff, cid){\n if(isEmpty(diff)) return false\n let html = this.rendered.componentToString(cid)\n let patch = new DOMPatch(this, this.el, this.id, html, cid)\n let childrenAdded = this.performPatch(patch, true)\n return childrenAdded\n }\n\n getHook(el){ return this.viewHooks[ViewHook.elementID(el)] }\n\n addHook(el){\n if(ViewHook.elementID(el) || !el.getAttribute){ return }\n let hookName = el.getAttribute(`data-phx-${PHX_HOOK}`) || el.getAttribute(this.binding(PHX_HOOK))\n if(hookName && !this.ownsElement(el)){ return }\n let callbacks = this.liveSocket.getHookCallbacks(hookName)\n\n if(callbacks){\n if(!el.id){ logError(`no DOM ID for hook \"${hookName}\". Hooks require a unique ID on each element.`, el) }\n let hook = new ViewHook(this, el, callbacks)\n this.viewHooks[ViewHook.elementID(hook.el)] = hook\n return hook\n } else if(hookName !== null){\n logError(`unknown hook found for \"${hookName}\"`, el)\n }\n }\n\n destroyHook(hook){\n hook.__destroyed()\n hook.__cleanup__()\n delete this.viewHooks[ViewHook.elementID(hook.el)]\n }\n\n applyPendingUpdates(){\n this.pendingDiffs.forEach(({diff, events}) => this.update(diff, events))\n this.pendingDiffs = []\n }\n\n onChannel(event, cb){\n this.liveSocket.onChannel(this.channel, event, resp => {\n if(this.isJoinPending()){\n this.root.pendingJoinOps.push([this, () => cb(resp)])\n } else {\n cb(resp)\n }\n })\n }\n\n bindChannel(){\n // The diff event should be handled by the regular update operations.\n // All other operations are queued to be applied only after join.\n this.liveSocket.onChannel(this.channel, \"diff\", (rawDiff) => {\n this.applyDiff(\"update\", rawDiff, ({diff, events}) => this.update(diff, events))\n })\n this.onChannel(\"redirect\", ({to, flash}) => this.onRedirect({to, flash}))\n this.onChannel(\"live_patch\", (redir) => this.onLivePatch(redir))\n this.onChannel(\"live_redirect\", (redir) => this.onLiveRedirect(redir))\n this.channel.onError(reason => this.onError(reason))\n this.channel.onClose(reason => this.onClose(reason))\n }\n\n destroyAllChildren(){\n for(let id in this.root.children[this.id]){\n this.getChildById(id).destroy()\n }\n }\n\n onLiveRedirect(redir){\n let {to, kind, flash} = redir\n let url = this.expandURL(to)\n this.liveSocket.historyRedirect(url, kind, flash)\n }\n\n onLivePatch(redir){\n let {to, kind} = redir\n this.href = this.expandURL(to)\n this.liveSocket.historyPatch(to, kind)\n }\n\n expandURL(to){\n return to.startsWith(\"/\") ? `${window.location.protocol}//${window.location.host}${to}` : to\n }\n\n onRedirect({to, flash}){ this.liveSocket.redirect(to, flash) }\n\n isDestroyed(){ return this.destroyed }\n\n join(callback){\n if(!this.parent){\n this.stopCallback = this.liveSocket.withPageLoading({to: this.href, kind: \"initial\"})\n }\n this.joinCallback = () => callback && callback(this.joinCount)\n this.liveSocket.wrapPush(this, {timeout: false}, () => {\n return this.channel.join()\n .receive(\"ok\", data => !this.isDestroyed() && this.onJoin(data))\n .receive(\"error\", resp => !this.isDestroyed() && this.onJoinError(resp))\n .receive(\"timeout\", () => !this.isDestroyed() && this.onJoinError({reason: \"timeout\"}))\n })\n }\n\n onJoinError(resp){\n if(resp.reason === \"unauthorized\" || resp.reason === \"stale\"){\n this.log(\"error\", () => [\"unauthorized live_redirect. Falling back to page request\", resp])\n return this.onRedirect({to: this.href})\n }\n if(resp.redirect || resp.live_redirect){\n this.joinPending = false\n this.channel.leave()\n }\n if(resp.redirect){ return this.onRedirect(resp.redirect) }\n if(resp.live_redirect){ return this.onLiveRedirect(resp.live_redirect) }\n this.log(\"error\", () => [\"unable to join\", resp])\n return this.liveSocket.reloadWithJitter(this)\n }\n\n onClose(reason){\n if(this.isDestroyed()){ return }\n if((this.isJoinPending() && document.visibilityState !== \"hidden\") ||\n (this.liveSocket.hasPendingLink() && reason !== \"leave\")){\n\n return this.liveSocket.reloadWithJitter(this)\n }\n this.destroyAllChildren()\n this.liveSocket.dropActiveElement(this)\n // document.activeElement can be null in Internet Explorer 11\n if(document.activeElement){ document.activeElement.blur() }\n if(this.liveSocket.isUnloaded()){\n this.showLoader(BEFORE_UNLOAD_LOADER_TIMEOUT)\n }\n }\n\n onError(reason){\n this.onClose(reason)\n this.log(\"error\", () => [\"view crashed\", reason])\n if(!this.liveSocket.isUnloaded()){ this.displayError() }\n }\n\n displayError(){\n if(this.isMain()){ DOM.dispatchEvent(window, \"phx:page-loading-start\", {to: this.href, kind: \"error\"}) }\n this.showLoader()\n this.setContainerClasses(PHX_DISCONNECTED_CLASS, PHX_ERROR_CLASS)\n }\n\n pushWithReply(refGenerator, event, payload, onReply = function (){ }){\n if(!this.isConnected()){ return }\n\n let [ref, [el]] = refGenerator ? refGenerator() : [null, []]\n let onLoadingDone = function (){ }\n if(el && (el.getAttribute(this.binding(PHX_PAGE_LOADING)) !== null)){\n onLoadingDone = this.liveSocket.withPageLoading({kind: \"element\", target: el})\n }\n\n if(typeof (payload.cid) !== \"number\"){ delete payload.cid }\n return (\n this.liveSocket.wrapPush(this, {timeout: true}, () => {\n return this.channel.push(event, payload, PUSH_TIMEOUT).receive(\"ok\", resp => {\n let hookReply = null\n if(ref !== null){ this.undoRefs(ref) }\n if(resp.diff){\n hookReply = this.applyDiff(\"update\", resp.diff, ({diff, events}) => {\n this.update(diff, events)\n })\n }\n if(resp.redirect){ this.onRedirect(resp.redirect) }\n if(resp.live_patch){ this.onLivePatch(resp.live_patch) }\n if(resp.live_redirect){ this.onLiveRedirect(resp.live_redirect) }\n onLoadingDone()\n onReply(resp, hookReply)\n })\n })\n )\n }\n\n undoRefs(ref){\n DOM.all(this.el, `[${PHX_REF}=\"${ref}\"]`, el => {\n // remove refs\n el.removeAttribute(PHX_REF)\n // restore inputs\n if(el.getAttribute(PHX_READONLY) !== null){\n el.readOnly = false\n el.removeAttribute(PHX_READONLY)\n }\n if(el.getAttribute(PHX_DISABLED) !== null){\n el.disabled = false\n el.removeAttribute(PHX_DISABLED)\n }\n // remove classes\n PHX_EVENT_CLASSES.forEach(className => DOM.removeClass(el, className))\n // restore disables\n let disableRestore = el.getAttribute(PHX_DISABLE_WITH_RESTORE)\n if(disableRestore !== null){\n el.innerText = disableRestore\n el.removeAttribute(PHX_DISABLE_WITH_RESTORE)\n }\n let toEl = DOM.private(el, PHX_REF)\n if(toEl){\n let hook = this.triggerBeforeUpdateHook(el, toEl)\n DOMPatch.patchEl(el, toEl, this.liveSocket.getActiveElement())\n if(hook){ hook.__updated() }\n DOM.deletePrivate(el, PHX_REF)\n }\n })\n }\n\n putRef(elements, event){\n let newRef = this.ref++\n let disableWith = this.binding(PHX_DISABLE_WITH)\n\n elements.forEach(el => {\n el.classList.add(`phx-${event}-loading`)\n el.setAttribute(PHX_REF, newRef)\n let disableText = el.getAttribute(disableWith)\n if(disableText !== null){\n if(!el.getAttribute(PHX_DISABLE_WITH_RESTORE)){\n el.setAttribute(PHX_DISABLE_WITH_RESTORE, el.innerText)\n }\n el.innerText = disableText\n }\n })\n return [newRef, elements]\n }\n\n componentID(el){\n let cid = el.getAttribute && el.getAttribute(PHX_COMPONENT)\n return cid ? parseInt(cid) : null\n }\n\n targetComponentID(target, targetCtx){\n if(target.getAttribute(this.binding(\"target\"))){\n return this.closestComponentID(targetCtx)\n } else {\n return null\n }\n }\n\n closestComponentID(targetCtx){\n if(targetCtx){\n return maybe(targetCtx.closest(`[${PHX_COMPONENT}]`), el => this.ownsElement(el) && this.componentID(el))\n } else {\n return null\n }\n }\n\n pushHookEvent(targetCtx, event, payload, onReply){\n if(!this.isConnected()){\n this.log(\"hook\", () => [\"unable to push hook event. LiveView not connected\", event, payload])\n return false\n }\n let [ref, els] = this.putRef([], \"hook\")\n this.pushWithReply(() => [ref, els], \"event\", {\n type: \"hook\",\n event: event,\n value: payload,\n cid: this.closestComponentID(targetCtx)\n }, (resp, reply) => onReply(reply, ref))\n\n return ref\n }\n\n extractMeta(el, meta){\n let prefix = this.binding(\"value-\")\n for(let i = 0; i < el.attributes.length; i++){\n let name = el.attributes[i].name\n if(name.startsWith(prefix)){ meta[name.replace(prefix, \"\")] = el.getAttribute(name) }\n }\n if(el.value !== undefined){\n meta.value = el.value\n\n if(el.tagName === \"INPUT\" && CHECKABLE_INPUTS.indexOf(el.type) >= 0 && !el.checked){\n delete meta.value\n }\n }\n return meta\n }\n\n pushEvent(type, el, targetCtx, phxEvent, meta){\n this.pushWithReply(() => this.putRef([el], type), \"event\", {\n type: type,\n event: phxEvent,\n value: this.extractMeta(el, meta),\n cid: this.targetComponentID(el, targetCtx)\n })\n }\n\n pushKey(keyElement, targetCtx, kind, phxEvent, meta){\n this.pushWithReply(() => this.putRef([keyElement], kind), \"event\", {\n type: kind,\n event: phxEvent,\n value: this.extractMeta(keyElement, meta),\n cid: this.targetComponentID(keyElement, targetCtx)\n })\n }\n\n pushFileProgress(fileEl, entryRef, progress, onReply = function (){ }){\n this.liveSocket.withinOwners(fileEl.form, (view, targetCtx) => {\n view.pushWithReply(null, \"progress\", {\n event: fileEl.getAttribute(view.binding(PHX_PROGRESS)),\n ref: fileEl.getAttribute(PHX_UPLOAD_REF),\n entry_ref: entryRef,\n progress: progress,\n cid: view.targetComponentID(fileEl.form, targetCtx)\n }, onReply)\n })\n }\n\n pushInput(inputEl, targetCtx, phxEvent, eventTarget, callback){\n let uploads\n let cid = this.targetComponentID(inputEl.form, targetCtx)\n let refGenerator = () => this.putRef([inputEl, inputEl.form], \"change\")\n let formData = serializeForm(inputEl.form, {_target: eventTarget.name})\n if(inputEl.files && inputEl.files.length > 0){\n LiveUploader.trackFiles(inputEl, Array.from(inputEl.files))\n }\n uploads = LiveUploader.serializeUploads(inputEl)\n let event = {\n type: \"form\",\n event: phxEvent,\n value: formData,\n uploads: uploads,\n cid: cid\n }\n this.pushWithReply(refGenerator, \"event\", event, resp => {\n DOM.showError(inputEl, this.liveSocket.binding(PHX_FEEDBACK_FOR))\n if(DOM.isUploadInput(inputEl) && inputEl.getAttribute(\"data-phx-auto-upload\") !== null){\n if(LiveUploader.filesAwaitingPreflight(inputEl).length > 0){\n let [ref, _els] = refGenerator()\n this.uploadFiles(inputEl.form, targetCtx, ref, cid, (_uploads) => {\n callback && callback(resp)\n this.triggerAwaitingSubmit(inputEl.form)\n })\n }\n } else {\n callback && callback(resp)\n }\n })\n }\n\n triggerAwaitingSubmit(formEl){\n let awaitingSubmit = this.getScheduledSubmit(formEl)\n if(awaitingSubmit){\n let [_el, _ref, callback] = awaitingSubmit\n this.cancelSubmit(formEl)\n callback()\n }\n }\n\n getScheduledSubmit(formEl){\n return this.formSubmits.find(([el, _callback]) => el.isSameNode(formEl))\n }\n\n scheduleSubmit(formEl, ref, callback){\n if(this.getScheduledSubmit(formEl)){ return true }\n this.formSubmits.push([formEl, ref, callback])\n }\n\n cancelSubmit(formEl){\n this.formSubmits = this.formSubmits.filter(([el, ref, _callback]) => {\n if(el.isSameNode(formEl)){\n this.undoRefs(ref)\n return false\n } else {\n return true\n }\n })\n }\n\n pushFormSubmit(formEl, targetCtx, phxEvent, onReply){\n let filterIgnored = el => {\n let userIgnored = closestPhxBinding(el, `${this.binding(PHX_UPDATE)}=ignore`, el.form)\n return !(userIgnored || closestPhxBinding(el, \"data-phx-update=ignore\", el.form))\n }\n let filterDisables = el => {\n return el.hasAttribute(this.binding(PHX_DISABLE_WITH))\n }\n let filterButton = el => el.tagName == \"BUTTON\"\n\n let filterInput = el => [\"INPUT\", \"TEXTAREA\", \"SELECT\"].includes(el.tagName)\n\n let refGenerator = () => {\n let formElements = Array.from(formEl.elements)\n let disables = formElements.filter(filterDisables)\n let buttons = formElements.filter(filterButton).filter(filterIgnored)\n let inputs = formElements.filter(filterInput).filter(filterIgnored)\n\n buttons.forEach(button => {\n button.setAttribute(PHX_DISABLED, button.disabled)\n button.disabled = true\n })\n inputs.forEach(input => {\n input.setAttribute(PHX_READONLY, input.readOnly)\n input.readOnly = true\n if(input.files){\n input.setAttribute(PHX_DISABLED, input.disabled)\n input.disabled = true\n }\n })\n formEl.setAttribute(this.binding(PHX_PAGE_LOADING), \"\")\n return this.putRef([formEl].concat(disables).concat(buttons).concat(inputs), \"submit\")\n }\n\n let cid = this.targetComponentID(formEl, targetCtx)\n if(LiveUploader.hasUploadsInProgress(formEl)){\n let [ref, _els] = refGenerator()\n return this.scheduleSubmit(formEl, ref, () => this.pushFormSubmit(formEl, targetCtx, phxEvent, onReply))\n } else if(LiveUploader.inputsAwaitingPreflight(formEl).length > 0){\n let [ref, els] = refGenerator()\n let proxyRefGen = () => [ref, els]\n this.uploadFiles(formEl, targetCtx, ref, cid, (_uploads) => {\n let formData = serializeForm(formEl, {})\n this.pushWithReply(proxyRefGen, \"event\", {\n type: \"form\",\n event: phxEvent,\n value: formData,\n cid: cid\n }, onReply)\n })\n } else {\n let formData = serializeForm(formEl)\n this.pushWithReply(refGenerator, \"event\", {\n type: \"form\",\n event: phxEvent,\n value: formData,\n cid: cid\n }, onReply)\n }\n }\n\n uploadFiles(formEl, targetCtx, ref, cid, onComplete){\n let joinCountAtUpload = this.joinCount\n let inputEls = LiveUploader.activeFileInputs(formEl)\n\n // get each file input\n inputEls.forEach(inputEl => {\n let uploader = new LiveUploader(inputEl, this, onComplete)\n this.uploaders[inputEl] = uploader\n let entries = uploader.entries().map(entry => entry.toPreflightPayload())\n\n let payload = {\n ref: inputEl.getAttribute(PHX_UPLOAD_REF),\n entries: entries,\n cid: this.targetComponentID(inputEl.form, targetCtx)\n }\n\n this.log(\"upload\", () => [\"sending preflight request\", payload])\n\n this.pushWithReply(null, \"allow_upload\", payload, resp => {\n this.log(\"upload\", () => [\"got preflight response\", resp])\n if(resp.error){\n this.undoRefs(ref)\n let [entry_ref, reason] = resp.error\n this.log(\"upload\", () => [`error for entry ${entry_ref}`, reason])\n } else {\n let onError = (callback) => {\n this.channel.onError(() => {\n if(this.joinCount === joinCountAtUpload){ callback() }\n })\n }\n uploader.initAdapterUpload(resp, onError, this.liveSocket)\n }\n })\n })\n }\n\n pushFormRecovery(form, callback){\n this.liveSocket.withinOwners(form, (view, targetCtx) => {\n let input = form.elements[0]\n let phxEvent = form.getAttribute(this.binding(PHX_AUTO_RECOVER)) || form.getAttribute(this.binding(\"change\"))\n view.pushInput(input, targetCtx, phxEvent, input, callback)\n })\n }\n\n pushLinkPatch(href, targetEl, callback){\n let linkRef = this.liveSocket.setPendingLink(href)\n let refGen = targetEl ? () => this.putRef([targetEl], \"click\") : null\n\n this.pushWithReply(refGen, \"live_patch\", {url: href}, resp => {\n if(resp.link_redirect){\n this.liveSocket.replaceMain(href, null, callback, linkRef)\n } else {\n if(this.liveSocket.commitPendingLink(linkRef)){\n this.href = href\n }\n this.applyPendingUpdates()\n callback && callback(linkRef)\n }\n }).receive(\"timeout\", () => this.liveSocket.redirect(window.location.href))\n }\n\n formsForRecovery(html){\n if(this.joinCount === 0){ return [] }\n\n let phxChange = this.binding(\"change\")\n let template = document.createElement(\"template\")\n template.innerHTML = html\n\n return (\n DOM.all(this.el, `form[${phxChange}]`)\n .filter(form => this.ownsElement(form))\n .filter(form => form.elements.length > 0)\n .filter(form => form.getAttribute(this.binding(PHX_AUTO_RECOVER)) !== \"ignore\")\n .filter(form => template.content.querySelector(`form[${phxChange}=\"${form.getAttribute(phxChange)}\"]`))\n )\n }\n\n maybePushComponentsDestroyed(destroyedCIDs){\n let willDestroyCIDs = destroyedCIDs.filter(cid => {\n return DOM.findComponentNodeList(this.el, cid).length === 0\n })\n if(willDestroyCIDs.length > 0){\n this.pruningCIDs.push(...willDestroyCIDs)\n\n this.pushWithReply(null, \"cids_will_destroy\", {cids: willDestroyCIDs}, () => {\n // The cids are either back on the page or they will be fully removed,\n // so we can remove them from the pruningCIDs.\n this.pruningCIDs = this.pruningCIDs.filter(cid => willDestroyCIDs.indexOf(cid) !== -1)\n\n // See if any of the cids we wanted to destroy were added back,\n // if they were added back, we don't actually destroy them.\n let completelyDestroyCIDs = willDestroyCIDs.filter(cid => {\n return DOM.findComponentNodeList(this.el, cid).length === 0\n })\n\n if(completelyDestroyCIDs.length > 0){\n this.pushWithReply(null, \"cids_destroyed\", {cids: completelyDestroyCIDs}, (resp) => {\n this.rendered.pruneCIDs(resp.cids)\n })\n }\n })\n }\n }\n\n ownsElement(el){\n return el.getAttribute(PHX_PARENT_ID) === this.id ||\n maybe(el.closest(PHX_VIEW_SELECTOR), node => node.id) === this.id\n }\n\n submitForm(form, targetCtx, phxEvent){\n DOM.putPrivate(form, PHX_HAS_SUBMITTED, true)\n this.liveSocket.blurActiveElement(this)\n this.pushFormSubmit(form, targetCtx, phxEvent, () => {\n this.liveSocket.restorePreviouslyActiveFocus()\n })\n }\n\n binding(kind){ return this.liveSocket.binding(kind) }\n}\n", "/** Initializes the LiveSocket\n *\n *\n * @param {string} endPoint - The string WebSocket endpoint, ie, `\"wss://example.com/live\"`,\n * `\"/live\"` (inherited host & protocol)\n * @param {Phoenix.Socket} socket - the required Phoenix Socket class imported from \"phoenix\". For example:\n *\n * import {Socket} from \"phoenix\"\n * import {LiveSocket} from \"phoenix_live_view\"\n * let liveSocket = new LiveSocket(\"/live\", Socket, {...})\n *\n * @param {Object} [opts] - Optional configuration. Outside of keys listed below, all\n * configuration is passed directly to the Phoenix Socket constructor.\n * @param {Object} [opts.defaults] - The optional defaults to use for various bindings,\n * such as `phx-debounce`. Supports the following keys:\n *\n * - debounce - the millisecond phx-debounce time. Defaults 300\n * - throttle - the millisecond phx-throttle time. Defaults 300\n *\n * @param {Function} [opts.params] - The optional function for passing connect params.\n * The function receives the element associated with a given LiveView. For example:\n *\n * (el) => {view: el.getAttribute(\"data-my-view-name\", token: window.myToken}\n *\n * @param {string} [opts.bindingPrefix] - The optional prefix to use for all phx DOM annotations.\n * Defaults to \"phx-\".\n * @param {Object} [opts.hooks] - The optional object for referencing LiveView hook callbacks.\n * @param {Object} [opts.uploaders] - The optional object for referencing LiveView uploader callbacks.\n * @param {integer} [opts.loaderTimeout] - The optional delay in milliseconds to wait before apply\n * loading states.\n * @param {Function} [opts.viewLogger] - The optional function to log debug information. For example:\n *\n * (view, kind, msg, obj) => console.log(`${view.id} ${kind}: ${msg} - `, obj)\n *\n * @param {Object} [opts.metadata] - The optional object mapping event names to functions for\n * populating event metadata. For example:\n *\n * metadata: {\n * click: (e, el) => {\n * return {\n * ctrlKey: e.ctrlKey,\n * metaKey: e.metaKey,\n * detail: e.detail || 1,\n * }\n * },\n * keydown: (e, el) => {\n * return {\n * key: e.key,\n * ctrlKey: e.ctrlKey,\n * metaKey: e.metaKey,\n * shiftKey: e.shiftKey\n * }\n * }\n * }\n * @param {Object} [opts.sessionStorage] - An optional Storage compatible object\n * Useful when LiveView won't have access to `sessionStorage`. For example, This could\n * happen if a site loads a cross-domain LiveView in an iframe. Example usage:\n *\n * class InMemoryStorage {\n * constructor() { this.storage = {} }\n * getItem(keyName) { return this.storage[keyName] }\n * removeItem(keyName) { delete this.storage[keyName] }\n * setItem(keyName, keyValue) { this.storage[keyName] = keyValue }\n * }\n *\n * @param {Object} [opts.localStorage] - An optional Storage compatible object\n * Useful for when LiveView won't have access to `localStorage`.\n * See `opts.sessionStorage` for examples.\n*/\n\nimport {\n BINDING_PREFIX,\n CONSECUTIVE_RELOADS,\n DEFAULTS,\n FAILSAFE_JITTER,\n LOADER_TIMEOUT,\n MAX_RELOADS,\n PHX_DEBOUNCE,\n PHX_DROP_TARGET,\n PHX_HAS_FOCUSED,\n PHX_KEY,\n PHX_LINK_STATE,\n PHX_LIVE_LINK,\n PHX_LV_DEBUG,\n PHX_LV_LATENCY_SIM,\n PHX_LV_PROFILE,\n PHX_MAIN,\n PHX_PARENT_ID,\n PHX_VIEW_SELECTOR,\n PHX_ROOT_ID,\n PHX_THROTTLE,\n RELOAD_JITTER\n\n} from \"./constants\"\n\nimport {\n clone,\n closestPhxBinding,\n closure,\n debug,\n maybe\n} from \"./utils\"\n\nimport Browser from \"./browser\"\nimport DOM from \"./dom\"\nimport Hooks from \"./hooks\"\nimport LiveUploader from \"./live_uploader\"\nimport View from \"./view\"\nimport {PHX_SESSION} from \"./constants\"\n\nexport default class LiveSocket {\n constructor(url, phxSocket, opts = {}){\n this.unloaded = false\n if(!phxSocket || phxSocket.constructor.name === \"Object\"){\n throw new Error(`\n a phoenix Socket must be provided as the second argument to the LiveSocket constructor. For example:\n\n import {Socket} from \"phoenix\"\n import LiveSocket from \"phoenix_live_view\"\n let liveSocket = new LiveSocket(\"/live\", Socket, {...})\n `)\n }\n this.socket = new phxSocket(url, opts)\n this.bindingPrefix = opts.bindingPrefix || BINDING_PREFIX\n this.opts = opts\n this.params = closure(opts.params || {})\n this.viewLogger = opts.viewLogger\n this.metadataCallbacks = opts.metadata || {}\n this.defaults = Object.assign(clone(DEFAULTS), opts.defaults || {})\n this.activeElement = null\n this.prevActive = null\n this.silenced = false\n this.main = null\n this.linkRef = 1\n this.roots = {}\n this.href = window.location.href\n this.pendingLink = null\n this.currentLocation = clone(window.location)\n this.hooks = opts.hooks || {}\n this.uploaders = opts.uploaders || {}\n this.loaderTimeout = opts.loaderTimeout || LOADER_TIMEOUT\n this.localStorage = opts.localStorage || window.localStorage\n this.sessionStorage = opts.sessionStorage || window.sessionStorage\n this.boundTopLevelEvents = false\n this.domCallbacks = Object.assign({onNodeAdded: closure(), onBeforeElUpdated: closure()}, opts.dom || {})\n window.addEventListener(\"pagehide\", _e => {\n this.unloaded = true\n })\n this.socket.onOpen(() => {\n if(this.isUnloaded()){\n // reload page if being restored from back/forward cache and browser does not emit \"pageshow\"\n window.location.reload()\n }\n })\n }\n\n // public\n\n isProfileEnabled(){ return this.sessionStorage.getItem(PHX_LV_PROFILE) === \"true\" }\n\n isDebugEnabled(){ return this.sessionStorage.getItem(PHX_LV_DEBUG) === \"true\" }\n\n enableDebug(){ this.sessionStorage.setItem(PHX_LV_DEBUG, \"true\") }\n\n enableProfiling(){ this.sessionStorage.setItem(PHX_LV_PROFILE, \"true\") }\n\n disableDebug(){ this.sessionStorage.removeItem(PHX_LV_DEBUG) }\n\n disableProfiling(){ this.sessionStorage.removeItem(PHX_LV_PROFILE) }\n\n enableLatencySim(upperBoundMs){\n this.enableDebug()\n console.log(\"latency simulator enabled for the duration of this browser session. Call disableLatencySim() to disable\")\n this.sessionStorage.setItem(PHX_LV_LATENCY_SIM, upperBoundMs)\n }\n\n disableLatencySim(){ this.sessionStorage.removeItem(PHX_LV_LATENCY_SIM) }\n\n getLatencySim(){\n let str = this.sessionStorage.getItem(PHX_LV_LATENCY_SIM)\n return str ? parseInt(str) : null\n }\n\n getSocket(){ return this.socket }\n\n connect(){\n let doConnect = () => {\n if(this.joinRootViews()){\n this.bindTopLevelEvents()\n this.socket.connect()\n }\n }\n if([\"complete\", \"loaded\", \"interactive\"].indexOf(document.readyState) >= 0){\n doConnect()\n } else {\n document.addEventListener(\"DOMContentLoaded\", () => doConnect())\n }\n }\n\n disconnect(callback){ this.socket.disconnect(callback) }\n\n // private\n\n triggerDOM(kind, args){ this.domCallbacks[kind](...args) }\n\n time(name, func){\n if(!this.isProfileEnabled() || !console.time){ return func() }\n console.time(name)\n let result = func()\n console.timeEnd(name)\n return result\n }\n\n log(view, kind, msgCallback){\n if(this.viewLogger){\n let [msg, obj] = msgCallback()\n this.viewLogger(view, kind, msg, obj)\n } else if(this.isDebugEnabled()){\n let [msg, obj] = msgCallback()\n debug(view, kind, msg, obj)\n }\n }\n\n onChannel(channel, event, cb){\n channel.on(event, data => {\n let latency = this.getLatencySim()\n if(!latency){\n cb(data)\n } else {\n console.log(`simulating ${latency}ms of latency from server to client`)\n setTimeout(() => cb(data), latency)\n }\n })\n }\n\n wrapPush(view, opts, push){\n let latency = this.getLatencySim()\n let oldJoinCount = view.joinCount\n if(!latency){\n if(opts.timeout){\n return push().receive(\"timeout\", () => {\n if(view.joinCount === oldJoinCount && !view.isDestroyed()){\n this.reloadWithJitter(view, () => {\n this.log(view, \"timeout\", () => [\"received timeout while communicating with server. Falling back to hard refresh for recovery\"])\n })\n }\n })\n } else {\n return push()\n }\n }\n\n console.log(`simulating ${latency}ms of latency from client to server`)\n let fakePush = {\n receives: [],\n receive(kind, cb){ this.receives.push([kind, cb]) }\n }\n setTimeout(() => {\n if(view.isDestroyed()){ return }\n fakePush.receives.reduce((acc, [kind, cb]) => acc.receive(kind, cb), push())\n }, latency)\n return fakePush\n }\n\n reloadWithJitter(view, log){\n view.destroy()\n this.disconnect()\n let [minMs, maxMs] = RELOAD_JITTER\n let afterMs = Math.floor(Math.random() * (maxMs - minMs + 1)) + minMs\n let tries = Browser.updateLocal(this.localStorage, window.location.pathname, CONSECUTIVE_RELOADS, 0, count => count + 1)\n log ? log() : this.log(view, \"join\", () => [`encountered ${tries} consecutive reloads`])\n if(tries > MAX_RELOADS){\n this.log(view, \"join\", () => [`exceeded ${MAX_RELOADS} consecutive reloads. Entering failsafe mode`])\n afterMs = FAILSAFE_JITTER\n }\n setTimeout(() => {\n if(this.hasPendingLink()){\n window.location = this.pendingLink\n } else {\n window.location.reload()\n }\n }, afterMs)\n }\n\n getHookCallbacks(name){\n return name && name.startsWith(\"Phoenix.\") ? Hooks[name.split(\".\")[1]] : this.hooks[name]\n }\n\n isUnloaded(){ return this.unloaded }\n\n isConnected(){ return this.socket.isConnected() }\n\n getBindingPrefix(){ return this.bindingPrefix }\n\n binding(kind){ return `${this.getBindingPrefix()}${kind}` }\n\n channel(topic, params){ return this.socket.channel(topic, params) }\n\n joinRootViews(){\n let rootsFound = false\n DOM.all(document, `${PHX_VIEW_SELECTOR}:not([${PHX_PARENT_ID}])`, rootEl => {\n if(!this.getRootById(rootEl.id)){\n let view = this.newRootView(rootEl)\n view.setHref(this.getHref())\n view.join()\n if(rootEl.getAttribute(PHX_MAIN)){ this.main = view }\n }\n rootsFound = true\n })\n return rootsFound\n }\n\n redirect(to, flash){\n this.disconnect()\n Browser.redirect(to, flash)\n }\n\n replaceMain(href, flash, callback = null, linkRef = this.setPendingLink(href)){\n let oldMainEl = this.main.el\n let newMainEl = DOM.cloneNode(oldMainEl)\n this.main.showLoader(this.loaderTimeout)\n this.main.destroy()\n oldMainEl.replaceWith(newMainEl)\n\n this.main = this.newRootView(newMainEl, flash)\n this.main.setRedirect(href)\n this.main.join(joinCount => {\n if(joinCount === 1 && this.commitPendingLink(linkRef)){\n callback && callback()\n }\n })\n }\n\n isPhxView(el){ return el.getAttribute && el.getAttribute(PHX_SESSION) !== null }\n\n newRootView(el, flash){\n let view = new View(el, this, null, flash)\n this.roots[view.id] = view\n return view\n }\n\n owner(childEl, callback){\n let view = maybe(childEl.closest(PHX_VIEW_SELECTOR), el => this.getViewByEl(el))\n if(view){ callback(view) }\n }\n\n withinOwners(childEl, callback){\n this.owner(childEl, view => {\n let phxTarget = childEl.getAttribute(this.binding(\"target\"))\n if(phxTarget === null){\n callback(view, childEl)\n } else {\n view.withinTargets(phxTarget, callback)\n }\n })\n }\n\n getViewByEl(el){\n let rootId = el.getAttribute(PHX_ROOT_ID)\n return maybe(this.getRootById(rootId), root => root.getDescendentByEl(el))\n }\n\n getRootById(id){ return this.roots[id] }\n\n destroyAllViews(){\n for(let id in this.roots){\n this.roots[id].destroy()\n delete this.roots[id]\n }\n }\n\n destroyViewByEl(el){\n let root = this.getRootById(el.getAttribute(PHX_ROOT_ID))\n if(root){ root.destroyDescendent(el.id) }\n }\n\n setActiveElement(target){\n if(this.activeElement === target){ return }\n this.activeElement = target\n let cancel = () => {\n if(target === this.activeElement){ this.activeElement = null }\n target.removeEventListener(\"mouseup\", this)\n target.removeEventListener(\"touchend\", this)\n }\n target.addEventListener(\"mouseup\", cancel)\n target.addEventListener(\"touchend\", cancel)\n }\n\n getActiveElement(){\n if(document.activeElement === document.body){\n return this.activeElement || document.activeElement\n } else {\n // document.activeElement can be null in Internet Explorer 11\n return document.activeElement || document.body\n }\n }\n\n dropActiveElement(view){\n if(this.prevActive && view.ownsElement(this.prevActive)){\n this.prevActive = null\n }\n }\n\n restorePreviouslyActiveFocus(){\n if(this.prevActive && this.prevActive !== document.body){\n this.prevActive.focus()\n }\n }\n\n blurActiveElement(){\n this.prevActive = this.getActiveElement()\n if(this.prevActive !== document.body){ this.prevActive.blur() }\n }\n\n bindTopLevelEvents(){\n if(this.boundTopLevelEvents){ return }\n\n this.boundTopLevelEvents = true\n document.body.addEventListener(\"click\", function (){ }) // ensure all click events bubble for mobile Safari\n window.addEventListener(\"pageshow\", e => {\n if(e.persisted){ // reload page if being restored from back/forward cache\n this.getSocket().disconnect()\n this.withPageLoading({to: window.location.href, kind: \"redirect\"})\n window.location.reload()\n }\n }, true)\n this.bindNav()\n this.bindClicks()\n this.bindForms()\n this.bind({keyup: \"keyup\", keydown: \"keydown\"}, (e, type, view, target, targetCtx, phxEvent, _phxTarget) => {\n let matchKey = target.getAttribute(this.binding(PHX_KEY))\n let pressedKey = e.key && e.key.toLowerCase() // chrome clicked autocompletes send a keydown without key\n if(matchKey && matchKey.toLowerCase() !== pressedKey){ return }\n\n view.pushKey(target, targetCtx, type, phxEvent, {key: e.key, ...this.eventMeta(type, e, target)})\n })\n this.bind({blur: \"focusout\", focus: \"focusin\"}, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {\n if(!phxTarget){\n view.pushEvent(type, targetEl, targetCtx, phxEvent, this.eventMeta(type, e, targetEl))\n }\n })\n this.bind({blur: \"blur\", focus: \"focus\"}, (e, type, view, targetEl, targetCtx, phxEvent, phxTarget) => {\n // blur and focus are triggered on document and window. Discard one to avoid dups\n if(phxTarget && !phxTarget !== \"window\"){\n view.pushEvent(type, targetEl, targetCtx, phxEvent, this.eventMeta(type, e, targetEl))\n }\n })\n window.addEventListener(\"dragover\", e => e.preventDefault())\n window.addEventListener(\"drop\", e => {\n e.preventDefault()\n let dropTargetId = maybe(closestPhxBinding(e.target, this.binding(PHX_DROP_TARGET)), trueTarget => {\n return trueTarget.getAttribute(this.binding(PHX_DROP_TARGET))\n })\n let dropTarget = dropTargetId && document.getElementById(dropTargetId)\n let files = Array.from(e.dataTransfer.files || [])\n if(!dropTarget || dropTarget.disabled || files.length === 0 || !(dropTarget.files instanceof FileList)){ return }\n\n LiveUploader.trackFiles(dropTarget, files)\n dropTarget.dispatchEvent(new Event(\"input\", {bubbles: true}))\n })\n }\n\n eventMeta(eventName, e, targetEl){\n let callback = this.metadataCallbacks[eventName]\n return callback ? callback(e, targetEl) : {}\n }\n\n setPendingLink(href){\n this.linkRef++\n this.pendingLink = href\n return this.linkRef\n }\n\n commitPendingLink(linkRef){\n if(this.linkRef !== linkRef){\n return false\n } else {\n this.href = this.pendingLink\n this.pendingLink = null\n return true\n }\n }\n\n getHref(){ return this.href }\n\n hasPendingLink(){ return !!this.pendingLink }\n\n bind(events, callback){\n for(let event in events){\n let browserEventName = events[event]\n\n this.on(browserEventName, e => {\n let binding = this.binding(event)\n let windowBinding = this.binding(`window-${event}`)\n let targetPhxEvent = e.target.getAttribute && e.target.getAttribute(binding)\n if(targetPhxEvent){\n this.debounce(e.target, e, () => {\n this.withinOwners(e.target, (view, targetCtx) => {\n callback(e, event, view, e.target, targetCtx, targetPhxEvent, null)\n })\n })\n } else {\n DOM.all(document, `[${windowBinding}]`, el => {\n let phxEvent = el.getAttribute(windowBinding)\n this.debounce(el, e, () => {\n this.withinOwners(el, (view, targetCtx) => {\n callback(e, event, view, el, targetCtx, phxEvent, \"window\")\n })\n })\n })\n }\n })\n }\n }\n\n bindClicks(){\n this.bindClick(\"click\", \"click\", false)\n this.bindClick(\"mousedown\", \"capture-click\", true)\n }\n\n bindClick(eventName, bindingName, capture){\n let click = this.binding(bindingName)\n window.addEventListener(eventName, e => {\n if(!this.isConnected()){ return }\n let target = null\n if(capture){\n target = e.target.matches(`[${click}]`) ? e.target : e.target.querySelector(`[${click}]`)\n } else {\n target = closestPhxBinding(e.target, click)\n }\n let phxEvent = target && target.getAttribute(click)\n if(!phxEvent){ return }\n if(target.getAttribute(\"href\") === \"#\"){ e.preventDefault() }\n\n this.debounce(target, e, () => {\n this.withinOwners(target, (view, targetCtx) => {\n view.pushEvent(\"click\", target, targetCtx, phxEvent, this.eventMeta(\"click\", e, target))\n })\n })\n }, capture)\n }\n\n bindNav(){\n if(!Browser.canPushState()){ return }\n if(history.scrollRestoration){ history.scrollRestoration = \"manual\" }\n let scrollTimer = null\n window.addEventListener(\"scroll\", _e => {\n clearTimeout(scrollTimer)\n scrollTimer = setTimeout(() => {\n Browser.updateCurrentState(state => Object.assign(state, {scroll: window.scrollY}))\n }, 100)\n })\n window.addEventListener(\"popstate\", event => {\n if(!this.registerNewLocation(window.location)){ return }\n let {type, id, root, scroll} = event.state || {}\n let href = window.location.href\n\n if(this.main.isConnected() && (type === \"patch\" && id === this.main.id)){\n this.main.pushLinkPatch(href, null)\n } else {\n this.replaceMain(href, null, () => {\n if(root){ this.replaceRootHistory() }\n if(typeof (scroll) === \"number\"){\n setTimeout(() => {\n window.scrollTo(0, scroll)\n }, 0) // the body needs to render before we scroll.\n }\n })\n }\n }, false)\n window.addEventListener(\"click\", e => {\n let target = closestPhxBinding(e.target, PHX_LIVE_LINK)\n let type = target && target.getAttribute(PHX_LIVE_LINK)\n let wantsNewTab = e.metaKey || e.ctrlKey || e.button === 1\n if(!type || !this.isConnected() || !this.main || wantsNewTab){ return }\n let href = target.href\n let linkState = target.getAttribute(PHX_LINK_STATE)\n e.preventDefault()\n if(this.pendingLink === href){ return }\n\n if(type === \"patch\"){\n this.pushHistoryPatch(href, linkState, target)\n } else if(type === \"redirect\"){\n this.historyRedirect(href, linkState)\n } else {\n throw new Error(`expected ${PHX_LIVE_LINK} to be \"patch\" or \"redirect\", got: ${type}`)\n }\n }, false)\n }\n\n withPageLoading(info, callback){\n DOM.dispatchEvent(window, \"phx:page-loading-start\", info)\n let done = () => DOM.dispatchEvent(window, \"phx:page-loading-stop\", info)\n return callback ? callback(done) : done\n }\n\n pushHistoryPatch(href, linkState, targetEl){\n this.withPageLoading({to: href, kind: \"patch\"}, done => {\n this.main.pushLinkPatch(href, targetEl, linkRef => {\n this.historyPatch(href, linkState, linkRef)\n done()\n })\n })\n }\n\n historyPatch(href, linkState, linkRef = this.setPendingLink(href)){\n if(!this.commitPendingLink(linkRef)){ return }\n\n Browser.pushState(linkState, {type: \"patch\", id: this.main.id}, href)\n this.registerNewLocation(window.location)\n }\n\n historyRedirect(href, linkState, flash){\n let scroll = window.scrollY\n this.withPageLoading({to: href, kind: \"redirect\"}, done => {\n this.replaceMain(href, flash, () => {\n Browser.pushState(linkState, {type: \"redirect\", id: this.main.id, scroll: scroll}, href)\n this.registerNewLocation(window.location)\n done()\n })\n })\n }\n\n replaceRootHistory(){\n Browser.pushState(\"replace\", {root: true, type: \"patch\", id: this.main.id})\n }\n\n registerNewLocation(newLocation){\n let {pathname, search} = this.currentLocation\n if(pathname + search === newLocation.pathname + newLocation.search){\n return false\n } else {\n this.currentLocation = clone(newLocation)\n return true\n }\n }\n\n bindForms(){\n let iterations = 0\n this.on(\"submit\", e => {\n let phxEvent = e.target.getAttribute(this.binding(\"submit\"))\n if(!phxEvent){ return }\n e.preventDefault()\n e.target.disabled = true\n this.withinOwners(e.target, (view, targetCtx) => view.submitForm(e.target, targetCtx, phxEvent))\n }, false)\n\n for(let type of [\"change\", \"input\"]){\n this.on(type, e => {\n let input = e.target\n let phxEvent = input.form && input.form.getAttribute(this.binding(\"change\"))\n if(!phxEvent){ return }\n if(input.type === \"number\" && input.validity && input.validity.badInput){ return }\n let currentIterations = iterations\n iterations++\n let {at: at, type: lastType} = DOM.private(input, \"prev-iteration\") || {}\n // detect dup because some browsers dispatch both \"input\" and \"change\"\n if(at === currentIterations - 1 && type !== lastType){ return }\n\n DOM.putPrivate(input, \"prev-iteration\", {at: currentIterations, type: type})\n\n this.debounce(input, e, () => {\n this.withinOwners(input.form, (view, targetCtx) => {\n DOM.putPrivate(input, PHX_HAS_FOCUSED, true)\n if(!DOM.isTextualInput(input)){\n this.setActiveElement(input)\n }\n view.pushInput(input, targetCtx, phxEvent, e.target)\n })\n })\n }, false)\n }\n }\n\n debounce(el, event, callback){\n let phxDebounce = this.binding(PHX_DEBOUNCE)\n let phxThrottle = this.binding(PHX_THROTTLE)\n let defaultDebounce = this.defaults.debounce.toString()\n let defaultThrottle = this.defaults.throttle.toString()\n DOM.debounce(el, event, phxDebounce, defaultDebounce, phxThrottle, defaultThrottle, callback)\n }\n\n silenceEvents(callback){\n this.silenced = true\n callback()\n this.silenced = false\n }\n\n on(event, callback){\n window.addEventListener(event, e => {\n if(!this.silenced){ callback(e) }\n })\n }\n}\n"],
- "mappings": "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",
- "names": []
-}
diff --git a/priv/static/phoenix_live_view.min.js b/priv/static/phoenix_live_view.min.js
index 674e7f5672..28830c00b6 100644
--- a/priv/static/phoenix_live_view.min.js
+++ b/priv/static/phoenix_live_view.min.js
@@ -1,17 +1,19 @@
-var LiveView=(()=>{var Ke=Object.defineProperty;var ct=Object.getOwnPropertySymbols;var Vt=Object.prototype.hasOwnProperty,jt=Object.prototype.propertyIsEnumerable;var ft=(n,e,t)=>e in n?Ke(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t,oe=(n,e)=>{for(var t in e||(e={}))Vt.call(e,t)&&ft(n,t,e[t]);if(ct)for(var t of ct(e))jt.call(e,t)&&ft(n,t,e[t]);return n};var Jt=n=>Ke(n,"__esModule",{value:!0});var Kt=(n,e)=>{Jt(n);for(var t in e)Ke(n,t,{get:e[t],enumerable:!0})};var di={};Kt(di,{LiveSocket:()=>Be});var Se="consecutive-reloads",We=10,pt=[1e3,3e3],gt=3e4,ye=["phx-click-loading","phx-change-loading","phx-submit-loading","phx-keydown-loading","phx-keyup-loading","phx-blur-loading","phx-focus-loading"],C="data-phx-component",Te="data-phx-link",mt="track-static",vt="data-phx-link-state",L="data-phx-ref",Ce="track-uploads",R="data-phx-upload-ref",te="data-phx-preflighted-refs",bt="data-phx-done-refs",Ge="drop-target",le="data-phx-active-refs",ae="phx:live-file:updated",he="data-phx-skip",ze="data-phx-remove",qe="page-loading",Ye="phx-connected",de="phx-disconnected",Qe="phx-no-feedback",Ze="phx-error",V="data-phx-parent-id",we="data-phx-main",W="data-phx-root-id",_t="trigger-action",Ie="feedback-for",Le="phx-has-focused",Et=["text","textarea","number","email","password","search","tel","url","date","time"],ke=["checkbox","radio"],xe="phx-has-submitted",k="data-phx-session",N=`[${k}]`,j="data-phx-static",De="data-phx-readonly",ue="data-phx-disabled",ce="disable-with",fe="data-phx-disable-with-restore",pe="hook",At="debounce",Pt="throttle",ge="update",St="key",H="phxPrivate",et="auto-recover",Re="phx:live-socket:debug",He="phx:live-socket:profiling",Oe="phx:live-socket:latency-sim",yt="progress",Tt=1,Ct=200,wt="phx-",It=3e4;var ie="debounce-trigger",me="throttled",tt="debounce-prev-key",Lt={debounce:300,throttle:300},it="d",O="s",w="c",rt="e",nt="r",st="t";var Ne=class{constructor(e,t,i){this.liveSocket=i,this.entry=e,this.offset=0,this.chunkSize=t,this.chunkTimer=null,this.uploadChannel=i.channel(`lvu:${e.ref}`,{token:e.metadata()})}error(e){clearTimeout(this.chunkTimer),this.uploadChannel.leave(),this.entry.error(e)}upload(){this.uploadChannel.onError(e=>this.error(e)),this.uploadChannel.join().receive("ok",e=>this.readNextChunk()).receive("error",e=>this.error(e))}isDone(){return this.offset>=this.entry.file.size}readNextChunk(){let e=new window.FileReader,t=this.entry.file.slice(this.offset,this.chunkSize+this.offset);e.onload=i=>{if(i.target.error===null)this.offset+=i.target.result.byteLength,this.pushChunk(i.target.result);else return y("Read error: "+i.target.error)},e.readAsArrayBuffer(t)}pushChunk(e){!this.uploadChannel.isJoined()||this.uploadChannel.push("chunk",e).receive("ok",()=>{this.entry.progress(this.offset/this.entry.file.size*100),this.isDone()||(this.chunkTimer=setTimeout(()=>this.readNextChunk(),this.liveSocket.getLatencySim()||0))})}};var y=(n,e)=>console.error&&console.error(n,e),G=n=>typeof n=="number";function kt(){let n=new Set,e=document.querySelectorAll("*[id]");for(let t=0,i=e.length;t{n.liveSocket.isDebugEnabled()&&console.log(`${n.id} ${e}: ${t} - `,i)},Ue=n=>typeof n=="function"?n:function(){return n},J=n=>JSON.parse(JSON.stringify(n)),z=(n,e,t)=>{do{if(n.matches(`[${e}]`))return n;n=n.parentElement||n.parentNode}while(n!==null&&n.nodeType===1&&!(t&&t.isSameNode(n)||n.matches(N)));return null},re=n=>n!==null&&typeof n=="object"&&!(n instanceof Array),Dt=(n,e)=>JSON.stringify(n)===JSON.stringify(e),ot=n=>{for(let e in n)return!1;return!0},x=(n,e)=>n&&e(n),Rt=function(n,e,t,i){n.forEach(r=>{new Ne(r,t.config.chunk_size,i).upload()})};var Ht={canPushState(){return typeof history.pushState!="undefined"},dropLocal(n,e,t){return n.removeItem(this.localKey(e,t))},updateLocal(n,e,t,i,r){let s=this.getLocal(n,e,t),o=this.localKey(e,t),l=s===null?i:r(s);return n.setItem(o,JSON.stringify(l)),l},getLocal(n,e,t){return JSON.parse(n.getItem(this.localKey(e,t)))},updateCurrentState(n){!this.canPushState()||history.replaceState(n(history.state||{}),"",window.location.href)},pushState(n,e,t){if(this.canPushState()){if(t!==window.location.href){if(e.type=="redirect"&&e.scroll){let r=history.state||{};r.scroll=e.scroll,history.replaceState(r,"",window.location.href)}delete e.scroll,history[n+"State"](e,"",t||null);let i=this.getHashTargetEl(window.location.hash);i?i.scrollIntoView():e.type==="redirect"&&window.scroll(0,0)}}else this.redirect(t)},setCookie(n,e){document.cookie=`${n}=${e}`},getCookie(n){return document.cookie.replace(new RegExp(`(?:(?:^|.*;s*)${n}s*=s*([^;]*).*$)|^.*$`),"$1")},redirect(n,e){e&&Ht.setCookie("__phoenix_flash__",e+"; max-age=60000; path=/"),window.location=n},localKey(n,e){return`${n}-${e}`},getHashTargetEl(n){let e=n.toString().substring(1);if(e!=="")return document.getElementById(e)||document.querySelector(`a[name="${e}"]`)}},U=Ht;var X={byId(n){return document.getElementById(n)||y(`no id found for ${n}`)},removeClass(n,e){n.classList.remove(e),n.classList.length===0&&n.removeAttribute("class")},all(n,e,t){if(!n)return[];let i=Array.from(n.querySelectorAll(e));return t?i.forEach(t):i},childNodeLength(n){let e=document.createElement("template");return e.innerHTML=n,e.content.childElementCount},isUploadInput(n){return n.type==="file"&&n.getAttribute(R)!==null},findUploadInputs(n){return this.all(n,`input[type="file"][${R}]`)},findComponentNodeList(n,e){return this.filterWithinSameLiveView(this.all(n,`[${C}="${e}"]`),n)},isPhxDestroyed(n){return!!(n.id&&X.private(n,"destroyed"))},markPhxChildDestroyed(n){n.setAttribute(k,""),this.putPrivate(n,"destroyed",!0)},findPhxChildrenInFragment(n,e){let t=document.createElement("template");return t.innerHTML=n,this.findPhxChildren(t.content,e)},isIgnored(n,e){return(n.getAttribute(e)||n.getAttribute("data-phx-update"))==="ignore"},isPhxUpdate(n,e,t){return n.getAttribute&&t.indexOf(n.getAttribute(e))>=0},findPhxChildren(n,e){return this.all(n,`${N}[${V}="${e}"]`)},findParentCIDs(n,e){let t=new Set(e);return e.reduce((i,r)=>{let s=`[${C}="${r}"] [${C}]`;return this.filterWithinSameLiveView(this.all(n,s),n).map(o=>parseInt(o.getAttribute(C))).forEach(o=>i.delete(o)),i},t)},filterWithinSameLiveView(n,e){return e.querySelector(N)?n.filter(t=>this.withinSameLiveView(t,e)):n},withinSameLiveView(n,e){for(;n=n.parentNode;){if(n.isSameNode(e))return!0;if(n.getAttribute(k)!==null)return!1}},private(n,e){return n[H]&&n[H][e]},deletePrivate(n,e){n[H]&&delete n[H][e]},putPrivate(n,e,t){n[H]||(n[H]={}),n[H][e]=t},copyPrivates(n,e){e[H]&&(n[H]=J(e[H]))},putTitle(n){let e=document.querySelector("title"),{prefix:t,suffix:i}=e.dataset;document.title=`${t||""}${n}${i||""}`},debounce(n,e,t,i,r,s,o){let l=n.getAttribute(t),h=n.getAttribute(r);l===""&&(l=i),h===""&&(h=s);let u=l||h;switch(u){case null:return o();case"blur":this.once(n,"debounce-blur")&&n.addEventListener("blur",()=>o());return;default:let m=parseInt(u),c=()=>h?this.deletePrivate(n,me):o(),g=this.incCycle(n,ie,c);if(isNaN(m))return y(`invalid throttle/debounce value: ${u}`);if(h){let P=!1;if(e.type==="keydown"){let b=this.private(n,tt);this.putPrivate(n,tt,e.key),P=b!==e.key}if(!P&&this.private(n,me))return!1;o(),this.putPrivate(n,me,!0),setTimeout(()=>this.triggerCycle(n,ie),m)}else setTimeout(()=>this.triggerCycle(n,ie,g),m);let E=n.form;E&&this.once(E,"bind-debounce")&&E.addEventListener("submit",()=>{Array.from(new FormData(E).entries(),([P])=>{let b=E.querySelector(`[name="${P}"]`);this.incCycle(b,ie),this.deletePrivate(b,me)})}),this.once(n,"bind-debounce")&&n.addEventListener("blur",()=>this.triggerCycle(n,ie))}},triggerCycle(n,e,t){let[i,r]=this.private(n,e);t||(t=i),t===i&&(this.incCycle(n,e),r())},once(n,e){return this.private(n,e)===!0?!1:(this.putPrivate(n,e,!0),!0)},incCycle(n,e,t=function(){}){let[i]=this.private(n,e)||[0,t];return i++,this.putPrivate(n,e,[i,t]),i},discardError(n,e,t){let i=e.getAttribute&&e.getAttribute(t),r=i&&n.querySelector(`[id="${i}"], [name="${i}"]`);!r||this.private(r,Le)||this.private(r.form,xe)||e.classList.add(Qe)},showError(n,e){(n.id||n.name)&&this.all(n.form,`[${e}="${n.id}"], [${e}="${n.name}"]`,t=>{this.removeClass(t,Qe)})},isPhxChild(n){return n.getAttribute&&n.getAttribute(V)},dispatchEvent(n,e,t={}){let i=new CustomEvent(e,{bubbles:!0,cancelable:!0,detail:t});n.dispatchEvent(i)},cloneNode(n,e){if(typeof e=="undefined")return n.cloneNode(!0);{let t=n.cloneNode(!1);return t.innerHTML=e,t}},mergeAttrs(n,e,t={}){let i=t.exclude||[],r=t.isIgnored,s=e.attributes;for(let l=s.length-1;l>=0;l--){let h=s[l].name;i.indexOf(h)<0&&n.setAttribute(h,e.getAttribute(h))}let o=n.attributes;for(let l=o.length-1;l>=0;l--){let h=o[l].name;r?h.startsWith("data-")&&!e.hasAttribute(h)&&n.removeAttribute(h):e.hasAttribute(h)||n.removeAttribute(h)}},mergeFocusedInput(n,e){n instanceof HTMLSelectElement||X.mergeAttrs(n,e,{except:["value"]}),e.readOnly?n.setAttribute("readonly",!0):n.removeAttribute("readonly")},hasSelectionRange(n){return n.setSelectionRange&&(n.type==="text"||n.type==="textarea")},restoreFocus(n,e,t){if(!X.isTextualInput(n))return;let i=n.matches(":focus");n.readOnly&&n.blur(),i||n.focus(),this.hasSelectionRange(n)&&n.setSelectionRange(e,t)},isFormInput(n){return/^(?:input|select|textarea)$/i.test(n.tagName)&&n.type!=="button"},syncAttrsToProps(n){n instanceof HTMLInputElement&&ke.indexOf(n.type.toLocaleLowerCase())>=0&&(n.checked=n.getAttribute("checked")!==null)},syncPropsToAttrs(n){if(n instanceof HTMLSelectElement){let e=n.options.item(n.selectedIndex);e&&e.getAttribute("selected")===null&&e.setAttribute("selected","")}},isTextualInput(n){return Et.indexOf(n.type)>=0},isNowTriggerFormExternal(n,e){return n.getAttribute&&n.getAttribute(e)!==null},syncPendingRef(n,e,t){let i=n.getAttribute(L);return i===null?!0:X.isFormInput(n)||n.getAttribute(t)!==null?(X.isUploadInput(n)&&X.mergeAttrs(n,e,{isIgnored:!0}),X.putPrivate(n,L,e),!1):(ye.forEach(r=>{n.classList.contains(r)&&e.classList.add(r)}),e.setAttribute(L,i),!0)},cleanChildNodes(n,e){if(X.isPhxUpdate(n,e,["append","prepend"])){let t=[];n.childNodes.forEach(i=>{i.id||(i.nodeType===Node.TEXT_NODE&&i.nodeValue.trim()===""||y(`only HTML element tags with an id are allowed inside containers with phx-update.
+var LiveView=(()=>{var it=Object.defineProperty;var At=Object.getOwnPropertySymbols;var si=Object.prototype.hasOwnProperty,ni=Object.prototype.propertyIsEnumerable;var St=(r,e,t)=>e in r?it(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t,oe=(r,e)=>{for(var t in e||(e={}))si.call(e,t)&&St(r,t,e[t]);if(At)for(var t of At(e))ni.call(e,t)&&St(r,t,e[t]);return r};var oi=r=>it(r,"__esModule",{value:!0});var ai=(r,e)=>{oi(r);for(var t in e)it(r,t,{get:e[t],enumerable:!0})};var xi={};ai(xi,{LiveSocket:()=>Ye});var Oe="consecutive-reloads",yt=10,wt=5e3,Pt=1e4,xt=3e4,He=["phx-click-loading","phx-change-loading","phx-submit-loading","phx-keydown-loading","phx-keyup-loading","phx-blur-loading","phx-focus-loading"],C="data-phx-component",Ne="data-phx-link",Ct="track-static",Tt="data-phx-link-state",O="data-phx-ref",j="data-phx-ref-src",Fe="track-uploads",M="data-phx-upload-ref",ae="data-phx-preflighted-refs",kt="data-phx-done-refs",rt="drop-target",Ee="data-phx-active-refs",le="phx:live-file:updated",_e="data-phx-skip",st="data-phx-prune",nt="page-loading",ot="phx-connected",Me="phx-loading",Ue="phx-no-feedback",at="phx-error",q="data-phx-parent-id",he="data-phx-main",W="data-phx-root-id",It="trigger-action",te="feedback-for",Ae="phx-has-focused",Rt=["text","textarea","number","email","password","search","tel","url","date","time","datetime-local","color","range"],Xe=["checkbox","radio"],de="phx-has-submitted",H="data-phx-session",J=`[${H}]`,lt="data-phx-sticky",Y="data-phx-static",$e="data-phx-readonly",Se="data-phx-disabled",ye="disable-with",we="data-phx-disable-with-restore",ue="hook",Dt="debounce",Lt="throttle",ce="update",fe="stream",Ot="key",U="phxPrivate",ht="auto-recover",Pe="phx:live-socket:debug",Be="phx:live-socket:profiling",Ve="phx:live-socket:latency-sim",Ht="progress",dt="mounted",Nt=1,Ft=200,Mt="phx-",Ut=3e4;var pe="debounce-trigger",xe="throttled",ut="debounce-prev-key",Xt={debounce:300,throttle:300},Ce="d",X="s",I="c",ct="e",ft="r",pt="t",$t="p",Bt="stream";var Je=class{constructor(e,t,i){this.liveSocket=i,this.entry=e,this.offset=0,this.chunkSize=t,this.chunkTimer=null,this.uploadChannel=i.channel(`lvu:${e.ref}`,{token:e.metadata()})}error(e){clearTimeout(this.chunkTimer),this.uploadChannel.leave(),this.entry.error(e)}upload(){this.uploadChannel.onError(e=>this.error(e)),this.uploadChannel.join().receive("ok",e=>this.readNextChunk()).receive("error",e=>this.error(e))}isDone(){return this.offset>=this.entry.file.size}readNextChunk(){let e=new window.FileReader,t=this.entry.file.slice(this.offset,this.chunkSize+this.offset);e.onload=i=>{if(i.target.error===null)this.offset+=i.target.result.byteLength,this.pushChunk(i.target.result);else return P("Read error: "+i.target.error)},e.readAsArrayBuffer(t)}pushChunk(e){!this.uploadChannel.isJoined()||this.uploadChannel.push("chunk",e).receive("ok",()=>{this.entry.progress(this.offset/this.entry.file.size*100),this.isDone()||(this.chunkTimer=setTimeout(()=>this.readNextChunk(),this.liveSocket.getLatencySim()||0))})}};var P=(r,e)=>console.error&&console.error(r,e),$=r=>{let e=typeof r;return e==="number"||e==="string"&&/^(0|[1-9]\d*)$/.test(r)};function Vt(){let r=new Set,e=document.querySelectorAll("*[id]");for(let t=0,i=e.length;t{r.liveSocket.isDebugEnabled()&&console.log(`${r.id} ${e}: ${t} - `,i)},je=r=>typeof r=="function"?r:function(){return r},me=r=>JSON.parse(JSON.stringify(r)),ie=(r,e,t)=>{do{if(r.matches(`[${e}]`)&&!r.disabled)return r;r=r.parentElement||r.parentNode}while(r!==null&&r.nodeType===1&&!(t&&t.isSameNode(r)||r.matches(J)));return null},ge=r=>r!==null&&typeof r=="object"&&!(r instanceof Array),jt=(r,e)=>JSON.stringify(r)===JSON.stringify(e),mt=r=>{for(let e in r)return!1;return!0},B=(r,e)=>r&&e(r),qt=function(r,e,t,i){r.forEach(s=>{new Je(s,t.config.chunk_size,i).upload()})};var Wt={canPushState(){return typeof history.pushState!="undefined"},dropLocal(r,e,t){return r.removeItem(this.localKey(e,t))},updateLocal(r,e,t,i,s){let n=this.getLocal(r,e,t),o=this.localKey(e,t),a=n===null?i:s(n);return r.setItem(o,JSON.stringify(a)),a},getLocal(r,e,t){return JSON.parse(r.getItem(this.localKey(e,t)))},updateCurrentState(r){!this.canPushState()||history.replaceState(r(history.state||{}),"",window.location.href)},pushState(r,e,t){if(this.canPushState()){if(t!==window.location.href){if(e.type=="redirect"&&e.scroll){let s=history.state||{};s.scroll=e.scroll,history.replaceState(s,"",window.location.href)}delete e.scroll,history[r+"State"](e,"",t||null);let i=this.getHashTargetEl(window.location.hash);i?i.scrollIntoView():e.type==="redirect"&&window.scroll(0,0)}}else this.redirect(t)},setCookie(r,e){document.cookie=`${r}=${e}`},getCookie(r){return document.cookie.replace(new RegExp(`(?:(?:^|.*;s*)${r}s*=s*([^;]*).*$)|^.*$`),"$1")},redirect(r,e){e&&Wt.setCookie("__phoenix_flash__",e+"; max-age=60000; path=/"),window.location=r},localKey(r,e){return`${r}-${e}`},getHashTargetEl(r){let e=r.toString().substring(1);if(e!=="")return document.getElementById(e)||document.querySelector(`a[name="${e}"]`)}},N=Wt;var V={byId(r){return document.getElementById(r)||P(`no id found for ${r}`)},removeClass(r,e){r.classList.remove(e),r.classList.length===0&&r.removeAttribute("class")},all(r,e,t){if(!r)return[];let i=Array.from(r.querySelectorAll(e));return t?i.forEach(t):i},childNodeLength(r){let e=document.createElement("template");return e.innerHTML=r,e.content.childElementCount},isUploadInput(r){return r.type==="file"&&r.getAttribute(M)!==null},findUploadInputs(r){return this.all(r,`input[type="file"][${M}]`)},findComponentNodeList(r,e){return this.filterWithinSameLiveView(this.all(r,`[${C}="${e}"]`),r)},isPhxDestroyed(r){return!!(r.id&&V.private(r,"destroyed"))},wantsNewTab(r){return r.ctrlKey||r.shiftKey||r.metaKey||r.button&&r.button===1||r.target.getAttribute("target")==="_blank"},isUnloadableFormSubmit(r){return!r.defaultPrevented&&!this.wantsNewTab(r)},isNewPageHref(r,e){let t;try{t=new URL(r)}catch(i){try{t=new URL(r,e)}catch(s){return!0}}return t.host===e.host&&t.protocol===e.protocol&&t.pathname===e.pathname&&t.search===e.search?t.hash===""&&!t.href.endsWith("#"):!0},markPhxChildDestroyed(r){this.isPhxChild(r)&&r.setAttribute(H,""),this.putPrivate(r,"destroyed",!0)},findPhxChildrenInFragment(r,e){let t=document.createElement("template");return t.innerHTML=r,this.findPhxChildren(t.content,e)},isIgnored(r,e){return(r.getAttribute(e)||r.getAttribute("data-phx-update"))==="ignore"},isPhxUpdate(r,e,t){return r.getAttribute&&t.indexOf(r.getAttribute(e))>=0},findPhxSticky(r){return this.all(r,`[${lt}]`)},findPhxChildren(r,e){return this.all(r,`${J}[${q}="${e}"]`)},findParentCIDs(r,e){let t=new Set(e),i=e.reduce((s,n)=>{let o=`[${C}="${n}"] [${C}]`;return this.filterWithinSameLiveView(this.all(r,o),r).map(a=>parseInt(a.getAttribute(C))).forEach(a=>s.delete(a)),s},t);return i.size===0?new Set(e):i},filterWithinSameLiveView(r,e){return e.querySelector(J)?r.filter(t=>this.withinSameLiveView(t,e)):r},withinSameLiveView(r,e){for(;r=r.parentNode;){if(r.isSameNode(e))return!0;if(r.getAttribute(H)!==null)return!1}},private(r,e){return r[U]&&r[U][e]},deletePrivate(r,e){r[U]&&delete r[U][e]},putPrivate(r,e,t){r[U]||(r[U]={}),r[U][e]=t},updatePrivate(r,e,t,i){let s=this.private(r,e);s===void 0?this.putPrivate(r,e,i(t)):this.putPrivate(r,e,i(s))},copyPrivates(r,e){e[U]&&(r[U]=e[U])},putTitle(r){let e=document.querySelector("title");if(e){let{prefix:t,suffix:i}=e.dataset;document.title=`${t||""}${r}${i||""}`}else document.title=r},debounce(r,e,t,i,s,n,o,a){let l=r.getAttribute(t),d=r.getAttribute(s);l===""&&(l=i),d===""&&(d=n);let f=l||d;switch(f){case null:return a();case"blur":this.once(r,"debounce-blur")&&r.addEventListener("blur",()=>a());return;default:let c=parseInt(f),m=()=>d?this.deletePrivate(r,xe):a(),g=this.incCycle(r,pe,m);if(isNaN(c))return P(`invalid throttle/debounce value: ${f}`);if(d){let _=!1;if(e.type==="keydown"){let x=this.private(r,ut);this.putPrivate(r,ut,e.key),_=x!==e.key}if(!_&&this.private(r,xe))return!1;a(),this.putPrivate(r,xe,!0),setTimeout(()=>{o()&&this.triggerCycle(r,pe)},c)}else setTimeout(()=>{o()&&this.triggerCycle(r,pe,g)},c);let p=r.form;p&&this.once(p,"bind-debounce")&&p.addEventListener("submit",()=>{Array.from(new FormData(p).entries(),([_])=>{let x=p.querySelector(`[name="${_}"]`);this.incCycle(x,pe),this.deletePrivate(x,xe)})}),this.once(r,"bind-debounce")&&r.addEventListener("blur",()=>this.triggerCycle(r,pe))}},triggerCycle(r,e,t){let[i,s]=this.private(r,e);t||(t=i),t===i&&(this.incCycle(r,e),s())},once(r,e){return this.private(r,e)===!0?!1:(this.putPrivate(r,e,!0),!0)},incCycle(r,e,t=function(){}){let[i]=this.private(r,e)||[0,t];return i++,this.putPrivate(r,e,[i,t]),i},discardError(r,e,t){let i=e.getAttribute&&e.getAttribute(t),s=i&&r.querySelector(`[id="${i}"], [name="${i}"], [name="${i}[]"]`);!s||this.private(s,Ae)||this.private(s,de)||e.classList.add(Ue)},resetForm(r,e){Array.from(r.elements).forEach(t=>{let i=`[${e}="${t.id}"],
+ [${e}="${t.name}"],
+ [${e}="${t.name.replace(/\[\]$/,"")}"]`;this.deletePrivate(t,Ae),this.deletePrivate(t,de),this.all(document,i,s=>{s.classList.add(Ue)})})},showError(r,e){(r.id||r.name)&&this.all(r.form,`[${e}="${r.id}"], [${e}="${r.name}"]`,t=>{this.removeClass(t,Ue)})},isPhxChild(r){return r.getAttribute&&r.getAttribute(q)},isPhxSticky(r){return r.getAttribute&&r.getAttribute(lt)!==null},firstPhxChild(r){return this.isPhxChild(r)?r:this.all(r,`[${q}]`)[0]},dispatchEvent(r,e,t={}){let s={bubbles:t.bubbles===void 0?!0:!!t.bubbles,cancelable:!0,detail:t.detail||{}},n=e==="click"?new MouseEvent("click",s):new CustomEvent(e,s);r.dispatchEvent(n)},cloneNode(r,e){if(typeof e=="undefined")return r.cloneNode(!0);{let t=r.cloneNode(!1);return t.innerHTML=e,t}},mergeAttrs(r,e,t={}){let i=t.exclude||[],s=t.isIgnored,n=e.attributes;for(let a=n.length-1;a>=0;a--){let l=n[a].name;i.indexOf(l)<0&&r.setAttribute(l,e.getAttribute(l))}let o=r.attributes;for(let a=o.length-1;a>=0;a--){let l=o[a].name;s?l.startsWith("data-")&&!e.hasAttribute(l)&&r.removeAttribute(l):e.hasAttribute(l)||r.removeAttribute(l)}},mergeFocusedInput(r,e){r instanceof HTMLSelectElement||V.mergeAttrs(r,e,{exclude:["value"]}),e.readOnly?r.setAttribute("readonly",!0):r.removeAttribute("readonly")},hasSelectionRange(r){return r.setSelectionRange&&(r.type==="text"||r.type==="textarea")},restoreFocus(r,e,t){if(!V.isTextualInput(r))return;let i=r.matches(":focus");r.readOnly&&r.blur(),i||r.focus(),this.hasSelectionRange(r)&&r.setSelectionRange(e,t)},isFormInput(r){return/^(?:input|select|textarea)$/i.test(r.tagName)&&r.type!=="button"},syncAttrsToProps(r){r instanceof HTMLInputElement&&Xe.indexOf(r.type.toLocaleLowerCase())>=0&&(r.checked=r.getAttribute("checked")!==null)},isTextualInput(r){return Rt.indexOf(r.type)>=0},isNowTriggerFormExternal(r,e){return r.getAttribute&&r.getAttribute(e)!==null},syncPendingRef(r,e,t){let i=r.getAttribute(O);if(i===null)return!0;let s=r.getAttribute(j);return V.isFormInput(r)||r.getAttribute(t)!==null?(V.isUploadInput(r)&&V.mergeAttrs(r,e,{isIgnored:!0}),V.putPrivate(r,O,e),!1):(He.forEach(n=>{r.classList.contains(n)&&e.classList.add(n)}),e.setAttribute(O,i),e.setAttribute(j,s),!0)},cleanChildNodes(r,e){if(V.isPhxUpdate(r,e,["append","prepend"])){let t=[];r.childNodes.forEach(i=>{i.id||(i.nodeType===Node.TEXT_NODE&&i.nodeValue.trim()===""||P(`only HTML element tags with an id are allowed inside containers with phx-update.
removing illegal node: "${(i.outerHTML||i.nodeValue).trim()}"
-`),t.push(i))}),t.forEach(i=>i.remove())}},replaceRootContainer(n,e,t){let i=new Set(["id",k,j,we]);if(n.tagName.toLowerCase()===e.toLowerCase())return Array.from(n.attributes).filter(r=>!i.has(r.name.toLowerCase())).forEach(r=>n.removeAttribute(r.name)),Object.keys(t).filter(r=>!i.has(r.toLowerCase())).forEach(r=>n.setAttribute(r,t[r])),n;{let r=document.createElement(e);return Object.keys(t).forEach(s=>r.setAttribute(s,t[s])),i.forEach(s=>r.setAttribute(s,n.getAttribute(s))),r.innerHTML=n.innerHTML,n.replaceWith(r),r}}},a=X;var ne=class{static isActive(e,t){let i=t._phxRef===void 0,s=e.getAttribute(le).split(",").indexOf(S.genFileRef(t))>=0;return t.size>0&&(i||s)}static isPreflighted(e,t){return e.getAttribute(te).split(",").indexOf(S.genFileRef(t))>=0&&this.isActive(e,t)}constructor(e,t,i){this.ref=S.genFileRef(t),this.fileEl=e,this.file=t,this.view=i,this.meta=null,this._isCancelled=!1,this._isDone=!1,this._progress=0,this._lastProgressSent=-1,this._onDone=function(){},this._onElUpdated=this.onElUpdated.bind(this),this.fileEl.addEventListener(ae,this._onElUpdated)}metadata(){return this.meta}progress(e){this._progress=Math.floor(e),this._progress>this._lastProgressSent&&(this._progress>=100?(this._progress=100,this._lastProgressSent=100,this._isDone=!0,this.view.pushFileProgress(this.fileEl,this.ref,100,()=>{S.untrackFile(this.fileEl,this.file),this._onDone()})):(this._lastProgressSent=this._progress,this.view.pushFileProgress(this.fileEl,this.ref,this._progress)))}cancel(){this._isCancelled=!0,this._isDone=!0,this._onDone()}isDone(){return this._isDone}error(e="failed"){this.view.pushFileProgress(this.fileEl,this.ref,{error:e}),S.clearFiles(this.fileEl)}onDone(e){this._onDone=()=>{this.fileEl.removeEventListener(ae,this._onElUpdated),e()}}onElUpdated(){this.fileEl.getAttribute(le).split(",").indexOf(this.ref)===-1&&this.cancel()}toPreflightPayload(){return{last_modified:this.file.lastModified,name:this.file.name,size:this.file.size,type:this.file.type,ref:this.ref}}uploader(e){if(this.meta.uploader){let t=e[this.meta.uploader]||y(`no uploader configured for ${this.meta.uploader}`);return{name:this.meta.uploader,callback:t}}else return{name:"channel",callback:Rt}}zipPostFlight(e){this.meta=e.entries[this.ref],this.meta||y(`no preflight upload response returned with ref ${this.ref}`,{input:this.fileEl,response:e})}};var Wt=0,S=class{static genFileRef(e){let t=e._phxRef;return t!==void 0?t:(e._phxRef=(Wt++).toString(),e._phxRef)}static getEntryDataURL(e,t,i){let r=this.activeFiles(e).find(s=>this.genFileRef(s)===t);i(URL.createObjectURL(r))}static hasUploadsInProgress(e){let t=0;return a.findUploadInputs(e).forEach(i=>{i.getAttribute(te)!==i.getAttribute(bt)&&t++}),t>0}static serializeUploads(e){let t=this.activeFiles(e),i={};return t.forEach(r=>{let s={path:e.name},o=e.getAttribute(R);i[o]=i[o]||[],s.ref=this.genFileRef(r),s.name=r.name||s.ref,s.type=r.type,s.size=r.size,i[o].push(s)}),i}static clearFiles(e){e.value=null,e.removeAttribute(R),a.putPrivate(e,"files",[])}static untrackFile(e,t){a.putPrivate(e,"files",a.private(e,"files").filter(i=>!Object.is(i,t)))}static trackFiles(e,t){if(e.getAttribute("multiple")!==null){let i=t.filter(r=>!this.activeFiles(e).find(s=>Object.is(s,r)));a.putPrivate(e,"files",this.activeFiles(e).concat(i)),e.value=null}else a.putPrivate(e,"files",t)}static activeFileInputs(e){let t=a.findUploadInputs(e);return Array.from(t).filter(i=>i.files&&this.activeFiles(i).length>0)}static activeFiles(e){return(a.private(e,"files")||[]).filter(t=>ne.isActive(e,t))}static inputsAwaitingPreflight(e){let t=a.findUploadInputs(e);return Array.from(t).filter(i=>this.filesAwaitingPreflight(i).length>0)}static filesAwaitingPreflight(e){return this.activeFiles(e).filter(t=>!ne.isPreflighted(e,t))}constructor(e,t,i){this.view=t,this.onComplete=i,this._entries=Array.from(S.filesAwaitingPreflight(e)||[]).map(r=>new ne(e,r,t)),this.numEntriesInProgress=this._entries.length}entries(){return this._entries}initAdapterUpload(e,t,i){this._entries=this._entries.map(s=>(s.zipPostFlight(e),s.onDone(()=>{this.numEntriesInProgress--,this.numEntriesInProgress===0&&this.onComplete()}),s));let r=this._entries.reduce((s,o)=>{let{name:l,callback:h}=o.uploader(i.uploaders);return s[l]=s[l]||{callback:h,entries:[]},s[l].entries.push(o),s},{});for(let s in r){let{callback:o,entries:l}=r[s];o(l,t,e,i)}}};var Gt={LiveFileUpload:{activeRefs(){return this.el.getAttribute(le)},preflightedRefs(){return this.el.getAttribute(te)},mounted(){this.preflightedWas=this.preflightedRefs()},updated(){let n=this.preflightedRefs();this.preflightedWas!==n&&(this.preflightedWas=n,n===""&&this.__view.cancelSubmit(this.el.form)),this.activeRefs()===""&&(this.el.value=null),this.el.dispatchEvent(new CustomEvent(ae))}},LiveImgPreview:{mounted(){this.ref=this.el.getAttribute("data-phx-entry-ref"),this.inputEl=document.getElementById(this.el.getAttribute(R)),S.getEntryDataURL(this.inputEl,this.ref,n=>{this.url=n,this.el.src=n})},destroyed(){URL.revokeObjectURL(this.url)}}},Ot=Gt;var Fe=class{constructor(e,t,i){let r=new Set,s=new Set([...t.children].map(l=>l.id)),o=[];Array.from(e.children).forEach(l=>{if(l.id&&(r.add(l.id),s.has(l.id))){let h=l.previousElementSibling&&l.previousElementSibling.id;o.push({elementId:l.id,previousElementId:h})}}),this.containerId=t.id,this.updateType=i,this.elementsToModify=o,this.elementIdsToAdd=[...s].filter(l=>!r.has(l))}perform(){let e=a.byId(this.containerId);this.elementsToModify.forEach(t=>{t.previousElementId?x(document.getElementById(t.previousElementId),i=>{x(document.getElementById(t.elementId),r=>{r.previousElementSibling&&r.previousElementSibling.id==i.id||i.insertAdjacentElement("afterend",r)})}):x(document.getElementById(t.elementId),i=>{i.previousElementSibling==null||e.insertAdjacentElement("afterbegin",i)})}),this.updateType=="prepend"&&this.elementIdsToAdd.reverse().forEach(t=>{x(document.getElementById(t),i=>e.insertAdjacentElement("afterbegin",i))})}};var Nt=11;function zt(n,e){var t=e.attributes,i,r,s,o,l;if(!(e.nodeType===Nt||n.nodeType===Nt)){for(var h=t.length-1;h>=0;h--)i=t[h],r=i.name,s=i.namespaceURI,o=i.value,s?(r=i.localName||r,l=n.getAttributeNS(s,r),l!==o&&(i.prefix==="xmlns"&&(r=i.name),n.setAttributeNS(s,r,o))):(l=n.getAttribute(r),l!==o&&n.setAttribute(r,o));for(var u=n.attributes,m=u.length-1;m>=0;m--)i=u[m],r=i.name,s=i.namespaceURI,s?(r=i.localName||r,e.hasAttributeNS(s,r)||n.removeAttributeNS(s,r)):e.hasAttribute(r)||n.removeAttribute(r)}}var Xe,qt="http://www.w3.org/1999/xhtml",I=typeof document=="undefined"?void 0:document,Yt=!!I&&"content"in I.createElement("template"),Qt=!!I&&I.createRange&&"createContextualFragment"in I.createRange();function Zt(n){var e=I.createElement("template");return e.innerHTML=n,e.content.childNodes[0]}function ei(n){Xe||(Xe=I.createRange(),Xe.selectNode(I.body));var e=Xe.createContextualFragment(n);return e.childNodes[0]}function ti(n){var e=I.createElement("body");return e.innerHTML=n,e.childNodes[0]}function ii(n){return n=n.trim(),Yt?Zt(n):Qt?ei(n):ti(n)}function Me(n,e){var t=n.nodeName,i=e.nodeName,r,s;return t===i?!0:(r=t.charCodeAt(0),s=i.charCodeAt(0),r<=90&&s>=97?t===i.toUpperCase():s<=90&&r>=97?i===t.toUpperCase():!1)}function ri(n,e){return!e||e===qt?I.createElement(n):I.createElementNS(e,n)}function ni(n,e){for(var t=n.firstChild;t;){var i=t.nextSibling;e.appendChild(t),t=i}return e}function lt(n,e,t){n[t]!==e[t]&&(n[t]=e[t],n[t]?n.setAttribute(t,""):n.removeAttribute(t))}var Ut={OPTION:function(n,e){var t=n.parentNode;if(t){var i=t.nodeName.toUpperCase();i==="OPTGROUP"&&(t=t.parentNode,i=t&&t.nodeName.toUpperCase()),i==="SELECT"&&!t.hasAttribute("multiple")&&(n.hasAttribute("selected")&&!e.selected&&(n.setAttribute("selected","selected"),n.removeAttribute("selected")),t.selectedIndex=-1)}lt(n,e,"selected")},INPUT:function(n,e){lt(n,e,"checked"),lt(n,e,"disabled"),n.value!==e.value&&(n.value=e.value),e.hasAttribute("value")||n.removeAttribute("value")},TEXTAREA:function(n,e){var t=e.value;n.value!==t&&(n.value=t);var i=n.firstChild;if(i){var r=i.nodeValue;if(r==t||!t&&r==n.placeholder)return;i.nodeValue=t}},SELECT:function(n,e){if(!e.hasAttribute("multiple")){for(var t=-1,i=0,r=n.firstChild,s,o;r;)if(o=r.nodeName&&r.nodeName.toUpperCase(),o==="OPTGROUP")s=r,r=s.firstChild;else{if(o==="OPTION"){if(r.hasAttribute("selected")){t=i;break}i++}r=r.nextSibling,!r&&s&&(r=s.nextSibling,s=null)}n.selectedIndex=t}}},ve=1,si=11,Ft=3,Xt=8;function q(){}function oi(n){if(n)return n.getAttribute&&n.getAttribute("id")||n.id}function li(n){return function(t,i,r){if(r||(r={}),typeof i=="string")if(t.nodeName==="#document"||t.nodeName==="HTML"||t.nodeName==="BODY"){var s=i;i=I.createElement("html"),i.innerHTML=s}else i=ii(i);var o=r.getNodeKey||oi,l=r.onBeforeNodeAdded||q,h=r.onNodeAdded||q,u=r.onBeforeElUpdated||q,m=r.onElUpdated||q,c=r.onBeforeNodeDiscarded||q,g=r.onNodeDiscarded||q,E=r.onBeforeElChildrenUpdated||q,P=r.childrenOnly===!0,b=Object.create(null),F=[];function Q(v){F.push(v)}function d(v,p){if(v.nodeType===ve)for(var f=v.firstChild;f;){var _=void 0;p&&(_=o(f))?Q(_):(g(f),f.firstChild&&d(f,p)),f=f.nextSibling}}function A(v,p,f){c(v)!==!1&&(p&&p.removeChild(v),g(v),d(v,f))}function $e(v){if(v.nodeType===ve||v.nodeType===si)for(var p=v.firstChild;p;){var f=o(p);f&&(b[f]=p),$e(p),p=p.nextSibling}}$e(t);function Z(v){h(v);for(var p=v.firstChild;p;){var f=p.nextSibling,_=o(p);if(_){var D=b[_];D&&Me(p,D)?(p.parentNode.replaceChild(D,p),_e(D,p)):Z(p)}else Z(p);p=f}}function Mt(v,p,f){for(;p;){var _=p.nextSibling;(f=o(p))?Q(f):A(p,v,!0),p=_}}function _e(v,p,f){var _=o(p);_&&delete b[_],!(!f&&(u(v,p)===!1||(n(v,p),m(v),E(v,p)===!1)))&&(v.nodeName!=="TEXTAREA"?Bt(v,p):Ut.TEXTAREA(v,p))}function Bt(v,p){var f=p.firstChild,_=v.firstChild,D,M,ee,Ae,B;e:for(;f;){for(Ae=f.nextSibling,D=o(f);_;){if(ee=_.nextSibling,f.isSameNode&&f.isSameNode(_)){f=Ae,_=ee;continue e}M=o(_);var Pe=_.nodeType,$=void 0;if(Pe===f.nodeType&&(Pe===ve?(D?D!==M&&((B=b[D])?ee===B?$=!1:(v.insertBefore(B,_),M?Q(M):A(_,v,!0),_=B):$=!1):M&&($=!1),$=$!==!1&&Me(_,f),$&&_e(_,f)):(Pe===Ft||Pe==Xt)&&($=!0,_.nodeValue!==f.nodeValue&&(_.nodeValue=f.nodeValue))),$){f=Ae,_=ee;continue e}M?Q(M):A(_,v,!0),_=ee}if(D&&(B=b[D])&&Me(B,f))v.appendChild(B),_e(B,f);else{var Je=l(f);Je!==!1&&(Je&&(f=Je),f.actualize&&(f=f.actualize(v.ownerDocument||I)),v.appendChild(f),Z(f))}f=Ae,_=ee}Mt(v,_,M);var ut=Ut[v.nodeName];ut&&ut(v,p)}var T=t,Ee=T.nodeType,dt=i.nodeType;if(!P){if(Ee===ve)dt===ve?Me(t,i)||(g(t),T=ni(t,ri(i.nodeName,i.namespaceURI))):T=i;else if(Ee===Ft||Ee===Xt){if(dt===Ee)return T.nodeValue!==i.nodeValue&&(T.nodeValue=i.nodeValue),T;T=i}}if(T===i)g(t);else{if(i.isSameNode&&i.isSameNode(T))return;if(_e(T,i,P),F)for(var Ve=0,$t=F.length;Ve<$t;Ve++){var je=b[F[Ve]];je&&A(je,je.parentNode,!1)}}return!P&&T!==t&&t.parentNode&&(T.actualize&&(T=T.actualize(t.ownerDocument||I)),t.parentNode.replaceChild(T,t)),T}}var ai=li(zt),at=ai;var Y=class{static patchEl(e,t,i){at(e,t,{childrenOnly:!1,onBeforeElUpdated:(r,s)=>{if(i&&i.isSameNode(r)&&a.isFormInput(r))return a.mergeFocusedInput(r,s),!1}})}constructor(e,t,i,r,s){this.view=e,this.liveSocket=e.liveSocket,this.container=t,this.id=i,this.rootID=e.root.id,this.html=r,this.targetCID=s,this.cidPatch=G(this.targetCID),this.callbacks={beforeadded:[],beforeupdated:[],beforephxChildAdded:[],afteradded:[],afterupdated:[],afterdiscarded:[],afterphxChildAdded:[]}}before(e,t){this.callbacks[`before${e}`].push(t)}after(e,t){this.callbacks[`after${e}`].push(t)}trackBefore(e,...t){this.callbacks[`before${e}`].forEach(i=>i(...t))}trackAfter(e,...t){this.callbacks[`after${e}`].forEach(i=>i(...t))}markPrunableContentForRemoval(){a.all(this.container,"[phx-update=append] > *, [phx-update=prepend] > *",e=>{e.setAttribute(ze,"")})}perform(){let{view:e,liveSocket:t,container:i,html:r}=this,s=this.isCIDPatch()?this.targetCIDContainer(r):i;if(this.isCIDPatch()&&!s)return;let o=t.getActiveElement(),{selectionStart:l,selectionEnd:h}=o&&a.hasSelectionRange(o)?o:{},u=t.binding(ge),m=t.binding(Ie),c=t.binding(ce),g=t.binding(_t),E=[],P=[],b=[],F=null,Q=t.time("premorph container prep",()=>this.buildDiffHTML(i,r,u,s));return this.trackBefore("added",i),this.trackBefore("updated",i,i),t.time("morphdom",()=>{at(s,Q,{childrenOnly:s.getAttribute(C)===null,getNodeKey:d=>a.isPhxDestroyed(d)?null:d.id,onBeforeNodeAdded:d=>(this.trackBefore("added",d),d),onNodeAdded:d=>{d instanceof HTMLImageElement&&d.srcset?d.srcset=d.srcset:d instanceof HTMLVideoElement&&d.autoplay&&d.play(),a.isNowTriggerFormExternal(d,g)&&(F=d),a.discardError(s,d,m),a.isPhxChild(d)&&e.ownsElement(d)&&this.trackAfter("phxChildAdded",d),E.push(d)},onNodeDiscarded:d=>{a.isPhxChild(d)&&t.destroyViewByEl(d),this.trackAfter("discarded",d)},onBeforeNodeDiscarded:d=>d.getAttribute&&d.getAttribute(ze)!==null?!0:!(d.parentNode!==null&&a.isPhxUpdate(d.parentNode,u,["append","prepend"])&&d.id||this.skipCIDSibling(d)),onElUpdated:d=>{a.isNowTriggerFormExternal(d,g)&&(F=d),P.push(d)},onBeforeElUpdated:(d,A)=>{if(a.cleanChildNodes(A,u),this.skipCIDSibling(A))return!1;if(a.isIgnored(d,u))return this.trackBefore("updated",d,A),a.mergeAttrs(d,A,{isIgnored:!0}),P.push(d),!1;if(d.type==="number"&&d.validity&&d.validity.badInput)return!1;if(!a.syncPendingRef(d,A,c))return a.isUploadInput(d)&&(this.trackBefore("updated",d,A),P.push(d)),!1;if(a.isPhxChild(A)){let Z=d.getAttribute(k);return a.mergeAttrs(d,A,{exclude:[j]}),Z!==""&&d.setAttribute(k,Z),d.setAttribute(W,this.rootID),!1}return a.copyPrivates(A,d),a.discardError(s,A,m),a.syncPropsToAttrs(A),o&&d.isSameNode(o)&&a.isFormInput(d)&&!this.forceFocusedSelectUpdate(d,A)?(this.trackBefore("updated",d,A),a.mergeFocusedInput(d,A),a.syncAttrsToProps(d),P.push(d),!1):(a.isPhxUpdate(A,u,["append","prepend"])&&b.push(new Fe(d,A,A.getAttribute(u))),a.syncAttrsToProps(A),this.trackBefore("updated",d,A),!0)}})}),t.isDebugEnabled()&&kt(),b.length>0&&t.time("post-morph append/prepend restoration",()=>{b.forEach(d=>d.perform())}),t.silenceEvents(()=>a.restoreFocus(o,l,h)),a.dispatchEvent(document,"phx:update"),E.forEach(d=>this.trackAfter("added",d)),P.forEach(d=>this.trackAfter("updated",d)),F&&(t.disconnect(),F.submit()),!0}forceFocusedSelectUpdate(e,t){let i=["select","select-one","select-multiple"].find(r=>r===e.type);return e.multiple===!0||i&&e.innerHTML!=t.innerHTML}isCIDPatch(){return this.cidPatch}skipCIDSibling(e){return e.nodeType===Node.ELEMENT_NODE&&e.getAttribute(he)!==null}targetCIDContainer(e){if(!this.isCIDPatch())return;let[t,...i]=a.findComponentNodeList(this.container,this.targetCID);return i.length===0&&a.childNodeLength(e)===1?t:t&&t.parentNode}buildDiffHTML(e,t,i,r){let s=this.isCIDPatch(),o=s&&r.getAttribute(C)===this.targetCID.toString();if(!s||o)return t;{let l=null,h=document.createElement("template");l=a.cloneNode(r);let[u,...m]=a.findComponentNodeList(l,this.targetCID);return h.innerHTML=t,m.forEach(c=>c.remove()),Array.from(l.childNodes).forEach(c=>{c.id&&c.nodeType===Node.ELEMENT_NODE&&c.getAttribute(C)!==this.targetCID.toString()&&(c.setAttribute(he,""),c.innerHTML="")}),Array.from(h.content.childNodes).forEach(c=>l.insertBefore(c,u)),u.remove(),l.outerHTML}}};var be=class{static extract(e){let{[nt]:t,[rt]:i,[st]:r}=e;return delete e[nt],delete e[rt],delete e[st],{diff:e,title:r,reply:t||null,events:i||[]}}constructor(e,t){this.viewId=e,this.rendered={},this.mergeDiff(t)}parentViewId(){return this.viewId}toString(e){return this.recursiveToString(this.rendered,this.rendered[w],e)}recursiveToString(e,t=e[w],i){i=i?new Set(i):null;let r={buffer:"",components:t,onlyCids:i};return this.toOutputBuffer(e,r),r.buffer}componentCIDs(e){return Object.keys(e[w]||{}).map(t=>parseInt(t))}isComponentOnlyDiff(e){return e[w]?Object.keys(e).length===1:!1}getComponent(e,t){return e[w][t]}mergeDiff(e){let t=e[w],i={};if(delete e[w],this.rendered=this.mutableMerge(this.rendered,e),this.rendered[w]=this.rendered[w]||{},t){let s=this.rendered[w];for(let o in t)t[o]=this.cachedFindComponent(o,t[o],s,t,i);for(var r in t)s[r]=t[r];e[w]=t}}cachedFindComponent(e,t,i,r,s){if(s[e])return s[e];{let o,l,h=t[O];if(G(h)){let u;h>0?u=this.cachedFindComponent(h,r[h],i,r,s):u=i[-h],l=u[O],o=this.cloneMerge(u,t),o[O]=l}else o=t[O]!==void 0?t:this.cloneMerge(i[e]||{},t);return s[e]=o,o}}mutableMerge(e,t){return t[O]!==void 0?t:(this.doMutableMerge(e,t),e)}doMutableMerge(e,t){for(let i in t){let r=t[i],s=e[i];re(r)&&r[O]===void 0&&re(s)?this.doMutableMerge(s,r):e[i]=r}}cloneMerge(e,t){let i=oe(oe({},e),t);for(let r in i){let s=t[r],o=e[r];re(s)&&s[O]===void 0&&re(o)&&(i[r]=this.cloneMerge(o,s))}return i}componentToString(e){return this.recursiveCIDToString(this.rendered[w],e)}pruneCIDs(e){e.forEach(t=>delete this.rendered[w][t])}get(){return this.rendered}isNewFingerprint(e={}){return!!e[O]}toOutputBuffer(e,t){if(e[it])return this.comprehensionToBuffer(e,t);let{[O]:i}=e;t.buffer+=i[0];for(let r=1;rg.nodeType===Node.ELEMENT_NODE?g.getAttribute(C)?[m,!0]:(g.setAttribute(C,t),g.id||(g.id=`${this.parentViewId()}-${t}-${E}`),l&&(g.setAttribute(he,""),g.innerHTML=""),[!0,c]):g.nodeValue.trim()!==""?(y(`only HTML element tags are allowed at the root of components.
+`),t.push(i))}),t.forEach(i=>i.remove())}},replaceRootContainer(r,e,t){let i=new Set(["id",H,Y,he,W]);if(r.tagName.toLowerCase()===e.toLowerCase())return Array.from(r.attributes).filter(s=>!i.has(s.name.toLowerCase())).forEach(s=>r.removeAttribute(s.name)),Object.keys(t).filter(s=>!i.has(s.toLowerCase())).forEach(s=>r.setAttribute(s,t[s])),r;{let s=document.createElement(e);return Object.keys(t).forEach(n=>s.setAttribute(n,t[n])),i.forEach(n=>s.setAttribute(n,r.getAttribute(n))),s.innerHTML=r.innerHTML,r.replaceWith(s),s}},getSticky(r,e,t){let i=(V.private(r,"sticky")||[]).find(([s])=>e===s);if(i){let[s,n,o]=i;return o}else return typeof t=="function"?t():t},deleteSticky(r,e){this.updatePrivate(r,"sticky",[],t=>t.filter(([i,s])=>i!==e))},putSticky(r,e,t){let i=t(r);this.updatePrivate(r,"sticky",[],s=>{let n=s.findIndex(([o])=>e===o);return n>=0?s[n]=[e,t,i]:s.push([e,t,i]),s})},applyStickyOperations(r){let e=V.private(r,"sticky");!e||e.forEach(([t,i,s])=>this.putSticky(r,t,i))}},h=V;var ve=class{static isActive(e,t){let i=t._phxRef===void 0,n=e.getAttribute(Ee).split(",").indexOf(y.genFileRef(t))>=0;return t.size>0&&(i||n)}static isPreflighted(e,t){return e.getAttribute(ae).split(",").indexOf(y.genFileRef(t))>=0&&this.isActive(e,t)}constructor(e,t,i){this.ref=y.genFileRef(t),this.fileEl=e,this.file=t,this.view=i,this.meta=null,this._isCancelled=!1,this._isDone=!1,this._progress=0,this._lastProgressSent=-1,this._onDone=function(){},this._onElUpdated=this.onElUpdated.bind(this),this.fileEl.addEventListener(le,this._onElUpdated)}metadata(){return this.meta}progress(e){this._progress=Math.floor(e),this._progress>this._lastProgressSent&&(this._progress>=100?(this._progress=100,this._lastProgressSent=100,this._isDone=!0,this.view.pushFileProgress(this.fileEl,this.ref,100,()=>{y.untrackFile(this.fileEl,this.file),this._onDone()})):(this._lastProgressSent=this._progress,this.view.pushFileProgress(this.fileEl,this.ref,this._progress)))}cancel(){this._isCancelled=!0,this._isDone=!0,this._onDone()}isDone(){return this._isDone}error(e="failed"){this.fileEl.removeEventListener(le,this._onElUpdated),this.view.pushFileProgress(this.fileEl,this.ref,{error:e}),y.clearFiles(this.fileEl)}onDone(e){this._onDone=()=>{this.fileEl.removeEventListener(le,this._onElUpdated),e()}}onElUpdated(){this.fileEl.getAttribute(Ee).split(",").indexOf(this.ref)===-1&&this.cancel()}toPreflightPayload(){return{last_modified:this.file.lastModified,name:this.file.name,relative_path:this.file.webkitRelativePath,size:this.file.size,type:this.file.type,ref:this.ref}}uploader(e){if(this.meta.uploader){let t=e[this.meta.uploader]||P(`no uploader configured for ${this.meta.uploader}`);return{name:this.meta.uploader,callback:t}}else return{name:"channel",callback:qt}}zipPostFlight(e){this.meta=e.entries[this.ref],this.meta||P(`no preflight upload response returned with ref ${this.ref}`,{input:this.fileEl,response:e})}};var li=0,y=class{static genFileRef(e){let t=e._phxRef;return t!==void 0?t:(e._phxRef=(li++).toString(),e._phxRef)}static getEntryDataURL(e,t,i){let s=this.activeFiles(e).find(n=>this.genFileRef(n)===t);i(URL.createObjectURL(s))}static hasUploadsInProgress(e){let t=0;return h.findUploadInputs(e).forEach(i=>{i.getAttribute(ae)!==i.getAttribute(kt)&&t++}),t>0}static serializeUploads(e){let t=this.activeFiles(e),i={};return t.forEach(s=>{let n={path:e.name},o=e.getAttribute(M);i[o]=i[o]||[],n.ref=this.genFileRef(s),n.last_modified=s.lastModified,n.name=s.name||n.ref,n.relative_path=s.webkitRelativePath,n.type=s.type,n.size=s.size,i[o].push(n)}),i}static clearFiles(e){e.value=null,e.removeAttribute(M),h.putPrivate(e,"files",[])}static untrackFile(e,t){h.putPrivate(e,"files",h.private(e,"files").filter(i=>!Object.is(i,t)))}static trackFiles(e,t,i){if(e.getAttribute("multiple")!==null){let s=t.filter(n=>!this.activeFiles(e).find(o=>Object.is(o,n)));h.putPrivate(e,"files",this.activeFiles(e).concat(s)),e.value=null}else i&&i.files.length>0&&(e.files=i.files),h.putPrivate(e,"files",t)}static activeFileInputs(e){let t=h.findUploadInputs(e);return Array.from(t).filter(i=>i.files&&this.activeFiles(i).length>0)}static activeFiles(e){return(h.private(e,"files")||[]).filter(t=>ve.isActive(e,t))}static inputsAwaitingPreflight(e){let t=h.findUploadInputs(e);return Array.from(t).filter(i=>this.filesAwaitingPreflight(i).length>0)}static filesAwaitingPreflight(e){return this.activeFiles(e).filter(t=>!ve.isPreflighted(e,t))}constructor(e,t,i){this.view=t,this.onComplete=i,this._entries=Array.from(y.filesAwaitingPreflight(e)||[]).map(s=>new ve(e,s,t)),this.numEntriesInProgress=this._entries.length}entries(){return this._entries}initAdapterUpload(e,t,i){this._entries=this._entries.map(n=>(n.zipPostFlight(e),n.onDone(()=>{this.numEntriesInProgress--,this.numEntriesInProgress===0&&this.onComplete()}),n));let s=this._entries.reduce((n,o)=>{let{name:a,callback:l}=o.uploader(i.uploaders);return n[a]=n[a]||{callback:l,entries:[]},n[a].entries.push(o),n},{});for(let n in s){let{callback:o,entries:a}=s[n];o(a,t,e,i)}}};var hi={focusMain(){let r=document.querySelector("main h1, main, h1");if(r){let e=r.tabIndex;r.tabIndex=-1,r.focus(),r.tabIndex=e}},anyOf(r,e){return e.find(t=>r instanceof t)},isFocusable(r,e){return r instanceof HTMLAnchorElement&&r.rel!=="ignore"||r instanceof HTMLAreaElement&&r.href!==void 0||!r.disabled&&this.anyOf(r,[HTMLInputElement,HTMLSelectElement,HTMLTextAreaElement,HTMLButtonElement])||r instanceof HTMLIFrameElement||r.tabIndex>0||!e&&r.tabIndex===0&&r.getAttribute("tabindex")!==null&&r.getAttribute("aria-hidden")!=="true"},attemptFocus(r,e){if(this.isFocusable(r,e))try{r.focus()}catch(t){}return!!document.activeElement&&document.activeElement.isSameNode(r)},focusFirstInteractive(r){let e=r.firstElementChild;for(;e;){if(this.attemptFocus(e,!0)||this.focusFirstInteractive(e,!0))return!0;e=e.nextElementSibling}},focusFirst(r){let e=r.firstElementChild;for(;e;){if(this.attemptFocus(e)||this.focusFirst(e))return!0;e=e.nextElementSibling}},focusLast(r){let e=r.lastElementChild;for(;e;){if(this.attemptFocus(e)||this.focusLast(e))return!0;e=e.previousElementSibling}}},Q=hi;var di={LiveFileUpload:{activeRefs(){return this.el.getAttribute(Ee)},preflightedRefs(){return this.el.getAttribute(ae)},mounted(){this.preflightedWas=this.preflightedRefs()},updated(){let r=this.preflightedRefs();this.preflightedWas!==r&&(this.preflightedWas=r,r===""&&this.__view.cancelSubmit(this.el.form)),this.activeRefs()===""&&(this.el.value=null),this.el.dispatchEvent(new CustomEvent(le))}},LiveImgPreview:{mounted(){this.ref=this.el.getAttribute("data-phx-entry-ref"),this.inputEl=document.getElementById(this.el.getAttribute(M)),y.getEntryDataURL(this.inputEl,this.ref,r=>{this.url=r,this.el.src=r})},destroyed(){URL.revokeObjectURL(this.url)}},FocusWrap:{mounted(){this.focusStart=this.el.firstElementChild,this.focusEnd=this.el.lastElementChild,this.focusStart.addEventListener("focus",()=>Q.focusLast(this.el)),this.focusEnd.addEventListener("focus",()=>Q.focusFirst(this.el)),this.el.addEventListener("phx:show-end",()=>this.el.focus()),window.getComputedStyle(this.el).display!=="none"&&Q.focusFirst(this.el)}}},Kt=di;var qe=class{constructor(e,t,i){let s=new Set,n=new Set([...t.children].map(a=>a.id)),o=[];Array.from(e.children).forEach(a=>{if(a.id&&(s.add(a.id),n.has(a.id))){let l=a.previousElementSibling&&a.previousElementSibling.id;o.push({elementId:a.id,previousElementId:l})}}),this.containerId=t.id,this.updateType=i,this.elementsToModify=o,this.elementIdsToAdd=[...n].filter(a=>!s.has(a))}perform(){let e=h.byId(this.containerId);this.elementsToModify.forEach(t=>{t.previousElementId?B(document.getElementById(t.previousElementId),i=>{B(document.getElementById(t.elementId),s=>{s.previousElementSibling&&s.previousElementSibling.id==i.id||i.insertAdjacentElement("afterend",s)})}):B(document.getElementById(t.elementId),i=>{i.previousElementSibling==null||e.insertAdjacentElement("afterbegin",i)})}),this.updateType=="prepend"&&this.elementIdsToAdd.reverse().forEach(t=>{B(document.getElementById(t),i=>e.insertAdjacentElement("afterbegin",i))})}};var Gt=11;function ui(r,e){var t=e.attributes,i,s,n,o,a;if(!(e.nodeType===Gt||r.nodeType===Gt)){for(var l=t.length-1;l>=0;l--)i=t[l],s=i.name,n=i.namespaceURI,o=i.value,n?(s=i.localName||s,a=r.getAttributeNS(n,s),a!==o&&(i.prefix==="xmlns"&&(s=i.name),r.setAttributeNS(n,s,o))):(a=r.getAttribute(s),a!==o&&r.setAttribute(s,o));for(var d=r.attributes,f=d.length-1;f>=0;f--)i=d[f],s=i.name,n=i.namespaceURI,n?(s=i.localName||s,e.hasAttributeNS(n,s)||r.removeAttributeNS(n,s)):e.hasAttribute(s)||r.removeAttribute(s)}}var We,ci="http://www.w3.org/1999/xhtml",R=typeof document=="undefined"?void 0:document,fi=!!R&&"content"in R.createElement("template"),pi=!!R&&R.createRange&&"createContextualFragment"in R.createRange();function mi(r){var e=R.createElement("template");return e.innerHTML=r,e.content.childNodes[0]}function gi(r){We||(We=R.createRange(),We.selectNode(R.body));var e=We.createContextualFragment(r);return e.childNodes[0]}function vi(r){var e=R.createElement("body");return e.innerHTML=r,e.childNodes[0]}function bi(r){return r=r.trim(),fi?mi(r):pi?gi(r):vi(r)}function Ke(r,e){var t=r.nodeName,i=e.nodeName,s,n;return t===i?!0:(s=t.charCodeAt(0),n=i.charCodeAt(0),s<=90&&n>=97?t===i.toUpperCase():n<=90&&s>=97?i===t.toUpperCase():!1)}function Ei(r,e){return!e||e===ci?R.createElement(r):R.createElementNS(e,r)}function _i(r,e){for(var t=r.firstChild;t;){var i=t.nextSibling;e.appendChild(t),t=i}return e}function gt(r,e,t){r[t]!==e[t]&&(r[t]=e[t],r[t]?r.setAttribute(t,""):r.removeAttribute(t))}var zt={OPTION:function(r,e){var t=r.parentNode;if(t){var i=t.nodeName.toUpperCase();i==="OPTGROUP"&&(t=t.parentNode,i=t&&t.nodeName.toUpperCase()),i==="SELECT"&&!t.hasAttribute("multiple")&&(r.hasAttribute("selected")&&!e.selected&&(r.setAttribute("selected","selected"),r.removeAttribute("selected")),t.selectedIndex=-1)}gt(r,e,"selected")},INPUT:function(r,e){gt(r,e,"checked"),gt(r,e,"disabled"),r.value!==e.value&&(r.value=e.value),e.hasAttribute("value")||r.removeAttribute("value")},TEXTAREA:function(r,e){var t=e.value;r.value!==t&&(r.value=t);var i=r.firstChild;if(i){var s=i.nodeValue;if(s==t||!t&&s==r.placeholder)return;i.nodeValue=t}},SELECT:function(r,e){if(!e.hasAttribute("multiple")){for(var t=-1,i=0,s=r.firstChild,n,o;s;)if(o=s.nodeName&&s.nodeName.toUpperCase(),o==="OPTGROUP")n=s,s=n.firstChild;else{if(o==="OPTION"){if(s.hasAttribute("selected")){t=i;break}i++}s=s.nextSibling,!s&&n&&(s=n.nextSibling,n=null)}r.selectedIndex=t}}},Te=1,Yt=11,Qt=3,Zt=8;function Z(){}function Ai(r){if(r)return r.getAttribute&&r.getAttribute("id")||r.id}function Si(r){return function(t,i,s){if(s||(s={}),typeof i=="string")if(t.nodeName==="#document"||t.nodeName==="HTML"||t.nodeName==="BODY"){var n=i;i=R.createElement("html"),i.innerHTML=n}else i=bi(i);else i.nodeType===Yt&&(i=i.firstElementChild);var o=s.getNodeKey||Ai,a=s.onBeforeNodeAdded||Z,l=s.onNodeAdded||Z,d=s.onBeforeElUpdated||Z,f=s.onElUpdated||Z,c=s.onBeforeNodeDiscarded||Z,m=s.onNodeDiscarded||Z,g=s.onBeforeElChildrenUpdated||Z,p=s.skipFromChildren||Z,_=s.addChild||function(b,v){return b.appendChild(v)},x=s.childrenOnly===!0,L=Object.create(null),u=[];function E(b){u.push(b)}function D(b,v){if(b.nodeType===Te)for(var w=b.firstChild;w;){var A=void 0;v&&(A=o(w))?E(A):(m(w),w.firstChild&&D(w,v)),w=w.nextSibling}}function k(b,v,w){c(b)!==!1&&(v&&v.removeChild(b),m(b),D(b,w))}function bt(b){if(b.nodeType===Te||b.nodeType===Yt)for(var v=b.firstChild;v;){var w=o(v);w&&(L[w]=v),bt(v),v=v.nextSibling}}bt(t);function Qe(b){l(b);for(var v=b.firstChild;v;){var w=v.nextSibling,A=o(v);if(A){var S=L[A];S&&Ke(v,S)?(v.parentNode.replaceChild(S,v),Ie(S,v)):Qe(v)}else Qe(v);v=w}}function ti(b,v,w){for(;v;){var A=v.nextSibling;(w=o(v))?E(w):k(v,b,!0),v=A}}function Ie(b,v,w){var A=o(v);A&&delete L[A],!(!w&&(d(b,v)===!1||(r(b,v),f(b),g(b,v)===!1)))&&(b.nodeName!=="TEXTAREA"?ii(b,v):zt.TEXTAREA(b,v))}function ii(b,v){var w=p(b),A=v.firstChild,S=b.firstChild,se,K,ne,De,G;e:for(;A;){for(De=A.nextSibling,se=o(A);!w&&S;){if(ne=S.nextSibling,A.isSameNode&&A.isSameNode(S)){A=De,S=ne;continue e}K=o(S);var Le=S.nodeType,z=void 0;if(Le===A.nodeType&&(Le===Te?(se?se!==K&&((G=L[se])?ne===G?z=!1:(b.insertBefore(G,S),K?E(K):k(S,b,!0),S=G):z=!1):K&&(z=!1),z=z!==!1&&Ke(S,A),z&&Ie(S,A)):(Le===Qt||Le==Zt)&&(z=!0,S.nodeValue!==A.nodeValue&&(S.nodeValue=A.nodeValue))),z){A=De,S=ne;continue e}K?E(K):k(S,b,!0),S=ne}if(se&&(G=L[se])&&Ke(G,A))w||_(b,G),Ie(G,A);else{var tt=a(A);tt!==!1&&(tt&&(A=tt),A.actualize&&(A=A.actualize(b.ownerDocument||R)),_(b,A),Qe(A))}A=De,S=ne}ti(b,S,K);var _t=zt[b.nodeName];_t&&_t(b,v)}var T=t,Re=T.nodeType,Et=i.nodeType;if(!x){if(Re===Te)Et===Te?Ke(t,i)||(m(t),T=_i(t,Ei(i.nodeName,i.namespaceURI))):T=i;else if(Re===Qt||Re===Zt){if(Et===Re)return T.nodeValue!==i.nodeValue&&(T.nodeValue=i.nodeValue),T;T=i}}if(T===i)m(t);else{if(i.isSameNode&&i.isSameNode(T))return;if(Ie(T,i,x),u)for(var Ze=0,ri=u.length;Ze{if(i&&i.isSameNode(s)&&h.isFormInput(s))return h.mergeFocusedInput(s,n),!1}})}constructor(e,t,i,s,n,o){this.view=e,this.liveSocket=e.liveSocket,this.container=t,this.id=i,this.rootID=e.root.id,this.html=s,this.streams=n,this.streamInserts={},this.targetCID=o,this.cidPatch=$(this.targetCID),this.pendingRemoves=[],this.phxRemove=this.liveSocket.binding("remove"),this.callbacks={beforeadded:[],beforeupdated:[],beforephxChildAdded:[],afteradded:[],afterupdated:[],afterdiscarded:[],afterphxChildAdded:[],aftertransitionsDiscarded:[]}}before(e,t){this.callbacks[`before${e}`].push(t)}after(e,t){this.callbacks[`after${e}`].push(t)}trackBefore(e,...t){this.callbacks[`before${e}`].forEach(i=>i(...t))}trackAfter(e,...t){this.callbacks[`after${e}`].forEach(i=>i(...t))}markPrunableContentForRemoval(){let e=this.liveSocket.binding(ce);h.all(this.container,`[${e}=${fe}]`,t=>t.innerHTML=""),h.all(this.container,`[${e}=append] > *, [${e}=prepend] > *`,t=>{t.setAttribute(st,"")})}perform(){let{view:e,liveSocket:t,container:i,html:s}=this,n=this.isCIDPatch()?this.targetCIDContainer(s):i;if(this.isCIDPatch()&&!n)return;let o=t.getActiveElement(),{selectionStart:a,selectionEnd:l}=o&&h.hasSelectionRange(o)?o:{},d=t.binding(ce),f=t.binding(te),c=t.binding(ye),m=t.binding(It),g=[],p=[],_=[],x=null,L=t.time("premorph container prep",()=>this.buildDiffHTML(i,s,d,n));return this.trackBefore("added",i),this.trackBefore("updated",i,i),t.time("morphdom",()=>{this.streams.forEach(([u,E])=>{this.streamInserts=Object.assign(this.streamInserts,u),E.forEach(D=>{let k=i.querySelector(`[id="${D}"]`);k&&(this.maybePendingRemove(k)||(k.remove(),this.onNodeDiscarded(k)))})}),vt(n,L,{childrenOnly:n.getAttribute(C)===null,getNodeKey:u=>h.isPhxDestroyed(u)?null:u.id,skipFromChildren:u=>u.getAttribute(d)===fe,addChild:(u,E)=>{let D=E.id?this.streamInserts[E.id]:void 0;if(D===void 0)return u.appendChild(E);if(h.putPrivate(E,fe,!0),D===0)u.insertAdjacentElement("afterbegin",E);else if(D===-1)u.appendChild(E);else if(D>0){let k=Array.from(u.children)[D];u.insertBefore(E,k)}},onBeforeNodeAdded:u=>(this.trackBefore("added",u),u),onNodeAdded:u=>{u instanceof HTMLImageElement&&u.srcset?u.srcset=u.srcset:u instanceof HTMLVideoElement&&u.autoplay&&u.play(),h.isNowTriggerFormExternal(u,m)&&(x=u),h.discardError(n,u,f),(h.isPhxChild(u)&&e.ownsElement(u)||h.isPhxSticky(u)&&e.ownsElement(u.parentNode))&&this.trackAfter("phxChildAdded",u),g.push(u)},onNodeDiscarded:u=>this.onNodeDiscarded(u),onBeforeNodeDiscarded:u=>u.getAttribute&&u.getAttribute(st)!==null?!0:!(h.private(u,fe)||u.parentElement!==null&&h.isPhxUpdate(u.parentElement,d,["append","prepend"])&&u.id||this.maybePendingRemove(u)||this.skipCIDSibling(u)),onElUpdated:u=>{h.isNowTriggerFormExternal(u,m)&&(x=u),p.push(u),this.maybeReOrderStream(u)},onBeforeElUpdated:(u,E)=>{if(h.cleanChildNodes(E,d),this.skipCIDSibling(E)||h.isPhxSticky(u))return!1;if(h.isIgnored(u,d)||u.form&&u.form.isSameNode(x))return this.trackBefore("updated",u,E),h.mergeAttrs(u,E,{isIgnored:!0}),p.push(u),h.applyStickyOperations(u),!1;if(u.type==="number"&&u.validity&&u.validity.badInput)return!1;if(!h.syncPendingRef(u,E,c))return h.isUploadInput(u)&&(this.trackBefore("updated",u,E),p.push(u)),h.applyStickyOperations(u),!1;if(h.isPhxChild(E)){let k=u.getAttribute(H);return h.mergeAttrs(u,E,{exclude:[Y]}),k!==""&&u.setAttribute(H,k),u.setAttribute(W,this.rootID),h.applyStickyOperations(u),!1}return h.copyPrivates(E,u),h.discardError(n,E,f),o&&u.isSameNode(o)&&h.isFormInput(u)&&u.type!=="hidden"?(this.trackBefore("updated",u,E),h.mergeFocusedInput(u,E),h.syncAttrsToProps(u),p.push(u),h.applyStickyOperations(u),!1):(h.isPhxUpdate(E,d,["append","prepend"])&&_.push(new qe(u,E,E.getAttribute(d))),h.syncAttrsToProps(E),h.applyStickyOperations(E),this.trackBefore("updated",u,E),!0)}})}),t.isDebugEnabled()&&Vt(),_.length>0&&t.time("post-morph append/prepend restoration",()=>{_.forEach(u=>u.perform())}),t.silenceEvents(()=>h.restoreFocus(o,a,l)),h.dispatchEvent(document,"phx:update"),g.forEach(u=>this.trackAfter("added",u)),p.forEach(u=>this.trackAfter("updated",u)),this.transitionPendingRemoves(),x&&(t.unload(),x.submit()),!0}onNodeDiscarded(e){(h.isPhxChild(e)||h.isPhxSticky(e))&&this.liveSocket.destroyViewByEl(e),this.trackAfter("discarded",e)}maybePendingRemove(e){return e.getAttribute&&e.getAttribute(this.phxRemove)!==null?(this.pendingRemoves.push(e),!0):!1}maybeReOrderStream(e){let t=e.id?this.streamInserts[e.id]:void 0;if(t!==void 0){if(h.putPrivate(e,fe,!0),t===0)e.parentElement.insertBefore(e,e.parentElement.firstElementChild);else if(t>0){let i=Array.from(e.parentElement.children),s=i.indexOf(e);if(t>=i.length-1)e.parentElement.appendChild(e);else{let n=i[t];s>t?e.parentElement.insertBefore(e,n):e.parentElement.insertBefore(e,n.nextElementSibling)}}}}transitionPendingRemoves(){let{pendingRemoves:e,liveSocket:t}=this;e.length>0&&(t.transitionRemoves(e),t.requestDOMUpdate(()=>{e.forEach(i=>{let s=h.firstPhxChild(i);s&&t.destroyViewByEl(s),i.remove()}),this.trackAfter("transitionsDiscarded",e)}))}isCIDPatch(){return this.cidPatch}skipCIDSibling(e){return e.nodeType===Node.ELEMENT_NODE&&e.getAttribute(_e)!==null}targetCIDContainer(e){if(!this.isCIDPatch())return;let[t,...i]=h.findComponentNodeList(this.container,this.targetCID);return i.length===0&&h.childNodeLength(e)===1?t:t&&t.parentNode}buildDiffHTML(e,t,i,s){let n=this.isCIDPatch(),o=n&&s.getAttribute(C)===this.targetCID.toString();if(!n||o)return t;{let a=null,l=document.createElement("template");a=h.cloneNode(s);let[d,...f]=h.findComponentNodeList(a,this.targetCID);return l.innerHTML=t,f.forEach(c=>c.remove()),Array.from(a.childNodes).forEach(c=>{c.id&&c.nodeType===Node.ELEMENT_NODE&&c.getAttribute(C)!==this.targetCID.toString()&&(c.setAttribute(_e,""),c.innerHTML="")}),Array.from(l.content.childNodes).forEach(c=>a.insertBefore(c,d)),d.remove(),a.outerHTML}}indexOf(e,t){return Array.from(e.children).indexOf(t)}};var ke=class{static extract(e){let{[ft]:t,[ct]:i,[pt]:s}=e;return delete e[ft],delete e[ct],delete e[pt],{diff:e,title:s,reply:t||null,events:i||[]}}constructor(e,t){this.viewId=e,this.rendered={},this.mergeDiff(t)}parentViewId(){return this.viewId}toString(e){let[t,i]=this.recursiveToString(this.rendered,this.rendered[I],e);return[t,i]}recursiveToString(e,t=e[I],i){i=i?new Set(i):null;let s={buffer:"",components:t,onlyCids:i,streams:new Set};return this.toOutputBuffer(e,null,s),[s.buffer,s.streams]}componentCIDs(e){return Object.keys(e[I]||{}).map(t=>parseInt(t))}isComponentOnlyDiff(e){return e[I]?Object.keys(e).length===1:!1}getComponent(e,t){return e[I][t]}mergeDiff(e){let t=e[I],i={};if(delete e[I],this.rendered=this.mutableMerge(this.rendered,e),this.rendered[I]=this.rendered[I]||{},t){let s=this.rendered[I];for(let n in t)t[n]=this.cachedFindComponent(n,t[n],s,t,i);for(let n in t)s[n]=t[n];e[I]=t}}cachedFindComponent(e,t,i,s,n){if(n[e])return n[e];{let o,a,l=t[X];if($(l)){let d;l>0?d=this.cachedFindComponent(l,s[l],i,s,n):d=i[-l],a=d[X],o=this.cloneMerge(d,t),o[X]=a}else o=t[X]!==void 0?t:this.cloneMerge(i[e]||{},t);return n[e]=o,o}}mutableMerge(e,t){return t[X]!==void 0?t:(this.doMutableMerge(e,t),e)}doMutableMerge(e,t){for(let i in t){let s=t[i],n=e[i];ge(s)&&s[X]===void 0&&ge(n)?this.doMutableMerge(n,s):e[i]=s}}cloneMerge(e,t){let i=oe(oe({},e),t);for(let s in i){let n=t[s],o=e[s];ge(n)&&n[X]===void 0&&ge(o)&&(i[s]=this.cloneMerge(o,n))}return i}componentToString(e){let[t,i]=this.recursiveCIDToString(this.rendered[I],e);return[t,i]}pruneCIDs(e){e.forEach(t=>delete this.rendered[I][t])}get(){return this.rendered}isNewFingerprint(e={}){return!!e[X]}templateStatic(e,t){return typeof e=="number"?t[e]:e}toOutputBuffer(e,t,i){if(e[Ce])return this.comprehensionToBuffer(e,t,i);let{[X]:s}=e;s=this.templateStatic(s,t),i.buffer+=s[0];for(let n=1;n0||l.length>0)&&(e[Ce]=[],i.streams.add(o))}dynamicToBuffer(e,t,i){if(typeof e=="number"){let[s,n]=this.recursiveCIDToString(i.components,e,i.onlyCids);i.buffer+=s,i.streams=new Set([...i.streams,...n])}else ge(e)?this.toOutputBuffer(e,t,i):i.buffer+=e}recursiveCIDToString(e,t,i){let s=e[t]||P(`no component for CID ${t}`,e),n=document.createElement("template"),[o,a]=this.recursiveToString(s,e,i);n.innerHTML=o;let l=n.content,d=i&&!i.has(t),[f,c]=Array.from(l.childNodes).reduce(([m,g],p,_)=>p.nodeType===Node.ELEMENT_NODE?p.getAttribute(C)?[m,!0]:(p.setAttribute(C,t),p.id||(p.id=`${this.parentViewId()}-${t}-${_}`),d&&(p.setAttribute(_e,""),p.innerHTML=""),[!0,g]):p.nodeValue.trim()!==""?(P(`only HTML element tags are allowed at the root of components.
-got: "${g.nodeValue.trim()}"
+got: "${p.nodeValue.trim()}"
within:
-`,s.innerHTML.trim()),g.replaceWith(this.createSpan(g.nodeValue,t)),[!0,c]):(g.remove(),[m,c]),[!1,!1]);return!h&&!u?(y(`expected at least one HTML element tag inside a component, but the component is empty:
-`,s.innerHTML.trim()),this.createSpan("",t).outerHTML):(!h&&u&&y("expected at least one HTML element tag directly inside a component, but only subcomponents were found. A component must render at least one HTML tag directly inside itself.",s.innerHTML.trim()),s.innerHTML)}createSpan(e,t){let i=document.createElement("span");return i.innerText=e,i.setAttribute(C,t),i}};var hi=1,K=class{static makeID(){return hi++}static elementID(e){return e.phxHookId}constructor(e,t,i){this.__view=e,this.__liveSocket=e.liveSocket,this.__callbacks=i,this.__listeners=new Set,this.__isDisconnected=!1,this.el=t,this.el.phxHookId=this.constructor.makeID();for(let r in this.__callbacks)this[r]=this.__callbacks[r]}__mounted(){this.mounted&&this.mounted()}__updated(){this.updated&&this.updated()}__beforeUpdate(){this.beforeUpdate&&this.beforeUpdate()}__destroyed(){this.destroyed&&this.destroyed()}__reconnected(){this.__isDisconnected&&(this.__isDisconnected=!1,this.reconnected&&this.reconnected())}__disconnected(){this.__isDisconnected=!0,this.disconnected&&this.disconnected()}pushEvent(e,t={},i=function(){}){return this.__view.pushHookEvent(null,e,t,i)}pushEventTo(e,t,i={},r=function(){}){return this.__view.withinTargets(e,(s,o)=>s.pushHookEvent(o,t,i,r))}handleEvent(e,t){let i=(r,s)=>s?e:t(r.detail);return window.addEventListener(`phx:hook:${e}`,i),this.__listeners.add(i),i}removeHandleEvent(e){let t=e(null,!0);window.removeEventListener(`phx:hook:${t}`,e),this.__listeners.delete(e)}upload(e,t){return this.__view.dispatchUploads(e,t)}uploadTo(e,t,i){return this.__view.withinTargets(e,r=>r.dispatchUploads(t,i))}__cleanup__(){this.__listeners.forEach(e=>this.removeHandleEvent(e))}};var ht=(n,e={})=>{let t=new FormData(n),i=[];t.forEach((s,o,l)=>{s instanceof File&&i.push(o)}),i.forEach(s=>t.delete(s));let r=new URLSearchParams;for(let[s,o]of t.entries())r.append(s,o);for(let s in e)r.append(s,e[s]);return r.toString()},se=class{constructor(e,t,i,r){this.liveSocket=t,this.flash=r,this.parent=i,this.root=i?i.root:this,this.el=e,this.id=this.el.id,this.ref=0,this.childJoins=0,this.loaderTimer=null,this.pendingDiffs=[],this.pruningCIDs=[],this.redirect=!1,this.href=null,this.joinCount=this.parent?this.parent.joinCount-1:0,this.joinPending=!0,this.destroyed=!1,this.joinCallback=function(){},this.stopCallback=function(){},this.pendingJoinOps=this.parent?null:[],this.viewHooks={},this.uploaders={},this.formSubmits=[],this.children=this.parent?null:{},this.root.children[this.id]={},this.channel=this.liveSocket.channel(`lv:${this.id}`,()=>({redirect:this.redirect?this.href:void 0,url:this.redirect?void 0:this.href||void 0,params:this.connectParams(),session:this.getSession(),static:this.getStatic(),flash:this.flash})),this.showLoader(this.liveSocket.loaderTimeout),this.bindChannel()}setHref(e){this.href=e}setRedirect(e){this.redirect=!0,this.href=e}isMain(){return this.liveSocket.main===this}connectParams(){let e=this.liveSocket.params(this.el),t=a.all(document,`[${this.binding(mt)}]`).map(i=>i.src||i.href).filter(i=>typeof i=="string");return t.length>0&&(e._track_static=t),e._mounts=this.joinCount,e}isConnected(){return this.channel.canPush()}getSession(){return this.el.getAttribute(k)}getStatic(){let e=this.el.getAttribute(j);return e===""?null:e}destroy(e=function(){}){this.destroyAllChildren(),this.destroyed=!0,delete this.root.children[this.id],this.parent&&delete this.root.children[this.parent.id][this.id],clearTimeout(this.loaderTimer);let t=()=>{e();for(let i in this.viewHooks)this.destroyHook(this.viewHooks[i])};a.markPhxChildDestroyed(this.el),this.log("destroyed",()=>["the child has been removed from the parent"]),this.channel.leave().receive("ok",t).receive("error",t).receive("timeout",t)}setContainerClasses(...e){this.el.classList.remove(Ye,de,Ze),this.el.classList.add(...e)}isLoading(){return this.el.classList.contains(de)}showLoader(e){if(clearTimeout(this.loaderTimer),e)this.loaderTimer=setTimeout(()=>this.showLoader(),e);else{for(let t in this.viewHooks)this.viewHooks[t].__disconnected();this.setContainerClasses(de)}}hideLoader(){clearTimeout(this.loaderTimer),this.setContainerClasses(Ye)}triggerReconnected(){for(let e in this.viewHooks)this.viewHooks[e].__reconnected()}log(e,t){this.liveSocket.log(this,e,t)}withinTargets(e,t){if(e instanceof HTMLElement)return this.liveSocket.owner(e,i=>t(i,e));if(/^(0|[1-9]\d*)$/.test(e)){let i=a.findComponentNodeList(this.el,e);i.length===0?y(`no component found matching phx-target of ${e}`):t(this,i[0])}else{let i=Array.from(document.querySelectorAll(e));i.length===0&&y(`nothing found matching the phx-target selector "${e}"`),i.forEach(r=>this.liveSocket.owner(r,s=>t(s,r)))}}applyDiff(e,t,i){this.log(e,()=>["",J(t)]);let{diff:r,reply:s,events:o,title:l}=be.extract(t);return l&&a.putTitle(l),i({diff:r,reply:s,events:o}),s}onJoin(e){let{rendered:t,container:i}=e;if(i){let[r,s]=i;this.el=a.replaceRootContainer(this.el,r,s)}this.childJoins=0,this.joinPending=!0,this.flash=null,U.dropLocal(this.liveSocket.localStorage,window.location.pathname,Se),this.applyDiff("mount",t,({diff:r,events:s})=>{this.rendered=new be(this.id,r);let o=this.renderContainer(null,"join");this.dropPendingRefs();let l=this.formsForRecovery(o);this.joinCount++,l.length>0?l.forEach(([h,u,m],c)=>{this.pushFormRecovery(h,m,g=>{c===l.length-1&&this.onJoinComplete(g,o,s)})}):this.onJoinComplete(e,o,s)})}dropPendingRefs(){a.all(this.el,`[${L}]`,e=>e.removeAttribute(L))}onJoinComplete({live_patch:e},t,i){if(this.joinCount>1||this.parent&&!this.parent.isJoinPending())return this.applyJoinPatch(e,t,i);a.findPhxChildrenInFragment(t,this.id).filter(s=>{let o=s.id&&this.el.querySelector(`[id="${s.id}"]`),l=o&&o.getAttribute(j);return l&&s.setAttribute(j,l),this.joinChild(s)}).length===0?this.parent?(this.root.pendingJoinOps.push([this,()=>this.applyJoinPatch(e,t,i)]),this.parent.ackJoin(this)):(this.onAllChildJoinsComplete(),this.applyJoinPatch(e,t,i)):this.root.pendingJoinOps.push([this,()=>this.applyJoinPatch(e,t,i)])}attachTrueDocEl(){this.el=a.byId(this.id),this.el.setAttribute(W,this.root.id)}dispatchEvents(e){e.forEach(([t,i])=>{window.dispatchEvent(new CustomEvent(`phx:hook:${t}`,{detail:i}))})}applyJoinPatch(e,t,i){this.attachTrueDocEl();let r=new Y(this,this.el,this.id,t,null);if(r.markPrunableContentForRemoval(),this.performPatch(r,!1),this.joinNewChildren(),a.all(this.el,`[${this.binding(pe)}], [data-phx-${pe}]`,s=>{let o=this.addHook(s);o&&o.__mounted()}),this.joinPending=!1,this.dispatchEvents(i),this.applyPendingUpdates(),e){let{kind:s,to:o}=e;this.liveSocket.historyPatch(o,s)}this.hideLoader(),this.joinCount>1&&this.triggerReconnected(),this.stopCallback()}triggerBeforeUpdateHook(e,t){this.liveSocket.triggerDOM("onBeforeElUpdated",[e,t]);let i=this.getHook(e),r=i&&a.isIgnored(e,this.binding(ge));if(i&&!e.isEqualNode(t)&&!(r&&Dt(e.dataset,t.dataset)))return i.__beforeUpdate(),i}performPatch(e,t){let i=[],r=!1,s=new Set;return e.after("added",o=>{this.liveSocket.triggerDOM("onNodeAdded",[o]);let l=this.addHook(o);l&&l.__mounted()}),e.after("phxChildAdded",o=>r=!0),e.before("updated",(o,l)=>{this.triggerBeforeUpdateHook(o,l)&&s.add(o.id)}),e.after("updated",o=>{s.has(o.id)&&this.getHook(o).__updated()}),e.after("discarded",o=>{let l=this.componentID(o);G(l)&&i.indexOf(l)===-1&&i.push(l);let h=this.getHook(o);h&&this.destroyHook(h)}),e.perform(),t&&this.maybePushComponentsDestroyed(i),r}joinNewChildren(){a.findPhxChildren(this.el,this.id).forEach(e=>this.joinChild(e))}getChildById(e){return this.root.children[this.id][e]}getDescendentByEl(e){return e.id===this.id?this:this.children[e.getAttribute(V)][e.id]}destroyDescendent(e){for(let t in this.root.children)for(let i in this.root.children[t])if(i===e)return this.root.children[t][i].destroy()}joinChild(e){if(!this.getChildById(e.id)){let i=new se(e,this.liveSocket,this);return this.root.children[this.id][i.id]=i,i.join(),this.childJoins++,!0}}isJoinPending(){return this.joinPending}ackJoin(e){this.childJoins--,this.childJoins===0&&(this.parent?this.parent.ackJoin(this):this.onAllChildJoinsComplete())}onAllChildJoinsComplete(){this.joinCallback(),this.pendingJoinOps.forEach(([e,t])=>{e.isDestroyed()||t()}),this.pendingJoinOps=[]}update(e,t){if(this.isJoinPending()||this.liveSocket.hasPendingLink())return this.pendingDiffs.push({diff:e,events:t});this.rendered.mergeDiff(e);let i=!1;this.rendered.isComponentOnlyDiff(e)?this.liveSocket.time("component patch complete",()=>{a.findParentCIDs(this.el,this.rendered.componentCIDs(e)).forEach(s=>{this.componentPatch(this.rendered.getComponent(e,s),s)&&(i=!0)})}):ot(e)||this.liveSocket.time("full patch complete",()=>{let r=this.renderContainer(e,"update"),s=new Y(this,this.el,this.id,r,null);i=this.performPatch(s,!0)}),this.dispatchEvents(t),i&&this.joinNewChildren()}renderContainer(e,t){return this.liveSocket.time(`toString diff (${t})`,()=>{let i=this.el.tagName,r=e?this.rendered.componentCIDs(e).concat(this.pruningCIDs):null,s=this.rendered.toString(r);return`<${i}>${s}${i}>`})}componentPatch(e,t){if(ot(e))return!1;let i=this.rendered.componentToString(t),r=new Y(this,this.el,this.id,i,t);return this.performPatch(r,!0)}getHook(e){return this.viewHooks[K.elementID(e)]}addHook(e){if(K.elementID(e)||!e.getAttribute)return;let t=e.getAttribute(`data-phx-${pe}`)||e.getAttribute(this.binding(pe));if(t&&!this.ownsElement(e))return;let i=this.liveSocket.getHookCallbacks(t);if(i){e.id||y(`no DOM ID for hook "${t}". Hooks require a unique ID on each element.`,e);let r=new K(this,e,i);return this.viewHooks[K.elementID(r.el)]=r,r}else t!==null&&y(`unknown hook found for "${t}"`,e)}destroyHook(e){e.__destroyed(),e.__cleanup__(),delete this.viewHooks[K.elementID(e.el)]}applyPendingUpdates(){this.pendingDiffs.forEach(({diff:e,events:t})=>this.update(e,t)),this.pendingDiffs=[]}onChannel(e,t){this.liveSocket.onChannel(this.channel,e,i=>{this.isJoinPending()?this.root.pendingJoinOps.push([this,()=>t(i)]):t(i)})}bindChannel(){this.liveSocket.onChannel(this.channel,"diff",e=>{this.applyDiff("update",e,({diff:t,events:i})=>this.update(t,i))}),this.onChannel("redirect",({to:e,flash:t})=>this.onRedirect({to:e,flash:t})),this.onChannel("live_patch",e=>this.onLivePatch(e)),this.onChannel("live_redirect",e=>this.onLiveRedirect(e)),this.channel.onError(e=>this.onError(e)),this.channel.onClose(e=>this.onClose(e))}destroyAllChildren(){for(let e in this.root.children[this.id])this.getChildById(e).destroy()}onLiveRedirect(e){let{to:t,kind:i,flash:r}=e,s=this.expandURL(t);this.liveSocket.historyRedirect(s,i,r)}onLivePatch(e){let{to:t,kind:i}=e;this.href=this.expandURL(t),this.liveSocket.historyPatch(t,i)}expandURL(e){return e.startsWith("/")?`${window.location.protocol}//${window.location.host}${e}`:e}onRedirect({to:e,flash:t}){this.liveSocket.redirect(e,t)}isDestroyed(){return this.destroyed}join(e){this.parent||(this.stopCallback=this.liveSocket.withPageLoading({to:this.href,kind:"initial"})),this.joinCallback=()=>e&&e(this.joinCount),this.liveSocket.wrapPush(this,{timeout:!1},()=>this.channel.join().receive("ok",t=>!this.isDestroyed()&&this.onJoin(t)).receive("error",t=>!this.isDestroyed()&&this.onJoinError(t)).receive("timeout",()=>!this.isDestroyed()&&this.onJoinError({reason:"timeout"})))}onJoinError(e){return e.reason==="unauthorized"||e.reason==="stale"?(this.log("error",()=>["unauthorized live_redirect. Falling back to page request",e]),this.onRedirect({to:this.href})):((e.redirect||e.live_redirect)&&(this.joinPending=!1,this.channel.leave()),e.redirect?this.onRedirect(e.redirect):e.live_redirect?this.onLiveRedirect(e.live_redirect):(this.log("error",()=>["unable to join",e]),this.liveSocket.reloadWithJitter(this)))}onClose(e){if(!this.isDestroyed()){if(this.isJoinPending()&&document.visibilityState!=="hidden"||this.liveSocket.hasPendingLink()&&e!=="leave")return this.liveSocket.reloadWithJitter(this);this.destroyAllChildren(),this.liveSocket.dropActiveElement(this),document.activeElement&&document.activeElement.blur(),this.liveSocket.isUnloaded()&&this.showLoader(Ct)}}onError(e){this.onClose(e),this.log("error",()=>["view crashed",e]),this.liveSocket.isUnloaded()||this.displayError()}displayError(){this.isMain()&&a.dispatchEvent(window,"phx:page-loading-start",{to:this.href,kind:"error"}),this.showLoader(),this.setContainerClasses(de,Ze)}pushWithReply(e,t,i,r=function(){}){if(!this.isConnected())return;let[s,[o]]=e?e():[null,[]],l=function(){};return o&&o.getAttribute(this.binding(qe))!==null&&(l=this.liveSocket.withPageLoading({kind:"element",target:o})),typeof i.cid!="number"&&delete i.cid,this.liveSocket.wrapPush(this,{timeout:!0},()=>this.channel.push(t,i,It).receive("ok",h=>{let u=null;s!==null&&this.undoRefs(s),h.diff&&(u=this.applyDiff("update",h.diff,({diff:m,events:c})=>{this.update(m,c)})),h.redirect&&this.onRedirect(h.redirect),h.live_patch&&this.onLivePatch(h.live_patch),h.live_redirect&&this.onLiveRedirect(h.live_redirect),l(),r(h,u)}))}undoRefs(e){a.all(this.el,`[${L}="${e}"]`,t=>{let i=t.getAttribute(ue);t.removeAttribute(L),t.getAttribute(De)!==null&&(t.readOnly=!1,t.removeAttribute(De)),i!==null&&(t.disabled=i==="true",t.removeAttribute(ue)),ye.forEach(o=>a.removeClass(t,o));let r=t.getAttribute(fe);r!==null&&(t.innerText=r,t.removeAttribute(fe));let s=a.private(t,L);if(s){let o=this.triggerBeforeUpdateHook(t,s);Y.patchEl(t,s,this.liveSocket.getActiveElement()),o&&o.__updated(),a.deletePrivate(t,L)}})}putRef(e,t){let i=this.ref++,r=this.binding(ce);return e.forEach(s=>{s.classList.add(`phx-${t}-loading`),s.setAttribute(L,i);let o=s.getAttribute(r);o!==null&&(s.getAttribute(fe)||s.setAttribute(fe,s.innerText),s.innerText=o)}),[i,e]}componentID(e){let t=e.getAttribute&&e.getAttribute(C);return t?parseInt(t):null}targetComponentID(e,t){return e.getAttribute(this.binding("target"))?this.closestComponentID(t):null}closestComponentID(e){return e?x(e.closest(`[${C}]`),t=>this.ownsElement(t)&&this.componentID(t)):null}pushHookEvent(e,t,i,r){if(!this.isConnected())return this.log("hook",()=>["unable to push hook event. LiveView not connected",t,i]),!1;let[s,o]=this.putRef([],"hook");return this.pushWithReply(()=>[s,o],"event",{type:"hook",event:t,value:i,cid:this.closestComponentID(e)},(l,h)=>r(h,s)),s}extractMeta(e,t){let i=this.binding("value-");for(let r=0;r=0&&!e.checked&&delete t.value),t}pushEvent(e,t,i,r,s){this.pushWithReply(()=>this.putRef([t],e),"event",{type:e,event:r,value:this.extractMeta(t,s),cid:this.targetComponentID(t,i)})}pushKey(e,t,i,r,s){this.pushWithReply(()=>this.putRef([e],i),"event",{type:i,event:r,value:this.extractMeta(e,s),cid:this.targetComponentID(e,t)})}pushFileProgress(e,t,i,r=function(){}){this.liveSocket.withinOwners(e.form,(s,o)=>{s.pushWithReply(null,"progress",{event:e.getAttribute(s.binding(yt)),ref:e.getAttribute(R),entry_ref:t,progress:i,cid:s.targetComponentID(e.form,o)},r)})}pushInput(e,t,i,r,s,o){let l,h=G(i)?i:this.targetComponentID(e.form,t),u=()=>this.putRef([e,e.form],"change"),m=ht(e.form,{_target:s.name});a.isUploadInput(e)&&e.files&&e.files.length>0&&S.trackFiles(e,Array.from(e.files)),l=S.serializeUploads(e);let c={type:"form",event:r,value:m,uploads:l,cid:h};this.pushWithReply(u,"event",c,g=>{if(a.showError(e,this.liveSocket.binding(Ie)),a.isUploadInput(e)&&e.getAttribute("data-phx-auto-upload")!==null){if(S.filesAwaitingPreflight(e).length>0){let[E,P]=u();this.uploadFiles(e.form,t,E,h,b=>{o&&o(g),this.triggerAwaitingSubmit(e.form)})}}else o&&o(g)})}triggerAwaitingSubmit(e){let t=this.getScheduledSubmit(e);if(t){let[i,r,s]=t;this.cancelSubmit(e),s()}}getScheduledSubmit(e){return this.formSubmits.find(([t,i])=>t.isSameNode(e))}scheduleSubmit(e,t,i){if(this.getScheduledSubmit(e))return!0;this.formSubmits.push([e,t,i])}cancelSubmit(e){this.formSubmits=this.formSubmits.filter(([t,i,r])=>t.isSameNode(e)?(this.undoRefs(i),!1):!0)}pushFormSubmit(e,t,i,r){let s=c=>!(z(c,`${this.binding(ge)}=ignore`,c.form)||z(c,"data-phx-update=ignore",c.form)),o=c=>c.hasAttribute(this.binding(ce)),l=c=>c.tagName=="BUTTON",h=c=>["INPUT","TEXTAREA","SELECT"].includes(c.tagName),u=()=>{let c=Array.from(e.elements),g=c.filter(o),E=c.filter(l).filter(s),P=c.filter(h).filter(s);return E.forEach(b=>{b.setAttribute(ue,b.disabled),b.disabled=!0}),P.forEach(b=>{b.setAttribute(De,b.readOnly),b.readOnly=!0,b.files&&(b.setAttribute(ue,b.disabled),b.disabled=!0)}),e.setAttribute(this.binding(qe),""),this.putRef([e].concat(g).concat(E).concat(P),"submit")},m=this.targetComponentID(e,t);if(S.hasUploadsInProgress(e)){let[c,g]=u();return this.scheduleSubmit(e,c,()=>this.pushFormSubmit(e,t,i,r))}else if(S.inputsAwaitingPreflight(e).length>0){let[c,g]=u(),E=()=>[c,g];this.uploadFiles(e,t,c,m,P=>{let b=ht(e,{});this.pushWithReply(E,"event",{type:"form",event:i,value:b,cid:m},r)})}else{let c=ht(e);this.pushWithReply(u,"event",{type:"form",event:i,value:c,cid:m},r)}}uploadFiles(e,t,i,r,s){let o=this.joinCount,l=S.activeFileInputs(e),h=l.length;l.forEach(u=>{let m=new S(u,this,()=>{h--,h===0&&s()});this.uploaders[u]=m;let c=m.entries().map(E=>E.toPreflightPayload()),g={ref:u.getAttribute(R),entries:c,cid:this.targetComponentID(u.form,t)};this.log("upload",()=>["sending preflight request",g]),this.pushWithReply(null,"allow_upload",g,E=>{if(this.log("upload",()=>["got preflight response",E]),E.error){this.undoRefs(i);let[P,b]=E.error;this.log("upload",()=>[`error for entry ${P}`,b])}else{let P=b=>{this.channel.onError(()=>{this.joinCount===o&&b()})};m.initAdapterUpload(E,P,this.liveSocket)}})})}dispatchUploads(e,t){let i=a.findUploadInputs(this.el).filter(r=>r.name===e);i.length===0?y(`no live file inputs found matching the name "${e}"`):i.length>1?y(`duplicate live file inputs found matching the name "${e}"`):a.dispatchEvent(i[0],Ce,{files:t})}pushFormRecovery(e,t,i){this.liveSocket.withinOwners(e,(r,s)=>{let o=e.elements[0],l=e.getAttribute(this.binding(et))||e.getAttribute(this.binding("change"));r.pushInput(o,s,t,l,o,i)})}pushLinkPatch(e,t,i){let r=this.liveSocket.setPendingLink(e),s=t?()=>this.putRef([t],"click"):null;this.pushWithReply(s,"live_patch",{url:e},o=>{o.link_redirect?this.liveSocket.replaceMain(e,null,i,r):(this.liveSocket.commitPendingLink(r)&&(this.href=e),this.applyPendingUpdates(),i&&i(r))}).receive("timeout",()=>this.liveSocket.redirect(window.location.href))}formsForRecovery(e){if(this.joinCount===0)return[];let t=this.binding("change"),i=document.createElement("template");return i.innerHTML=e,a.all(this.el,`form[${t}]`).filter(r=>r.id&&this.ownsElement(r)).filter(r=>r.elements.length>0).filter(r=>r.getAttribute(this.binding(et))!=="ignore").map(r=>{let s=i.content.querySelector(`form[id="${r.id}"][${t}="${r.getAttribute(t)}"]`);return s?[r,s,this.componentID(s)]:[r,null,null]}).filter(([r,s,o])=>s)}maybePushComponentsDestroyed(e){let t=e.filter(i=>a.findComponentNodeList(this.el,i).length===0);t.length>0&&(this.pruningCIDs.push(...t),this.pushWithReply(null,"cids_will_destroy",{cids:t},()=>{this.pruningCIDs=this.pruningCIDs.filter(r=>t.indexOf(r)!==-1);let i=t.filter(r=>a.findComponentNodeList(this.el,r).length===0);i.length>0&&this.pushWithReply(null,"cids_destroyed",{cids:i},r=>{this.rendered.pruneCIDs(r.cids)})}))}ownsElement(e){return e.getAttribute(V)===this.id||x(e.closest(N),t=>t.id)===this.id}submitForm(e,t,i){a.putPrivate(e,xe,!0),this.liveSocket.blurActiveElement(this),this.pushFormSubmit(e,t,i,()=>{this.liveSocket.restorePreviouslyActiveFocus()})}binding(e){return this.liveSocket.binding(e)}};var Be=class{constructor(e,t,i={}){if(this.unloaded=!1,!t||t.constructor.name==="Object")throw new Error(`
+`,n.innerHTML.trim()),p.replaceWith(this.createSpan(p.nodeValue,t)),[!0,g]):(p.remove(),[m,g]),[!1,!1]);return!f&&!c?(P(`expected at least one HTML element tag inside a component, but the component is empty:
+`,n.innerHTML.trim()),[this.createSpan("",t).outerHTML,a]):!f&&c?(P("expected at least one HTML element tag directly inside a component, but only subcomponents were found. A component must render at least one HTML tag directly inside itself.",n.innerHTML.trim()),[n.innerHTML,a]):[n.innerHTML,a]}createSpan(e,t){let i=document.createElement("span");return i.innerText=e,i.setAttribute(C,t),i}};var wi=1,ee=class{static makeID(){return wi++}static elementID(e){return e.phxHookId}constructor(e,t,i){this.__view=e,this.liveSocket=e.liveSocket,this.__callbacks=i,this.__listeners=new Set,this.__isDisconnected=!1,this.el=t,this.el.phxHookId=this.constructor.makeID();for(let s in this.__callbacks)this[s]=this.__callbacks[s]}__mounted(){this.mounted&&this.mounted()}__updated(){this.updated&&this.updated()}__beforeUpdate(){this.beforeUpdate&&this.beforeUpdate()}__destroyed(){this.destroyed&&this.destroyed()}__reconnected(){this.__isDisconnected&&(this.__isDisconnected=!1,this.reconnected&&this.reconnected())}__disconnected(){this.__isDisconnected=!0,this.disconnected&&this.disconnected()}pushEvent(e,t={},i=function(){}){return this.__view.pushHookEvent(null,e,t,i)}pushEventTo(e,t,i={},s=function(){}){return this.__view.withinTargets(e,(n,o)=>n.pushHookEvent(o,t,i,s))}handleEvent(e,t){let i=(s,n)=>n?e:t(s.detail);return window.addEventListener(`phx:${e}`,i),this.__listeners.add(i),i}removeHandleEvent(e){let t=e(null,!0);window.removeEventListener(`phx:${t}`,e),this.__listeners.delete(e)}upload(e,t){return this.__view.dispatchUploads(e,t)}uploadTo(e,t,i){return this.__view.withinTargets(e,s=>s.dispatchUploads(t,i))}__cleanup__(){this.__listeners.forEach(e=>this.removeHandleEvent(e))}};var Ge=null,Pi={exec(r,e,t,i,s){let[n,o]=s||[null,{}];(e.charAt(0)==="["?JSON.parse(e):[[n,o]]).forEach(([l,d])=>{l===n&&o.data&&(d.data=Object.assign(d.data||{},o.data)),this.filterToEls(i,d).forEach(f=>{this[`exec_${l}`](r,e,t,i,f,d)})})},isVisible(r){return!!(r.offsetWidth||r.offsetHeight||r.getClientRects().length>0)},exec_dispatch(r,e,t,i,s,{to:n,event:o,detail:a,bubbles:l}){a=a||{},a.dispatcher=i,h.dispatchEvent(s,o,{detail:a,bubbles:l})},exec_push(r,e,t,i,s,n){if(!t.isConnected())return;let{event:o,data:a,target:l,page_loading:d,loading:f,value:c,dispatcher:m}=n,g={loading:f,value:c,target:l,page_loading:!!d},p=r==="change"&&m?m:i,_=l||p.getAttribute(t.binding("target"))||p;t.withinTargets(_,(x,L)=>{if(r==="change"){let{newCid:u,_target:E,callback:D}=n;E=E||(h.isFormInput(i)?i.name:void 0),E&&(g._target=E),x.pushInput(i,L,u,o||e,g,D)}else r==="submit"?x.submitForm(i,L,o||e,g):x.pushEvent(r,i,L,o||e,a,g)})},exec_navigate(r,e,t,i,s,{href:n,replace:o}){t.liveSocket.historyRedirect(n,o?"replace":"push")},exec_patch(r,e,t,i,s,{href:n,replace:o}){t.liveSocket.pushHistoryPatch(n,o?"replace":"push",i)},exec_focus(r,e,t,i,s){window.requestAnimationFrame(()=>Q.attemptFocus(s))},exec_focus_first(r,e,t,i,s){window.requestAnimationFrame(()=>Q.focusFirstInteractive(s)||Q.focusFirst(s))},exec_push_focus(r,e,t,i,s){window.requestAnimationFrame(()=>Ge=s||i)},exec_pop_focus(r,e,t,i,s){window.requestAnimationFrame(()=>{Ge&&Ge.focus(),Ge=null})},exec_add_class(r,e,t,i,s,{names:n,transition:o,time:a}){this.addOrRemoveClasses(s,n,[],o,a,t)},exec_remove_class(r,e,t,i,s,{names:n,transition:o,time:a}){this.addOrRemoveClasses(s,[],n,o,a,t)},exec_transition(r,e,t,i,s,{time:n,transition:o}){this.addOrRemoveClasses(s,[],[],o,n,t)},exec_toggle(r,e,t,i,s,{display:n,ins:o,outs:a,time:l}){this.toggle(r,t,s,n,o,a,l)},exec_show(r,e,t,i,s,{display:n,transition:o,time:a}){this.show(r,t,s,n,o,a)},exec_hide(r,e,t,i,s,{display:n,transition:o,time:a}){this.hide(r,t,s,n,o,a)},exec_set_attr(r,e,t,i,s,{attr:[n,o]}){this.setOrRemoveAttrs(s,[[n,o]],[])},exec_remove_attr(r,e,t,i,s,{attr:n}){this.setOrRemoveAttrs(s,[],[n])},show(r,e,t,i,s,n){this.isVisible(t)||this.toggle(r,e,t,i,s,null,n)},hide(r,e,t,i,s,n){this.isVisible(t)&&this.toggle(r,e,t,i,null,s,n)},toggle(r,e,t,i,s,n,o){let[a,l,d]=s||[[],[],[]],[f,c,m]=n||[[],[],[]];if(a.length>0||f.length>0)if(this.isVisible(t)){let g=()=>{this.addOrRemoveClasses(t,c,a.concat(l).concat(d)),window.requestAnimationFrame(()=>{this.addOrRemoveClasses(t,f,[]),window.requestAnimationFrame(()=>this.addOrRemoveClasses(t,m,c))})};t.dispatchEvent(new Event("phx:hide-start")),e.transition(o,g,()=>{this.addOrRemoveClasses(t,[],f.concat(m)),h.putSticky(t,"toggle",p=>p.style.display="none"),t.dispatchEvent(new Event("phx:hide-end"))})}else{if(r==="remove")return;let g=()=>{this.addOrRemoveClasses(t,l,f.concat(c).concat(m));let p=i||this.defaultDisplay(t);h.putSticky(t,"toggle",_=>_.style.display=p),window.requestAnimationFrame(()=>{this.addOrRemoveClasses(t,a,[]),window.requestAnimationFrame(()=>this.addOrRemoveClasses(t,d,l))})};t.dispatchEvent(new Event("phx:show-start")),e.transition(o,g,()=>{this.addOrRemoveClasses(t,[],a.concat(d)),t.dispatchEvent(new Event("phx:show-end"))})}else this.isVisible(t)?window.requestAnimationFrame(()=>{t.dispatchEvent(new Event("phx:hide-start")),h.putSticky(t,"toggle",g=>g.style.display="none"),t.dispatchEvent(new Event("phx:hide-end"))}):window.requestAnimationFrame(()=>{t.dispatchEvent(new Event("phx:show-start"));let g=i||this.defaultDisplay(t);h.putSticky(t,"toggle",p=>p.style.display=g),t.dispatchEvent(new Event("phx:show-end"))})},addOrRemoveClasses(r,e,t,i,s,n){let[o,a,l]=i||[[],[],[]];if(o.length>0){let d=()=>this.addOrRemoveClasses(r,a.concat(o),[]),f=()=>this.addOrRemoveClasses(r,e.concat(l),t.concat(o).concat(a));return n.transition(s,d,f)}window.requestAnimationFrame(()=>{let[d,f]=h.getSticky(r,"classes",[[],[]]),c=e.filter(_=>d.indexOf(_)<0&&!r.classList.contains(_)),m=t.filter(_=>f.indexOf(_)<0&&r.classList.contains(_)),g=d.filter(_=>t.indexOf(_)<0).concat(c),p=f.filter(_=>e.indexOf(_)<0).concat(m);h.putSticky(r,"classes",_=>(_.classList.remove(...p),_.classList.add(...g),[g,p]))})},setOrRemoveAttrs(r,e,t){let[i,s]=h.getSticky(r,"attrs",[[],[]]),n=e.map(([l,d])=>l).concat(t),o=i.filter(([l,d])=>!n.includes(l)).concat(e),a=s.filter(l=>!n.includes(l)).concat(t);h.putSticky(r,"attrs",l=>(a.forEach(d=>l.removeAttribute(d)),o.forEach(([d,f])=>l.setAttribute(d,f)),[o,a]))},hasAllClasses(r,e){return e.every(t=>r.classList.contains(t))},isToggledOut(r,e){return!this.isVisible(r)||this.hasAllClasses(r,e)},filterToEls(r,{to:e}){return e?h.all(document,e):[r]},defaultDisplay(r){return{tr:"table-row",td:"table-cell"}[r.tagName.toLowerCase()]||"block"}},F=Pi;var ze=(r,e,t=[])=>{let i=new FormData(r),s=[];i.forEach((o,a,l)=>{o instanceof File&&s.push(a)}),s.forEach(o=>i.delete(o));let n=new URLSearchParams;for(let[o,a]of i.entries())(t.length===0||t.indexOf(o)>=0)&&n.append(o,a);for(let o in e)n.append(o,e[o]);return n.toString()},be=class{constructor(e,t,i,s,n){this.isDead=!1,this.liveSocket=t,this.flash=s,this.parent=i,this.root=i?i.root:this,this.el=e,this.id=this.el.id,this.ref=0,this.childJoins=0,this.loaderTimer=null,this.pendingDiffs=[],this.pruningCIDs=[],this.redirect=!1,this.href=null,this.joinCount=this.parent?this.parent.joinCount-1:0,this.joinPending=!0,this.destroyed=!1,this.joinCallback=function(o){o&&o()},this.stopCallback=function(){},this.pendingJoinOps=this.parent?null:[],this.viewHooks={},this.uploaders={},this.formSubmits=[],this.children=this.parent?null:{},this.root.children[this.id]={},this.channel=this.liveSocket.channel(`lv:${this.id}`,()=>({redirect:this.redirect?this.href:void 0,url:this.redirect?void 0:this.href||void 0,params:this.connectParams(n),session:this.getSession(),static:this.getStatic(),flash:this.flash}))}setHref(e){this.href=e}setRedirect(e){this.redirect=!0,this.href=e}isMain(){return this.el.hasAttribute(he)}connectParams(e){let t=this.liveSocket.params(this.el),i=h.all(document,`[${this.binding(Ct)}]`).map(s=>s.src||s.href).filter(s=>typeof s=="string");return i.length>0&&(t._track_static=i),t._mounts=this.joinCount,t._live_referer=e,t}isConnected(){return this.channel.canPush()}getSession(){return this.el.getAttribute(H)}getStatic(){let e=this.el.getAttribute(Y);return e===""?null:e}destroy(e=function(){}){this.destroyAllChildren(),this.destroyed=!0,delete this.root.children[this.id],this.parent&&delete this.root.children[this.parent.id][this.id],clearTimeout(this.loaderTimer);let t=()=>{e();for(let i in this.viewHooks)this.destroyHook(this.viewHooks[i])};h.markPhxChildDestroyed(this.el),this.log("destroyed",()=>["the child has been removed from the parent"]),this.channel.leave().receive("ok",t).receive("error",t).receive("timeout",t)}setContainerClasses(...e){this.el.classList.remove(ot,Me,at),this.el.classList.add(...e)}showLoader(e){if(clearTimeout(this.loaderTimer),e)this.loaderTimer=setTimeout(()=>this.showLoader(),e);else{for(let t in this.viewHooks)this.viewHooks[t].__disconnected();this.setContainerClasses(Me)}}execAll(e){h.all(this.el,`[${e}]`,t=>this.liveSocket.execJS(t,t.getAttribute(e)))}hideLoader(){clearTimeout(this.loaderTimer),this.setContainerClasses(ot),this.execAll(this.binding("connected"))}triggerReconnected(){for(let e in this.viewHooks)this.viewHooks[e].__reconnected()}log(e,t){this.liveSocket.log(this,e,t)}transition(e,t,i=function(){}){this.liveSocket.transition(e,t,i)}withinTargets(e,t){if(e instanceof HTMLElement||e instanceof SVGElement)return this.liveSocket.owner(e,i=>t(i,e));if($(e))h.findComponentNodeList(this.el,e).length===0?P(`no component found matching phx-target of ${e}`):t(this,parseInt(e));else{let i=Array.from(document.querySelectorAll(e));i.length===0&&P(`nothing found matching the phx-target selector "${e}"`),i.forEach(s=>this.liveSocket.owner(s,n=>t(n,s)))}}applyDiff(e,t,i){this.log(e,()=>["",me(t)]);let{diff:s,reply:n,events:o,title:a}=ke.extract(t);i({diff:s,reply:n,events:o}),a&&window.requestAnimationFrame(()=>h.putTitle(a))}onJoin(e){let{rendered:t,container:i}=e;if(i){let[s,n]=i;this.el=h.replaceRootContainer(this.el,s,n)}this.childJoins=0,this.joinPending=!0,this.flash=null,N.dropLocal(this.liveSocket.localStorage,window.location.pathname,Oe),this.applyDiff("mount",t,({diff:s,events:n})=>{this.rendered=new ke(this.id,s);let[o,a]=this.renderContainer(null,"join");this.dropPendingRefs();let l=this.formsForRecovery(o);this.joinCount++,l.length>0?l.forEach(([d,f,c],m)=>{this.pushFormRecovery(d,c,g=>{m===l.length-1&&this.onJoinComplete(g,o,a,n)})}):this.onJoinComplete(e,o,a,n)})}dropPendingRefs(){h.all(document,`[${j}="${this.id}"][${O}]`,e=>{e.removeAttribute(O),e.removeAttribute(j)})}onJoinComplete({live_patch:e},t,i,s){if(this.joinCount>1||this.parent&&!this.parent.isJoinPending())return this.applyJoinPatch(e,t,i,s);h.findPhxChildrenInFragment(t,this.id).filter(o=>{let a=o.id&&this.el.querySelector(`[id="${o.id}"]`),l=a&&a.getAttribute(Y);return l&&o.setAttribute(Y,l),this.joinChild(o)}).length===0?this.parent?(this.root.pendingJoinOps.push([this,()=>this.applyJoinPatch(e,t,i,s)]),this.parent.ackJoin(this)):(this.onAllChildJoinsComplete(),this.applyJoinPatch(e,t,i,s)):this.root.pendingJoinOps.push([this,()=>this.applyJoinPatch(e,t,i,s)])}attachTrueDocEl(){this.el=h.byId(this.id),this.el.setAttribute(W,this.root.id)}execNewMounted(){h.all(this.el,`[${this.binding(ue)}], [data-phx-${ue}]`,e=>{this.maybeAddNewHook(e)}),h.all(this.el,`[${this.binding(dt)}]`,e=>this.maybeMounted(e))}applyJoinPatch(e,t,i,s){this.attachTrueDocEl();let n=new re(this,this.el,this.id,t,i,null);if(n.markPrunableContentForRemoval(),this.performPatch(n,!1),this.joinNewChildren(),this.execNewMounted(),this.joinPending=!1,this.liveSocket.dispatchEvents(s),this.applyPendingUpdates(),e){let{kind:o,to:a}=e;this.liveSocket.historyPatch(a,o)}this.hideLoader(),this.joinCount>1&&this.triggerReconnected(),this.stopCallback()}triggerBeforeUpdateHook(e,t){this.liveSocket.triggerDOM("onBeforeElUpdated",[e,t]);let i=this.getHook(e),s=i&&h.isIgnored(e,this.binding(ce));if(i&&!e.isEqualNode(t)&&!(s&&jt(e.dataset,t.dataset)))return i.__beforeUpdate(),i}maybeMounted(e){let t=e.getAttribute(this.binding(dt)),i=t&&h.private(e,"mounted");t&&!i&&(this.liveSocket.execJS(e,t),h.putPrivate(e,"mounted",!0))}maybeAddNewHook(e,t){let i=this.addHook(e);i&&i.__mounted()}performPatch(e,t){let i=[],s=!1,n=new Set;return e.after("added",o=>{this.liveSocket.triggerDOM("onNodeAdded",[o]),this.maybeAddNewHook(o),o.getAttribute&&this.maybeMounted(o)}),e.after("phxChildAdded",o=>{h.isPhxSticky(o)?this.liveSocket.joinRootViews():s=!0}),e.before("updated",(o,a)=>{this.triggerBeforeUpdateHook(o,a)&&n.add(o.id)}),e.after("updated",o=>{n.has(o.id)&&this.getHook(o).__updated()}),e.after("discarded",o=>{o.nodeType===Node.ELEMENT_NODE&&i.push(o)}),e.after("transitionsDiscarded",o=>this.afterElementsRemoved(o,t)),e.perform(),this.afterElementsRemoved(i,t),s}afterElementsRemoved(e,t){let i=[];e.forEach(s=>{let n=h.all(s,`[${C}]`),o=h.all(s,`[${this.binding(ue)}]`);n.concat(s).forEach(a=>{let l=this.componentID(a);$(l)&&i.indexOf(l)===-1&&i.push(l)}),o.concat(s).forEach(a=>{let l=this.getHook(a);l&&this.destroyHook(l)})}),t&&this.maybePushComponentsDestroyed(i)}joinNewChildren(){h.findPhxChildren(this.el,this.id).forEach(e=>this.joinChild(e))}getChildById(e){return this.root.children[this.id][e]}getDescendentByEl(e){return e.id===this.id?this:this.children[e.getAttribute(q)][e.id]}destroyDescendent(e){for(let t in this.root.children)for(let i in this.root.children[t])if(i===e)return this.root.children[t][i].destroy()}joinChild(e){if(!this.getChildById(e.id)){let i=new be(e,this.liveSocket,this);return this.root.children[this.id][i.id]=i,i.join(),this.childJoins++,!0}}isJoinPending(){return this.joinPending}ackJoin(e){this.childJoins--,this.childJoins===0&&(this.parent?this.parent.ackJoin(this):this.onAllChildJoinsComplete())}onAllChildJoinsComplete(){this.joinCallback(()=>{this.pendingJoinOps.forEach(([e,t])=>{e.isDestroyed()||t()}),this.pendingJoinOps=[]})}update(e,t){if(this.isJoinPending()||this.liveSocket.hasPendingLink()&&this.root.isMain())return this.pendingDiffs.push({diff:e,events:t});this.rendered.mergeDiff(e);let i=!1;this.rendered.isComponentOnlyDiff(e)?this.liveSocket.time("component patch complete",()=>{h.findParentCIDs(this.el,this.rendered.componentCIDs(e)).forEach(n=>{this.componentPatch(this.rendered.getComponent(e,n),n)&&(i=!0)})}):mt(e)||this.liveSocket.time("full patch complete",()=>{let[s,n]=this.renderContainer(e,"update"),o=new re(this,this.el,this.id,s,n,null);i=this.performPatch(o,!0)}),this.liveSocket.dispatchEvents(t),i&&this.joinNewChildren()}renderContainer(e,t){return this.liveSocket.time(`toString diff (${t})`,()=>{let i=this.el.tagName,s=e?this.rendered.componentCIDs(e).concat(this.pruningCIDs):null,[n,o]=this.rendered.toString(s);return[`<${i}>${n}${i}>`,o]})}componentPatch(e,t){if(mt(e))return!1;let[i,s]=this.rendered.componentToString(t),n=new re(this,this.el,this.id,i,s,t);return this.performPatch(n,!0)}getHook(e){return this.viewHooks[ee.elementID(e)]}addHook(e){if(ee.elementID(e)||!e.getAttribute)return;let t=e.getAttribute(`data-phx-${ue}`)||e.getAttribute(this.binding(ue));if(t&&!this.ownsElement(e))return;let i=this.liveSocket.getHookCallbacks(t);if(i){e.id||P(`no DOM ID for hook "${t}". Hooks require a unique ID on each element.`,e);let s=new ee(this,e,i);return this.viewHooks[ee.elementID(s.el)]=s,s}else t!==null&&P(`unknown hook found for "${t}"`,e)}destroyHook(e){e.__destroyed(),e.__cleanup__(),delete this.viewHooks[ee.elementID(e.el)]}applyPendingUpdates(){this.pendingDiffs.forEach(({diff:e,events:t})=>this.update(e,t)),this.pendingDiffs=[],this.eachChild(e=>e.applyPendingUpdates())}eachChild(e){let t=this.root.children[this.id]||{};for(let i in t)e(this.getChildById(i))}onChannel(e,t){this.liveSocket.onChannel(this.channel,e,i=>{this.isJoinPending()?this.root.pendingJoinOps.push([this,()=>t(i)]):this.liveSocket.requestDOMUpdate(()=>t(i))})}bindChannel(){this.liveSocket.onChannel(this.channel,"diff",e=>{this.liveSocket.requestDOMUpdate(()=>{this.applyDiff("update",e,({diff:t,events:i})=>this.update(t,i))})}),this.onChannel("redirect",({to:e,flash:t})=>this.onRedirect({to:e,flash:t})),this.onChannel("live_patch",e=>this.onLivePatch(e)),this.onChannel("live_redirect",e=>this.onLiveRedirect(e)),this.channel.onError(e=>this.onError(e)),this.channel.onClose(e=>this.onClose(e))}destroyAllChildren(){this.eachChild(e=>e.destroy())}onLiveRedirect(e){let{to:t,kind:i,flash:s}=e,n=this.expandURL(t);this.liveSocket.historyRedirect(n,i,s)}onLivePatch(e){let{to:t,kind:i}=e;this.href=this.expandURL(t),this.liveSocket.historyPatch(t,i)}expandURL(e){return e.startsWith("/")?`${window.location.protocol}//${window.location.host}${e}`:e}onRedirect({to:e,flash:t}){this.liveSocket.redirect(e,t)}isDestroyed(){return this.destroyed}joinDead(){this.isDead=!0}join(e){this.showLoader(this.liveSocket.loaderTimeout),this.bindChannel(),this.isMain()&&(this.stopCallback=this.liveSocket.withPageLoading({to:this.href,kind:"initial"})),this.joinCallback=t=>{t=t||function(){},e?e(this.joinCount,t):t()},this.liveSocket.wrapPush(this,{timeout:!1},()=>this.channel.join().receive("ok",t=>{this.isDestroyed()||this.liveSocket.requestDOMUpdate(()=>this.onJoin(t))}).receive("error",t=>!this.isDestroyed()&&this.onJoinError(t)).receive("timeout",()=>!this.isDestroyed()&&this.onJoinError({reason:"timeout"})))}onJoinError(e){if(e.reason==="reload")return this.log("error",()=>[`failed mount with ${e.status}. Falling back to page request`,e]),this.onRedirect({to:this.href});if(e.reason==="unauthorized"||e.reason==="stale")return this.log("error",()=>["unauthorized live_redirect. Falling back to page request",e]),this.onRedirect({to:this.href});if((e.redirect||e.live_redirect)&&(this.joinPending=!1,this.channel.leave()),e.redirect)return this.onRedirect(e.redirect);if(e.live_redirect)return this.onLiveRedirect(e.live_redirect);this.log("error",()=>["unable to join",e]),this.liveSocket.isConnected()&&this.liveSocket.reloadWithJitter(this)}onClose(e){if(!this.isDestroyed()){if(this.liveSocket.hasPendingLink()&&e!=="leave")return this.liveSocket.reloadWithJitter(this);this.destroyAllChildren(),this.liveSocket.dropActiveElement(this),document.activeElement&&document.activeElement.blur(),this.liveSocket.isUnloaded()&&this.showLoader(Ft)}}onError(e){this.onClose(e),this.liveSocket.isConnected()&&this.log("error",()=>["view crashed",e]),this.liveSocket.isUnloaded()||this.displayError()}displayError(){this.isMain()&&h.dispatchEvent(window,"phx:page-loading-start",{detail:{to:this.href,kind:"error"}}),this.showLoader(),this.setContainerClasses(Me,at),this.execAll(this.binding("disconnected"))}pushWithReply(e,t,i,s=function(){}){if(!this.isConnected())return;let[n,[o],a]=e?e():[null,[],{}],l=function(){};return(a.page_loading||o&&o.getAttribute(this.binding(nt))!==null)&&(l=this.liveSocket.withPageLoading({kind:"element",target:o})),typeof i.cid!="number"&&delete i.cid,this.liveSocket.wrapPush(this,{timeout:!0},()=>this.channel.push(t,i,Ut).receive("ok",d=>{let f=c=>{d.redirect&&this.onRedirect(d.redirect),d.live_patch&&this.onLivePatch(d.live_patch),d.live_redirect&&this.onLiveRedirect(d.live_redirect),n!==null&&this.undoRefs(n),l(),s(d,c)};d.diff?this.liveSocket.requestDOMUpdate(()=>{this.applyDiff("update",d.diff,({diff:c,reply:m,events:g})=>{this.update(c,g),f(m)})}):f(null)}))}undoRefs(e){!this.isConnected()||h.all(document,`[${j}="${this.id}"][${O}="${e}"]`,t=>{let i=t.getAttribute(Se);t.removeAttribute(O),t.removeAttribute(j),t.getAttribute($e)!==null&&(t.readOnly=!1,t.removeAttribute($e)),i!==null&&(t.disabled=i==="true",t.removeAttribute(Se)),He.forEach(o=>h.removeClass(t,o));let s=t.getAttribute(we);s!==null&&(t.innerText=s,t.removeAttribute(we));let n=h.private(t,O);if(n){let o=this.triggerBeforeUpdateHook(t,n);re.patchEl(t,n,this.liveSocket.getActiveElement()),o&&o.__updated(),h.deletePrivate(t,O)}})}putRef(e,t,i={}){let s=this.ref++,n=this.binding(ye);return i.loading&&(e=e.concat(h.all(document,i.loading))),e.forEach(o=>{o.classList.add(`phx-${t}-loading`),o.setAttribute(O,s),o.setAttribute(j,this.el.id);let a=o.getAttribute(n);a!==null&&(o.getAttribute(we)||o.setAttribute(we,o.innerText),a!==""&&(o.innerText=a),o.setAttribute("disabled",""))}),[s,e,i]}componentID(e){let t=e.getAttribute&&e.getAttribute(C);return t?parseInt(t):null}targetComponentID(e,t,i={}){if($(t))return t;let s=e.getAttribute(this.binding("target"));return $(s)?parseInt(s):t&&(s!==null||i.target)?this.closestComponentID(t):null}closestComponentID(e){return $(e)?e:e?B(e.closest(`[${C}]`),t=>this.ownsElement(t)&&this.componentID(t)):null}pushHookEvent(e,t,i,s){if(!this.isConnected())return this.log("hook",()=>["unable to push hook event. LiveView not connected",t,i]),!1;let[n,o,a]=this.putRef([],"hook");return this.pushWithReply(()=>[n,o,a],"event",{type:"hook",event:t,value:i,cid:this.closestComponentID(e)},(l,d)=>s(d,n)),n}extractMeta(e,t,i){let s=this.binding("value-");for(let n=0;n=0&&!e.checked&&delete t.value),i){t||(t={});for(let n in i)t[n]=i[n]}return t}pushEvent(e,t,i,s,n,o={}){this.pushWithReply(()=>this.putRef([t],e,o),"event",{type:e,event:s,value:this.extractMeta(t,n,o.value),cid:this.targetComponentID(t,i,o)})}pushFileProgress(e,t,i,s=function(){}){this.liveSocket.withinOwners(e.form,(n,o)=>{n.pushWithReply(null,"progress",{event:e.getAttribute(n.binding(Ht)),ref:e.getAttribute(M),entry_ref:t,progress:i,cid:n.targetComponentID(e.form,o)},s)})}pushInput(e,t,i,s,n,o){let a,l=$(i)?i:this.targetComponentID(e.form,t),d=()=>this.putRef([e,e.form],"change",n),f;e.getAttribute(this.binding("change"))?f=ze(e.form,{_target:n._target},[e.name]):f=ze(e.form,{_target:n._target}),h.isUploadInput(e)&&e.files&&e.files.length>0&&y.trackFiles(e,Array.from(e.files)),a=y.serializeUploads(e);let c={type:"form",event:s,value:f,uploads:a,cid:l};this.pushWithReply(d,"event",c,m=>{if(h.showError(e,this.liveSocket.binding(te)),h.isUploadInput(e)&&e.getAttribute("data-phx-auto-upload")!==null){if(y.filesAwaitingPreflight(e).length>0){let[g,p]=d();this.uploadFiles(e.form,t,g,l,_=>{o&&o(m),this.triggerAwaitingSubmit(e.form)})}}else o&&o(m)})}triggerAwaitingSubmit(e){let t=this.getScheduledSubmit(e);if(t){let[i,s,n,o]=t;this.cancelSubmit(e),o()}}getScheduledSubmit(e){return this.formSubmits.find(([t,i,s,n])=>t.isSameNode(e))}scheduleSubmit(e,t,i,s){if(this.getScheduledSubmit(e))return!0;this.formSubmits.push([e,t,i,s])}cancelSubmit(e){this.formSubmits=this.formSubmits.filter(([t,i,s])=>t.isSameNode(e)?(this.undoRefs(i),!1):!0)}disableForm(e,t={}){let i=c=>!(ie(c,`${this.binding(ce)}=ignore`,c.form)||ie(c,"data-phx-update=ignore",c.form)),s=c=>c.hasAttribute(this.binding(ye)),n=c=>c.tagName=="BUTTON",o=c=>["INPUT","TEXTAREA","SELECT"].includes(c.tagName),a=Array.from(e.elements),l=a.filter(s),d=a.filter(n).filter(i),f=a.filter(o).filter(i);return d.forEach(c=>{c.setAttribute(Se,c.disabled),c.disabled=!0}),f.forEach(c=>{c.setAttribute($e,c.readOnly),c.readOnly=!0,c.files&&(c.setAttribute(Se,c.disabled),c.disabled=!0)}),e.setAttribute(this.binding(nt),""),this.putRef([e].concat(l).concat(d).concat(f),"submit",t)}pushFormSubmit(e,t,i,s,n){let o=()=>this.disableForm(e,s),a=this.targetComponentID(e,t);if(y.hasUploadsInProgress(e)){let[l,d]=o(),f=()=>this.pushFormSubmit(e,t,i,s,n);return this.scheduleSubmit(e,l,s,f)}else if(y.inputsAwaitingPreflight(e).length>0){let[l,d]=o(),f=()=>[l,d,s];this.uploadFiles(e,t,l,a,c=>{let m=ze(e,{});this.pushWithReply(f,"event",{type:"form",event:i,value:m,cid:a},n)})}else{let l=ze(e,{});this.pushWithReply(o,"event",{type:"form",event:i,value:l,cid:a},n)}}uploadFiles(e,t,i,s,n){let o=this.joinCount,a=y.activeFileInputs(e),l=a.length;a.forEach(d=>{let f=new y(d,this,()=>{l--,l===0&&n()});this.uploaders[d]=f;let c=f.entries().map(g=>g.toPreflightPayload()),m={ref:d.getAttribute(M),entries:c,cid:this.targetComponentID(d.form,t)};this.log("upload",()=>["sending preflight request",m]),this.pushWithReply(null,"allow_upload",m,g=>{if(this.log("upload",()=>["got preflight response",g]),g.error){this.undoRefs(i);let[p,_]=g.error;this.log("upload",()=>[`error for entry ${p}`,_])}else{let p=_=>{this.channel.onError(()=>{this.joinCount===o&&_()})};f.initAdapterUpload(g,p,this.liveSocket)}})})}dispatchUploads(e,t){let i=h.findUploadInputs(this.el).filter(s=>s.name===e);i.length===0?P(`no live file inputs found matching the name "${e}"`):i.length>1?P(`duplicate live file inputs found matching the name "${e}"`):h.dispatchEvent(i[0],Fe,{detail:{files:t}})}pushFormRecovery(e,t,i){this.liveSocket.withinOwners(e,(s,n)=>{let o=Array.from(e.elements).find(l=>h.isFormInput(l)&&l.type!=="hidden"&&!l.hasAttribute(this.binding("change"))),a=e.getAttribute(this.binding(ht))||e.getAttribute(this.binding("change"));F.exec("change",a,s,o,["push",{_target:o.name,newCid:t,callback:i}])})}pushLinkPatch(e,t,i){let s=this.liveSocket.setPendingLink(e),n=t?()=>this.putRef([t],"click"):null,o=()=>this.liveSocket.redirect(window.location.href),a=this.pushWithReply(n,"live_patch",{url:e},l=>{this.liveSocket.requestDOMUpdate(()=>{l.link_redirect?this.liveSocket.replaceMain(e,null,i,s):(this.liveSocket.commitPendingLink(s)&&(this.href=e),this.applyPendingUpdates(),i&&i(s))})});a?a.receive("timeout",o):o()}formsForRecovery(e){if(this.joinCount===0)return[];let t=this.binding("change"),i=document.createElement("template");return i.innerHTML=e,h.all(this.el,`form[${t}]`).filter(s=>s.id&&this.ownsElement(s)).filter(s=>s.elements.length>0).filter(s=>s.getAttribute(this.binding(ht))!=="ignore").map(s=>{let n=i.content.querySelector(`form[id="${s.id}"][${t}="${s.getAttribute(t)}"]`);return n?[s,n,this.targetComponentID(n)]:[s,null,null]}).filter(([s,n,o])=>n)}maybePushComponentsDestroyed(e){let t=e.filter(i=>h.findComponentNodeList(this.el,i).length===0);t.length>0&&(this.pruningCIDs.push(...t),this.pushWithReply(null,"cids_will_destroy",{cids:t},()=>{this.pruningCIDs=this.pruningCIDs.filter(s=>t.indexOf(s)!==-1);let i=t.filter(s=>h.findComponentNodeList(this.el,s).length===0);i.length>0&&this.pushWithReply(null,"cids_destroyed",{cids:i},s=>{this.rendered.pruneCIDs(s.cids)})}))}ownsElement(e){let t=e.closest(J);return e.getAttribute(q)===this.id||t&&t.id===this.id||!t&&this.isDead}submitForm(e,t,i,s={}){h.putPrivate(e,de,!0);let n=this.liveSocket.binding(te),o=Array.from(e.elements);o.forEach(a=>h.putPrivate(a,de,!0)),this.liveSocket.blurActiveElement(this),this.pushFormSubmit(e,t,i,s,()=>{o.forEach(a=>h.showError(a,n)),this.liveSocket.restorePreviouslyActiveFocus()})}binding(e){return this.liveSocket.binding(e)}};var Ye=class{constructor(e,t,i={}){if(this.unloaded=!1,!t||t.constructor.name==="Object")throw new Error(`
a phoenix Socket must be provided as the second argument to the LiveSocket constructor. For example:
import {Socket} from "phoenix"
- import LiveSocket from "phoenix_live_view"
+ import {LiveSocket} from "phoenix_live_view"
let liveSocket = new LiveSocket("/live", Socket, {...})
- `);this.socket=new t(e,i),this.bindingPrefix=i.bindingPrefix||wt,this.opts=i,this.params=Ue(i.params||{}),this.viewLogger=i.viewLogger,this.metadataCallbacks=i.metadata||{},this.defaults=Object.assign(J(Lt),i.defaults||{}),this.activeElement=null,this.prevActive=null,this.silenced=!1,this.main=null,this.linkRef=1,this.roots={},this.href=window.location.href,this.pendingLink=null,this.currentLocation=J(window.location),this.hooks=i.hooks||{},this.uploaders=i.uploaders||{},this.loaderTimeout=i.loaderTimeout||Tt,this.localStorage=i.localStorage||window.localStorage,this.sessionStorage=i.sessionStorage||window.sessionStorage,this.boundTopLevelEvents=!1,this.domCallbacks=Object.assign({onNodeAdded:Ue(),onBeforeElUpdated:Ue()},i.dom||{}),window.addEventListener("pagehide",r=>{this.unloaded=!0}),this.socket.onOpen(()=>{this.isUnloaded()&&window.location.reload()})}isProfileEnabled(){return this.sessionStorage.getItem(He)==="true"}isDebugEnabled(){return this.sessionStorage.getItem(Re)==="true"}enableDebug(){this.sessionStorage.setItem(Re,"true")}enableProfiling(){this.sessionStorage.setItem(He,"true")}disableDebug(){this.sessionStorage.removeItem(Re)}disableProfiling(){this.sessionStorage.removeItem(He)}enableLatencySim(e){this.enableDebug(),console.log("latency simulator enabled for the duration of this browser session. Call disableLatencySim() to disable"),this.sessionStorage.setItem(Oe,e)}disableLatencySim(){this.sessionStorage.removeItem(Oe)}getLatencySim(){let e=this.sessionStorage.getItem(Oe);return e?parseInt(e):null}getSocket(){return this.socket}connect(){let e=()=>{this.joinRootViews()&&(this.bindTopLevelEvents(),this.socket.connect())};["complete","loaded","interactive"].indexOf(document.readyState)>=0?e():document.addEventListener("DOMContentLoaded",()=>e())}disconnect(e){this.socket.disconnect(e)}triggerDOM(e,t){this.domCallbacks[e](...t)}time(e,t){if(!this.isProfileEnabled()||!console.time)return t();console.time(e);let i=t();return console.timeEnd(e),i}log(e,t,i){if(this.viewLogger){let[r,s]=i();this.viewLogger(e,t,r,s)}else if(this.isDebugEnabled()){let[r,s]=i();xt(e,t,r,s)}}onChannel(e,t,i){e.on(t,r=>{let s=this.getLatencySim();s?(console.log(`simulating ${s}ms of latency from server to client`),setTimeout(()=>i(r),s)):i(r)})}wrapPush(e,t,i){let r=this.getLatencySim(),s=e.joinCount;if(!r)return t.timeout?i().receive("timeout",()=>{e.joinCount===s&&!e.isDestroyed()&&this.reloadWithJitter(e,()=>{this.log(e,"timeout",()=>["received timeout while communicating with server. Falling back to hard refresh for recovery"])})}):i();console.log(`simulating ${r}ms of latency from client to server`);let o={receives:[],receive(l,h){this.receives.push([l,h])}};return setTimeout(()=>{e.isDestroyed()||o.receives.reduce((l,[h,u])=>l.receive(h,u),i())},r),o}reloadWithJitter(e,t){e.destroy(),this.disconnect();let[i,r]=pt,s=Math.floor(Math.random()*(r-i+1))+i,o=U.updateLocal(this.localStorage,window.location.pathname,Se,0,l=>l+1);t?t():this.log(e,"join",()=>[`encountered ${o} consecutive reloads`]),o>We&&(this.log(e,"join",()=>[`exceeded ${We} consecutive reloads. Entering failsafe mode`]),s=gt),setTimeout(()=>{this.hasPendingLink()?window.location=this.pendingLink:window.location.reload()},s)}getHookCallbacks(e){return e&&e.startsWith("Phoenix.")?Ot[e.split(".")[1]]:this.hooks[e]}isUnloaded(){return this.unloaded}isConnected(){return this.socket.isConnected()}getBindingPrefix(){return this.bindingPrefix}binding(e){return`${this.getBindingPrefix()}${e}`}channel(e,t){return this.socket.channel(e,t)}joinRootViews(){let e=!1;return a.all(document,`${N}:not([${V}])`,t=>{if(!this.getRootById(t.id)){let i=this.newRootView(t);i.setHref(this.getHref()),i.join(),t.getAttribute(we)&&(this.main=i)}e=!0}),e}redirect(e,t){this.disconnect(),U.redirect(e,t)}replaceMain(e,t,i=null,r=this.setPendingLink(e)){let s=this.main.el,o=a.cloneNode(s,"");this.main.showLoader(this.loaderTimeout),this.main.destroy(),this.main=this.newRootView(o,t),this.main.setRedirect(e),this.main.join(l=>{l===1&&this.commitPendingLink(r)&&(s.replaceWith(o),i&&i())})}isPhxView(e){return e.getAttribute&&e.getAttribute(k)!==null}newRootView(e,t){let i=new se(e,this,null,t);return this.roots[i.id]=i,i}owner(e,t){let i=x(e.closest(N),r=>this.getViewByEl(r));i&&t(i)}withinOwners(e,t){this.owner(e,i=>{let r=e.getAttribute(this.binding("target"));r===null?t(i,e):i.withinTargets(r,t)})}getViewByEl(e){let t=e.getAttribute(W);return x(this.getRootById(t),i=>i.getDescendentByEl(e))}getRootById(e){return this.roots[e]}destroyAllViews(){for(let e in this.roots)this.roots[e].destroy(),delete this.roots[e]}destroyViewByEl(e){let t=this.getRootById(e.getAttribute(W));t&&t.destroyDescendent(e.id)}setActiveElement(e){if(this.activeElement===e)return;this.activeElement=e;let t=()=>{e===this.activeElement&&(this.activeElement=null),e.removeEventListener("mouseup",this),e.removeEventListener("touchend",this)};e.addEventListener("mouseup",t),e.addEventListener("touchend",t)}getActiveElement(){return document.activeElement===document.body?this.activeElement||document.activeElement:document.activeElement||document.body}dropActiveElement(e){this.prevActive&&e.ownsElement(this.prevActive)&&(this.prevActive=null)}restorePreviouslyActiveFocus(){this.prevActive&&this.prevActive!==document.body&&this.prevActive.focus()}blurActiveElement(){this.prevActive=this.getActiveElement(),this.prevActive!==document.body&&this.prevActive.blur()}bindTopLevelEvents(){this.boundTopLevelEvents||(this.boundTopLevelEvents=!0,document.body.addEventListener("click",function(){}),window.addEventListener("pageshow",e=>{e.persisted&&(this.getSocket().disconnect(),this.withPageLoading({to:window.location.href,kind:"redirect"}),window.location.reload())},!0),this.bindNav(),this.bindClicks(),this.bindForms(),this.bind({keyup:"keyup",keydown:"keydown"},(e,t,i,r,s,o,l)=>{let h=r.getAttribute(this.binding(St)),u=e.key&&e.key.toLowerCase();h&&h.toLowerCase()!==u||i.pushKey(r,s,t,o,oe({key:e.key},this.eventMeta(t,e,r)))}),this.bind({blur:"focusout",focus:"focusin"},(e,t,i,r,s,o,l)=>{l||i.pushEvent(t,r,s,o,this.eventMeta(t,e,r))}),this.bind({blur:"blur",focus:"focus"},(e,t,i,r,s,o,l)=>{l&&!l!=="window"&&i.pushEvent(t,r,s,o,this.eventMeta(t,e,r))}),window.addEventListener("dragover",e=>e.preventDefault()),window.addEventListener("drop",e=>{e.preventDefault();let t=x(z(e.target,this.binding(Ge)),s=>s.getAttribute(this.binding(Ge))),i=t&&document.getElementById(t),r=Array.from(e.dataTransfer.files||[]);!i||i.disabled||r.length===0||!(i.files instanceof FileList)||(S.trackFiles(i,r),i.dispatchEvent(new Event("input",{bubbles:!0})))}),this.on(Ce,e=>{let t=e.target;if(!a.isUploadInput(t))return;let i=Array.from(e.detail.files||[]).filter(r=>r instanceof File||r instanceof Blob);S.trackFiles(t,i),t.dispatchEvent(new Event("input",{bubbles:!0}))}))}eventMeta(e,t,i){let r=this.metadataCallbacks[e];return r?r(t,i):{}}setPendingLink(e){return this.linkRef++,this.pendingLink=e,this.linkRef}commitPendingLink(e){return this.linkRef!==e?!1:(this.href=this.pendingLink,this.pendingLink=null,!0)}getHref(){return this.href}hasPendingLink(){return!!this.pendingLink}bind(e,t){for(let i in e){let r=e[i];this.on(r,s=>{let o=this.binding(i),l=this.binding(`window-${i}`),h=s.target.getAttribute&&s.target.getAttribute(o);h?this.debounce(s.target,s,()=>{this.withinOwners(s.target,(u,m)=>{t(s,i,u,s.target,m,h,null)})}):a.all(document,`[${l}]`,u=>{let m=u.getAttribute(l);this.debounce(u,s,()=>{this.withinOwners(u,(c,g)=>{t(s,i,c,u,g,m,"window")})})})})}}bindClicks(){this.bindClick("click","click",!1),this.bindClick("mousedown","capture-click",!0)}bindClick(e,t,i){let r=this.binding(t);window.addEventListener(e,s=>{if(!this.isConnected())return;let o=null;i?o=s.target.matches(`[${r}]`)?s.target:s.target.querySelector(`[${r}]`):o=z(s.target,r);let l=o&&o.getAttribute(r);!l||(o.getAttribute("href")==="#"&&s.preventDefault(),this.debounce(o,s,()=>{this.withinOwners(o,(h,u)=>{h.pushEvent("click",o,u,l,this.eventMeta("click",s,o))})}))},i)}bindNav(){if(!U.canPushState())return;history.scrollRestoration&&(history.scrollRestoration="manual");let e=null;window.addEventListener("scroll",t=>{clearTimeout(e),e=setTimeout(()=>{U.updateCurrentState(i=>Object.assign(i,{scroll:window.scrollY}))},100)}),window.addEventListener("popstate",t=>{if(!this.registerNewLocation(window.location))return;let{type:i,id:r,root:s,scroll:o}=t.state||{},l=window.location.href;this.main.isConnected()&&i==="patch"&&r===this.main.id?this.main.pushLinkPatch(l,null):this.replaceMain(l,null,()=>{s&&this.replaceRootHistory(),typeof o=="number"&&setTimeout(()=>{window.scrollTo(0,o)},0)})},!1),window.addEventListener("click",t=>{let i=z(t.target,Te),r=i&&i.getAttribute(Te),s=t.metaKey||t.ctrlKey||t.button===1;if(!r||!this.isConnected()||!this.main||s)return;let o=i.href,l=i.getAttribute(vt);if(t.preventDefault(),this.pendingLink!==o)if(r==="patch")this.pushHistoryPatch(o,l,i);else if(r==="redirect")this.historyRedirect(o,l);else throw new Error(`expected ${Te} to be "patch" or "redirect", got: ${r}`)},!1)}withPageLoading(e,t){a.dispatchEvent(window,"phx:page-loading-start",e);let i=()=>a.dispatchEvent(window,"phx:page-loading-stop",e);return t?t(i):i}pushHistoryPatch(e,t,i){this.withPageLoading({to:e,kind:"patch"},r=>{this.main.pushLinkPatch(e,i,s=>{this.historyPatch(e,t,s),r()})})}historyPatch(e,t,i=this.setPendingLink(e)){!this.commitPendingLink(i)||(U.pushState(t,{type:"patch",id:this.main.id},e),this.registerNewLocation(window.location))}historyRedirect(e,t,i){let r=window.scrollY;this.withPageLoading({to:e,kind:"redirect"},s=>{this.replaceMain(e,i,()=>{U.pushState(t,{type:"redirect",id:this.main.id,scroll:r},e),this.registerNewLocation(window.location),s()})})}replaceRootHistory(){U.pushState("replace",{root:!0,type:"patch",id:this.main.id})}registerNewLocation(e){let{pathname:t,search:i}=this.currentLocation;return t+i===e.pathname+e.search?!1:(this.currentLocation=J(e),!0)}bindForms(){let e=0;this.on("submit",t=>{let i=t.target.getAttribute(this.binding("submit"));!i||(t.preventDefault(),t.target.disabled=!0,this.withinOwners(t.target,(r,s)=>r.submitForm(t.target,s,i)))},!1);for(let t of["change","input"])this.on(t,i=>{let r=i.target,s=r.form&&r.form.getAttribute(this.binding("change"));if(!s||r.type==="number"&&r.validity&&r.validity.badInput)return;let o=e;e++;let{at:l,type:h}=a.private(r,"prev-iteration")||{};l===o-1&&t!==h||(a.putPrivate(r,"prev-iteration",{at:o,type:t}),this.debounce(r,i,()=>{this.withinOwners(r.form,(u,m)=>{a.putPrivate(r,Le,!0),a.isTextualInput(r)||this.setActiveElement(r),u.pushInput(r,m,null,s,i.target)})}))},!1)}debounce(e,t,i){let r=this.binding(At),s=this.binding(Pt),o=this.defaults.debounce.toString(),l=this.defaults.throttle.toString();a.debounce(e,t,r,o,s,l,i)}silenceEvents(e){this.silenced=!0,e(),this.silenced=!1}on(e,t){window.addEventListener(e,i=>{this.silenced||t(i)})}};return di;})();
+ `);this.socket=new t(e,i),this.bindingPrefix=i.bindingPrefix||Mt,this.opts=i,this.params=je(i.params||{}),this.viewLogger=i.viewLogger,this.metadataCallbacks=i.metadata||{},this.defaults=Object.assign(me(Xt),i.defaults||{}),this.activeElement=null,this.prevActive=null,this.silenced=!1,this.main=null,this.outgoingMainEl=null,this.clickStartedAtTarget=null,this.linkRef=1,this.roots={},this.href=window.location.href,this.pendingLink=null,this.currentLocation=me(window.location),this.hooks=i.hooks||{},this.uploaders=i.uploaders||{},this.loaderTimeout=i.loaderTimeout||Nt,this.reloadWithJitterTimer=null,this.maxReloads=i.maxReloads||yt,this.reloadJitterMin=i.reloadJitterMin||wt,this.reloadJitterMax=i.reloadJitterMax||Pt,this.failsafeJitter=i.failsafeJitter||xt,this.localStorage=i.localStorage||window.localStorage,this.sessionStorage=i.sessionStorage||window.sessionStorage,this.boundTopLevelEvents=!1,this.domCallbacks=Object.assign({onNodeAdded:je(),onBeforeElUpdated:je()},i.dom||{}),this.transitions=new ei,window.addEventListener("pagehide",s=>{this.unloaded=!0}),this.socket.onOpen(()=>{this.isUnloaded()&&window.location.reload()})}isProfileEnabled(){return this.sessionStorage.getItem(Be)==="true"}isDebugEnabled(){return this.sessionStorage.getItem(Pe)==="true"}isDebugDisabled(){return this.sessionStorage.getItem(Pe)==="false"}enableDebug(){this.sessionStorage.setItem(Pe,"true")}enableProfiling(){this.sessionStorage.setItem(Be,"true")}disableDebug(){this.sessionStorage.setItem(Pe,"false")}disableProfiling(){this.sessionStorage.removeItem(Be)}enableLatencySim(e){this.enableDebug(),console.log("latency simulator enabled for the duration of this browser session. Call disableLatencySim() to disable"),this.sessionStorage.setItem(Ve,e)}disableLatencySim(){this.sessionStorage.removeItem(Ve)}getLatencySim(){let e=this.sessionStorage.getItem(Ve);return e?parseInt(e):null}getSocket(){return this.socket}connect(){window.location.hostname==="localhost"&&!this.isDebugDisabled()&&this.enableDebug();let e=()=>{this.joinRootViews()?(this.bindTopLevelEvents(),this.socket.connect()):this.main?this.socket.connect():this.bindTopLevelEvents({dead:!0}),this.joinDeadView()};["complete","loaded","interactive"].indexOf(document.readyState)>=0?e():document.addEventListener("DOMContentLoaded",()=>e())}disconnect(e){clearTimeout(this.reloadWithJitterTimer),this.socket.disconnect(e)}replaceTransport(e){clearTimeout(this.reloadWithJitterTimer),this.socket.replaceTransport(e),this.connect()}execJS(e,t,i=null){this.owner(e,s=>F.exec(i,t,s,e))}unload(){this.unloaded||(this.main&&this.isConnected()&&this.log(this.main,"socket",()=>["disconnect for page nav"]),this.unloaded=!0,this.destroyAllViews(),this.disconnect())}triggerDOM(e,t){this.domCallbacks[e](...t)}time(e,t){if(!this.isProfileEnabled()||!console.time)return t();console.time(e);let i=t();return console.timeEnd(e),i}log(e,t,i){if(this.viewLogger){let[s,n]=i();this.viewLogger(e,t,s,n)}else if(this.isDebugEnabled()){let[s,n]=i();Jt(e,t,s,n)}}requestDOMUpdate(e){this.transitions.after(e)}transition(e,t,i=function(){}){this.transitions.addTransition(e,t,i)}onChannel(e,t,i){e.on(t,s=>{let n=this.getLatencySim();n?setTimeout(()=>i(s),n):i(s)})}wrapPush(e,t,i){let s=this.getLatencySim(),n=e.joinCount;if(!s)return this.isConnected()&&t.timeout?i().receive("timeout",()=>{e.joinCount===n&&!e.isDestroyed()&&this.reloadWithJitter(e,()=>{this.log(e,"timeout",()=>["received timeout while communicating with server. Falling back to hard refresh for recovery"])})}):i();let o={receives:[],receive(a,l){this.receives.push([a,l])}};return setTimeout(()=>{e.isDestroyed()||o.receives.reduce((a,[l,d])=>a.receive(l,d),i())},s),o}reloadWithJitter(e,t){clearTimeout(this.reloadWithJitterTimer),this.disconnect();let i=this.reloadJitterMin,s=this.reloadJitterMax,n=Math.floor(Math.random()*(s-i+1))+i,o=N.updateLocal(this.localStorage,window.location.pathname,Oe,0,a=>a+1);o>this.maxReloads&&(n=this.failsafeJitter),this.reloadWithJitterTimer=setTimeout(()=>{e.isDestroyed()||e.isConnected()||(e.destroy(),t?t():this.log(e,"join",()=>[`encountered ${o} consecutive reloads`]),o>this.maxReloads&&this.log(e,"join",()=>[`exceeded ${this.maxReloads} consecutive reloads. Entering failsafe mode`]),this.hasPendingLink()?window.location=this.pendingLink:window.location.reload())},n)}getHookCallbacks(e){return e&&e.startsWith("Phoenix.")?Kt[e.split(".")[1]]:this.hooks[e]}isUnloaded(){return this.unloaded}isConnected(){return this.socket.isConnected()}getBindingPrefix(){return this.bindingPrefix}binding(e){return`${this.getBindingPrefix()}${e}`}channel(e,t){return this.socket.channel(e,t)}joinDeadView(){let e=document.body;if(e&&!this.isPhxView(e)&&!this.isPhxView(document.firstElementChild)){let t=this.newRootView(e);t.setHref(this.getHref()),t.joinDead(),this.main||(this.main=t),window.requestAnimationFrame(()=>t.execNewMounted())}}joinRootViews(){let e=!1;return h.all(document,`${J}:not([${q}])`,t=>{if(!this.getRootById(t.id)){let i=this.newRootView(t);i.setHref(this.getHref()),i.join(),t.hasAttribute(he)&&(this.main=i)}e=!0}),e}redirect(e,t){this.unload(),N.redirect(e,t)}replaceMain(e,t,i=null,s=this.setPendingLink(e)){let n=this.currentLocation.href;this.outgoingMainEl=this.outgoingMainEl||this.main.el;let o=h.cloneNode(this.outgoingMainEl,"");this.main.showLoader(this.loaderTimeout),this.main.destroy(),this.main=this.newRootView(o,t,n),this.main.setRedirect(e),this.transitionRemoves(),this.main.join((a,l)=>{a===1&&this.commitPendingLink(s)&&this.requestDOMUpdate(()=>{h.findPhxSticky(document).forEach(d=>o.appendChild(d)),this.outgoingMainEl.replaceWith(o),this.outgoingMainEl=null,i&&requestAnimationFrame(i),l()})})}transitionRemoves(e){let t=this.binding("remove");e=e||h.all(document,`[${t}]`),e.forEach(i=>{document.body.contains(i)&&this.execJS(i,i.getAttribute(t),"remove")})}isPhxView(e){return e.getAttribute&&e.getAttribute(H)!==null}newRootView(e,t,i){let s=new be(e,this,null,t,i);return this.roots[s.id]=s,s}owner(e,t){let i=B(e.closest(J),s=>this.getViewByEl(s))||this.main;i&&t(i)}withinOwners(e,t){this.owner(e,i=>t(i,e))}getViewByEl(e){let t=e.getAttribute(W);return B(this.getRootById(t),i=>i.getDescendentByEl(e))}getRootById(e){return this.roots[e]}destroyAllViews(){for(let e in this.roots)this.roots[e].destroy(),delete this.roots[e];this.main=null}destroyViewByEl(e){let t=this.getRootById(e.getAttribute(W));t&&t.id===e.id?(t.destroy(),delete this.roots[t.id]):t&&t.destroyDescendent(e.id)}setActiveElement(e){if(this.activeElement===e)return;this.activeElement=e;let t=()=>{e===this.activeElement&&(this.activeElement=null),e.removeEventListener("mouseup",this),e.removeEventListener("touchend",this)};e.addEventListener("mouseup",t),e.addEventListener("touchend",t)}getActiveElement(){return document.activeElement===document.body?this.activeElement||document.activeElement:document.activeElement||document.body}dropActiveElement(e){this.prevActive&&e.ownsElement(this.prevActive)&&(this.prevActive=null)}restorePreviouslyActiveFocus(){this.prevActive&&this.prevActive!==document.body&&this.prevActive.focus()}blurActiveElement(){this.prevActive=this.getActiveElement(),this.prevActive!==document.body&&this.prevActive.blur()}bindTopLevelEvents({dead:e}={}){this.boundTopLevelEvents||(this.boundTopLevelEvents=!0,this.socket.onClose(t=>{if(t&&t.code===1001)return this.unload();if(t&&t.code===1e3&&this.main)return this.reloadWithJitter(this.main)}),document.body.addEventListener("click",function(){}),window.addEventListener("pageshow",t=>{t.persisted&&(this.getSocket().disconnect(),this.withPageLoading({to:window.location.href,kind:"redirect"}),window.location.reload())},!0),e||this.bindNav(),this.bindClicks(),e||this.bindForms(),this.bind({keyup:"keyup",keydown:"keydown"},(t,i,s,n,o,a)=>{let l=n.getAttribute(this.binding(Ot)),d=t.key&&t.key.toLowerCase();if(l&&l.toLowerCase()!==d)return;let f=oe({key:t.key},this.eventMeta(i,t,n));F.exec(i,o,s,n,["push",{data:f}])}),this.bind({blur:"focusout",focus:"focusin"},(t,i,s,n,o,a)=>{if(!a){let l=oe({key:t.key},this.eventMeta(i,t,n));F.exec(i,o,s,n,["push",{data:l}])}}),this.bind({blur:"blur",focus:"focus"},(t,i,s,n,o,a,l)=>{if(l==="window"){let d=this.eventMeta(i,t,n);F.exec(i,a,s,n,["push",{data:d}])}}),window.addEventListener("dragover",t=>t.preventDefault()),window.addEventListener("drop",t=>{t.preventDefault();let i=B(ie(t.target,this.binding(rt)),o=>o.getAttribute(this.binding(rt))),s=i&&document.getElementById(i),n=Array.from(t.dataTransfer.files||[]);!s||s.disabled||n.length===0||!(s.files instanceof FileList)||(y.trackFiles(s,n,t.dataTransfer),s.dispatchEvent(new Event("input",{bubbles:!0})))}),this.on(Fe,t=>{let i=t.target;if(!h.isUploadInput(i))return;let s=Array.from(t.detail.files||[]).filter(n=>n instanceof File||n instanceof Blob);y.trackFiles(i,s),i.dispatchEvent(new Event("input",{bubbles:!0}))}))}eventMeta(e,t,i){let s=this.metadataCallbacks[e];return s?s(t,i):{}}setPendingLink(e){return this.linkRef++,this.pendingLink=e,this.linkRef}commitPendingLink(e){return this.linkRef!==e?!1:(this.href=this.pendingLink,this.pendingLink=null,!0)}getHref(){return this.href}hasPendingLink(){return!!this.pendingLink}bind(e,t){for(let i in e){let s=e[i];this.on(s,n=>{let o=this.binding(i),a=this.binding(`window-${i}`),l=n.target.getAttribute&&n.target.getAttribute(o);l?this.debounce(n.target,n,s,()=>{this.withinOwners(n.target,d=>{t(n,i,d,n.target,l,null)})}):h.all(document,`[${a}]`,d=>{let f=d.getAttribute(a);this.debounce(d,n,s,()=>{this.withinOwners(d,c=>{t(n,i,c,d,f,"window")})})})})}}bindClicks(){window.addEventListener("click",e=>this.clickStartedAtTarget=e.target),this.bindClick("click","click",!1),this.bindClick("mousedown","capture-click",!0)}bindClick(e,t,i){let s=this.binding(t);window.addEventListener(e,n=>{let o=null;if(i)o=n.target.matches(`[${s}]`)?n.target:n.target.querySelector(`[${s}]`);else{let l=this.clickStartedAtTarget||n.target;o=ie(l,s),this.dispatchClickAway(n,l),this.clickStartedAtTarget=null}let a=o&&o.getAttribute(s);if(!a){let l=n.target instanceof HTMLAnchorElement?n.target.getAttribute("href"):null;!i&&l!==null&&!h.wantsNewTab(n)&&h.isNewPageHref(l,window.location)&&this.unload();return}o.getAttribute("href")==="#"&&n.preventDefault(),this.debounce(o,n,"click",()=>{this.withinOwners(o,l=>{F.exec("click",a,l,o,["push",{data:this.eventMeta("click",n,o)}])})})},i)}dispatchClickAway(e,t){let i=this.binding("click-away");h.all(document,`[${i}]`,s=>{s.isSameNode(t)||s.contains(t)||this.withinOwners(e.target,n=>{let o=s.getAttribute(i);F.isVisible(s)&&F.exec("click",o,n,s,["push",{data:this.eventMeta("click",e,e.target)}])})})}bindNav(){if(!N.canPushState())return;history.scrollRestoration&&(history.scrollRestoration="manual");let e=null;window.addEventListener("scroll",t=>{clearTimeout(e),e=setTimeout(()=>{N.updateCurrentState(i=>Object.assign(i,{scroll:window.scrollY}))},100)}),window.addEventListener("popstate",t=>{if(!this.registerNewLocation(window.location))return;let{type:i,id:s,root:n,scroll:o}=t.state||{},a=window.location.href;this.requestDOMUpdate(()=>{this.main.isConnected()&&i==="patch"&&s===this.main.id?this.main.pushLinkPatch(a,null,()=>{this.maybeScroll(o)}):this.replaceMain(a,null,()=>{n&&this.replaceRootHistory(),this.maybeScroll(o)})})},!1),window.addEventListener("click",t=>{let i=ie(t.target,Ne),s=i&&i.getAttribute(Ne);if(!s||!this.isConnected()||!this.main||h.wantsNewTab(t))return;let n=i.href,o=i.getAttribute(Tt);t.preventDefault(),t.stopImmediatePropagation(),this.pendingLink!==n&&this.requestDOMUpdate(()=>{if(s==="patch")this.pushHistoryPatch(n,o,i);else if(s==="redirect")this.historyRedirect(n,o);else throw new Error(`expected ${Ne} to be "patch" or "redirect", got: ${s}`);let a=i.getAttribute(this.binding("click"));a&&this.requestDOMUpdate(()=>this.execJS(i,a,"click"))})},!1)}maybeScroll(e){typeof e=="number"&&requestAnimationFrame(()=>{window.scrollTo(0,e)})}dispatchEvent(e,t={}){h.dispatchEvent(window,`phx:${e}`,{detail:t})}dispatchEvents(e){e.forEach(([t,i])=>this.dispatchEvent(t,i))}withPageLoading(e,t){h.dispatchEvent(window,"phx:page-loading-start",{detail:e});let i=()=>h.dispatchEvent(window,"phx:page-loading-stop",{detail:e});return t?t(i):i}pushHistoryPatch(e,t,i){if(!this.isConnected())return N.redirect(e);this.withPageLoading({to:e,kind:"patch"},s=>{this.main.pushLinkPatch(e,i,n=>{this.historyPatch(e,t,n),s()})})}historyPatch(e,t,i=this.setPendingLink(e)){!this.commitPendingLink(i)||(N.pushState(t,{type:"patch",id:this.main.id},e),this.registerNewLocation(window.location))}historyRedirect(e,t,i){if(!this.isConnected())return N.redirect(e,i);if(/^\/$|^\/[^\/]+.*$/.test(e)){let{protocol:n,host:o}=window.location;e=`${n}//${o}${e}`}let s=window.scrollY;this.withPageLoading({to:e,kind:"redirect"},n=>{this.replaceMain(e,i,()=>{N.pushState(t,{type:"redirect",id:this.main.id,scroll:s},e),this.registerNewLocation(window.location),n()})})}replaceRootHistory(){N.pushState("replace",{root:!0,type:"patch",id:this.main.id})}registerNewLocation(e){let{pathname:t,search:i}=this.currentLocation;return t+i===e.pathname+e.search?!1:(this.currentLocation=me(e),!0)}bindForms(){let e=0,t=!1;this.on("submit",i=>{let s=i.target.getAttribute(this.binding("submit")),n=i.target.getAttribute(this.binding("change"));!t&&n&&!s&&(t=!0,i.preventDefault(),this.withinOwners(i.target,o=>{o.disableForm(i.target),window.requestAnimationFrame(()=>{h.isUnloadableFormSubmit(i)&&this.unload(),i.target.submit()})}))},!0),this.on("submit",i=>{let s=i.target.getAttribute(this.binding("submit"));if(!s){h.isUnloadableFormSubmit(i)&&this.unload();return}i.preventDefault(),i.target.disabled=!0,this.withinOwners(i.target,n=>{F.exec("submit",s,n,i.target,["push",{}])})},!1);for(let i of["change","input"])this.on(i,s=>{let n=this.binding("change"),o=s.target,a=o.getAttribute(n),l=o.form&&o.form.getAttribute(n),d=a||l;if(!d||o.type==="number"&&o.validity&&o.validity.badInput)return;let f=a?o:o.form,c=e;e++;let{at:m,type:g}=h.private(o,"prev-iteration")||{};m===c-1&&i!==g||(h.putPrivate(o,"prev-iteration",{at:c,type:i}),this.debounce(o,s,i,()=>{this.withinOwners(f,p=>{h.putPrivate(o,Ae,!0),h.isTextualInput(o)||this.setActiveElement(o),F.exec("change",d,p,o,["push",{_target:s.target.name,dispatcher:f}])})}))},!1);this.on("reset",i=>{let s=i.target;h.resetForm(s,this.binding(te));let n=Array.from(s.elements).find(o=>o.type==="reset");window.requestAnimationFrame(()=>{n.dispatchEvent(new Event("input",{bubbles:!0,cancelable:!1}))})})}debounce(e,t,i,s){if(i==="blur"||i==="focusout")return s();let n=this.binding(Dt),o=this.binding(Lt),a=this.defaults.debounce.toString(),l=this.defaults.throttle.toString();this.withinOwners(e,d=>{let f=()=>!d.isDestroyed()&&document.body.contains(e);h.debounce(e,t,n,a,o,l,f,()=>{s()})})}silenceEvents(e){this.silenced=!0,e(),this.silenced=!1}on(e,t){window.addEventListener(e,i=>{this.silenced||t(i)})}},ei=class{constructor(){this.transitions=new Set,this.pendingOps=[]}reset(){this.transitions.forEach(e=>{clearTimeout(e),this.transitions.delete(e)}),this.flushPendingOps()}after(e){this.size()===0?e():this.pushPendingOp(e)}addTransition(e,t,i){t();let s=setTimeout(()=>{this.transitions.delete(s),i(),this.flushPendingOps()},e);this.transitions.add(s)}pushPendingOp(e){this.pendingOps.push(e)}size(){return this.transitions.size}flushPendingOps(){if(this.size()>0)return;let e=this.pendingOps.shift();e&&(e(),this.flushPendingOps())}};return xi;})();
diff --git a/test/phoenix_component/components_test.exs b/test/phoenix_component/components_test.exs
new file mode 100644
index 0000000000..1781836667
--- /dev/null
+++ b/test/phoenix_component/components_test.exs
@@ -0,0 +1,669 @@
+defmodule Phoenix.LiveView.ComponentsTest do
+ use ExUnit.Case, async: true
+
+ import Phoenix.HTML.Form
+ import Phoenix.Component
+
+ defp render(template) do
+ template
+ |> Phoenix.HTML.Safe.to_iodata()
+ |> IO.iodata_to_binary()
+ end
+
+ defp parse(template) do
+ template
+ |> render()
+ |> Phoenix.LiveViewTest.DOM.parse()
+ end
+
+ describe "link patch" do
+ test "basic usage" do
+ assigns = %{}
+ dom = render(~H|<.link patch="/home">text|)
+
+ assert dom =~ ~s|data-phx-link="patch"|
+ assert dom =~ ~s|data-phx-link-state="push"|
+ assert dom =~ ~s|text|
+ refute dom =~ ~s|to="/|
+ end
+
+ test "forwards global dom attributes" do
+ assigns = %{}
+
+ dom =
+ render(~H|<.link patch="/" class="btn btn-large" data={[page_number: 2]}>next|)
+
+ assert dom =~ ~s|class="btn btn-large"|
+ assert dom =~ ~s|data-page-number="2"|
+ assert dom =~ ~s|data-phx-link="patch"|
+ assert dom =~ ~s|data-phx-link-state="push"|
+ end
+ end
+
+ describe "link navigate" do
+ test "basic usage" do
+ assigns = %{}
+ dom = render(~H|<.link navigate="/">text|)
+
+ assert dom =~ ~s|data-phx-link="redirect"|
+ assert dom =~ ~s|data-phx-link-state="push"|
+ assert dom =~ ~s|text|
+ refute dom =~ ~s|to="/|
+ end
+
+ test "forwards global dom attributes" do
+ assigns = %{}
+
+ dom =
+ render(~H|<.link navigate="/" class="btn btn-large" data={[page_number: 2]}>text|)
+
+ assert dom =~ ~s|class="btn btn-large"|
+ assert dom =~ ~s|data-page-number="2"|
+ assert dom =~ ~s|data-phx-link="redirect"|
+ assert dom =~ ~s|data-phx-link-state="push"|
+ end
+ end
+
+ describe "link href" do
+ test "basic usage" do
+ assigns = %{}
+ assert render(~H|<.link href="/">text|) == ~s|text |
+ end
+
+ test "arbitrary attrs" do
+ assigns = %{}
+
+ assert render(~H|<.link href="/" class="foo">text|) ==
+ ~s|text |
+ end
+
+ test "with no href" do
+ assigns = %{}
+
+ assert render(~H|<.link phx-click="click">text|) ==
+ ~s|text |
+ end
+
+ test "with # ref" do
+ assigns = %{}
+
+ assert render(~H|<.link href="#" phx-click="click">text|) ==
+ ~s|text |
+ end
+
+ test "with nil href" do
+ assigns = %{}
+
+ assert render(~H|<.link href={nil} phx-click="click">text|) ==
+ ~s|text |
+ end
+
+ test "csrf with get method" do
+ assigns = %{}
+
+ assert render(~H|<.link href="/" method="get">text|) ==
+ ~s|text |
+
+ assert render(~H|<.link href="/" method="get" csrf_token="123">text|) ==
+ ~s|text |
+ end
+
+ test "csrf with non-get method" do
+ assigns = %{}
+ csrf = Plug.CSRFProtection.get_csrf_token_for("/users")
+
+ assert render(~H|<.link href="/users" method="delete">delete|) ==
+ ~s|delete |
+
+ assert render(~H|<.link href="/users" method="delete" csrf_token={true}>delete|) ==
+ ~s|delete |
+
+ assert render(~H|<.link href="/users" method="delete" csrf_token={false}>delete|) ==
+ ~s|delete |
+ end
+
+ test "csrf with custom token" do
+ assigns = %{}
+
+ assert render(~H|<.link href="/users" method="post" csrf_token="123">delete|) ==
+ ~s|delete |
+ end
+
+ test "csrf with confirm" do
+ assigns = %{}
+
+ assert render(
+ ~H|<.link href="/users" method="post" csrf_token="123" data-confirm="are you sure?">delete|
+ ) ==
+ "delete "
+ end
+
+ test "js schemes" do
+ assigns = %{}
+
+ assert render(~H|<.link href={{:javascript, "alert('bad')"}}>js|) ==
+ ~s|js |
+ end
+
+ test "invalid schemes" do
+ assigns = %{}
+
+ assert_raise ArgumentError, ~r/unsupported scheme given to <.link>/, fn ->
+ render(~H|<.link href="javascript:alert('bad')">bad|) ==
+ ~s|delete |
+ end
+ end
+ end
+
+ describe "focus_wrap" do
+ test "basic usage" do
+ assigns = %{}
+
+ template = ~H"""
+ <.focus_wrap id="wrap" class="foo">
+ content
+
+ """
+
+ dom = render(template)
+
+ assert dom ==
+ ~s||
+ end
+ end
+
+ describe "live_title/2" do
+ test "dynamic attrs" do
+ assigns = %{prefix: "MyApp – ", title: "My Title"}
+
+ assert render(~H|<.live_title prefix={@prefix}><%= @title %>|) ==
+ ~s|MyApp – My Title |
+ end
+
+ test "prefix only" do
+ assigns = %{}
+
+ assert render(~H|<.live_title prefix="MyApp – ">My Title|) ==
+ ~s|MyApp – My Title |
+ end
+
+ test "suffix only" do
+ assigns = %{}
+
+ assert render(~H|<.live_title suffix=" – MyApp">My Title|) ==
+ ~s|My Title – MyApp |
+ end
+
+ test "prefix and suffix" do
+ assigns = %{}
+
+ assert render(~H|<.live_title prefix="Welcome: " suffix=" – MyApp">My Title|) ==
+ ~s|Welcome: My Title – MyApp |
+ end
+
+ test "without prefix or suffix" do
+ assigns = %{}
+
+ assert render(~H|<.live_title>My Title|) ==
+ ~s|My Title |
+ end
+ end
+
+ describe "dynamic_tag/1" do
+ test "ensures HTML safe tag names" do
+ assigns = %{}
+
+ assert_raise ArgumentError, ~r/expected dynamic_tag name to be safe HTML/, fn ->
+ render(~H|<.dynamic_tag name="p>"/>|)
+ end
+ end
+
+ test "escapes attribute values" do
+ assigns = %{}
+
+ assert render(
+ ~H|<.dynamic_tag name="p" class="">|
+ ) == ~s|
|
+ end
+
+ test "escapes attribute names" do
+ assigns = %{}
+
+ assert render(
+ ~H|<.dynamic_tag name="p" {%{"" => ""}}>|
+ ) == ~s|
|
+ end
+
+ test "with empty inner block" do
+ assigns = %{}
+
+ assert render(~H|<.dynamic_tag name="tr">|) == ~s| |
+
+ assert render(~H|<.dynamic_tag name="tr" class="foo">|) ==
+ ~s| |
+ end
+
+ test "with inner block" do
+ assigns = %{}
+
+ assert render(~H|<.dynamic_tag name="tr">content|) == ~s|content |
+
+ assert render(~H|<.dynamic_tag name="tr" class="foo">content|) ==
+ ~s|content |
+ end
+
+ test "self closing without inner block" do
+ assigns = %{}
+
+ assert render(~H|<.dynamic_tag name="br"/>|) == ~s| |
+ assert render(~H|<.dynamic_tag name="input" type="text"/>|) == ~s| |
+ end
+ end
+
+ describe "form" do
+ test "let without :for" do
+ assigns = %{}
+
+ template = ~H"""
+ <.form :let={f}>
+ <%= text_input f, :foo %>
+
+ """
+
+ assert parse(template)
+ end
+
+ test "generates form with prebuilt form" do
+ assigns = %{form: to_form(%{})}
+
+ template = ~H"""
+ <.form for={@form}>
+ <%= text_input @form, :foo %>
+
+ """
+
+ html = parse(template)
+
+ assert [
+ {"form", [],
+ [
+ {"input", [{"id", "foo"}, {"name", "foo"}, {"type", "text"}], []}
+ ]}
+ ] = html
+ end
+
+ test "generates form with prebuilt form and :as" do
+ assigns = %{form: to_form(%{}, as: :data)}
+
+ template = ~H"""
+ <.form :let={f} for={@form}>
+
+
+ """
+
+ html = parse(template)
+
+ assert [
+ {"form", [],
+ [
+ {"input", [{"id", "data_foo"}, {"name", "data[foo]"}, {"type", "text"}], []}
+ ]}
+ ] = html
+ end
+
+ test "generates form with prebuilt form and options" do
+ assigns = %{form: to_form(%{})}
+
+ template = ~H"""
+ <.form :let={f} for={@form} as="base" data-foo="bar" class="pretty" phx-change="valid">
+ <%= text_input f, :foo %>
+
+ """
+
+ html = parse(template)
+
+ assert [
+ {"form", [{"class", "pretty"}, {"data-foo", "bar"}, {"phx-change", "valid"}],
+ [
+ {"input", [{"id", "base_foo"}, {"name", "base[foo]"}, {"type", "text"}], []}
+ ]}
+ ] = html
+ end
+
+ test "generates form with prebuilt form and errors" do
+ assigns = %{form: to_form(%{})}
+
+ template = ~H"""
+ <.form :let={form} for={@form} errors={[name: "can't be blank"]}>
+ <%= inspect(form.errors) %>
+
+ """
+
+ html = parse(template)
+
+ assert [
+ {"form", [], ["\n \n \n \n [name: \"can't be blank\"]\n\n"]}
+ ] = html
+ end
+
+ test "generates form with form data" do
+ assigns = %{}
+
+ template = ~H"""
+ <.form :let={f} for={%{}}>
+ <%= text_input f, :foo %>
+
+ """
+
+ html = parse(template)
+
+ assert [
+ {"form", [],
+ [
+ {"input", [{"id", "foo"}, {"name", "foo"}, {"type", "text"}], []}
+ ]}
+ ] = html
+ end
+
+ test "does not raise when action is given and method is missing" do
+ assigns = %{}
+
+ template = ~H"""
+ <.form for={%{}} action="/">
+
+ """
+
+ html = parse(template)
+ assert [{"form", [{"action", "/"}, {"method", "post"}], _}] = html
+ end
+
+ test "generates a csrf_token if if an action is set" do
+ assigns = %{}
+
+ template = ~H"""
+ <.form :let={f} for={%{}} action="/">
+ <%= text_input f, :foo %>
+
+ """
+
+ html = parse(template)
+ csrf_token = Plug.CSRFProtection.get_csrf_token_for("/")
+
+ assert [
+ {"form", [{"action", "/"}, {"method", "post"}],
+ [
+ {"input",
+ [
+ {"name", "_csrf_token"},
+ {"type", "hidden"},
+ {"hidden", "hidden"},
+ {"value", ^csrf_token}
+ ], []},
+ {"input", [{"id", "foo"}, {"name", "foo"}, {"type", "text"}], []}
+ ]}
+ ] = html
+ end
+
+ test "does not generate csrf_token if method is not post or if no action" do
+ assigns = %{}
+
+ template = ~H"""
+ <.form :let={f} for={%{}} method="get" action="/">
+ <%= text_input f, :foo %>
+
+ """
+
+ html = parse(template)
+
+ assert [
+ {"form", [{"action", "/"}, {"method", "get"}],
+ [
+ {"input", [{"id", "foo"}, {"name", "foo"}, {"type", "text"}], []}
+ ]}
+ ] = html
+
+ template = ~H"""
+ <.form :let={f} for={%{}}>
+ <%= text_input f, :foo %>
+
+ """
+
+ html = parse(template)
+
+ assert [
+ {"form", [],
+ [
+ {"input", [{"id", "foo"}, {"name", "foo"}, {"type", "text"}], []}
+ ]}
+ ] = html
+ end
+
+ test "generates form with available options and custom attributes" do
+ assigns = %{}
+
+ template = ~H"""
+ <.form :let={user_form}
+ for={%{}}
+ id="form"
+ action="/"
+ method="put"
+ multipart
+ csrf_token="123"
+ as={:user}
+ errors={[name: "can't be blank"]}
+ data-foo="bar"
+ class="pretty"
+ phx-change="valid"
+ >
+ <%= text_input user_form, :foo %>
+ <%= inspect(user_form.errors) %>
+
+ """
+
+ html = parse(template)
+
+ assert [
+ {"form",
+ [
+ {"enctype", "multipart/form-data"},
+ {"action", "/"},
+ {"method", "post"},
+ {"class", "pretty"},
+ {"data-foo", "bar"},
+ {"id", "form"},
+ {"phx-change", "valid"}
+ ],
+ [
+ {"input",
+ [
+ {"name", "_method"},
+ {"type", "hidden"},
+ {"hidden", "hidden"},
+ {"value", "put"}
+ ], []},
+ {"input",
+ [
+ {"name", "_csrf_token"},
+ {"type", "hidden"},
+ {"hidden", "hidden"},
+ {"value", "123"}
+ ], []},
+ {"input", [{"id", "form_foo"}, {"name", "user[foo]"}, {"type", "text"}], []},
+ "\n [name: \"can't be blank\"]\n\n"
+ ]}
+ ] = html
+ end
+ end
+
+ describe "inputs_for" do
+ test "generates nested inputs with no options" do
+ assigns = %{}
+
+ template = ~H"""
+ <.form :let={f} as={:myform}>
+ <.inputs_for :let={finner} field={f[:inner]}}>
+ <%= text_input finner, :foo %>
+
+
+ """
+
+ html = parse(template)
+
+ assert [
+ {"form", [],
+ [
+ {"input",
+ [
+ {"id", "myform_inner_foo"},
+ {"name", "myform[inner][foo]"},
+ {"type", "text"}
+ ], []}
+ ]}
+ ] = html
+ end
+
+ test "with naming options" do
+ assigns = %{}
+
+ template = ~H"""
+ <.form :let={f} as={:myform}>
+ <.inputs_for :let={finner} field={f[:inner]}} id="test" as={:name}>
+ <%= text_input finner, :foo %>
+
+
+ """
+
+ html = parse(template)
+
+ assert [
+ {"form", [],
+ [
+ {"input",
+ [
+ {"id", "test_foo"},
+ {"name", "name[foo]"},
+ {"type", "text"}
+ ], []}
+ ]}
+ ] = html
+ end
+
+ test "with default map option" do
+ assigns = %{}
+
+ template = ~H"""
+ <.form :let={f} as={:myform}>
+ <.inputs_for :let={finner} field={f[:inner]}} default={%{foo: "123"}}>
+ <%= text_input finner, :foo %>
+
+
+ """
+
+ html = parse(template)
+
+ assert [
+ {"form", [],
+ [
+ {"input",
+ [
+ {"id", "myform_inner_foo"},
+ {"name", "myform[inner][foo]"},
+ {"type", "text"},
+ {"value", "123"}
+ ], []}
+ ]}
+ ] = html
+ end
+
+ test "with default list and list related options" do
+ assigns = %{}
+
+ template = ~H"""
+ <.form :let={f} as={:myform}>
+ <.inputs_for
+ :let={finner}
+ field={f[:inner]}}
+ default={[%{foo: "456"}]}
+ prepend={[%{foo: "123"}]}
+ append={[%{foo: "789"}]}
+ >
+ <%= text_input finner, :foo %>
+
+
+ """
+
+ html = parse(template)
+
+ assert [
+ {"form", [],
+ [
+ {
+ "input",
+ [
+ {"id", "myform_inner_0_foo"},
+ {"name", "myform[inner][0][foo]"},
+ {"type", "text"},
+ {"value", "123"}
+ ],
+ []
+ },
+ {"input",
+ [
+ {"id", "myform_inner_1_foo"},
+ {"name", "myform[inner][1][foo]"},
+ {"type", "text"},
+ {"value", "456"}
+ ], []},
+ {"input",
+ [
+ {"id", "myform_inner_2_foo"},
+ {"name", "myform[inner][2][foo]"},
+ {"type", "text"},
+ {"value", "789"}
+ ], []}
+ ]}
+ ] = html
+ end
+ end
+
+ describe "live_file_input/1" do
+ test "renders attributes" do
+ assigns = %{
+ conf: %Phoenix.LiveView.UploadConfig{
+ auto_upload?: true,
+ entries: [%{preflighted?: false, done?: false, ref: "foo"}]
+ }
+ }
+
+ assert render(
+ ~H|<.live_file_input upload={@conf} class={""} />|
+ ) ==
+ ~s| |
+ end
+ end
+
+ describe "intersperse" do
+ test "generates form with no options" do
+ assigns = %{}
+
+ template = ~H"""
+ <.intersperse :let={item} enum={[1, 2, 3]}>
+ <:separator>|
+ Item<%= item %>
+
+ """
+
+ assert render(template) ==
+ "\n \n \n Item1\n\n \n\n \n | \n \n\n \n \n Item2\n\n \n\n \n | \n \n\n \n \n Item3\n\n \n"
+
+ template = ~H"""
+ <.intersperse :let={item} enum={[1]}>
+ <:separator>|
+ Item<%= item %>
+
+ """
+
+ assert render(template) == "\n \n \n Item1\n\n \n"
+ end
+ end
+end
diff --git a/test/phoenix_component/declarative_assigns_test.exs b/test/phoenix_component/declarative_assigns_test.exs
new file mode 100644
index 0000000000..3f5a224cc1
--- /dev/null
+++ b/test/phoenix_component/declarative_assigns_test.exs
@@ -0,0 +1,1572 @@
+defmodule Phoenix.ComponentDeclarativeAssignsTest do
+ use ExUnit.Case, async: true
+
+ import Phoenix.LiveViewTest
+ use Phoenix.Component
+
+ defp render(mod, func, assigns) do
+ mod
+ |> apply(func, [Map.put(assigns, :__changed__, %{})])
+ |> h2s()
+ end
+
+ defp h2s(template) do
+ template
+ |> Phoenix.HTML.Safe.to_iodata()
+ |> IO.iodata_to_binary()
+ end
+
+ test "__global__?" do
+ assert Phoenix.Component.Declarative.__global__?("id")
+ refute Phoenix.Component.Declarative.__global__?("idnope")
+ refute Phoenix.Component.Declarative.__global__?("not-global")
+
+ # prefixes
+ assert Phoenix.Component.Declarative.__global__?("aria-label")
+ assert Phoenix.Component.Declarative.__global__?("data-whatever")
+ assert Phoenix.Component.Declarative.__global__?("phx-click")
+ end
+
+ defmodule RemoteFunctionComponentWithAttrs do
+ use Phoenix.Component
+
+ attr :id, :any, required: true
+ slot :inner_block
+ def remote(assigns), do: ~H[]
+ end
+
+ defmodule FunctionComponentWithAttrs do
+ use Phoenix.Component
+ import RemoteFunctionComponentWithAttrs
+ alias RemoteFunctionComponentWithAttrs, as: Remote
+
+ def func1_line, do: __ENV__.line
+ attr :id, :any, required: true
+ attr :email, :string, default: nil
+ slot :inner_block
+ def func1(assigns), do: ~H[]
+
+ def func2_line, do: __ENV__.line
+ attr :name, :any, required: true
+ attr :age, :integer, default: 0
+ def func2(assigns), do: ~H[]
+
+ def with_global_line, do: __ENV__.line
+ attr :id, :string, default: "container"
+ def with_global(assigns), do: ~H[<.button id={@id} class="btn" aria-hidden="true"/>]
+
+ attr :id, :string, required: true
+ attr :rest, :global
+ def button(assigns), do: ~H[ ]
+
+ def button_with_defaults_line, do: __ENV__.line
+ attr :rest, :global, default: %{class: "primary"}
+ def button_with_defaults(assigns), do: ~H[ ]
+
+ def button_with_values_line, do: __ENV__.line
+ attr :text, :string, values: ["Save", "Cancel"]
+ def button_with_values(assigns), do: ~H[<%= @text %> ]
+
+ def button_with_values_and_default_1_line, do: __ENV__.line
+ attr :text, :string, values: ["Save", "Cancel"], default: "Save"
+ def button_with_values_and_default_1(assigns), do: ~H[<%= @text %> ]
+
+ def button_with_values_and_default_2_line, do: __ENV__.line
+ attr :text, :string, default: "Save", values: ["Save", "Cancel"]
+ def button_with_values_and_default_2(assigns), do: ~H[<%= @text %> ]
+
+ def button_with_examples_line, do: __ENV__.line
+ attr :text, :string, examples: ["Save", "Cancel"]
+ def button_with_examples(assigns), do: ~H[<%= @text %> ]
+
+ def render_line, do: __ENV__.line
+
+ def render(assigns) do
+ ~H"""
+
+ <.func1 id="1"/>
+
+ <.func1 id="2" email="foo@bar">CONTENT
+
+ <.remote id="3"/>
+
+
+
+ CONTENT
+
+
+ """
+ end
+ end
+
+ test "merges globals" do
+ assert render(FunctionComponentWithAttrs, :with_global, %{}) ==
+ " "
+ end
+
+ test "merges globals with defaults" do
+ assigns = %{id: "btn", style: "display: none;"}
+
+ assert render(FunctionComponentWithAttrs, :button_with_defaults, assigns) ==
+ " "
+
+ assert render(FunctionComponentWithAttrs, :button_with_defaults, %{class: "hidden"}) ==
+ " "
+
+ # caller passes no globals
+ assert render(FunctionComponentWithAttrs, :button_with_defaults, %{}) ==
+ " "
+ end
+
+ test "stores attributes definitions" do
+ func1_line = FunctionComponentWithAttrs.func1_line()
+ func2_line = FunctionComponentWithAttrs.func2_line()
+ with_global_line = FunctionComponentWithAttrs.with_global_line()
+ button_with_defaults_line = FunctionComponentWithAttrs.button_with_defaults_line()
+ button_with_values_line = FunctionComponentWithAttrs.button_with_values_line()
+
+ button_with_values_and_default_1_line =
+ FunctionComponentWithAttrs.button_with_values_and_default_1_line()
+
+ button_with_values_and_default_2_line =
+ FunctionComponentWithAttrs.button_with_values_and_default_2_line()
+
+ button_with_examples_line = FunctionComponentWithAttrs.button_with_examples_line()
+
+ assert FunctionComponentWithAttrs.__components__() == %{
+ func1: %{
+ kind: :def,
+ attrs: [
+ %{
+ name: :email,
+ type: :string,
+ opts: [default: nil],
+ required: false,
+ doc: nil,
+ slot: nil,
+ line: func1_line + 2
+ },
+ %{
+ name: :id,
+ type: :any,
+ opts: [],
+ required: true,
+ doc: nil,
+ slot: nil,
+ line: func1_line + 1
+ }
+ ],
+ slots: [
+ %{
+ attrs: [],
+ doc: nil,
+ line: func1_line + 3,
+ name: :inner_block,
+ opts: [],
+ required: false
+ }
+ ]
+ },
+ func2: %{
+ kind: :def,
+ attrs: [
+ %{
+ name: :age,
+ type: :integer,
+ opts: [default: 0],
+ required: false,
+ doc: nil,
+ slot: nil,
+ line: func2_line + 2
+ },
+ %{
+ name: :name,
+ type: :any,
+ opts: [],
+ required: true,
+ doc: nil,
+ slot: nil,
+ line: func2_line + 1
+ }
+ ],
+ slots: []
+ },
+ with_global: %{
+ kind: :def,
+ attrs: [
+ %{
+ line: with_global_line + 1,
+ name: :id,
+ opts: [default: "container"],
+ required: false,
+ doc: nil,
+ slot: nil,
+ type: :string
+ }
+ ],
+ slots: []
+ },
+ button_with_defaults: %{
+ kind: :def,
+ attrs: [
+ %{
+ line: button_with_defaults_line + 1,
+ name: :rest,
+ opts: [default: %{class: "primary"}],
+ required: false,
+ doc: nil,
+ slot: nil,
+ type: :global
+ }
+ ],
+ slots: []
+ },
+ button: %{
+ kind: :def,
+ attrs: [
+ %{
+ line: with_global_line + 4,
+ name: :id,
+ opts: [],
+ required: true,
+ doc: nil,
+ slot: nil,
+ type: :string
+ },
+ %{
+ line: with_global_line + 5,
+ name: :rest,
+ opts: [],
+ required: false,
+ doc: nil,
+ slot: nil,
+ type: :global
+ }
+ ],
+ slots: []
+ },
+ button_with_values: %{
+ kind: :def,
+ attrs: [
+ %{
+ line: button_with_values_line + 1,
+ name: :text,
+ opts: [values: ["Save", "Cancel"]],
+ required: false,
+ doc: nil,
+ slot: nil,
+ type: :string
+ }
+ ],
+ slots: []
+ },
+ button_with_values_and_default_1: %{
+ kind: :def,
+ attrs: [
+ %{
+ line: button_with_values_and_default_1_line + 1,
+ name: :text,
+ opts: [values: ["Save", "Cancel"], default: "Save"],
+ required: false,
+ doc: nil,
+ slot: nil,
+ type: :string
+ }
+ ],
+ slots: []
+ },
+ button_with_values_and_default_2: %{
+ kind: :def,
+ attrs: [
+ %{
+ line: button_with_values_and_default_2_line + 1,
+ name: :text,
+ opts: [default: "Save", values: ["Save", "Cancel"]],
+ required: false,
+ doc: nil,
+ slot: nil,
+ type: :string
+ }
+ ],
+ slots: []
+ },
+ button_with_examples: %{
+ kind: :def,
+ attrs: [
+ %{
+ line: button_with_examples_line + 1,
+ name: :text,
+ opts: [examples: ["Save", "Cancel"]],
+ required: false,
+ doc: nil,
+ slot: nil,
+ type: :string
+ }
+ ],
+ slots: []
+ }
+ }
+ end
+
+ defmodule FunctionComponentWithSlots do
+ use Phoenix.Component
+
+ def fun_with_slot_line, do: __ENV__.line + 3
+
+ slot :inner_block
+ def fun_with_slot(assigns), do: ~H[]
+
+ def fun_with_named_slots_line, do: __ENV__.line + 4
+
+ slot :header
+ slot :footer
+ def fun_with_named_slots(assigns), do: ~H[]
+
+ def fun_with_slot_attrs_line, do: __ENV__.line + 6
+
+ slot :slot, required: true do
+ attr :attr, :any
+ end
+
+ def fun_with_slot_attrs(assigns), do: ~H[]
+
+ def table_line, do: __ENV__.line + 8
+
+ slot :col do
+ attr :label, :string
+ end
+
+ attr :rows, :list
+
+ def table(assigns) do
+ ~H"""
+
+
+ <%= for col <- @col do %>
+ <%= col.label %>
+ <% end %>
+
+ <%= for row <- @rows do %>
+
+ <%= for col <- @col do %>
+ <%= render_slot(col, row) %>
+ <% end %>
+
+ <% end %>
+
+ """
+ end
+
+ def render_line, do: __ENV__.line + 2
+
+ def render(assigns) do
+ ~H"""
+ <.fun_with_slot>
+ Hello, World
+
+
+ <.fun_with_named_slots>
+ <:header>
+ This is a header.
+
+
+ Hello, World
+
+ <:footer>
+ This is a footer.
+
+
+
+ <.fun_with_slot_attrs>
+ <:slot attr="1" />
+
+
+ <.table rows={@users}>
+ <:col :let={user} label={@name}>
+ <%= user.name %>
+
+
+ <:col :let={user} label="Address">
+ <%= user.address %>
+
+
+ """
+ end
+ end
+
+ test "stores slots definitions" do
+ assert FunctionComponentWithSlots.__components__() == %{
+ fun_with_slot: %{
+ attrs: [],
+ kind: :def,
+ slots: [
+ %{
+ doc: nil,
+ line: FunctionComponentWithSlots.fun_with_slot_line() - 1,
+ name: :inner_block,
+ opts: [],
+ attrs: [],
+ required: false
+ }
+ ]
+ },
+ fun_with_named_slots: %{
+ attrs: [],
+ kind: :def,
+ slots: [
+ %{
+ doc: nil,
+ line: FunctionComponentWithSlots.fun_with_named_slots_line() - 1,
+ name: :footer,
+ opts: [],
+ attrs: [],
+ required: false
+ },
+ %{
+ doc: nil,
+ line: FunctionComponentWithSlots.fun_with_named_slots_line() - 2,
+ name: :header,
+ opts: [],
+ attrs: [],
+ required: false
+ }
+ ]
+ },
+ fun_with_slot_attrs: %{
+ attrs: [],
+ kind: :def,
+ slots: [
+ %{
+ doc: nil,
+ line: FunctionComponentWithSlots.fun_with_slot_attrs_line() - 4,
+ name: :slot,
+ opts: [],
+ attrs: [
+ %{
+ doc: nil,
+ line: FunctionComponentWithSlots.fun_with_slot_attrs_line() - 3,
+ name: :attr,
+ opts: [],
+ required: false,
+ slot: :slot,
+ type: :any
+ }
+ ],
+ required: true
+ }
+ ]
+ },
+ table: %{
+ attrs: [
+ %{
+ doc: nil,
+ line: FunctionComponentWithSlots.table_line() - 2,
+ name: :rows,
+ opts: [],
+ required: false,
+ slot: nil,
+ type: :list
+ }
+ ],
+ kind: :def,
+ slots: [
+ %{
+ doc: nil,
+ line: FunctionComponentWithSlots.table_line() - 6,
+ name: :col,
+ opts: [],
+ attrs: [
+ %{
+ doc: nil,
+ line: FunctionComponentWithSlots.table_line() - 5,
+ name: :label,
+ opts: [],
+ required: false,
+ slot: :col,
+ type: :string
+ }
+ ],
+ required: false
+ }
+ ]
+ }
+ }
+ end
+
+ test "stores components for bodyless clauses" do
+ defmodule Bodyless do
+ use Phoenix.Component
+
+ def example_line, do: __ENV__.line + 2
+
+ attr :example, :any, required: true
+ def example(assigns)
+
+ def example(_assigns) do
+ "hello"
+ end
+
+ def example2_line, do: __ENV__.line + 2
+
+ slot :slot
+ def example2(assigns)
+
+ def example2(_assigns) do
+ "world"
+ end
+ end
+
+ assert Bodyless.__components__() == %{
+ example: %{
+ kind: :def,
+ attrs: [
+ %{
+ line: Bodyless.example_line(),
+ name: :example,
+ opts: [],
+ doc: nil,
+ required: true,
+ type: :any,
+ slot: nil
+ }
+ ],
+ slots: []
+ },
+ example2: %{
+ kind: :def,
+ attrs: [],
+ slots: [
+ %{
+ doc: nil,
+ line: Bodyless.example2_line(),
+ name: :slot,
+ opts: [],
+ attrs: [],
+ required: false
+ }
+ ]
+ }
+ }
+ end
+
+ test "matches on struct types" do
+ defmodule StructTypes do
+ use Phoenix.Component
+
+ attr :uri, URI, required: true
+ attr :other, :any
+ def example(%{other: 1}), do: "one"
+ def example(%{other: 2}), do: "two"
+ end
+
+ assert_raise FunctionClauseError, fn -> StructTypes.example(%{other: 1, uri: :not_uri}) end
+ assert_raise FunctionClauseError, fn -> StructTypes.example(%{other: 2, uri: :not_uri}) end
+
+ uri = URI.parse("/relative")
+ assert StructTypes.example(%{other: 1, uri: uri}) == "one"
+ assert StructTypes.example(%{other: 2, uri: uri}) == "two"
+ end
+
+ test "provides attr defaults" do
+ defmodule AttrDefaults do
+ use Phoenix.Component
+
+ attr :one, :integer, default: 1
+ attr :two, :integer, default: 2
+
+ def add(assigns) do
+ assigns = Phoenix.Component.assign(assigns, :foo, :bar)
+ ~H[<%= @one + @two %>]
+ end
+
+ attr :nil_default, :string, default: nil
+ def example(assigns), do: ~H[<%= inspect @nil_default %>]
+
+ attr :value, :string
+ def no_default(assigns), do: ~H[<%= inspect @value %>]
+
+ attr :id, :any
+ attr :errors, :list, default: []
+ def assigned_with_same_default(assigns) do
+ assign(assigns, errors: [])
+ end
+ end
+
+ assert render(AttrDefaults, :add, %{}) == "3"
+ assert render(AttrDefaults, :example, %{}) == "nil"
+ assert render(AttrDefaults, :no_default, %{value: 123}) == "123"
+
+ assert_raise KeyError, ~r":value not found", fn ->
+ render(AttrDefaults, :no_default, %{})
+ end
+
+ assigns = AttrDefaults.assigned_with_same_default(%{__changed__: %{}})
+ assert Phoenix.Component.changed?(assigns, :errors)
+
+ assigns = AttrDefaults.assigned_with_same_default(%{__changed__: %{}, errors: []})
+ refute Phoenix.Component.changed?(assigns, :errors)
+
+ assigns = AttrDefaults.assigned_with_same_default(%{__changed__: %{errors: true}, errors: []})
+ assert Phoenix.Component.changed?(assigns, :errors)
+ end
+
+ test "provides slot defaults" do
+ defmodule SlotDefaults do
+ use Phoenix.Component
+
+ slot :inner_block
+ def func(assigns), do: ~H[<%= render_slot(@inner_block) %>]
+
+ slot :inner_block, required: true
+ def func_required(assigns), do: ~H[<%= render_slot(@inner_block) %>]
+ end
+
+ assigns = %{}
+ assert "" == rendered_to_string(~H[ ])
+ assert "hello" == rendered_to_string(~H[hello ])
+ end
+
+ test "slots with rest" do
+ defmodule SlotWithGlobal do
+ use Phoenix.Component
+
+ attr :rest, :global
+ slot :inner_block, required: true
+ slot :col, required: true
+
+ def test(assigns) do
+ ~H"""
+
+ <%= render_slot(@inner_block) %>
+ <%= for col <- @col do %><%= render_slot(col) %>,<% end %>
+
+ """
+ end
+ end
+
+ assigns = %{}
+
+ template = ~H"""
+
+ block
+ <:col>col1
+ <:col>col2
+
+ """
+
+ assert h2s(template) == ~s|\n \n block\n \n col1,col2,\n
|
+ end
+
+ defp lookup(_key \\ :one)
+
+ for {k, v} <- [one: 1, two: 2, three: 3] do
+ defp lookup(unquote(k)), do: unquote(v)
+ end
+
+ test "does not change Elixir semantics" do
+ assert lookup() == 1
+ assert lookup(:two) == 2
+ assert lookup(:three) == 3
+ end
+
+ test "does not raise when there is a nested module" do
+ mod = fn ->
+ defmodule NestedModules do
+ use Phoenix.Component
+
+ defmodule Nested do
+ def fun(arg), do: arg
+ end
+ end
+ end
+
+ assert mod.()
+ end
+
+ test "supports :doc for attr and slot documentation" do
+ defmodule AttrDocs do
+ use Phoenix.Component
+
+ def attr_line, do: __ENV__.line
+ attr :single, :any, doc: "a single line description"
+
+ attr :break, :any, doc: "a description
+ with a line break"
+
+ attr :multi, :any,
+ doc: """
+ a description
+ that spans
+ multiple lines
+ """
+
+ attr :sigil, :any,
+ doc: ~S"""
+ a description
+ within a multi-line
+ sigil
+ """
+
+ attr :no_doc, :any
+
+ @doc "my function component with attrs"
+ def func_with_attr_docs(assigns), do: ~H[]
+
+ slot :slot, doc: "a named slot" do
+ attr :attr, :any, doc: "a slot attr"
+ end
+
+ def func_with_slot_docs(assigns), do: ~H[]
+ end
+
+ line = AttrDocs.attr_line()
+
+ assert AttrDocs.__components__() == %{
+ func_with_attr_docs: %{
+ attrs: [
+ %{
+ line: line + 3,
+ doc: "a description\n with a line break",
+ slot: nil,
+ name: :break,
+ opts: [],
+ required: false,
+ type: :any
+ },
+ %{
+ line: line + 6,
+ doc: "a description\nthat spans\nmultiple lines\n",
+ slot: nil,
+ name: :multi,
+ opts: [],
+ required: false,
+ type: :any
+ },
+ %{
+ line: line + 20,
+ doc: nil,
+ slot: nil,
+ name: :no_doc,
+ opts: [],
+ required: false,
+ type: :any
+ },
+ %{
+ line: line + 13,
+ doc: "a description\nwithin a multi-line\nsigil\n",
+ slot: nil,
+ name: :sigil,
+ opts: [],
+ required: false,
+ type: :any
+ },
+ %{
+ line: line + 1,
+ doc: "a single line description",
+ slot: nil,
+ name: :single,
+ opts: [],
+ required: false,
+ type: :any
+ }
+ ],
+ kind: :def,
+ slots: []
+ },
+ func_with_slot_docs: %{
+ attrs: [],
+ kind: :def,
+ slots: [
+ %{
+ doc: "a named slot",
+ line: line + 25,
+ name: :slot,
+ attrs: [
+ %{
+ doc: "a slot attr",
+ line: line + 26,
+ name: :attr,
+ opts: [],
+ required: false,
+ slot: :slot,
+ type: :any
+ }
+ ],
+ opts: [],
+ required: false
+ }
+ ]
+ }
+ }
+ end
+
+ test "inserts attr & slot docs into function component @doc string" do
+ {_, _, :elixir, "text/markdown", _, _, docs} =
+ Code.fetch_docs(Phoenix.LiveViewTest.FunctionComponentWithAttrs)
+
+ components = %{
+ fun_attr_any: """
+ ## Attributes
+
+ * `attr` (`:any`)
+ """,
+ fun_attr_string: """
+ ## Attributes
+
+ * `attr` (`:string`)
+ """,
+ fun_attr_atom: """
+ ## Attributes
+
+ * `attr` (`:atom`)
+ """,
+ fun_attr_boolean: """
+ ## Attributes
+
+ * `attr` (`:boolean`)
+ """,
+ fun_attr_integer: """
+ ## Attributes
+
+ * `attr` (`:integer`)
+ """,
+ fun_attr_float: """
+ ## Attributes
+
+ * `attr` (`:float`)
+ """,
+ fun_attr_map: """
+ ## Attributes
+
+ * `attr` (`:map`)
+ """,
+ fun_attr_list: """
+ ## Attributes
+
+ * `attr` (`:list`)
+ """,
+ fun_attr_global: """
+ ## Attributes
+
+ Global attributes are accepted.
+ """,
+ fun_attr_struct: """
+ ## Attributes
+
+ * `attr` (`Phoenix.LiveViewTest.FunctionComponentWithAttrs.Struct`)
+ """,
+ fun_attr_required: """
+ ## Attributes
+
+ * `attr` (`:any`) (required)
+ """,
+ fun_attr_default: """
+ ## Attributes
+
+ * `attr` (`:any`) - Defaults to `%{}`.
+ """,
+ fun_doc_false: :hidden,
+ fun_doc_injection: """
+ fun docs
+
+ ## Attributes
+
+ * `attr` (`:any`)
+
+ fun docs
+ """,
+ fun_multiple_attr: """
+ ## Attributes
+
+ * `attr1` (`:any`)
+ * `attr2` (`:any`)
+ """,
+ fun_with_attr_doc: """
+ ## Attributes
+
+ * `attr` (`:any`) - attr docs.
+ """,
+ fun_with_attr_doc_period: """
+ ## Attributes
+
+ * `attr` (`:any`) - attr docs. Defaults to `\"foo\"`.
+ """,
+ fun_with_attr_doc_multiline: """
+ ## Attributes
+
+ * `attr` (`:any`) - attr docs with bullets:
+
+ * foo
+ * bar
+
+ and that's it.
+
+ Defaults to `"foo"`.
+ """,
+ fun_with_hidden_attr: """
+ ## Attributes
+
+ * `attr1` (`:any`)
+ """,
+ fun_with_doc: """
+ fun docs
+ ## Attributes
+
+ * `attr` (`:any`)
+ """,
+ fun_slot: """
+ ## Slots
+
+ * `inner_block`
+ """,
+ fun_slot_doc: """
+ ## Slots
+
+ * `inner_block` - slot docs.
+ """,
+ fun_slot_required: """
+ ## Slots
+
+ * `inner_block` (required)
+ """,
+ fun_slot_with_attrs: """
+ ## Slots
+
+ * `named` (required) - a named slot. Accepts attributes:
+
+ * `attr1` (`:any`) (required) - a slot attr doc.
+ * `attr2` (`:any`) - a slot attr doc.
+ """,
+ fun_slot_no_doc_with_attrs: """
+ ## Slots
+
+ * `named` (required) - Accepts attributes:
+
+ * `attr1` (`:any`) (required) - a slot attr doc.
+ * `attr2` (`:any`) - a slot attr doc.
+ """,
+ fun_slot_doc_multiline_with_attrs: """
+ ## Slots
+
+ * `named` (required) - Important slot:
+
+ * for a
+ * for b
+
+ Accepts attributes:
+
+ * `attr1` (`:any`) (required) - a slot attr doc.
+ * `attr2` (`:any`) - a slot attr doc.
+ """,
+ fun_slot_doc_with_attrs_multiline: """
+ ## Slots
+
+ * `named` (required) - Accepts attributes:
+
+ * `attr1` (`:any`) (required) - attr docs with bullets:
+
+ * foo
+ * bar
+
+ and that's it.
+
+ * `attr2` (`:any`) - a slot attr doc.
+ """,
+ fun_attr_values_examples: """
+ ## Attributes
+
+ * `attr1` (`:atom`) - Must be one of `:foo`, `:bar`, or `:baz`.
+ * `attr2` (`:atom`) - Examples include `:foo`, `:bar`, and `:baz`.
+ * `attr3` (`:list`) - Must be one of `[60, 40]`.
+ * `attr4` (`:list`) - Examples include `[60, 40]`.
+ """
+ }
+
+ for {{_, fun, _}, _, _, %{"en" => doc}, _} <- docs do
+ assert components[fun] == doc
+ end
+ end
+
+ test "does not override signature of Elixir functions" do
+ {:docs_v1, _, :elixir, "text/markdown", _, _, docs} =
+ Code.fetch_docs(Phoenix.LiveViewTest.FunctionComponentWithAttrs)
+
+ assert {{:function, :identity, 1}, _, ["identity(var)"], _, %{}} =
+ List.keyfind(docs, {:function, :identity, 1}, 0)
+
+ assert {{:function, :map_identity, 1}, _, ["map_identity(map)"], _, %{}} =
+ List.keyfind(docs, {:function, :map_identity, 1}, 0)
+
+ assert Phoenix.LiveViewTest.FunctionComponentWithAttrs.identity(:not_a_map) == :not_a_map
+ assert Phoenix.LiveViewTest.FunctionComponentWithAttrs.identity(%{}) == %{}
+ end
+
+ test "raise if attr :doc is not a string" do
+ msg = ~r"doc must be a string or false, got: :foo"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.AttrDocsInvalidType do
+ use Elixir.Phoenix.Component
+
+ attr :invalid, :any, doc: :foo
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if slot :doc is not a string" do
+ msg = ~r"doc must be a string or false, got: :foo"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.SlotDocsInvalidType do
+ use Elixir.Phoenix.Component
+
+ slot :invalid, doc: :foo
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise on invalid attr/2 args" do
+ assert_raise FunctionClauseError, fn ->
+ defmodule Phoenix.ComponentTest.AttrMacroInvalidName do
+ use Elixir.Phoenix.Component
+
+ attr "not an atom", :any
+ def func(assigns), do: ~H[]
+ end
+ end
+
+ assert_raise FunctionClauseError, fn ->
+ defmodule Phoenix.ComponentTest.AttrMacroInvalidOpts do
+ use Elixir.Phoenix.Component
+
+ attr :attr, :any, "not a list"
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise on invalid slot/3 args" do
+ assert_raise FunctionClauseError, fn ->
+ defmodule Phoenix.ComponentTest.SlotMacroInvalidName do
+ use Elixir.Phoenix.Component
+
+ slot "not an atom"
+ def func(assigns), do: ~H[]
+ end
+ end
+
+ assert_raise FunctionClauseError, fn ->
+ defmodule Phoenix.ComponentTest.SlotMacroInvalidOpts do
+ use Elixir.Phoenix.Component
+
+ slot :slot, "not a list"
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if attr is declared between multiple function heads" do
+ msg = ~r"attributes must be defined before the first function clause at line \d+"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.MultiClauseWrong do
+ use Elixir.Phoenix.Component
+
+ attr :foo, :any
+ def func(assigns = %{foo: _}), do: ~H[]
+ def func(assigns = %{bar: _}), do: ~H[]
+
+ attr :bar, :any
+ def func(assigns = %{baz: _}), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if slot is declared between multiple function heads" do
+ msg = ~r"slots must be defined before the first function clause at line \d+"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.MultiClauseWrong do
+ use Elixir.Phoenix.Component
+
+ slot :inner_block
+ def func(assigns = %{foo: _}), do: ~H[]
+ def func(assigns = %{bar: _}), do: ~H[]
+
+ slot :named
+ def func(assigns = %{baz: _}), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if attr is declared on an invalid function" do
+ msg =
+ ~r"cannot declare attributes for function func\/2\. Components must be functions with arity 1"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.AttrOnInvalidFunction do
+ use Elixir.Phoenix.Component
+
+ attr :foo, :any
+ def func(a, b), do: a + b
+ end
+ end
+ end
+
+ test "raise if slot is declared on an invalid function" do
+ msg =
+ ~r"cannot declare slots for function func\/2\. Components must be functions with arity 1"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.SlotOnInvalidFunction do
+ use Elixir.Phoenix.Component
+
+ slot :inner_block
+ def func(a, b), do: a + b
+ end
+ end
+ end
+
+ test "raise if attr is declared without a related function" do
+ msg = ~r"cannot define attributes without a related function component"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.AttrOnInvalidFunction do
+ use Elixir.Phoenix.Component
+
+ def func(assigns = %{baz: _}), do: ~H[]
+
+ attr :foo, :any
+ end
+ end
+ end
+
+ test "raise if slot is declared without a related function" do
+ msg = ~r"cannot define slots without a related function component"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.SlotOnInvalidFunction do
+ use Elixir.Phoenix.Component
+
+ def func(assigns = %{baz: _}), do: ~H[]
+
+ slot :inner_block
+ end
+ end
+ end
+
+ test "raise if attr type is not supported" do
+ msg = ~r"invalid type :not_a_type for attr :foo"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.AttrTypeNotSupported do
+ use Elixir.Phoenix.Component
+
+ attr :foo, :not_a_type
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if slot attr type is not supported" do
+ msg = ~r"invalid type :not_a_type for attr :foo in slot :named"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.SlotAttrTypeNotSupported do
+ use Elixir.Phoenix.Component
+
+ slot :named do
+ attr :foo, :not_a_type
+ end
+
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if slot attr type is :global" do
+ msg = ~r"cannot define :global slot attributes"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.SlotAttrGlobalNotSupported do
+ use Elixir.Phoenix.Component
+
+ slot :named do
+ attr :foo, :global
+ end
+
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "reraise exceptions in slot/3 blocks" do
+ assert_raise RuntimeError, "boom!", fn ->
+ defmodule Phoenix.ComponentTest.SlotExceptionRaised do
+ use Elixir.Phoenix.Component
+
+ slot :named do
+ raise "boom!"
+ end
+
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if attr :values does not match the type" do
+ msg = ~r"expected the values for attr :foo to be a :string, got: :not_a_string"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.AttrValueTypeMismatch do
+ use Elixir.Phoenix.Component
+
+ attr :foo, :string, values: ["a string", :not_a_string]
+
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if attr :example does not match the type" do
+ msg = ~r"expected the examples for attr :foo to be a :string, got: :not_a_string"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.AttrExampleTypeMismatch do
+ use Elixir.Phoenix.Component
+
+ attr :foo, :string, examples: ["a string", :not_a_string]
+
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if attr :values is not a enum" do
+ msg = ~r":values must be a non-empty enumerable, got: :ok"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.AttrsValuesNotAList do
+ use Elixir.Phoenix.Component
+
+ attr :foo, :string, values: :ok
+
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if attr :examples is not a list" do
+ msg = ~r":examples must be a non-empty list, got: :ok"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.AttrsExamplesNotAList do
+ use Elixir.Phoenix.Component
+
+ attr :foo, :string, examples: :ok
+
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if attr :values is an empty enum" do
+ msg = ~r":values must be a non-empty enumerable, got: \[\]"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.AttrsValuesEmptyList do
+ use Elixir.Phoenix.Component
+
+ attr :foo, :string, values: []
+
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if attr :examples is an empty list" do
+ msg = ~r":examples must be a non-empty list, got: \[\]"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.AttrsExamplesEmptyList do
+ use Elixir.Phoenix.Component
+
+ attr :foo, :string, examples: []
+
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if attr has both :values and :examples" do
+ msg = ~r"only one of :values or :examples must be given"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.AttrDefaultTypeMismatch do
+ use Elixir.Phoenix.Component
+
+ attr :foo, :string, values: ["a string"], examples: ["a string"]
+
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if attr :default does not match the type" do
+ msg = ~r"expected the default value for attr :foo to be a :string, got: :not_a_string"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.AttrDefaultTypeMismatch do
+ use Elixir.Phoenix.Component
+
+ attr :foo, :string, default: :not_a_string
+
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if attr :default is not one of :values" do
+ msg =
+ ~r'expected the default value for attr :foo to be one of \["foo", "bar", "baz"\], got: "boom"'
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.AttrDefaultValuesMismatch do
+ use Elixir.Phoenix.Component
+
+ attr :foo, :string, default: "boom", values: ["foo", "bar", "baz"]
+
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if attr :default is not in range" do
+ msg = ~r'expected the default value for attr :foo to be one of 1\.\.10, got: 11'
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.AttrDefaultValuesMismatch do
+ use Elixir.Phoenix.Component
+
+ attr :foo, :integer, default: 11, values: 1..10
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if slot attr has :default" do
+ msg = ~r" invalid option :default for attr :foo in slot :named"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.SlotAttrDefault do
+ use Elixir.Phoenix.Component
+
+ slot :named do
+ attr :foo, :any, default: :whatever
+ end
+
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if attr option is not supported" do
+ msg = ~r"invalid option :not_an_opt for attr :foo"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.AttrOptionNotSupported do
+ use Elixir.Phoenix.Component
+
+ attr :foo, :any, not_an_opt: true
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if slot attr option is not supported" do
+ msg = ~r"invalid option :not_an_opt for attr :foo in slot :named"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.SlotAttrOptionNotSupported do
+ use Elixir.Phoenix.Component
+
+ slot :named do
+ attr :foo, :any, not_an_opt: true
+ end
+
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if attr is duplicated" do
+ msg = ~r"a duplicate attribute with name :foo already exists"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.AttrDup do
+ use Elixir.Phoenix.Component
+
+ attr :foo, :any, required: true
+ attr :foo, :string
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if slot is duplicated" do
+ msg = ~r"a duplicate slot with name :foo already exists"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.SlotDup do
+ use Elixir.Phoenix.Component
+
+ slot :foo
+ slot :foo
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if slot attr is duplicated" do
+ msg = ~r"a duplicate attribute with name :foo in slot :named already exists"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.SlotAttrDup do
+ use Elixir.Phoenix.Component
+
+ slot :named do
+ attr :foo, :any, required: true
+ attr :foo, :string
+ end
+
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if a slot and attr share the same name" do
+ msg = ~r"cannot define a slot with name :named, as an attribute with that name already exists"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule SlotAttrNameConflict do
+ use Elixir.Phoenix.Component
+
+ slot :named
+ attr :named, :any
+
+ def func(assigns), do: ~H[]
+ end
+ end
+
+ assert_raise CompileError, msg, fn ->
+ defmodule SlotAttrNameConflict do
+ use Elixir.Phoenix.Component
+
+ attr :named, :any
+ slot :named
+
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "does not raise if multiple slots with different names share the same attr names" do
+ defmodule MultipleSlotAttrs do
+ use Phoenix.Component
+
+ slot :foo do
+ attr :attr, :any
+ end
+
+ slot :bar do
+ attr :attr, :any
+ end
+
+ def func(assigns), do: ~H[]
+ end
+ end
+
+ test "raise if slot with name :inner_block has slot attrs" do
+ msg = ~r"cannot define attributes in a slot with name :inner_block"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule AttrsInDefaultSlot do
+ use Elixir.Phoenix.Component
+
+ slot :inner_block do
+ attr :attr, :any
+ end
+
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if :inner_block is attribute" do
+ msg = ~r"cannot define attribute called :inner_block. Maybe you wanted to use `slot` instead?"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule InnerSlotAttr do
+ use Elixir.Phoenix.Component
+
+ attr :inner_block, :string
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise on more than one :global attr" do
+ msg = ~r"cannot define :global attribute :rest2 because one is already defined as :rest"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.MultiGlobal do
+ use Elixir.Phoenix.Component
+
+ attr :rest, :global
+ attr :rest2, :global
+ def func(assigns), do: ~H[]
+ end
+ end
+ end
+
+ test "raise if global provides :required" do
+ msg = ~r"global attributes do not support the :required option"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.GlobalRequiredOpts do
+ use Elixir.Phoenix.Component
+
+ attr :rest, :global, required: true
+ def func(assigns), do: ~H[<%= @rest %>]
+ end
+ end
+ end
+
+ test "raise if global provides :values" do
+ msg = ~r"global attributes do not support the :values option"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.GlobalValueOpts do
+ use Elixir.Phoenix.Component
+
+ attr :rest, :global, values: ["placeholder", "rel"]
+ def func(assigns), do: ~H[<%= @rest %>]
+ end
+ end
+ end
+
+ test "raise if global provides :examples" do
+ msg = ~r"global attributes do not support the :examples option"
+
+ assert_raise CompileError, msg, fn ->
+ defmodule Phoenix.ComponentTest.GlobalExampleOpts do
+ use Elixir.Phoenix.Component
+
+ attr :rest, :global, examples: ["placeholder", "rel"]
+ def func(assigns), do: ~H[<%= @rest %>]
+ end
+ end
+ end
+end
diff --git a/test/phoenix_component/pages/about_page.html.heex b/test/phoenix_component/pages/about_page.html.heex
new file mode 100644
index 0000000000..660f88f08b
--- /dev/null
+++ b/test/phoenix_component/pages/about_page.html.heex
@@ -0,0 +1 @@
+About us
\ No newline at end of file
diff --git a/test/phoenix_component/pages/another_root/root.html.heex b/test/phoenix_component/pages/another_root/root.html.heex
new file mode 100644
index 0000000000..146adb9a77
--- /dev/null
+++ b/test/phoenix_component/pages/another_root/root.html.heex
@@ -0,0 +1 @@
+root!
\ No newline at end of file
diff --git a/test/phoenix_component/pages/another_root/root.text.eex b/test/phoenix_component/pages/another_root/root.text.eex
new file mode 100644
index 0000000000..28825e4d48
--- /dev/null
+++ b/test/phoenix_component/pages/another_root/root.text.eex
@@ -0,0 +1 @@
+root plain text!
diff --git a/test/phoenix_component/pages/welcome_page.html.heex b/test/phoenix_component/pages/welcome_page.html.heex
new file mode 100644
index 0000000000..c3e8b02584
--- /dev/null
+++ b/test/phoenix_component/pages/welcome_page.html.heex
@@ -0,0 +1 @@
+Welcome <%= @name %>
\ No newline at end of file
diff --git a/test/phoenix_component/rendering_test.exs b/test/phoenix_component/rendering_test.exs
new file mode 100644
index 0000000000..d050439fdb
--- /dev/null
+++ b/test/phoenix_component/rendering_test.exs
@@ -0,0 +1,347 @@
+defmodule Phoenix.ComponentRenderingTest do
+ use ExUnit.Case, async: true
+ use Phoenix.Component
+
+ import ExUnit.CaptureIO
+ import Phoenix.LiveViewTest
+
+ embed_templates "pages/*"
+ embed_templates "another_root/*.html", root: "pages"
+ embed_templates "another_root/*.text", root: "pages", suffix: "_text"
+
+ defp h2s(template) do
+ template
+ |> Phoenix.HTML.Safe.to_iodata()
+ |> IO.iodata_to_binary()
+ end
+
+ defp hello(assigns) do
+ assigns = assign_new(assigns, :name, fn -> "World" end)
+
+ ~H"""
+ Hello <%= @name %>
+ """
+ end
+
+ describe "rendering" do
+ test "renders component" do
+ assigns = %{}
+
+ assert h2s(~H"""
+ <.hello name="WORLD" />
+ """) == """
+ Hello WORLD\
+ """
+ end
+ end
+
+ describe "embed_templates" do
+ attr :name, :string, default: "chris"
+ def welcome_page(assigns)
+
+ test "embed from directory pattern" do
+ # generic template
+ assert render_component(&about_page/1) == "About us"
+
+ # root
+ assert render_component(&root/1) == "root!"
+ assert Phoenix.Template.render(__MODULE__, "root_text", "text", []) == "root plain text!\n"
+
+ # attr'd bodyless definition
+ assert render_component(&welcome_page/1) == "Welcome chris"
+ end
+ end
+
+ describe "testing" do
+ test "render_component/1" do
+ assert render_component(&hello/1) == "Hello World"
+ assert render_component(&hello/1, name: "WORLD!") == "Hello WORLD!"
+ end
+ end
+
+ describe "change tracking" do
+ defp eval(%Phoenix.LiveView.Rendered{dynamic: dynamic}), do: Enum.map(dynamic.(true), &eval/1)
+ defp eval(other), do: other
+
+ def changed(assigns) do
+ ~H"""
+ <%= inspect(Map.get(assigns, :__changed__)) %>
+ """
+ end
+
+ test "without changed assigns on root" do
+ assigns = %{foo: 1}
+ assert eval(~H"<.changed foo={@foo} />") == [["nil"]]
+ end
+
+ @compile {:no_warn_undefined, __MODULE__.Tainted}
+
+ test "with tainted variable" do
+ assert capture_io(:stderr, fn ->
+ defmodule Tainted do
+ def run(assigns) do
+ foo = 1
+ ~H" "
+ end
+ end
+ end) =~ "you are accessing the variable \"foo\" inside a LiveView template"
+
+ assert eval(__MODULE__.Tainted.run(%{foo: 1})) == [["nil"]]
+ assert eval(__MODULE__.Tainted.run(%{foo: 1, __changed__: %{}})) == [["%{foo: true}"]]
+ end
+
+ test "with changed assigns on root" do
+ assigns = %{foo: 1, __changed__: %{}}
+ assert eval(~H"<.changed foo={@foo} />") == [nil]
+
+ assigns = %{foo: 1, __changed__: %{bar: true}}
+ assert eval(~H"<.changed foo={@foo} />") == [nil]
+
+ assigns = %{foo: 1, __changed__: %{foo: true}}
+ assert eval(~H"<.changed foo={@foo} />") == [["%{foo: true}"]]
+
+ assigns = %{foo: 1, __changed__: %{foo: %{bar: true}}}
+ assert eval(~H"<.changed foo={@foo} />") == [["%{foo: %{bar: true}}"]]
+ end
+
+ test "with changed assigns on map" do
+ assigns = %{foo: %{bar: :bar}, __changed__: %{}}
+ assert eval(~H"<.changed foo={@foo.bar} />") == [nil]
+
+ assigns = %{foo: %{bar: :bar}, __changed__: %{bar: true}}
+ assert eval(~H"<.changed foo={@foo.bar} />") == [nil]
+
+ assigns = %{foo: %{bar: :bar}, __changed__: %{foo: true}}
+ assert eval(~H"<.changed foo={@foo.bar} />") == [["%{foo: true}"]]
+
+ assigns = %{foo: %{bar: :bar}, __changed__: %{foo: %{bar: :bar}}}
+ assert eval(~H"<.changed foo={@foo.bar} />") == [nil]
+
+ assigns = %{foo: %{bar: :bar}, __changed__: %{foo: %{bar: :baz}}}
+ assert eval(~H"<.changed foo={@foo.bar} />") == [["%{foo: true}"]]
+
+ assigns = %{foo: %{bar: %{bar: :bar}}, __changed__: %{foo: %{bar: %{bar: :bat}}}}
+ assert eval(~H"<.changed foo={@foo.bar} />") == [["%{foo: %{bar: :bat}}"]]
+ end
+
+ test "with multiple changed assigns" do
+ assigns = %{foo: 1, bar: 2, __changed__: %{}}
+ assert eval(~H"<.changed foo={@foo + @bar} />") == [nil]
+
+ assigns = %{foo: 1, bar: 2, __changed__: %{bar: true}}
+ assert eval(~H"<.changed foo={@foo + @bar} />") == [["%{foo: true}"]]
+
+ assigns = %{foo: 1, bar: 2, __changed__: %{foo: true}}
+ assert eval(~H"<.changed foo={@foo + @bar} />") == [["%{foo: true}"]]
+
+ assigns = %{foo: 1, bar: 2, __changed__: %{baz: true}}
+ assert eval(~H"<.changed foo={@foo + @bar} />") == [nil]
+ end
+
+ test "with multiple keys" do
+ assigns = %{foo: 1, bar: 2, __changed__: %{}}
+ assert eval(~H"<.changed foo={@foo} bar={@bar} />") == [nil]
+
+ assigns = %{foo: 1, bar: 2, __changed__: %{bar: true}}
+ assert eval(~H"<.changed foo={@foo} bar={@bar} />") == [["%{bar: true}"]]
+
+ assigns = %{foo: 1, bar: 2, __changed__: %{foo: true}}
+ assert eval(~H"<.changed foo={@foo} bar={@bar} />") == [["%{foo: true}"]]
+
+ assigns = %{foo: 1, bar: 2, __changed__: %{baz: true}}
+ assert eval(~H"<.changed foo={@foo} bar={@bar} />") == [nil]
+ end
+
+ test "with multiple keys and one is static" do
+ assigns = %{foo: 1, __changed__: %{}}
+ assert eval(~H|<.changed foo={@foo} bar="2" />|) == [nil]
+
+ assigns = %{foo: 1, __changed__: %{bar: true}}
+ assert eval(~H|<.changed foo={@foo} bar="2" />|) == [nil]
+
+ assigns = %{foo: 1, __changed__: %{foo: true}}
+ assert eval(~H|<.changed foo={@foo} bar="2" />|) == [["%{foo: true}"]]
+ end
+
+ test "with multiple keys and one is tainted" do
+ assigns = %{foo: 1, __changed__: %{}}
+ assert eval(~H|<.changed foo={@foo} bar={assigns} />|) == [["%{bar: true}"]]
+
+ assigns = %{foo: 1, __changed__: %{foo: true}}
+ assert eval(~H|<.changed foo={@foo} bar={assigns} />|) == [["%{bar: true, foo: true}"]]
+ end
+
+ test "with conflict on changed assigns" do
+ assigns = %{foo: 1, bar: %{foo: 2}, __changed__: %{}}
+ assert eval(~H"<.changed foo={@foo} {@bar} />") == [nil]
+
+ assigns = %{foo: 1, bar: %{foo: 2}, __changed__: %{bar: true}}
+ assert eval(~H"<.changed foo={@foo} {@bar} />") == [["%{foo: true}"]]
+
+ assigns = %{foo: 1, bar: %{foo: 2}, __changed__: %{foo: true}}
+ assert eval(~H"<.changed foo={@foo} {@bar} />") == [["%{foo: true}"]]
+
+ assigns = %{foo: 1, bar: %{foo: 2}, baz: 3, __changed__: %{baz: true}}
+ assert eval(~H"<.changed foo={@foo} {@bar} baz={@baz} />") == [["%{baz: true}"]]
+ end
+
+ test "with dynamic assigns" do
+ assigns = %{foo: %{a: 1, b: 2}, __changed__: %{}}
+ assert eval(~H"<.changed {@foo} />") == [nil]
+
+ assigns = %{foo: %{a: 1, b: 2}, __changed__: %{foo: true}}
+ assert eval(~H"<.changed {@foo} />") == [["%{a: true, b: true}"]]
+
+ assigns = %{foo: %{a: 1, b: 2}, bar: 3, __changed__: %{bar: true}}
+ assert eval(~H"<.changed {@foo} bar={@bar} />") == [["%{bar: true}"]]
+
+ assigns = %{foo: %{a: 1, b: 2}, bar: 3, __changed__: %{bar: true}}
+ assert eval(~H"<.changed {%{a: 1, b: 2}} bar={@bar} />") == [["%{bar: true}"]]
+
+ assigns = %{bar: 3, __changed__: %{bar: true}}
+
+ assert eval(~H"<.changed {%{a: assigns[:b], b: assigns[:a]}} bar={@bar} />") ==
+ [["%{bar: true}"]]
+
+ assigns = %{a: 1, b: 2, bar: 3, __changed__: %{a: true, b: true, bar: true}}
+
+ assert eval(~H"<.changed {%{a: assigns[:b], b: assigns[:a]}} bar={@bar} />") ==
+ [["%{a: true, b: true, bar: true}"]]
+ end
+
+ defp wrapper(assigns) do
+ ~H"""
+ <%= render_slot(@inner_block) %>
+ """
+ end
+
+ defp inner_changed(assigns) do
+ ~H"""
+ <%= inspect(Map.get(assigns, :__changed__)) %>
+ <%= render_slot(@inner_block, "var") %>
+ """
+ end
+
+ test "with @inner_block" do
+ assigns = %{foo: 1, __changed__: %{}}
+ assert eval(~H|<.inner_changed foo={@foo}>|) == [nil]
+ assert eval(~H|<.inner_changed><%= @foo %>|) == [nil]
+
+ assigns = %{foo: 1, __changed__: %{foo: true}}
+
+ assert eval(~H|<.inner_changed foo={@foo}>|) ==
+ [["%{foo: true}", nil]]
+
+ assert eval(
+ ~H|<.inner_changed foo={@foo}><%= inspect(Map.get(assigns, :__changed__)) %>|
+ ) ==
+ [["%{foo: true, inner_block: true}", ["%{foo: true}"]]]
+
+ assert eval(~H|<.inner_changed><%= @foo %>|) ==
+ [["%{inner_block: true}", ["1"]]]
+
+ assigns = %{foo: 1, __changed__: %{foo: %{bar: true}}}
+
+ assert eval(~H|<.inner_changed foo={@foo}>|) ==
+ [["%{foo: %{bar: true}}", nil]]
+
+ assert eval(
+ ~H|<.inner_changed foo={@foo}><%= inspect(Map.get(assigns, :__changed__)) %>|
+ ) ==
+ [["%{foo: %{bar: true}, inner_block: true}", ["%{foo: %{bar: true}}"]]]
+
+ assert eval(~H|<.inner_changed><%= @foo %>|) ==
+ [["%{inner_block: %{bar: true}}", ["1"]]]
+ end
+
+ test "with let" do
+ assigns = %{foo: 1, __changed__: %{}}
+ assert eval(~H|<.inner_changed :let={_foo} foo={@foo}>|) == [nil]
+
+ assigns = %{foo: 1, __changed__: %{foo: true}}
+
+ assert eval(~H|<.inner_changed :let={_foo} foo={@foo}>|) ==
+ [["%{foo: true}", nil]]
+
+ assert eval(
+ ~H|<.inner_changed :let={_foo} foo={@foo}><%= inspect(Map.get(assigns, :__changed__)) %>|
+ ) ==
+ [["%{foo: true, inner_block: true}", ["%{foo: true}"]]]
+
+ assert eval(
+ ~H|<.inner_changed :let={_foo} foo={@foo}><%= "constant" %><%= inspect(Map.get(assigns, :__changed__)) %>|
+ ) ==
+ [["%{foo: true, inner_block: true}", [nil, "%{foo: true}"]]]
+
+ assert eval(
+ ~H|<.inner_changed :let={foo} foo={@foo}><.inner_changed :let={_bar} bar={foo}><%= "constant" %><%= inspect(Map.get(assigns, :__changed__)) %>|
+ ) ==
+ [
+ [
+ "%{foo: true, inner_block: true}",
+ [["%{bar: true, inner_block: true}", [nil, "%{foo: true}"]]]
+ ]
+ ]
+
+ assert eval(
+ ~H|<.inner_changed :let={foo} foo={@foo}><%= foo %><%= inspect(Map.get(assigns, :__changed__)) %>|
+ ) ==
+ [["%{foo: true, inner_block: true}", ["var", "%{foo: true}"]]]
+
+ assert eval(
+ ~H|<.inner_changed :let={foo} foo={@foo}><.inner_changed :let={bar} bar={foo}><%= bar %><%= inspect(Map.get(assigns, :__changed__)) %>|
+ ) ==
+ [
+ [
+ "%{foo: true, inner_block: true}",
+ [["%{bar: true, inner_block: true}", ["var", "%{foo: true}"]]]
+ ]
+ ]
+ end
+
+ test "with :let inside @inner_block" do
+ assigns = %{foo: 1, bar: 2, __changed__: %{foo: true}}
+
+ assert eval(~H"""
+ <.wrapper>
+ <%= @foo %>
+ <.inner_changed foo={@bar} :let={var}>
+ <%= var %>
+
+
+ """) == [[["1", nil]]]
+ end
+
+ defp optional_wrapper(assigns) do
+ assigns = assign_new(assigns, :inner_block, fn -> [] end)
+
+ ~H"""
+ <%= render_slot(@inner_block) || "DEFAULT!" %>
+ """
+ end
+
+ test "with optional @inner_block" do
+ assigns = %{foo: 1}
+
+ assert eval(~H"""
+ <.optional_wrapper>
+ <%= @foo %>
+
+ """) == [[["1"]]]
+
+ assigns = %{foo: 2, __changed__: %{foo: true}}
+
+ assert eval(~H"""
+ <.optional_wrapper>
+ <%= @foo %>
+
+ """) == [[["2"]]]
+
+ assigns = %{foo: 3}
+
+ assert eval(~H"""
+ <.optional_wrapper />
+ """) == [["DEFAULT!"]]
+ end
+ end
+end
diff --git a/test/phoenix_component/verify_test.exs b/test/phoenix_component/verify_test.exs
new file mode 100644
index 0000000000..04bc794396
--- /dev/null
+++ b/test/phoenix_component/verify_test.exs
@@ -0,0 +1,1042 @@
+defmodule Phoenix.ComponentVerifyTest do
+ use ExUnit.Case, async: true
+
+ @moduletag :after_verify
+ import ExUnit.CaptureIO
+
+ test "validate required attributes" do
+ warnings =
+ capture_io(:stderr, fn ->
+ defmodule RequiredAttrs do
+ use Phoenix.Component
+
+ attr :name, :any, required: true
+ attr :phone, :any
+ attr :email, :any, required: true
+
+ def func(assigns), do: ~H[]
+
+ def line, do: __ENV__.line + 4
+
+ def render(assigns) do
+ ~H"""
+ <.func/>
+ """
+ end
+ end
+ end)
+
+ line = get_line(__MODULE__.RequiredAttrs)
+
+ assert warnings =~ """
+ missing required attribute "email" for component \
+ Phoenix.ComponentVerifyTest.RequiredAttrs.func/1
+ test/phoenix_component/verify_test.exs:#{line}: (file)
+ """
+
+ assert warnings =~ """
+ missing required attribute "name" for component \
+ Phoenix.ComponentVerifyTest.RequiredAttrs.func/1
+ test/phoenix_component/verify_test.exs:#{line}: (file)
+ """
+ end
+
+ test "validate undefined attributes" do
+ warnings =
+ capture_io(:stderr, fn ->
+ defmodule UndefinedAttrs do
+ use Phoenix.Component
+
+ attr :class, :any
+ def func(assigns), do: ~H[]
+
+ def line, do: __ENV__.line + 4
+
+ def render(assigns) do
+ ~H"""
+ <.func width="btn" size={@size} phx-no-format />
+ """
+ end
+ end
+ end)
+
+ line = get_line(__MODULE__.UndefinedAttrs)
+
+ assert warnings =~ """
+ undefined attribute "size" for component \
+ Phoenix.ComponentVerifyTest.UndefinedAttrs.func/1
+ test/phoenix_component/verify_test.exs:#{line}: (file)
+ """
+
+ assert warnings =~ """
+ undefined attribute "width" for component \
+ Phoenix.ComponentVerifyTest.UndefinedAttrs.func/1
+ test/phoenix_component/verify_test.exs:#{line}: (file)
+ """
+ end
+
+ test "validates attrs and slots for external function components" do
+ warnings =
+ capture_io(:stderr, fn ->
+ defmodule External do
+ use Phoenix.Component
+ attr :id, :string, required: true
+
+ slot :named do
+ attr :attr, :any, required: true
+ end
+
+ def render(assigns), do: ~H[]
+ end
+
+ defmodule ExternalCalls do
+ use Phoenix.Component
+
+ def line, do: __ENV__.line + 4
+
+ def render(assigns) do
+ ~H"""
+
+ <:named />
+
+ """
+ end
+ end
+ end)
+
+ line = get_line(__MODULE__.ExternalCalls)
+
+ assert warnings =~ """
+ missing required attribute "id" for component \
+ Phoenix.ComponentVerifyTest.External.render/1
+ test/phoenix_component/verify_test.exs:#{line}: (file)
+ """
+
+ assert warnings =~ """
+ missing required attribute "attr" in slot "named" for component \
+ Phoenix.ComponentVerifyTest.External.render/1
+ test/phoenix_component/verify_test.exs:#{line + 1}: (file)
+ """
+ end
+
+ test "validate literal types" do
+ warnings =
+ capture_io(:stderr, fn ->
+ defmodule TypeAttrs do
+ use Phoenix.Component, global_prefixes: ~w(myprefix-)
+
+ attr :any, :any
+ attr :string, :string
+ attr :atom, :atom
+ attr :boolean, :boolean
+ attr :integer, :integer
+ attr :float, :float
+ attr :map, :map
+ attr :list, :list
+ attr :global, :global
+
+ def func(assigns), do: ~H[]
+
+ def global_line, do: __ENV__.line + 4
+
+ def global_render(assigns) do
+ ~H"""
+ <.func global="global" />
+ <.func phx-click="click" id="id"/>
+ """
+ end
+
+ def any_line, do: __ENV__.line + 4
+
+ def any_render(assigns) do
+ ~H"""
+ <.func any="any" />
+ <.func any={:any} />
+ <.func any={true} />
+ <.func any={1} />
+ <.func any={1.0} />
+ <.func any={%{}} />
+ <.func any={[]} />
+ <.func any={nil} />
+ """
+ end
+
+ def render_string_line, do: __ENV__.line + 4
+
+ def string_render(assigns) do
+ ~H"""
+ <.func string="string" />
+ <.func string={:string} />
+ <.func string={true} />
+ <.func string={1} />
+ <.func string={1.0} />
+ <.func string={%{}} />
+ <.func string={[]} />
+ <.func string={nil} />
+ """
+ end
+
+ def render_atom_line, do: __ENV__.line + 4
+
+ def atom_render(assigns) do
+ ~H"""
+ <.func atom="atom" />
+ <.func atom={:atom} />
+ <.func atom={true} />
+ <.func atom={1} />
+ <.func atom={1.0} />
+ <.func atom={%{}} />
+ <.func atom={[]} />
+ <.func atom={nil} />
+ """
+ end
+
+ def render_boolean_line, do: __ENV__.line + 4
+
+ def boolean_render(assigns) do
+ ~H"""
+ <.func boolean="boolean" />
+ <.func boolean={:boolean} />
+ <.func boolean={true} />
+ <.func boolean={1} />
+ <.func boolean={1.0} />
+ <.func boolean={%{}} />
+ <.func boolean={[]} />
+ <.func boolean={nil} />
+ """
+ end
+
+ def render_integer_line, do: __ENV__.line + 4
+
+ def integer_render(assigns) do
+ ~H"""
+ <.func integer="integer" />
+ <.func integer={:integer} />
+ <.func integer={true} />
+ <.func integer={1} />
+ <.func integer={1.0} />
+ <.func integer={%{}} />
+ <.func integer={[]} />
+ <.func integer={nil} />
+ """
+ end
+
+ def render_float_line, do: __ENV__.line + 4
+
+ def float_render(assigns) do
+ ~H"""
+ <.func float="float" />
+ <.func float={:float} />
+ <.func float={true} />
+ <.func float={1} />
+ <.func float={1.0} />
+ <.func float={%{}} />
+ <.func float={[]} />
+ <.func float={nil} />
+ """
+ end
+
+ def render_map_line, do: __ENV__.line + 4
+
+ def map_render(assigns) do
+ ~H"""
+ <.func map="map" />
+ <.func map={:map} />
+ <.func map={true} />
+ <.func map={1} />
+ <.func map={1.0} />
+ <.func map={%{}} />
+ <.func map={[]} />
+ <.func map={nil} />
+ """
+ end
+
+ def render_list_line, do: __ENV__.line + 4
+
+ def list_render(assigns) do
+ ~H"""
+ <.func list="list" />
+ <.func list={:list} />
+ <.func list={true} />
+ <.func list={1} />
+ <.func list={1.0} />
+ <.func list={%{}} />
+ <.func list={[]} />
+ <.func list={nil} />
+ """
+ end
+ end
+ end)
+
+ line = get_line(__MODULE__.TypeAttrs, :global_line)
+
+ assert warnings =~ """
+ global attribute "global" in component \
+ Phoenix.ComponentVerifyTest.TypeAttrs.func/1 \
+ may not be provided directly
+ test/phoenix_component/verify_test.exs:#{line}: (file)
+ """
+
+ line = get_line(__MODULE__.TypeAttrs, :render_string_line)
+
+ for {value, offset} <- [
+ {:string, 1},
+ {true, 2},
+ {1, 3},
+ {1.0, 4},
+ {%{}, 5},
+ {[], 6},
+ {nil, 7}
+ ] do
+ assert warnings =~ """
+ attribute "string" in component \
+ Phoenix.ComponentVerifyTest.TypeAttrs.func/1 \
+ must be a :string, got: #{inspect(value)}
+ test/phoenix_component/verify_test.exs:#{line + offset}: (file)
+ """
+ end
+
+ line = get_line(__MODULE__.TypeAttrs, :render_atom_line)
+
+ for {value, offset} <- [
+ {"atom", 0},
+ {1, 3},
+ {1.0, 4},
+ {%{}, 5},
+ {[], 6}
+ ] do
+ assert warnings =~ """
+ attribute "atom" in component \
+ Phoenix.ComponentVerifyTest.TypeAttrs.func/1 \
+ must be an :atom, got: #{inspect(value)}
+ test/phoenix_component/verify_test.exs:#{line + offset}: (file)
+ """
+ end
+
+ line = get_line(__MODULE__.TypeAttrs, :render_boolean_line)
+
+ for {value, offset} <- [
+ {"boolean", 0},
+ {:boolean, 1},
+ {1, 3},
+ {1.0, 4},
+ {%{}, 5},
+ {[], 6},
+ {nil, 7}
+ ] do
+ assert warnings =~ """
+ attribute "boolean" in component \
+ Phoenix.ComponentVerifyTest.TypeAttrs.func/1 \
+ must be a :boolean, got: #{inspect(value)}
+ test/phoenix_component/verify_test.exs:#{line + offset}: (file)
+ """
+ end
+
+ line = get_line(__MODULE__.TypeAttrs, :render_integer_line)
+
+ for {value, offset} <- [
+ {"integer", 0},
+ {:integer, 1},
+ {true, 2},
+ {1.0, 4},
+ {%{}, 5},
+ {[], 6},
+ {nil, 7}
+ ] do
+ assert warnings =~ """
+ attribute "integer" in component \
+ Phoenix.ComponentVerifyTest.TypeAttrs.func/1 \
+ must be an :integer, got: #{inspect(value)}
+ test/phoenix_component/verify_test.exs:#{line + offset}: (file)
+ """
+ end
+
+ line = get_line(__MODULE__.TypeAttrs, :render_float_line)
+
+ for {value, offset} <- [
+ {"float", 0},
+ {:float, 1},
+ {true, 2},
+ {1, 3},
+ {%{}, 5},
+ {[], 6},
+ {nil, 7}
+ ] do
+ assert warnings =~ """
+ attribute "float" in component \
+ Phoenix.ComponentVerifyTest.TypeAttrs.func/1 \
+ must be a :float, got: #{inspect(value)}
+ test/phoenix_component/verify_test.exs:#{line + offset}: (file)
+ """
+ end
+
+ line = get_line(__MODULE__.TypeAttrs, :render_map_line)
+
+ for {value, offset} <- [
+ {"map", 0},
+ {:map, 1},
+ {true, 2},
+ {1, 3},
+ {1.0, 4},
+ {[], 6},
+ {nil, 7}
+ ] do
+ assert warnings =~ """
+ attribute "map" in component \
+ Phoenix.ComponentVerifyTest.TypeAttrs.func/1 \
+ must be a :map, got: #{inspect(value)}
+ test/phoenix_component/verify_test.exs:#{line + offset}: (file)
+ """
+ end
+
+ line = get_line(__MODULE__.TypeAttrs, :render_list_line)
+
+ for {value, offset} <- [
+ {"list", 0},
+ {:list, 1},
+ {true, 2},
+ {1, 3},
+ {1.0, 4},
+ {%{}, 5},
+ {nil, 7}
+ ] do
+ assert warnings =~ """
+ attribute "list" in component \
+ Phoenix.ComponentVerifyTest.TypeAttrs.func/1 \
+ must be a :list, got: #{inspect(value)}
+ test/phoenix_component/verify_test.exs:#{line + offset}: (file)
+ """
+ end
+ end
+
+ test "validates attr values" do
+ warnings =
+ capture_io(:stderr, fn ->
+ defmodule AttrValues do
+ use Phoenix.Component
+
+ attr :attr, :string, values: ["foo", "bar", "baz"]
+ def func_string(assigns), do: ~H[]
+
+ attr :attr, :atom, values: [:foo, :bar, :baz]
+ def func_atom(assigns), do: ~H[]
+
+ attr :attr, :integer, values: 1..10
+ def func_integer(assigns), do: ~H[]
+
+ def line, do: __ENV__.line + 2
+
+ def render(assigns) do
+ ~H"""
+ <.func_string attr="boom" />
+ <.func_atom attr={:boom} />
+ <.func_integer attr={11} />
+ <.func_string attr={"bar"} />
+ <.func_string attr={@string} />
+ <.func_atom attr={:bar} />
+ <.func_atom attr={@atom} />
+ <.func_integer attr={5} />
+ <.func_integer attr={@integer} />
+ """
+ end
+ end
+ end)
+
+ line = get_line(__MODULE__.AttrValues, :line)
+
+ assert Regex.scan(~r/attribute "attr" in component/, warnings) |> length() == 3
+
+ assert warnings =~ """
+ attribute "attr" in component \
+ Phoenix.ComponentVerifyTest.AttrValues.func_string/1 \
+ must be one of ["foo", "bar", "baz"], got: "boom"
+ test/phoenix_component/verify_test.exs:#{line + 2}: (file)
+ """
+
+ assert warnings =~ """
+ attribute "attr" in component \
+ Phoenix.ComponentVerifyTest.AttrValues.func_atom/1 \
+ must be one of [:foo, :bar, :baz], got: :boom
+ test/phoenix_component/verify_test.exs:#{line + 3}: (file)
+ """
+
+ assert warnings =~ """
+ attribute "attr" in component \
+ Phoenix.ComponentVerifyTest.AttrValues.func_integer/1 \
+ must be one of 1..10, got: 11
+ test/phoenix_component/verify_test.exs:#{line + 4}: (file)
+ """
+ end
+
+ test "validates slot attr values" do
+ warnings =
+ capture_io(:stderr, fn ->
+ defmodule SlotAttrValues do
+ use Phoenix.Component
+
+ slot :named do
+ attr :string, :string, values: ["foo", "bar", "baz"]
+ attr :atom, :atom, values: [:foo, :bar, :baz]
+ attr :integer, :integer, values: 1..10
+ end
+
+ def func(assigns), do: ~H[]
+
+ def line, do: __ENV__.line + 2
+
+ def render(assigns) do
+ ~H"""
+ <.func>
+ <:named string="boom" atom={:boom} integer={11}/>
+ <:named string={@string} atom={@atom} integer={@integer}/>
+ <:named string="bar" atom={:bar} integer={5}/>
+
+ """
+ end
+ end
+ end)
+
+ line = get_line(__MODULE__.SlotAttrValues, :line)
+
+ assert Regex.scan(~r/attribute "\w+" in slot "named"/, warnings) |> length() == 3
+
+ assert warnings =~ """
+ attribute "string" in slot "named" for component \
+ Phoenix.ComponentVerifyTest.SlotAttrValues.func/1 \
+ must be one of ["foo", "bar", "baz"], got: "boom"
+ test/phoenix_component/verify_test.exs:#{line + 3}: (file)
+ """
+
+ assert warnings =~ """
+ attribute "atom" in slot "named" for component \
+ Phoenix.ComponentVerifyTest.SlotAttrValues.func/1 \
+ must be one of [:foo, :bar, :baz], got: :boom
+ test/phoenix_component/verify_test.exs:#{line + 3}: (file)
+ """
+
+ assert warnings =~ """
+ attribute "integer" in slot "named" for component \
+ Phoenix.ComponentVerifyTest.SlotAttrValues.func/1 \
+ must be one of 1..10, got: 11
+ test/phoenix_component/verify_test.exs:#{line + 3}: (file)
+ """
+ end
+
+ test "validate required slots" do
+ warnings =
+ capture_io(:stderr, fn ->
+ defmodule RequiredSlots do
+ use Phoenix.Component
+
+ slot :inner_block, required: true
+
+ def func(assigns), do: ~H[]
+
+ slot :named, required: true
+
+ def func_named_slot(assigns), do: ~H[]
+
+ def line, do: __ENV__.line + 2
+
+ def render(assigns) do
+ ~H"""
+
+ <.func/>
+
+
+ <.func>
+
+
+ <.func>Hello!
+
+
+ <.func_named_slot/>
+
+
+ <.func_named_slot>
+ <:named />
+
+
+
+ <.func_named_slot>
+ <:named>
+ Hello!
+
+
+
+
+ <.func_named_slot>
+ <:named>
+ Hello,
+
+ <:named>
+ World!
+
+
+ """
+ end
+ end
+ end)
+
+ line = get_line(__MODULE__.RequiredSlots)
+
+ assert warnings =~ """
+ missing required slot "inner_block" for component \
+ Phoenix.ComponentVerifyTest.RequiredSlots.func/1
+ test/phoenix_component/verify_test.exs:#{line + 3}: (file)
+ """
+
+ assert warnings =~ """
+ missing required slot "named" for component \
+ Phoenix.ComponentVerifyTest.RequiredSlots.func_named_slot/1
+ test/phoenix_component/verify_test.exs:#{line + 12}: (file)
+ """
+ end
+
+ test "validate slot attr types" do
+ warnings =
+ capture_io(:stderr, fn ->
+ defmodule SlotAttrs do
+ use Phoenix.Component
+
+ slot :slot do
+ attr :any, :any
+ attr :string, :string
+ attr :atom, :atom
+ attr :boolean, :boolean
+ attr :integer, :integer
+ attr :float, :float
+ attr :list, :list
+ end
+
+ def func(assigns), do: ~H[]
+
+ def render_any_line, do: __ENV__.line + 5
+
+ def render_any(assigns) do
+ ~H"""
+ <.func>
+ <:slot any />
+ <:slot any="any" />
+ <:slot any={:any} />
+ <:slot any={true} />
+ <:slot any={1} />
+ <:slot any={1.0} />
+ <:slot any={[]} />
+
+ """
+ end
+
+ def render_string_line, do: __ENV__.line + 5
+
+ def render_string(assigns) do
+ ~H"""
+ <.func>
+ <:slot string="string" />
+ <:slot string={:string} />
+ <:slot string={true} />
+ <:slot string={1} />
+ <:slot string={1.0} />
+ <:slot string={[]} />
+ <:slot string={nil} />
+
+ """
+ end
+
+ def render_atom_line, do: __ENV__.line + 5
+
+ def render_atom(assigns) do
+ ~H"""
+ <.func>
+ <:slot atom="atom" />
+ <:slot atom={:atom} />
+ <:slot atom={true} />
+ <:slot atom={1} />
+ <:slot atom={1.0} />
+ <:slot atom={[]} />
+ <:slot atom={nil} />
+
+ """
+ end
+
+ def render_boolean_line, do: __ENV__.line + 5
+
+ def render_boolean(assigns) do
+ ~H"""
+ <.func>
+ <:slot boolean="boolean" />
+ <:slot boolean={:boolean} />
+ <:slot boolean={true} />
+ <:slot boolean={1} />
+ <:slot boolean={1.0} />
+ <:slot boolean={[]} />
+ <:slot boolean={nil} />
+
+ """
+ end
+
+ def render_integer_line, do: __ENV__.line + 5
+
+ def render_integer(assigns) do
+ ~H"""
+ <.func>
+ <:slot integer="integer" />
+ <:slot integer={:integer} />
+ <:slot integer={true} />
+ <:slot integer={1} />
+ <:slot integer={1.0} />
+ <:slot integer={[]} />
+ <:slot integer={nil} />
+
+ """
+ end
+
+ def render_float_line, do: __ENV__.line + 5
+
+ def render_float(assigns) do
+ ~H"""
+ <.func>
+ <:slot float="float" />
+ <:slot float={:float} />
+ <:slot float={true} />
+ <:slot float={1} />
+ <:slot float={1.0} />
+ <:slot float={[]} />
+ <:slot float={nil} />
+
+ """
+ end
+
+ def render_list_line, do: __ENV__.line + 5
+
+ def render_list(assigns) do
+ ~H"""
+ <.func>
+ <:slot list="list" />
+ <:slot list={:list} />
+ <:slot list={true} />
+ <:slot list={1} />
+ <:slot list={1.0} />
+ <:slot list={[]} />
+ <:slot list={nil} />
+
+ """
+ end
+ end
+ end)
+
+ line = get_line(__MODULE__.SlotAttrs, :render_string_line)
+
+ for {value, offset} <- [
+ {:string, 1},
+ {true, 2},
+ {1, 3},
+ {1.0, 4},
+ {[], 5},
+ {nil, 6}
+ ] do
+ assert warnings =~ """
+ attribute "string" \
+ in slot \"slot\" \
+ for component Phoenix.ComponentVerifyTest.SlotAttrs.func/1 \
+ must be a :string, got: #{inspect(value)}
+ test/phoenix_component/verify_test.exs:#{line + offset}: (file)
+ """
+ end
+
+ line = get_line(__MODULE__.SlotAttrs, :render_atom_line)
+
+ for {value, offset} <- [
+ {"atom", 0},
+ {1, 3},
+ {1.0, 4},
+ {[], 5}
+ ] do
+ assert warnings =~ """
+ attribute "atom" \
+ in slot \"slot\" \
+ for component Phoenix.ComponentVerifyTest.SlotAttrs.func/1 \
+ must be an :atom, got: #{inspect(value)}
+ test/phoenix_component/verify_test.exs:#{line + offset}: (file)
+ """
+ end
+
+ line = get_line(__MODULE__.SlotAttrs, :render_boolean_line)
+
+ for {value, offset} <- [
+ {"boolean", 0},
+ {:boolean, 1},
+ {1, 3},
+ {1.0, 4},
+ {[], 5},
+ {nil, 6}
+ ] do
+ assert warnings =~ """
+ attribute "boolean" \
+ in slot \"slot\" \
+ for component Phoenix.ComponentVerifyTest.SlotAttrs.func/1 \
+ must be a :boolean, got: #{inspect(value)}
+ test/phoenix_component/verify_test.exs:#{line + offset}: (file)
+ """
+ end
+
+ line = get_line(__MODULE__.SlotAttrs, :render_integer_line)
+
+ for {value, offset} <- [
+ {"integer", 0},
+ {:integer, 1},
+ {true, 2},
+ {1.0, 4},
+ {[], 5},
+ {nil, 6}
+ ] do
+ assert warnings =~ """
+ attribute "integer" \
+ in slot \"slot\" \
+ for component Phoenix.ComponentVerifyTest.SlotAttrs.func/1 \
+ must be an :integer, got: #{inspect(value)}
+ test/phoenix_component/verify_test.exs:#{line + offset}: (file)
+ """
+ end
+
+ line = get_line(__MODULE__.SlotAttrs, :render_float_line)
+
+ for {value, offset} <- [
+ {"float", 0},
+ {:float, 1},
+ {true, 2},
+ {1, 3},
+ {[], 5},
+ {nil, 6}
+ ] do
+ assert warnings =~ """
+ attribute "float" \
+ in slot \"slot\" \
+ for component Phoenix.ComponentVerifyTest.SlotAttrs.func/1 \
+ must be a :float, got: #{inspect(value)}
+ test/phoenix_component/verify_test.exs:#{line + offset}: (file)
+ """
+ end
+
+ line = get_line(__MODULE__.SlotAttrs, :render_list_line)
+
+ for {value, offset} <- [
+ {"list", 0},
+ {:list, 1},
+ {true, 2},
+ {1, 3},
+ {1.0, 4},
+ {nil, 6}
+ ] do
+ assert warnings =~ """
+ attribute "list" \
+ in slot \"slot\" \
+ for component Phoenix.ComponentVerifyTest.SlotAttrs.func/1 \
+ must be a :list, got: #{inspect(value)}
+ test/phoenix_component/verify_test.exs:#{line + offset}: (file)
+ """
+ end
+ end
+
+ test "validates required slot attrs" do
+ warnings =
+ capture_io(:stderr, fn ->
+ defmodule RequiredSlotAttrs do
+ use Phoenix.Component
+
+ slot :slot do
+ attr :attr, :string, required: true
+ end
+
+ def func(assigns) do
+ ~H"""
+
+ <%= render_slot(@slot) %>
+
+ """
+ end
+
+ def line(), do: __ENV__.line + 4
+
+ def render(assigns) do
+ ~H"""
+ <.func>
+ <:slot />
+ <:slot attr="foo" />
+ <:slot>
+ foo
+
+ <:slot attr="bar">
+ bar
+
+ <:slot {[attr: "bar"]} />
+
+ """
+ end
+ end
+ end)
+
+ line = get_line(__MODULE__.RequiredSlotAttrs)
+
+ assert warnings =~ """
+ missing required attribute "attr" \
+ in slot "slot" \
+ for component \
+ Phoenix.ComponentVerifyTest.RequiredSlotAttrs.func/1
+ test/phoenix_component/verify_test.exs:#{line + 1}: (file)
+ """
+
+ assert warnings =~ """
+ missing required attribute "attr" \
+ in slot "slot" \
+ for component \
+ Phoenix.ComponentVerifyTest.RequiredSlotAttrs.func/1
+ test/phoenix_component/verify_test.exs:#{line + 3}: (file)
+ """
+ end
+
+ test "validates undefined slots" do
+ warnings =
+ capture_io(:stderr, fn ->
+ defmodule UndefinedSlots do
+ use Phoenix.Component
+
+ attr :attr, :any
+
+ def fun_no_slots(assigns), do: ~H[]
+
+ slot :inner_block
+
+ def func(assigns), do: ~H[]
+
+ slot :named
+
+ def func_undefined_slot_attrs(assigns), do: ~H[]
+
+ def line, do: __ENV__.line + 2
+
+ def render(assigns) do
+ ~H"""
+
+ <.fun_no_slots>
+ hello
+
+
+
+ <.func>
+ <:undefined />
+
+
+
+ <.func_undefined_slot_attrs>
+ <:named undefined />
+ <:named undefined="undefined" />
+
+ """
+ end
+ end
+ end)
+
+ line = get_line(__MODULE__.UndefinedSlots)
+
+ assert warnings =~ """
+ undefined slot "inner_block" for component \
+ Phoenix.ComponentVerifyTest.UndefinedSlots.fun_no_slots/1
+ test/phoenix_component/verify_test.exs:#{line + 3}: (file)
+ """
+
+ assert warnings =~ """
+ undefined slot "undefined" for component \
+ Phoenix.ComponentVerifyTest.UndefinedSlots.func/1
+ test/phoenix_component/verify_test.exs:#{line + 9}: (file)
+ """
+
+ assert warnings =~ """
+ undefined attribute "undefined" \
+ in slot "named" \
+ for component \
+ Phoenix.ComponentVerifyTest.UndefinedSlots.func_undefined_slot_attrs/1
+ test/phoenix_component/verify_test.exs:#{line + 14}: (file)
+ """
+
+ assert warnings =~ """
+ undefined attribute "undefined" \
+ in slot "named" \
+ for component \
+ Phoenix.ComponentVerifyTest.UndefinedSlots.func_undefined_slot_attrs/1
+ test/phoenix_component/verify_test.exs:#{line + 15}: (file)
+ """
+ end
+
+ test "validates calls for locally defined components" do
+ warnings =
+ capture_io(:stderr, fn ->
+ defmodule LocalComponents do
+ use Phoenix.Component
+
+ attr :attr, :string, required: true
+
+ def public(assigns) do
+ ~H"""
+ <%= @attr %>
+ """
+ end
+
+ attr :attr, :string, required: true
+
+ defp private(assigns) do
+ ~H"""
+ <%= @attr %>
+ """
+ end
+
+ def line, do: __ENV__.line + 2
+
+ def render(assigns) do
+ ~H"""
+ <.public />
+ <.private />
+ """
+ end
+ end
+ end)
+
+ line = get_line(__MODULE__.LocalComponents)
+
+ assert warnings =~ """
+ missing required attribute "attr" \
+ for component \
+ Phoenix.ComponentVerifyTest.LocalComponents.public/1
+ test/phoenix_component/verify_test.exs:#{line + 2}: (file)
+ """
+
+ assert warnings =~ """
+ missing required attribute "attr" \
+ for component \
+ Phoenix.ComponentVerifyTest.LocalComponents.private/1
+ test/phoenix_component/verify_test.exs:#{line + 3}: (file)
+ """
+ end
+
+ test "global includes" do
+ import Phoenix.LiveViewTest
+
+ defmodule GlobalIncludes do
+ use Phoenix.Component
+
+ attr :id, :any, required: true
+ attr :rest, :global, include: ~w(form)
+ def button(assigns), do: ~H|button |
+ def any_render(assigns), do: ~H|<.button id="123" form="my-form" />|
+ end
+
+ assigns = %{id: "abc", form: "my-form"}
+
+ assert render_component(&GlobalIncludes.button/1, assigns) ==
+ "button "
+ end
+
+ defp get_line(module, fun \\ :line) do
+ apply(module, fun, [])
+ end
+end
diff --git a/test/phoenix_component_test.exs b/test/phoenix_component_test.exs
index a217489607..7999dde852 100644
--- a/test/phoenix_component_test.exs
+++ b/test/phoenix_component_test.exs
@@ -1,240 +1,289 @@
-defmodule Phoenix.ComponentTest do
+defmodule Phoenix.ComponentUnitTest do
use ExUnit.Case, async: true
- use Phoenix.Component
+ alias Phoenix.LiveView.{Socket, Utils}
+ import Phoenix.Component
+
+ @socket Utils.configure_socket(
+ %Socket{
+ endpoint: Endpoint,
+ router: Phoenix.LiveViewTest.Router,
+ view: Phoenix.LiveViewTest.ParamCounterLive
+ },
+ %{
+ connect_params: %{},
+ connect_info: %{},
+ root_view: Phoenix.LiveViewTest.ParamCounterLive,
+ __changed__: %{}
+ },
+ nil,
+ %{},
+ URI.parse("https://www.example.com")
+ )
+
+ @assigns_changes %{key: "value", map: %{foo: :bar}, __changed__: %{}}
+ @assigns_nil_changes %{key: "value", map: %{foo: :bar}, __changed__: nil}
+
+ describe "assign with socket" do
+ test "tracks changes" do
+ socket = assign(@socket, existing: "foo")
+ assert changed?(socket, :existing)
+
+ socket = Utils.clear_changed(socket)
+ socket = assign(socket, existing: "foo")
+ refute changed?(socket, :existing)
+ end
- defp h2s(template) do
- template
- |> Phoenix.HTML.Safe.to_iodata()
- |> IO.iodata_to_binary()
- end
+ test "keeps whole maps in changes" do
+ socket = assign(@socket, existing: %{foo: :bar})
+ socket = Utils.clear_changed(socket)
- describe "rendering" do
- defp hello(assigns) do
- ~H"""
- Hello <%= @name %>
- """
- end
+ socket = assign(socket, existing: %{foo: :baz})
+ assert socket.assigns.existing == %{foo: :baz}
+ assert socket.assigns.__changed__.existing == %{foo: :bar}
- test "renders component" do
- assigns = %{}
+ socket = assign(socket, existing: %{foo: :bat})
+ assert socket.assigns.existing == %{foo: :bat}
+ assert socket.assigns.__changed__.existing == %{foo: :bar}
- assert h2s(~H"""
- <%= component &hello/1, name: "WORLD" %>
- """) == """
- Hello WORLD\
- """
+ socket = assign(socket, %{existing: %{foo: :bam}})
+ assert socket.assigns.existing == %{foo: :bam}
+ assert socket.assigns.__changed__.existing == %{foo: :bar}
end
end
- describe "change tracking" do
- defp eval(%Phoenix.LiveView.Rendered{dynamic: dynamic}), do: Enum.map(dynamic.(true), &eval/1)
- defp eval(other), do: other
+ describe "assign with assigns" do
+ test "tracks changes" do
+ assigns = assign(@assigns_changes, key: "value")
+ assert assigns.key == "value"
+ refute changed?(assigns, :key)
- defp changed(assigns) do
- ~H"""
- <%= inspect(assigns.__changed__) %>
- """
- end
+ assigns = assign(@assigns_changes, key: "changed")
+ assert assigns.key == "changed"
+ assert changed?(assigns, :key)
- test "without changed assigns on root" do
- assigns = %{foo: 1}
- assert eval(~H"<.changed foo={@foo} />") == [["nil"]]
+ assigns = assign(@assigns_nil_changes, key: "changed")
+ assert assigns.key == "changed"
+ assert assigns.__changed__ == nil
+ assert changed?(assigns, :key)
end
- test "with changed assigns on root" do
- assigns = %{foo: 1, __changed__: %{}}
- assert eval(~H"<.changed foo={@foo} />") == [nil]
-
- assigns = %{foo: 1, __changed__: %{bar: true}}
- assert eval(~H"<.changed foo={@foo} />") == [nil]
+ test "track changes on unknown vars" do
+ assigns = assign(@assigns_changes, unknown: nil)
+ assert assigns.unknown == nil
+ assert changed?(assigns, :unknown)
- assigns = %{foo: 1, __changed__: %{foo: true}}
- assert eval(~H"<.changed foo={@foo} />") == [["%{foo: true}"]]
-
- assigns = %{foo: 1, __changed__: %{foo: %{bar: true}}}
- assert eval(~H"<.changed foo={@foo} />") == [["%{foo: %{bar: true}}"]]
+ assigns = assign(@assigns_changes, unknown: "changed")
+ assert assigns.unknown == "changed"
+ assert changed?(assigns, :unknown)
end
- test "with changed assigns on map" do
- assigns = %{foo: %{bar: :bar}, __changed__: %{}}
- assert eval(~H"<.changed foo={@foo.bar} />") == [nil]
-
- assigns = %{foo: %{bar: :bar}, __changed__: %{bar: true}}
- assert eval(~H"<.changed foo={@foo.bar} />") == [nil]
-
- assigns = %{foo: %{bar: :bar}, __changed__: %{foo: true}}
- assert eval(~H"<.changed foo={@foo.bar} />") == [["%{foo: true}"]]
-
- assigns = %{foo: %{bar: :bar}, __changed__: %{foo: %{bar: :bar}}}
- assert eval(~H"<.changed foo={@foo.bar} />") == [nil]
+ test "keeps whole maps in changes" do
+ assigns = assign(@assigns_changes, map: %{foo: :baz})
+ assert assigns.map == %{foo: :baz}
+ assert assigns.__changed__[:map] == %{foo: :bar}
- assigns = %{foo: %{bar: :bar}, __changed__: %{foo: %{bar: :baz}}}
- assert eval(~H"<.changed foo={@foo.bar} />") == [["%{foo: true}"]]
-
- assigns = %{foo: %{bar: %{bar: :bar}}, __changed__: %{foo: %{bar: %{bar: :bat}}}}
- assert eval(~H"<.changed foo={@foo.bar} />") == [["%{foo: %{bar: :bat}}"]]
+ assigns = assign(@assigns_nil_changes, map: %{foo: :baz})
+ assert assigns.map == %{foo: :baz}
+ assert assigns.__changed__ == nil
end
+ end
- test "with multiple changed assigns" do
- assigns = %{foo: 1, bar: 2, __changed__: %{}}
- assert eval(~H"<.changed foo={@foo + @bar} />") == [nil]
-
- assigns = %{foo: 1, bar: 2, __changed__: %{bar: true}}
- assert eval(~H"<.changed foo={@foo + @bar} />") == [["%{foo: true}"]]
-
- assigns = %{foo: 1, bar: 2, __changed__: %{foo: true}}
- assert eval(~H"<.changed foo={@foo + @bar} />") == [["%{foo: true}"]]
-
- assigns = %{foo: 1, bar: 2, __changed__: %{baz: true}}
- assert eval(~H"<.changed foo={@foo + @bar} />") == [nil]
+ describe "assign_new with socket" do
+ test "uses socket assigns if no parent assigns are present" do
+ socket =
+ @socket
+ |> assign(existing: "existing")
+ |> assign_new(:existing, fn -> "new-existing" end)
+ |> assign_new(:notexisting, fn -> "new-notexisting" end)
+
+ assert socket.assigns == %{
+ existing: "existing",
+ notexisting: "new-notexisting",
+ live_action: nil,
+ flash: %{},
+ __changed__: %{existing: true, notexisting: true}
+ }
end
- test "with multiple keys" do
- assigns = %{foo: 1, bar: 2, __changed__: %{}}
- assert eval(~H"<.changed foo={@foo} bar={@bar} />") == [nil]
-
- assigns = %{foo: 1, bar: 2, __changed__: %{bar: true}}
- assert eval(~H"<.changed foo={@foo} bar={@bar} />") == [["%{bar: true}"]]
-
- assigns = %{foo: 1, bar: 2, __changed__: %{foo: true}}
- assert eval(~H"<.changed foo={@foo} bar={@bar} />") == [["%{foo: true}"]]
-
- assigns = %{foo: 1, bar: 2, __changed__: %{baz: true}}
- assert eval(~H"<.changed foo={@foo} bar={@bar} />") == [nil]
+ test "uses parent assigns when present and falls back to socket assigns" do
+ socket =
+ put_in(@socket.private[:assign_new], {%{existing: "existing-parent"}, []})
+ |> assign(existing2: "existing2")
+ |> assign_new(:existing, fn -> "new-existing" end)
+ |> assign_new(:existing2, fn -> "new-existing2" end)
+ |> assign_new(:notexisting, fn -> "new-notexisting" end)
+
+ assert socket.assigns == %{
+ existing: "existing-parent",
+ existing2: "existing2",
+ notexisting: "new-notexisting",
+ live_action: nil,
+ flash: %{},
+ __changed__: %{existing: true, notexisting: true, existing2: true}
+ }
end
- test "with multiple keys and one is static" do
- assigns = %{foo: 1, __changed__: %{}}
- assert eval(~H|<.changed foo={@foo} bar="2" />|) == [nil]
-
- assigns = %{foo: 1, __changed__: %{bar: true}}
- assert eval(~H|<.changed foo={@foo} bar="2" />|) == [nil]
+ test "has access to assigns" do
+ socket =
+ put_in(@socket.private[:assign_new], {%{existing: "existing-parent"}, []})
+ |> assign(existing2: "existing2")
+ |> assign_new(:existing, fn _ -> "new-existing" end)
+ |> assign_new(:existing2, fn _ -> "new-existing2" end)
+ |> assign_new(:notexisting, fn %{existing: existing} -> existing end)
+ |> assign_new(:notexisting2, fn %{existing2: existing2} -> existing2 end)
+ |> assign_new(:notexisting3, fn %{notexisting: notexisting} -> notexisting end)
+
+ assert socket.assigns == %{
+ existing: "existing-parent",
+ existing2: "existing2",
+ notexisting: "existing-parent",
+ notexisting2: "existing2",
+ notexisting3: "existing-parent",
+ live_action: nil,
+ flash: %{},
+ __changed__: %{
+ existing: true,
+ existing2: true,
+ notexisting: true,
+ notexisting2: true,
+ notexisting3: true
+ }
+ }
+ end
+ end
- assigns = %{foo: 1, __changed__: %{foo: true}}
- assert eval(~H|<.changed foo={@foo} bar="2" />|) == [["%{foo: true}"]]
+ describe "assign_new with assigns" do
+ test "tracks changes" do
+ assigns = assign_new(@assigns_changes, :key, fn -> raise "won't be invoked" end)
+ assert assigns.key == "value"
+ refute changed?(assigns, :key)
+ refute assigns.__changed__[:key]
+
+ assigns = assign_new(@assigns_changes, :another, fn -> "changed" end)
+ assert assigns.another == "changed"
+ assert changed?(assigns, :another)
+
+ assigns = assign_new(@assigns_nil_changes, :another, fn -> "changed" end)
+ assert assigns.another == "changed"
+ assert changed?(assigns, :another)
+ assert assigns.__changed__ == nil
end
- test "with multiple keys and one is tainted" do
- assigns = %{foo: 1, __changed__: %{}}
- assert eval(~H|<.changed foo={@foo} bar={assigns} />|) == [["%{bar: true}"]]
+ test "has access to new assigns" do
+ assigns =
+ assign_new(@assigns_changes, :another, fn -> "changed" end)
+ |> assign_new(:and_another, fn %{another: another} -> another end)
- assigns = %{foo: 1, __changed__: %{foo: true}}
- assert eval(~H|<.changed foo={@foo} bar={assigns} />|) == [["%{bar: true, foo: true}"]]
+ assert assigns.and_another == "changed"
+ assert changed?(assigns, :another)
+ assert changed?(assigns, :and_another)
end
+ end
- test "with conflict on changed assigns" do
- assigns = %{foo: 1, bar: %{foo: 2}, __changed__: %{}}
- assert eval(~H"<.changed foo={@foo} {@bar} />") == [nil]
-
- assigns = %{foo: 1, bar: %{foo: 2}, __changed__: %{bar: true}}
- assert eval(~H"<.changed foo={@foo} {@bar} />") == [["%{foo: true}"]]
+ describe "update with socket" do
+ test "tracks changes" do
+ socket = @socket |> assign(key: "value") |> Utils.clear_changed()
- assigns = %{foo: 1, bar: %{foo: 2}, __changed__: %{foo: true}}
- assert eval(~H"<.changed foo={@foo} {@bar} />") == [["%{foo: true}"]]
+ socket = update(socket, :key, fn "value" -> "value" end)
+ assert socket.assigns.key == "value"
+ refute changed?(socket, :key)
- assigns = %{foo: 1, bar: %{foo: 2}, baz: 3, __changed__: %{baz: true}}
- assert eval(~H"<.changed foo={@foo} {@bar} baz={@baz} />") == [["%{baz: true}"]]
+ socket = update(socket, :key, fn "value" -> "changed" end)
+ assert socket.assigns.key == "changed"
+ assert changed?(socket, :key)
end
+ end
- test "with dynamic assigns" do
- assigns = %{foo: %{a: 1, b: 2}, __changed__: %{}}
- assert eval(~H"<.changed {@foo} />") == [nil]
-
- assigns = %{foo: %{a: 1, b: 2}, __changed__: %{foo: true}}
- assert eval(~H"<.changed {@foo} />") == [["%{a: true, b: true}"]]
+ describe "update with assigns" do
+ test "tracks changes" do
+ assigns = update(@assigns_changes, :key, fn "value" -> "value" end)
+ assert assigns.key == "value"
+ refute changed?(assigns, :key)
- assigns = %{foo: %{a: 1, b: 2}, bar: 3, __changed__: %{bar: true}}
- assert eval(~H"<.changed {@foo} bar={@bar} />") == [["%{bar: true}"]]
+ assigns = update(@assigns_changes, :key, fn "value" -> "changed" end)
+ assert assigns.key == "changed"
+ assert changed?(assigns, :key)
- assigns = %{foo: %{a: 1, b: 2}, bar: 3, __changed__: %{bar: true}}
- assert eval(~H"<.changed {%{a: 1, b: 2}} bar={@bar} />") == [["%{bar: true}"]]
+ assigns = update(@assigns_nil_changes, :key, fn "value" -> "changed" end)
+ assert assigns.key == "changed"
+ assert changed?(assigns, :key)
+ assert assigns.__changed__ == nil
+ end
+ end
- assigns = %{foo: %{a: 1, b: 2}, bar: 3, __changed__: %{bar: true}}
+ describe "update with arity 2 function" do
+ test "passes socket assigns to update function" do
+ socket = @socket |> assign(key: "value", key2: "another") |> Utils.clear_changed()
- assert eval(~H"<.changed {%{a: assigns[:b], b: assigns[:a]}} bar={@bar} />") ==
- [["%{a: true, b: true, bar: true}"]]
+ socket = update(socket, :key2, fn key2, %{key: key} -> key2 <> " " <> key end)
+ assert socket.assigns.key2 == "another value"
+ assert changed?(socket, :key2)
end
- defp inner_changed(assigns) do
- ~H"""
- <%= inspect(assigns.__changed__) %>
- <%= render_slot(@inner_block, "var") %>
- """
+ test "passes assigns to update function" do
+ assigns = update(@assigns_changes, :key, fn _, %{map: %{foo: bar}} -> bar end)
+ assert assigns.key == :bar
+ assert changed?(assigns, :key)
end
+ end
- test "with @inner_block" do
- assigns = %{foo: 1, __changed__: %{}}
- assert eval(~H|<.inner_changed foo={@foo}>|) == [nil]
- assert eval(~H|<.inner_changed><%= @foo %>|) == [nil]
-
- assigns = %{foo: 1, __changed__: %{foo: true}}
-
- assert eval(~H|<.inner_changed foo={@foo}>|) ==
- [["%{foo: true}", nil]]
+ test "assigns_to_attributes/2" do
+ assert assigns_to_attributes(%{}) == []
+ assert assigns_to_attributes(%{}, [:non_exists]) == []
+ assert assigns_to_attributes(%{one: 1, two: 2}) == [one: 1, two: 2]
+ assert assigns_to_attributes(%{one: 1, two: 2}, [:one]) == [two: 2]
+ assert assigns_to_attributes(%{__changed__: %{}, one: 1, two: 2}, [:one]) == [two: 2]
+ assert assigns_to_attributes(%{__changed__: %{}, inner_block: fn -> :ok end, a: 1}) == [a: 1]
+ assert assigns_to_attributes(%{__slot__: :foo, inner_block: fn -> :ok end, a: 1}) == [a: 1]
+ end
- assert eval(
- ~H|<.inner_changed foo={@foo}><%= inspect(assigns.__changed__) %>|
- ) ==
- [["%{foo: true, inner_block: true}", ["%{foo: true}"]]]
+ describe "to_form/2" do
+ test "with a map" do
+ form = to_form(%{})
+ assert form.name == nil
+ assert form.id == nil
- assert eval(
- ~H|<.inner_changed><%= @foo %>|
- ) ==
- [["%{inner_block: true}", ["1"]]]
- end
+ form = to_form(%{}, as: :foo)
+ assert form.name == "foo"
+ assert form.id == "foo"
- test "with let" do
- assigns = %{foo: 1, __changed__: %{}}
- assert eval(~H|<.inner_changed let={_foo} foo={@foo}>|) == [nil]
+ form = to_form(%{}, as: :foo, id: "bar")
+ assert form.name == "foo"
+ assert form.id == "bar"
- assigns = %{foo: 1, __changed__: %{foo: true}}
+ form = to_form(%{}, custom: "attr")
+ assert form.options == [custom: "attr"]
- assert eval(~H|<.inner_changed let={_foo} foo={@foo}>|) ==
- [["%{foo: true}", nil]]
+ form = to_form(%{}, errors: [name: "can't be blank"])
+ assert form.errors == [name: "can't be blank"]
+ end
- assert eval(
- ~H|<.inner_changed let={_foo} foo={@foo}><%= inspect(assigns.__changed__) %>|
- ) ==
- [["%{foo: true, inner_block: true}", ["%{foo: true}"]]]
+ test "with a form" do
+ base = to_form(%{}, as: "name", id: "id")
+ assert to_form(base, []) == base
- assert eval(
- ~H|<.inner_changed let={_foo} foo={@foo}><%= "constant" %><%= inspect(assigns.__changed__) %>|
- ) ==
- [["%{foo: true, inner_block: true}", [nil, "%{foo: true}"]]]
+ form = to_form(base, as: :foo)
+ assert form.name == "foo"
+ assert form.id == "foo"
- assert eval(
- ~H|<.inner_changed let={foo} foo={@foo}><.inner_changed let={_bar} bar={foo}><%= "constant" %><%= inspect(assigns.__changed__) %>|
- ) ==
- [
- [
- "%{foo: true, inner_block: true}",
- [["%{bar: true, inner_block: true}", [nil, "%{foo: true}"]]]
- ]
- ]
+ form = to_form(base, id: "bar")
+ assert form.name == "name"
+ assert form.id == "bar"
- assert eval(
- ~H|<.inner_changed let={foo} foo={@foo}><%= foo %><%= inspect(assigns.__changed__) %>|
- ) ==
- [["%{foo: true, inner_block: true}", ["var", "%{foo: true}"]]]
+ form = to_form(base, as: :foo, id: "bar")
+ assert form.name == "foo"
+ assert form.id == "bar"
- assert eval(
- ~H|<.inner_changed let={foo} foo={@foo}><.inner_changed let={bar} bar={foo}><%= bar %><%= inspect(assigns.__changed__) %>|
- ) ==
- [
- [
- "%{foo: true, inner_block: true}",
- [["%{bar: true, inner_block: true}", ["var", "%{foo: true}"]]]
- ]
- ]
- end
- end
+ form = to_form(base, as: nil, id: nil)
+ assert form.name == nil
+ assert form.id == nil
- describe "testing" do
- import Phoenix.LiveViewTest
+ form = to_form(base, custom: "attr")
+ assert form.options[:custom] == "attr"
- test "render_component/1" do
- assert render_component(&hello/1, name: "World!") == "Hello World!"
+ form = to_form(base, errors: [name: "can't be blank"])
+ assert form.errors == [name: "can't be blank"]
end
end
end
diff --git a/test/phoenix_live_view/diff_test.exs b/test/phoenix_live_view/diff_test.exs
index 74c1dbbde0..c4b2cd0a99 100644
--- a/test/phoenix_live_view/diff_test.exs
+++ b/test/phoenix_live_view/diff_test.exs
@@ -1,7 +1,7 @@
defmodule Phoenix.LiveView.DiffTest do
use ExUnit.Case, async: true
- import Phoenix.LiveView.Helpers
+ import Phoenix.Component
alias Phoenix.LiveView.{Socket, Diff, Rendered, Component}
alias Phoenix.LiveComponent.CID
@@ -35,6 +35,20 @@ defmodule Phoenix.LiveView.DiffTest do
"""
end
+ def nested_comprehension_template(assigns) do
+ ~H"""
+
+
<%= @title %>
+ <%= for name <- @names do %>
+ <%= name %>
+ <%= for score <- @scores do %>
+ <%= score %>
+ <% end %>
+ <% end %>
+
+ """
+ end
+
defp nested_rendered(changed? \\ true) do
%Rendered{
static: ["", " ", "", " "],
@@ -229,6 +243,34 @@ defmodule Phoenix.LiveView.DiffTest do
assert {^fingerprint, %{1 => ^comprehension_print}} = socket.fingerprints
end
+
+ test "nested comprehensions" do
+ %{fingerprint: fingerprint} =
+ rendered =
+ nested_comprehension_template(%{
+ title: "Users",
+ names: ["phoenix", "elixir"],
+ scores: [1, 2]
+ })
+
+ {socket, full_render, _} = render(rendered)
+
+ assert full_render == %{
+ 0 => "Users",
+ 1 => %{
+ d: [
+ ["phoenix", %{d: [["1"], ["2"]], s: 0}],
+ ["elixir", %{d: [["1"], ["2"]], s: 0}]
+ ],
+ p: %{0 => ["\n ", "\n "]},
+ s: ["\n ", "\n ", "\n "]
+ },
+ :s => ["\n
", " \n ", "\n"]
+ }
+
+ assert {^fingerprint, %{1 => comprehension_print}} = socket.fingerprints
+ assert is_integer(comprehension_print)
+ end
end
describe "diffed render with fingerprints" do
@@ -433,7 +475,7 @@ defmodule Phoenix.LiveView.DiffTest do
def render_with_live_component(assigns) do
~H"""
COMPONENT
- <.live_component module={SlotComponent} let={%{value: value}} id="WORLD">
+ <.live_component module={SlotComponent} :let={%{value: value}} id="WORLD">
WITH VALUE <%= value %>
"""
@@ -519,7 +561,7 @@ defmodule Phoenix.LiveView.DiffTest do
assigns = %{socket: %Socket{}}
rendered = ~H"""
- <%= component &FunctionComponent.render_only/1, from: :component %>
+
"""
{socket, full_render, components} = render(rendered)
@@ -536,6 +578,38 @@ defmodule Phoenix.LiveView.DiffTest do
assert components == Diff.new_components()
end
+ @raises_inside_rendered_line __ENV__.line + 3
+ defp raises_inside_rendered(assigns) do
+ ~H"""
+ <%= raise "oops" %>
+ """
+ end
+
+ test "stacktrace" do
+ assigns = %{socket: %Socket{}}
+ line = __ENV__.line + 3
+
+ rendered = ~H"""
+ <.raises_inside_rendered />
+ """
+
+ try do
+ render(rendered)
+ rescue
+ RuntimeError ->
+ [{__MODULE__, _anonymous_fun, _anonymous_arity, info} | rest] = __STACKTRACE__
+ assert List.to_string(info[:file]) =~ "diff_test.exs"
+ assert info[:line] == @raises_inside_rendered_line
+
+ assert Enum.any?(rest, fn {mod, fun, arity, info} ->
+ mod == __MODULE__ and __ENV__.function == {fun, arity} and
+ List.to_string(info[:file]) =~ "diff_test.exs" and info[:line] == line
+ end)
+ else
+ _ -> flunk("should have raised runtime error")
+ end
+ end
+
test "@inner_block without args" do
assigns = %{socket: %Socket{}}
@@ -592,7 +666,7 @@ defmodule Phoenix.LiveView.DiffTest do
defp function_tracking(assigns) do
~H"""
-
+
WITH VALUE <%= value %> - <%= @value %>
"""
@@ -641,7 +715,7 @@ defmodule Phoenix.LiveView.DiffTest do
assert full_render == %{
0 => %{
0 => "DEFAULT",
- 2 => "DEFAULT",
+ 2 => "DEFAULT"
}
}
@@ -738,11 +812,34 @@ defmodule Phoenix.LiveView.DiffTest do
}
end
+ def conditional_slot_tracking(assigns) do
+ ~H"""
+ <.render_multiple_slots>
+ <:header :if={@if}>
+ <.live_component module={MyComponent} id="header" from={:component} />
+
+ <:footer :if={@if}>
+ <.live_component module={MyComponent} id="footer" from={:component} />
+
+
+ """
+ end
+
+ test "slot tracking with live component inside conditional slot" do
+ assigns = %{socket: %Socket{}, if: true}
+ {_socket, _full_render, components} = render(conditional_slot_tracking(assigns))
+ assert {_, _, 3} = components
+
+ assigns = %{socket: %Socket{}, if: false}
+ {_socket, _full_render, components} = render(conditional_slot_tracking(assigns))
+ assert {_, _, 1} = components
+ end
+
test "with live_component" do
assigns = %{socket: %Socket{}}
rendered = ~H"""
- <%= component &FunctionComponent.render_with_live_component/1 %>
+
"""
{socket, full_render, components} = render(rendered)
@@ -1236,10 +1333,11 @@ defmodule Phoenix.LiveView.DiffTest do
assert full_render == %{
0 => %{
d: [
- ["foo", %{d: [["0", 1], ["1", 2]], s: ["\n ", ": ", "\n "]}],
- ["bar", %{d: [["0", 3], ["1", 4]], s: ["\n ", ": ", "\n "]}]
+ ["foo", %{d: [["0", 1], ["1", 2]], s: 0}],
+ ["bar", %{d: [["0", 3], ["1", 4]], s: 0}]
],
- s: ["\n ", "\n ", "\n "]
+ s: ["\n ", "\n ", "\n "],
+ p: %{0 => ["\n ", ": ", "\n "]}
},
:c => %{
1 => %{0 => "index_1", 1 => "world", :s => ["FROM ", " ", "
"]},
@@ -1344,7 +1442,7 @@ defmodule Phoenix.LiveView.DiffTest do
# Now let's add one level of nesting directly
{diff, diff_components, :extra} =
Diff.write_component(socket, 1, diff_components, fn socket, _component ->
- {Phoenix.LiveView.assign(socket, children: [{2, []}]), :extra}
+ {Phoenix.Component.assign(socket, children: [{2, []}]), :extra}
end)
assert diff == %{
@@ -1379,11 +1477,9 @@ defmodule Phoenix.LiveView.DiffTest do
assert full_render == %{
0 => %{
- d: [
- ["1", %{0 => 1, :s => ["\n ", "\n
"]}],
- ["2", %{0 => 2, :s => ["\n ", "\n
"]}]
- ],
- s: ["\n ", ": ", "\n "]
+ d: [["1", %{0 => 1, :s => 0}], ["2", %{0 => 2, :s => 0}]],
+ s: ["\n ", ": ", "\n "],
+ p: %{0 => ["\n ", "\n
"]}
},
:c => %{
1 => %{0 => "index_1", 1 => "world", :s => ["FROM ", " ", "
"]},
@@ -1427,12 +1523,11 @@ defmodule Phoenix.LiveView.DiffTest do
assert full_render == %{
0 => %{
- d: [[""], [%{0 => "2", 1 => 1, :s => ["", ": ", ""]}]],
- s: ["\n ", "\n "]
- },
- :c => %{
- 1 => %{0 => "index_2", 1 => "world", :s => ["FROM ", " ", "
"]}
+ d: [[""], [%{0 => "2", 1 => 1, :s => 0}]],
+ s: ["\n ", "\n "],
+ p: %{0 => ["", ": ", ""]}
},
+ :c => %{1 => %{0 => "index_2", 1 => "world", :s => ["FROM ", " ", "
"]}},
:s => ["\n ", "\n
"]
}
@@ -1481,7 +1576,7 @@ defmodule Phoenix.LiveView.DiffTest do
defp tracking(assigns) do
~H"""
- <.live_component module={SlotComponent} let={%{value: value}} id="TRACKING">
+ <.live_component module={SlotComponent} :let={%{value: value}} id="TRACKING">
WITH PARENT VALUE <%= @parent_value %>
WITH VALUE <%= value %>
diff --git a/test/phoenix_live_view/engine_test.exs b/test/phoenix_live_view/engine_test.exs
index 4d1359c87e..5a709028ba 100644
--- a/test/phoenix_live_view/engine_test.exs
+++ b/test/phoenix_live_view/engine_test.exs
@@ -77,10 +77,8 @@ defmodule Phoenix.LiveView.EngineTest do
"prea\n\nposta\n<hello>\npreb\n\nmiddleb\n\npostb\n"
end
- test "raises ArgumentError for missing assigns" do
- assert_raise ArgumentError,
- ~r/assign @foo not available in template.*Available assigns: \[:bar\]/s,
- fn -> render("<%= @foo %>", %{bar: true}) end
+ test "raises KeyError for missing assigns" do
+ assert_raise KeyError, fn -> render("<%= @foo %>", %{bar: true}) end
end
end
@@ -162,6 +160,20 @@ defmodule Phoenix.LiveView.EngineTest do
assert changed(template, %{foo: 123}, %{foo: true}) == ["123"]
end
+ test "does not render dynamic if it is unchanged via assigns dot" do
+ template = "<%= assigns.foo %>"
+ assert changed(template, %{foo: 123}, nil) == ["123"]
+ assert changed(template, %{foo: 123}, %{}) == [nil]
+ assert changed(template, %{foo: 123}, %{foo: true}) == ["123"]
+ end
+
+ test "does not render dynamic if it is unchanged via assigns access" do
+ template = "<%= assigns[:foo] %>"
+ assert changed(template, %{foo: 123}, nil) == ["123"]
+ assert changed(template, %{foo: 123}, %{}) == [nil]
+ assert changed(template, %{foo: 123}, %{foo: true}) == ["123"]
+ end
+
test "renders dynamic if any of the assigns change" do
template = "<%= @foo + @bar %>"
assert changed(template, %{foo: 123, bar: 456}, nil) == ["579"]
@@ -251,6 +263,21 @@ defmodule Phoenix.LiveView.EngineTest do
assert changed(template, new_changed_bar, old) == ["777"]
end
+ test "renders dynamic with access tracking for forms" do
+ form1 = Phoenix.Component.to_form(%{"foo" => "bar"})
+ form2 = Phoenix.Component.to_form(%{"foo" => "bar", "baz" => "bat"})
+ form3 = Phoenix.Component.to_form(%{"foo" => "baz"})
+
+ template = "<%= Map.fetch!(@form[:foo], :value) %>"
+ assert changed(template, %{form: form1}, nil) == ["bar"]
+
+ template = "<%= Map.fetch!(@form[:foo], :value) %>"
+ assert changed(template, %{form: form1}, %{}) == [nil]
+ assert changed(template, %{form: form1}, %{form: form1}) == [nil]
+ assert changed(template, %{form: form2}, %{form: form1}) == [nil]
+ assert changed(template, %{form: form3}, %{form: form1}) == ["baz"]
+ end
+
test "renders dynamic with access tracking inside comprehension" do
template = """
<%= for x <- [:a, :b, :c] do %>
@@ -265,13 +292,6 @@ defmodule Phoenix.LiveView.EngineTest do
assert [%Phoenix.LiveView.Comprehension{}] = changed(template, old, old)
end
- test "renders dynamic if it has a lexical form" do
- template = "<%= import List %><%= flatten(@foo) %>"
- assert changed(template, %{foo: '123'}, nil) == ["Elixir.List", '123']
- assert changed(template, %{foo: '123'}, %{}) == ["Elixir.List", nil]
- assert changed(template, %{foo: '123'}, %{foo: true}) == ["Elixir.List", '123']
- end
-
test "renders dynamic if it has variables" do
template = "<%= foo = 1 + 2 %><%= foo %>"
assert changed(template, %{}, nil) == ["3", "3"]
@@ -312,6 +332,16 @@ defmodule Phoenix.LiveView.EngineTest do
assert changed(template, %{foo: [1, 2, 3]}, %{foo: true}) == [[1, 2, 3]]
end
+ test "does not render dynamic if it has variables as comprehension generators" do
+ template = "<%= for x <- foo do %><%= x %><% end %>"
+
+ rendered = eval(template, %{__changed__: nil}, [foo: [1, 2, 3]])
+ assert [%{dynamics: [["1"], ["2"], ["3"]]}] = expand_dynamic(rendered.dynamic, true)
+
+ rendered = eval(template, %{__changed__: %{}}, [foo: [1, 2, 3]])
+ assert [%{dynamics: [["1"], ["2"], ["3"]]}] = expand_dynamic(rendered.dynamic, true)
+ end
+
test "does not render dynamic if it has variables inside optimized comprehension" do
template = "<%= for foo <- @foo do %><%= foo %><% end %>"
@@ -377,7 +407,7 @@ defmodule Phoenix.LiveView.EngineTest do
end
test "renders dynamic if it uses assigns directly" do
- template = "<%= for _ <- [1, 2, 3], do: assigns.foo %>"
+ template = "<%= for _ <- [1, 2, 3], do: Map.get(assigns, :foo) %>"
assert changed(template, %{foo: "a"}, nil) == [["a", "a", "a"]]
assert changed(template, %{foo: "a"}, %{}) == [["a", "a", "a"]]
assert changed(template, %{foo: "a"}, %{foo: true}) == [["a", "a", "a"]]
@@ -427,7 +457,7 @@ defmodule Phoenix.LiveView.EngineTest do
[""]
end
- test "converts if-do with var into rendered" do
+ test "converts if-do with var assignments into rendered" do
template = "<%= if var = @foo do %>one<%= var %>two<% end %>"
assert [%Rendered{dynamic: ["true"], static: ["one", "two"]}] =
@@ -437,6 +467,19 @@ defmodule Phoenix.LiveView.EngineTest do
assert changed(template, %{foo: false}, %{foo: true}) == [""]
end
+ test "converts if-do with external var assignments into rendered but tainted" do
+ template = "<%= var = @foo %><%= if var do %>one<%= var %>two<% end %>"
+
+ assert ["true", %Rendered{dynamic: ["true"], static: ["one", "two"]}] =
+ changed(template, %{foo: true}, nil)
+
+ assert ["true", %Rendered{dynamic: ["true"], static: ["one", "two"]}] =
+ changed(template, %{foo: true}, %{})
+
+ assert ["false", ""] =
+ changed(template, %{foo: false}, %{foo: true})
+ end
+
test "converts if-do-else into rendered with dynamic condition" do
template = "<%= if @bar do %>one<%= @foo %>two<% else %>uno<%= @baz %>dos<% end %>"
@@ -500,7 +543,7 @@ defmodule Phoenix.LiveView.EngineTest do
] = changed(template, %{foo: 123}, %{foo: true})
end
- test "converts if-do if-do with var into rendered" do
+ test "converts if-do if-do with var assignment into rendered" do
template = "<%= if var = @foo do %>one<%= if var do %>uno<%= var %>dos<% end %>two<% end %>"
assert [
@@ -838,8 +881,8 @@ defmodule Phoenix.LiveView.EngineTest do
end
end
- defp eval(string, assigns \\ %{}) do
- EEx.eval_string(string, [assigns: assigns], file: __ENV__.file, engine: Engine)
+ defp eval(string, assigns \\ %{}, binding \\ []) do
+ EEx.eval_string(string, [assigns: assigns] ++ binding, file: __ENV__.file, engine: Engine)
end
defp changed(string, assigns, changed, track_changes? \\ true) do
diff --git a/test/phoenix_live_view/heex_extension_test.exs b/test/phoenix_live_view/heex_extension_test.exs
index 7ab794cae3..be7da6dafa 100644
--- a/test/phoenix_live_view/heex_extension_test.exs
+++ b/test/phoenix_live_view/heex_extension_test.exs
@@ -1,7 +1,7 @@
defmodule Phoenix.LiveView.HEExExtensionTest do
use ExUnit.Case, async: true
- alias Phoenix.LiveView.{HTMLEngine, Rendered, Component}
+ alias Phoenix.LiveView.Rendered
defmodule View do
use Phoenix.View, root: "test/support/templates/heex", path: ""
@@ -12,15 +12,6 @@ defmodule Phoenix.LiveView.HEExExtensionTest do
def render(assigns), do: ~H"FROM COMPONENT"
end
- defmacrop compile(string) do
- EEx.compile_string(
- string,
- engine: HTMLEngine,
- file: __CALLER__.file,
- line: __CALLER__.line + 1
- )
- end
-
@assigns %{
pre: "pre",
inner_content: "inner",
@@ -106,20 +97,6 @@ defmodule Phoenix.LiveView.HEExExtensionTest do
|> expand_rendered(true)
end
- test "renders inside render_layout/4" do
- import Phoenix.View
- assigns = @assigns
-
- assert %Rendered{} =
- compile("""
- <%= render_layout(View, "inner_live.html", %{}) do %>
- WITH COMPONENT:
- <%= %Component{assigns: %{}, component: SampleComponent} %>
- <% end %>
- """)
- |> expand_rendered(true)
- end
-
defp expand_dynamic(dynamic, track_changes?) do
Enum.map(dynamic.(track_changes?), &expand_rendered(&1, track_changes?))
end
diff --git a/test/phoenix_live_view/helpers_test.exs b/test/phoenix_live_view/helpers_test.exs
index dec11ce0db..cbee1e3a83 100644
--- a/test/phoenix_live_view/helpers_test.exs
+++ b/test/phoenix_live_view/helpers_test.exs
@@ -2,236 +2,20 @@ defmodule Phoenix.LiveView.HelpersTest do
use ExUnit.Case, async: true
import Phoenix.LiveView.Helpers
- import Phoenix.HTML
- import Phoenix.HTML.Form
+ import Phoenix.Component
- describe "live_patch" do
- test "single arity" do
- dom =
- live_patch(to: "/", do: "text")
- |> safe_to_string()
-
- assert dom =~ ~s|data-phx-link="patch"|
- assert dom =~ ~s|data-phx-link-state="push"|
- assert dom =~ ~s|text|
- refute dom =~ ~s|to="/|
- end
-
- test "forwards dom attribute options" do
- dom =
- live_patch("next", to: "/", class: "btn btn-large", data: [page_number: 2])
- |> safe_to_string()
-
- assert dom =~ ~s|class="btn btn-large"|
- assert dom =~ ~s|data-page-number="2"|
- assert dom =~ ~s|data-phx-link="patch"|
- assert dom =~ ~s|data-phx-link-state="push"|
- end
-
- test "overwrites reserved options" do
- dom =
- live_patch("next", to: "page-1", href: "page-2", data: [phx_link: "other"], replace: true)
- |> safe_to_string()
-
- assert dom =~ ~s|href="page-1"|
- refute dom =~ ~s|href="page-2"|
- assert dom =~ ~s|data-phx-link="patch"|
- assert dom =~ ~s|data-phx-link-state="replace"|
- refute dom =~ ~s|data-phx-link="other"|
- end
-
- test "uses HTML safe protocol" do
- assert live_patch(123, to: "page") |> safe_to_string() =~ "123"
- end
- end
-
- describe "live_redirect" do
- test "single arity" do
- dom =
- live_redirect(to: "/", do: "text")
- |> safe_to_string()
-
- assert dom =~ ~s|data-phx-link="redirect"|
- assert dom =~ ~s|data-phx-link-state="push"|
- assert dom =~ ~s|text|
- refute dom =~ ~s|to="/|
- end
-
- test "forwards dom attribute options" do
- dom =
- live_redirect("next", to: "/", class: "btn btn-large", data: [page_number: 2])
- |> safe_to_string()
-
- assert dom =~ ~s|class="btn btn-large"|
- assert dom =~ ~s|data-page-number="2"|
- assert dom =~ ~s|data-phx-link="redirect"|
- assert dom =~ ~s|data-phx-link-state="push"|
- end
-
- test "overwrites reserved options" do
- dom =
- live_redirect("next",
- to: "page-1",
- href: "page-2",
- data: [phx_link: "other"],
- replace: true
- )
- |> safe_to_string()
-
- assert dom =~ ~s|href="page-1"|
- refute dom =~ ~s|href="page-2"|
- assert dom =~ ~s|data-phx-link="redirect"|
- assert dom =~ ~s|data-phx-link-state="replace"|
- refute dom =~ ~s|data-phx-link="other"|
- end
-
- test "uses HTML safe protocol" do
- assert live_redirect(123, to: "page") |> safe_to_string() =~ "123"
- end
- end
-
- describe "live_title_tag/2" do
- test "prefix only" do
- assert safe_to_string(live_title_tag("My Title", prefix: "MyApp – ")) ==
- ~s|MyApp – My Title |
- end
-
- test "suffix only" do
- assert safe_to_string(live_title_tag("My Title", suffix: " – MyApp")) ==
- ~s|My Title – MyApp |
- end
-
- test "prefix and suffix" do
- assert safe_to_string(live_title_tag("My Title", prefix: "Welcome: ", suffix: " – MyApp")) ==
- ~s|Welcome: My Title – MyApp |
- end
-
- test "without prefix or suffix" do
- assert safe_to_string(live_title_tag("My Title")) ==
- ~s|My Title |
- end
-
- test "bad options" do
- assert_raise ArgumentError, ~r/expects a :prefix and\/or :suffix/, fn ->
- live_title_tag("bad", bad: :bad)
- end
- end
- end
-
- defp parse(template) do
+ defp render(template) do
template
|> Phoenix.HTML.Safe.to_iodata()
|> IO.iodata_to_binary()
- |> Phoenix.LiveViewTest.DOM.parse()
end
- describe "form" do
- test "raises when missing required assigns" do
- assert_raise ArgumentError, ~r/missing :for assign/, fn ->
- assigns = %{}
- parse(~H"""
- <.form let={f}>
- <%= text_input f, :foo %>
-
- """)
- end
-
- assert_raise ArgumentError, ~r/This means a component requires a do-block or HTML children/, fn ->
- assigns = %{}
- parse(~H"""
- <.form for={:myform} />
- """)
- end
- end
-
- test "generates form with no options" do
- assigns = %{}
-
- html =
- parse(~H"""
- <.form let={f} for={:myform}>
- <%= text_input f, :foo %>
-
- """)
-
- assert [
- {"form", [{"action", "#"}, {"method", "post"}],
- [
- {"input", [{"name", "_csrf_token"}, {"type", "hidden"}, {"value", _}], []},
- {"input", [{"id", "myform_foo"}, {"name", "myform[foo]"}, {"type", "text"}], []}
- ]}
- ] = html
- end
-
- test "does not generate csrf_token if method is not post" do
- assigns = %{}
-
- html =
- parse(~H"""
- <.form let={f} for={:myform} method="get">
- <%= text_input f, :foo %>
-
- """)
-
- assert [
- {"form", [{"action", "#"}, {"method", "get"}],
- [
- {"input", [{"id", "myform_foo"}, {"name", "myform[foo]"}, {"type", "text"}], []}
- ]}
- ] = html
- end
-
- test "generates form with available options and custom attributes" do
- assigns = %{}
-
- html =
- parse(~H"""
- <.form let={user_form}
- for={%Plug.Conn{}}
- id="form"
- action="/"
- method="put"
- multipart
- csrf_token="123"
- as="user"
- errors={[name: "can't be blank"]}
- data-foo="bar"
- class="pretty"
- phx-change="valid"
- >
- <%= text_input user_form, :foo %>
- <%= inspect(user_form.errors) %>
-
- """)
-
- assert [
- {"form",
- [
- {"action", "/"},
- {"method", "post"},
- {"enctype", "multipart/form-data"},
- {"class", "pretty"},
- {"data-foo", "bar"},
- {"id", "form"},
- {"phx-change", "valid"}
- ],
- [
- {"input", [{"name", "_method"}, {"type", "hidden"}, {"value", "put"}], []},
- {"input", [{"name", "_csrf_token"}, {"type", "hidden"}, {"value", "123"}], []},
- {"input", [{"id", "form_foo"}, {"name", "user[foo]"}, {"type", "text"}], []},
- "\n [name: \"can't be blank\"]\n\n"
- ]}
- ] = html
- end
- end
+ test "deprecated live_file_input escapes attributes" do
+ assigns = %{}
- test "assigns_to_attributes/2" do
- assert assigns_to_attributes(%{}) == []
- assert assigns_to_attributes(%{}, [:non_exists]) == []
- assert assigns_to_attributes(%{one: 1, two: 2}) == [one: 1, two: 2]
- assert assigns_to_attributes(%{one: 1, two: 2}, [:one]) == [two: 2]
- assert assigns_to_attributes(%{__changed__: %{}, one: 1, two: 2}, [:one]) == [two: 2]
- assert assigns_to_attributes(%{__changed__: %{}, inner_block: fn -> :ok end, a: 1}) == [a: 1]
- assert assigns_to_attributes(%{__slot__: :foo, inner_block: fn -> :ok end, a: 1}) == [a: 1]
+ assert render(
+ ~H|<%= live_file_input %Phoenix.LiveView.UploadConfig{}, class: "" %>|
+ ) ==
+ ~s| |
end
end
diff --git a/test/phoenix_live_view/lifecycle_test.exs b/test/phoenix_live_view/hooks_test.exs
similarity index 98%
rename from test/phoenix_live_view/lifecycle_test.exs
rename to test/phoenix_live_view/hooks_test.exs
index a1d4a3d3a0..5af51c0f94 100644
--- a/test/phoenix_live_view/lifecycle_test.exs
+++ b/test/phoenix_live_view/hooks_test.exs
@@ -1,4 +1,4 @@
-defmodule Phoenix.LiveView.HookTest do
+defmodule Phoenix.LiveView.IntegrationHooksTest do
use ExUnit.Case, async: true
alias Phoenix.LiveView
diff --git a/test/phoenix_live_view/html_engine_test.exs b/test/phoenix_live_view/html_engine_test.exs
index 618fa3c22d..dbea90e6dd 100644
--- a/test/phoenix_live_view/html_engine_test.exs
+++ b/test/phoenix_live_view/html_engine_test.exs
@@ -1,18 +1,18 @@
defmodule Phoenix.LiveView.HTMLEngineTest do
use ExUnit.Case, async: true
- import Phoenix.LiveView.Helpers,
- only: [sigil_H: 2, render_slot: 1, render_slot: 2]
-
- alias Phoenix.LiveView.HTMLEngine
- alias Phoenix.LiveView.HTMLTokenizer.ParseError
+ import Phoenix.Component
+ alias Phoenix.LiveView.Tokenizer.ParseError
defp eval(string, assigns \\ %{}, opts \\ []) do
opts =
Keyword.merge(opts,
file: __ENV__.file,
- engine: HTMLEngine,
- subengine: Phoenix.LiveView.Engine
+ engine: Phoenix.LiveView.TagEngine,
+ subengine: Phoenix.LiveView.Engine,
+ caller: __ENV__,
+ source: string,
+ tag_handler: Phoenix.LiveView.HTMLEngine
)
EEx.eval_string(string, [assigns: assigns], opts)
@@ -27,7 +27,16 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
defmacrop compile(string) do
quote do
- unquote(EEx.compile_string(string, file: __ENV__.file, engine: HTMLEngine))
+ unquote(
+ EEx.compile_string(string,
+ file: __ENV__.file,
+ engine: Phoenix.LiveView.TagEngine,
+ module: __MODULE__,
+ caller: __CALLER__,
+ source: string,
+ tag_handler: Phoenix.LiveView.HTMLEngine
+ )
+ )
|> Phoenix.HTML.Safe.to_iodata()
|> IO.iodata_to_binary()
end
@@ -37,6 +46,13 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
~H"<%= inspect(Map.delete(assigns, :__changed__)) %>"
end
+ def textarea(assigns) do
+ assigns =
+ Phoenix.Component.assign(assigns, :extra_assigns, assigns_to_attributes(assigns, []))
+
+ ~H""
+ end
+
def remote_function_component(assigns) do
~H"REMOTE COMPONENT: Value: <%= @value %>"
end
@@ -180,12 +196,53 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
assert %Phoenix.LiveView.Rendered{static: ["
"]} =
eval(template, assigns)
+ # binaries in lists for classes are extracted out
+ template = ~S(", "
"]} />)
+ assert render(template, assigns) == ~S(
)
+
+ assert %Phoenix.LiveView.Rendered{static: ["
"]} =
+ eval(template, assigns)
+
+ # binaries in lists for classes are extracted out even with dynamic bits
+ template = ~S(", @unsafe]} />)
+ assert render(template, assigns) == ~S(
)
+
+ assert %Phoenix.LiveView.Rendered{static: ["
"]} =
+ eval(template, assigns)
+
# raises if not a binary
assert_raise ArgumentError, "expected a binary in <>, got: {:safe, \"
\"}", fn ->
render(~S( @safe} />), assigns)
end
end
+ def do_block(do: block), do: block
+
+ test "handles do blocks with expressions" do
+ assigns = %{not_text: "not text", text: "text"}
+
+ template = ~S"""
+ <%= @text %>
+ <%= Phoenix.LiveView.HTMLEngineTest.do_block do %><%= assigns[:not_text] %><% end %>
+ """
+
+ # A bug made it so "not text" appeared inside @text.
+ assert render(template, assigns) == "text\nnot text"
+
+ template = ~S"""
+ <%= for i <- ["id1", "id2", "id3"] do %>
+
+ <%= Phoenix.LiveView.HTMLEngineTest.do_block do %>
+ <%= i %>
+ <% end %>
+
+ <% end %>
+ """
+
+ # A bug made it so "id={id}" was not handled properly
+ assert render(template, assigns) =~ ~s'
'
+ end
+
test "optimizes class attributes" do
assigns = %{
nil_assign: nil,
@@ -193,7 +250,8 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
false_assign: false,
unsafe: "
",
safe: {:safe, ""},
- list: ["safe", false, nil, ""]
+ list: ["safe", false, nil, ""],
+ recursive_list: ["safe", false, [nil, ""]]
}
assert %Phoenix.LiveView.Rendered{static: ["
"]} =
@@ -216,6 +274,9 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
template = ~S(
)
assert render(template, assigns) == ~S(
)
+
+ template = ~S(
)
+ assert render(template, assigns) == ~S(
)
end
test "optimizes attributes that can be empty" do
@@ -301,6 +362,27 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
""") == "REMOTE COMPONENT: Value: 1, Content: \n The inner content\n"
end
+ test "remote call with :let" do
+ expected = """
+ LOCAL COMPONENT WITH ARGS: Value: aBcD
+
+ Upcase: ABCD
+ Downcase: abcd
+ """
+
+ assigns = %{}
+
+ assert compile("""
+ <.local_function_component_with_inner_block_args
+ value="aBcD"
+ :let={%{upcase: upcase, downcase: downcase}}
+ >
+ Upcase: <%= upcase %>
+ Downcase: <%= downcase %>
+
+ """) =~ expected
+ end
+
test "remote call with inner content with args" do
expected = """
REMOTE COMPONENT WITH ARGS: Value: aBcD
@@ -314,7 +396,7 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
assert compile("""
Upcase: <%= upcase %>
Downcase: <%= downcase %>
@@ -324,9 +406,9 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
test "raise on remote call with inner content passing non-matching args" do
message = ~r"""
- cannot match arguments sent from `render_slot/2` against the pattern in `let`.
+ cannot match arguments sent from render_slot/2 against the pattern in :let.
- Expected a value matching `%{wrong: _}`, got: `%{downcase: "abcd", upcase: "ABCD"}`.
+ Expected a value matching `%{wrong: _}`, got: %{downcase: "abcd", upcase: "ABCD"}\
"""
assigns = %{}
@@ -335,7 +417,7 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
compile("""
...
@@ -344,12 +426,12 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
end
test "raise on remote call passing args to self close components" do
- message = ~r".exs:2: cannot use `let` on a component without inner content"
+ message = ~r".exs:2: cannot use :let on a component without inner content"
assert_raise(CompileError, message, fn ->
eval("""
-
+
""")
end)
end
@@ -384,7 +466,7 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
assert compile("""
<.local_function_component_with_inner_block_args
value="aBcD"
- let={%{upcase: upcase, downcase: downcase}}
+ :let={%{upcase: upcase, downcase: downcase}}
>
Upcase: <%= upcase %>
Downcase: <%= downcase %>
@@ -394,7 +476,7 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
assert compile("""
<.local_function_component_with_inner_block_args
{[value: "aBcD"]}
- let={%{upcase: upcase, downcase: downcase}}
+ :let={%{upcase: upcase, downcase: downcase}}
>
Upcase: <%= upcase %>
Downcase: <%= downcase %>
@@ -404,9 +486,9 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
test "raise on local call with inner content passing non-matching args" do
message = ~r"""
- cannot match arguments sent from `render_slot/2` against the pattern in `let`.
+ cannot match arguments sent from render_slot/2 against the pattern in :let.
- Expected a value matching `%{wrong: _}`, got: `%{downcase: "abcd", upcase: "ABCD"}`.
+ Expected a value matching `%{wrong: _}`, got: %{downcase: "abcd", upcase: "ABCD"}\
"""
assigns = %{}
@@ -415,7 +497,7 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
compile("""
<.local_function_component_with_inner_block_args
{[value: "aBcD"]}
- let={%{wrong: _}}
+ :let={%{wrong: _}}
>
...
@@ -424,41 +506,176 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
end
test "raise on local call passing args to self close components" do
- message = ~r".exs:2: cannot use `let` on a component without inner content"
+ message = ~r".exs:2: cannot use :let on a component without inner content"
assert_raise(CompileError, message, fn ->
eval("""
- <.local_function_component value='1' let={var}/>
+ <.local_function_component value='1' :let={var}/>
""")
end)
end
- test "raise on duplicated `let`" do
- message =
- ~r".exs:4:(8:)? cannot define multiple `let` attributes. Another `let` has already been defined at line 3"
+ test "raise on duplicated :let" do
+ message = """
+ test/phoenix_live_view/html_engine_test.exs:4:3: cannot define multiple :let attributes. Another :let has already been defined at line 3
+ |
+ 1 |
+ 2 |
eval("""
""")
end)
+ message = """
+ test/phoenix_live_view/html_engine_test.exs:4:3: cannot define multiple :let attributes. Another :let has already been defined at line 3
+ |
+ 1 |
+ 2 | <.local_function_component value='1'
+ 3 | :let={var1}
+ 4 | :let={var2}
+ | ^\
+ """
+
assert_raise(ParseError, message, fn ->
eval("""
<.local_function_component value='1'
- let={var1}
- let={var2}
+ :let={var1}
+ :let={var2}
+ />
+ """)
+ end)
+ end
+
+ test "invalid :let expr" do
+ message = """
+ test/phoenix_live_view/html_engine_test.exs:2:70: :let must be a pattern between {...} in remote_component Phoenix.LiveView.HTMLEngineTest.remote_function_component
+ |
+ 1 |
+ 2 |
+ eval("""
+
+
+ """)
+ end)
+
+ message = """
+ test/phoenix_live_view/html_engine_test.exs:2:38: :let must be a pattern between {...} in local_component .local_function_component
+ |
+ 1 |
+ 2 | <.local_function_component value='1' :let=\"1\"
+ | ^\
+ """
+
+ assert_raise(ParseError, message, fn ->
+ eval("""
+
+ <.local_function_component value='1' :let="1"
/>
""")
end)
end
+ test "raise with invalid special attr" do
+ message = """
+ test/phoenix_live_view/html_engine_test.exs:2:38: unsupported attribute \":bar\" in local_component .local_function_component
+ |
+ 1 |
+ 2 | <.local_function_component value='1' :bar=\"1\"}
+ | ^\
+ """
+
+ assert_raise(ParseError, message, fn ->
+ eval("""
+
+ <.local_function_component value='1' :bar="1"}
+ />
+ """)
+ end)
+ end
+
+ test "raise on unclosed local call" do
+ message = """
+ test/phoenix_live_view/html_engine_test.exs:1:1: end of template reached without closing tag for <.local_function_component>
+ |
+ 1 | <.local_function_component value='1' :let={var}>
+ | ^\
+ """
+
+ assert_raise(ParseError, message, fn ->
+ eval("""
+ <.local_function_component value='1' :let={var}>
+ """)
+ end)
+
+ message = """
+ test/phoenix_live_view/html_engine_test.exs:2:3: end of do-block reached without closing tag for <.local_function_component>
+ |
+ 1 | <%= if true do %>
+ 2 | <.local_function_component value='1' :let={var}>
+ | ^\
+ """
+
+ assert_raise(ParseError, message, fn ->
+ eval("""
+ <%= if true do %>
+ <.local_function_component value='1' :let={var}>
+ <% end %>
+ """)
+ end)
+ end
+
+ test "when tag is unclosed" do
+ message = """
+ test/phoenix_live_view/html_engine_test.exs:2:1: end of template reached without closing tag for
+ |
+ 1 |
Foo
+ 2 |
+ | ^\
+ """
+
+ assert_raise(ParseError, message, fn ->
+ eval("""
+
Foo
+
+
Bar
+ """)
+ end)
+ end
+
+ test "when syntax error on HTML attributes" do
+ message = """
+ test/phoenix_live_view/html_engine_test.exs:2:9: invalid attribute value after `=`. Expected either a value between quotes (such as \"value\" or 'value') or an Elixir expression between curly brackets (such as `{expr}`)
+ |
+ 1 |
Bar
+ 2 |
Foo
+ | ^\
+ """
+
+ assert_raise(ParseError, message, fn ->
+ eval("""
+
Bar
+
Foo
+ """)
+ end)
+ end
+
test "empty attributes" do
assigns = %{}
assert compile("<.assigns_component />") == "%{}"
@@ -518,7 +735,7 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
"""
end
- def function_component_with_slot_props(assigns) do
+ def function_component_with_slot_attrs(assigns) do
~H"""
<%= for entry <- @sample do %>
<%= entry.a %>
@@ -673,20 +890,20 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
""") == expected
end
- test "slot props" do
+ test "slot attrs" do
assigns = %{a: "A"}
expected = "\nA\n and \nB\n"
assert compile("""
- <.function_component_with_slot_props>
+ <.function_component_with_slot_attrs>
<:sample a={@a} b="B"> and
-
+
""") == expected
assert compile("""
-
+
<:sample a={@a} b="B"> and
-
+
""") == expected
end
@@ -738,7 +955,7 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
end
test "multiple slots with default" do
- assigns = %{}
+ assigns = %{middle: "middle"}
expected = """
BEFORE COMPONENT
@@ -747,8 +964,9 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
The header content
\
- TEXT:top
- mid
+ TEXT:
+ top
+ foo middle bar
bot
:TEXT
@@ -767,7 +985,7 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
<:header>
The header content
- mid
+ foo <%= @middle %> bar
<:footer>
The footer content
@@ -783,7 +1001,7 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
<:header>
The header content
- mid
+ foo <%= @middle %> bar
<:footer>
The footer content
@@ -810,7 +1028,7 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
assert compile("""
COMPONENT WITH SLOTS:
<.function_component_with_slots_and_args>
- <:sample let={arg}>
+ <:sample :let={arg}>
The sample slot
Arg: <%= arg %>
@@ -820,7 +1038,7 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
assert compile("""
COMPONENT WITH SLOTS:
- <:sample let={arg}>
+ <:sample :let={arg}>
The sample slot
Arg: <%= arg %>
@@ -899,13 +1117,19 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
""") == expected
end
- test "raise if self close slot uses let" do
- message = ~r".exs:2:(24:)? cannot use `let` on a slot without inner content"
+ test "raise if self close slot uses :let" do
+ message = """
+ test/phoenix_live_view/html_engine_test.exs:2:19: cannot use :let on a slot without inner content
+ |
+ 1 | <.function_component_with_self_close_slots>
+ 2 | <:sample id="1" :let={var}/>
+ | ^\
+ """
assert_raise(ParseError, message, fn ->
eval("""
<.function_component_with_self_close_slots>
- <:sample id="1" let={var}/>
+ <:sample id="1" :let={var}/>
""")
end)
@@ -941,7 +1165,13 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
end
test "raise if the slot entry is not a direct child of a component" do
- message = ~r".exs:2:(3:)? invalid slot entry <:sample>. A slot entry must be a direct child of a component"
+ message = """
+ test/phoenix_live_view/html_engine_test.exs:2:3: invalid slot entry <:sample>. A slot entry must be a direct child of a component
+ |
+ 1 |
+ 2 | <:sample>
+ | ^\
+ """
assert_raise(ParseError, message, fn ->
eval("""
@@ -953,21 +1183,55 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
""")
end)
- message = ~r".exs:3:(5:)? invalid slot entry <:footer>. A slot entry must be a direct child of a component"
+ message = """
+ test/phoenix_live_view/html_engine_test.exs:3:3: invalid slot entry <:sample>. A slot entry must be a direct child of a component
+ |
+ 1 |
+ 2 | <%= if true do %>
+ 3 | <:sample>
+ | ^\
+ """
assert_raise(ParseError, message, fn ->
eval("""
-
+
+ <%= if true do %>
+ <:sample>
+ Content
+
+ <% end %>
+
+ """)
+ end)
+
+ message = """
+ test/phoenix_live_view/html_engine_test.exs:3:5: invalid slot entry <:footer>. A slot entry must be a direct child of a component
+ |
+ 1 | <.mydiv>
+ 2 | <:sample>
+ 3 | <:footer>
+ | ^\
+ """
+
+ assert_raise(ParseError, message, fn ->
+ eval("""
+ <.mydiv>
<:sample>
<:footer>
Content
-
+
""")
end)
- message = ~r".exs:1:(1:)? invalid slot entry <:sample>. A slot entry must be a direct child of a component"
+ message = """
+ test/phoenix_live_view/html_engine_test.exs:1:1: invalid slot entry <:sample>. A slot entry must be a direct child of a component
+ |
+ 1 | <:sample>
+ | ^\
+ """
+
assert_raise(ParseError, message, fn ->
eval("""
<:sample>
@@ -975,6 +1239,21 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
""")
end)
+
+ message = """
+ test/phoenix_live_view/html_engine_test.exs:1:1: invalid slot entry <:sample>. A slot entry must be a direct child of a component
+ |
+ 1 | <:sample>
+ | ^\
+ """
+
+ assert_raise(ParseError, message, fn ->
+ eval("""
+ <:sample>
+ Content
+
+ """)
+ end)
end
end
@@ -1007,10 +1286,16 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
assert eval("<.to_string>").root == false
assert eval(" ").root == false
assert eval(" ").root == false
+ assert eval("<%= item %>
").root == false
end
end
describe "tag validations" do
+ test "handles style" do
+ assert render("") ==
+ ""
+ end
+
test "handles script" do
assert render("") ==
""
@@ -1022,7 +1307,12 @@ defmodule Phoenix.LiveView.HTMLEngineTest do
end
test "unmatched comment" do
- message = ~r".exs:1:(11:)? expected closing `-->` for comment"
+ message = """
+ test/phoenix_live_view/html_engine_test.exs:1:6: expected closing `-->` for comment
+ |
+ 1 | Begin
+
+ """,
+ """
+
+
+ """
+ )
+
+ assert_formatter_doesnt_change("""
+
+ <%= render_slot(@inner_block) %>
+ """)
+
+ assert_formatter_doesnt_change("""
+
+
+ """)
+ end
+
+ test "handle case end when previous block is blank" do
+ input = """
+ <%= case :foo do %>
+ <% :foo -> %>
+ something
+ <% _ -> %>
+ <% end %>
+ """
+
+ expected = """
+ <%= case :foo do %>
+ <% :foo -> %>
+ something
+ <% _ -> %>
+ <% end %>
+ """
+
+ assert_formatter_output(input, expected)
+ end
+
+ test "keep intentional spaces" do
+ input = """
+
+ Last <%= length(@backlog_feeds) %> of <%= @feedcount %> backlog feeds
+ """
+
+ expected = """
+
+ Last <%= length(@backlog_feeds) %> of <%= @feedcount %> backlog feeds
+
+ """
+
+ assert_formatter_output(input, expected)
+ end
+
+ test "handle comment block with eex" do
+ assert_formatter_doesnt_change("""
+
+
+
+ """)
+ end
+
+ test "handle spaces properly" do
+ input = """
+
+
+ Close
+
+ """
+
+ expected = """
+
+ Close
+
+ """
+
+ assert_formatter_output(input, expected)
+ end
+
+ test "keep at least one space around inline tags" do
+ assert_formatter_doesnt_change("""
+ Foo:
+ """)
+
+ assert_formatter_doesnt_change("""
+ Foo:
+ """)
+
+ assert_formatter_doesnt_change("""
+ Foo:
+ """)
+
+ assert_formatter_doesnt_change("""
+
+ Foo: bar
+
+ """)
+
+ assert_formatter_doesnt_change("""
+
+ Foo: bar
+
+ """)
+
+ assert_formatter_output(
+ """
+ bar
+ """,
+ """
+ bar
+ """
+ )
+
+ assert_formatter_doesnt_change("""
+ Foo: bar
+ """)
+
+ assert_formatter_doesnt_change("""
+
+ Foo: <%= some_var %>
+
+ """)
+
+ assert_formatter_doesnt_change("""
+
+ Foo: <%= some_var %>
+
+ """)
+
+ assert_formatter_output(
+ """
+ Foo Bar
+ """,
+ """
+ Foo Bar
+ """
+ )
+
+ assert_formatter_doesnt_change("""
+ Foo Bar
+ """)
+
+ assert_formatter_output(
+ """
+ Foo:
+ """,
+ """
+ Foo:
+ """
+ )
+
+ assert_formatter_output(
+ """
+ Foo:
+ """,
+ """
+ Foo:
+ """
+ )
+ end
+
+ test "does not keep empty lines on script and styles tags" do
+ input = """
+
+ """
+
+ expected = """
+
+ """
+
+ assert_formatter_output(input, expected)
+
+ input = """
+
+ """
+
+ expected = """
+
+ """
+
+ assert_formatter_output(input, expected)
+ end
+
+ test "does not break lines in self closed elements" do
+ assert_formatter_doesnt_change("""
+
+ This should not wrap on a new line .
+
+ """)
+ end
+
+ test "keep eex along with the text" do
+ assert_formatter_doesnt_change("""
+
+ _______________________________________________________ result<%= if(@row_count != 1, do: "s") %>
+
+ """)
+
+ assert_formatter_doesnt_change("""
+
+ _______________________________________________________ result <%= if(@row_count != 1, do: "s") %>
+
+ """)
+ end
+
+ test "keep single quote delimiter when value has quotes" do
+ assert_formatter_doesnt_change("""
+
+ """)
+ end
+
+ test "transform single quotes to double when value has no quotes" do
+ input = """
+
+ """
+
+ expected = """
+
+ """
+
+ assert_formatter_output(input, expected)
+ end
+
+ test "does not format inline elements surrounded by texts without white spaces" do
+ assert_formatter_output(
+ """
+
+ text text textlink
+
+ """,
+ """
+
+ text text textlink
+
+ """,
+ line_length: 50
+ )
+
+ assert_formatter_output(
+ """
+
+ first first link second.
+
+ """,
+ """
+
+ first first link second.
+
+ """,
+ line_length: 50
+ )
+
+ assert_formatter_output(
+ """
+
+ link text text text text.
+
+ """,
+ """
+
+ link text text text text.
+
+ """,
+ line_length: 50
+ )
+
+ assert_formatter_doesnt_change(
+ """
+
+ long line of text span 1
+ more text span 2
+
+ """,
+ line_length: 45
+ )
+ end
+
+ test "preserve inline element when there aren't whitespaces" do
+ assert_formatter_doesnt_change(
+ """
+ foo bar baz
+ """,
+ line_length: 20
+ )
+
+ assert_formatter_output(
+ """
+ Foo bar baz
+ """,
+ """
+ Foo bar
+ baz
+ """,
+ line_length: 20
+ )
+
+ assert_formatter_doesnt_change(
+ """
+ foo bar <%= @user.name %>baz
+ """,
+ line_length: 20
+ )
+
+ assert_formatter_output(
+ """
+ foo bar baz
+ """,
+ """
+ foo bar baz
+ """,
+ line_length: 20
+ )
+
+ assert_formatter_doesnt_change(
+ """
+ foo bar
baz
+ """,
+ line_length: 20
+ )
+ end
+
+ test "preserve inline element on the same line when followed by a eex expression without whitespaces" do
+ assert_formatter_doesnt_change(
+ """
+ <%= some_function("arg") %>content
+ """,
+ line_length: 25
+ )
+
+ assert_formatter_output(
+ """
+ <%= some_function("arg") %> content
+ """,
+ """
+ <%= some_function("arg") %>
+ content
+ """,
+ line_length: 25
+ )
+ end
+
+ test "does not format when contenteditable is present" do
+ assert_formatter_doesnt_change(
+ """
+ The content content content content content
+ """,
+ line_length: 10
+ )
+
+ assert_formatter_doesnt_change(
+ """
+ The content content content content content
+ """,
+ line_length: 10
+ )
+
+ assert_formatter_output(
+ """
+ The content content content content content
+ """,
+ """
+
+ The content content content content content
+
+ """,
+ line_length: 10
+ )
+ end
+
+ test "does not format textarea" do
+ assert_formatter_doesnt_change(
+ """
+
+ """,
+ line_length: 5
+ )
+
+ assert_formatter_doesnt_change(
+ """
+
+ """,
+ line_length: 5
+ )
+
+ assert_formatter_doesnt_change("""
+
+ """)
+
+ assert_formatter_doesnt_change("""
+
+ """)
+ end
+
+ test "keeps right format for inline elements within block elements" do
+ assert_formatter_doesnt_change("""
+
+ """)
+ end
+
+ test "respects heex_line_length" do
+ assert_formatter_doesnt_change(
+ """
+
+ Please let me be in the same line Value Please let me be in the same line .
+
+ """,
+ heex_line_length: 1000
+ )
+ end
+
+ test "does not format when phx-no-format attr is present" do
+ assert_formatter_doesnt_change(
+ """
+ <.textarea phx-no-format>My content
+ """,
+ line_length: 5
+ )
+ end
+
+ test "respect nesting of children when phx-no-format is present" do
+ assert_formatter_doesnt_change(
+ """
+ <%= for user <- @users do %>
+
+
+ text
+
+
+ <% end %>
+ """,
+ line_length: 100
+ )
+
+ assert_formatter_doesnt_change(
+ """
+
+ """,
+ line_length: 100
+ )
+ end
+
+ test "transform 'let' to :let" do
+ assert_formatter_output(
+ """
+ <.form let={f} for={@changeset}>
+ <%= input(f, :foo) %>
+
+ """,
+ """
+ <.form :let={f} for={@changeset}>
+ <%= input(f, :foo) %>
+
+ """
+ )
+ end
+
+ test "order :let :for and :if over HTML attributes" do
+ assert_formatter_output(
+ """
+ <.form for={@changeset} :let={f} class="form">
+ <%= input(f, :foo) %>
+
+ """,
+ """
+ <.form :let={f} for={@changeset} class="form">
+ <%= input(f, :foo) %>
+
+ """
+ )
+
+ assert_formatter_output(
+ """
+
+ """,
+ """
+
+ """
+ )
+
+ assert_formatter_output(
+ """
+
+ """,
+ """
+
+ """
+ )
+ end
+
+ test "handle html comments + eex expressions" do
+ assert_formatter_output(
+ """
+ <%= if @comment do %><% end %>
+ """,
+ """
+ <%= if @comment do %>
+
+ <% end %>
+ """
+ )
+ end
+
+ test "keep intentional line breaks between slots" do
+ assert_formatter_doesnt_change("""
+ <.component>
+ <:title>Guides & Docs
+
+ <:desc>View our step-by-step guides, or browse the comprehensive API docs
+
+ """)
+
+ assert_formatter_output(
+ """
+ <.component>
+
+ <:title>Guides & Docs
+
+
+ <:desc>View our step-by-step guides, or browse the comprehensive API docs
+
+
+ """,
+ """
+ <.component>
+ <:title>Guides & Docs
+
+ <:desc>View our step-by-step guides, or browse the comprehensive API docs
+
+ """
+ )
+ end
+
+ test "does not not break lines for long css lines when there are interpolation" do
+ assert_formatter_doesnt_change(
+ ~S"""
+
+ Hi
+
+ """,
+ heex_line_length: 10
+ )
+
+ assert_formatter_doesnt_change(
+ """
+ "mt-1 block w-full"}>
+ Hi
+
+ """,
+ heex_line_length: 10
+ )
+ end
+
+ test "break text to next line when previous inline element is indented" do
+ assert_formatter_output(
+ """
+ foo <%= some_function() %> baz
+ """,
+ """
+
+ foo
+
+ <%= some_function() %>
+
+ baz
+
+ """,
+ heex_line_length: 15
+ )
+ end
+
+ test "format attrs from self tag close correctly within preserve mode" do
+ assert_formatter_doesnt_change("""
+
+
+
+ Back to previous page
+
+ """)
+
+ assert_formatter_doesnt_change("""
+
+
+
+
+
+ Back to previous page
+
+ """)
+ end
+
+ test "does not break attrs" do
+ assert_formatter_output(
+ """
+
+ <%= render_slot(@inner_block) %>
+
+ """,
+ """
+
+ <%= render_slot(@inner_block) %>
+
+ """
+ )
+
+ assert_formatter_doesnt_change("""
+
+ """)
+
+ assert_formatter_doesnt_change("""
+
+ """)
+
+ assert_formatter_doesnt_change("""
+
+ """)
+ end
+
+ test "doesn't break line when tag/component is right after the text" do
+ assert_formatter_doesnt_change("""
+
+ (API ).
+
+ """)
+
+ assert_formatter_doesnt_change("""
+
+ (
API
).
+
+ """)
+
+ assert_formatter_doesnt_change("""
+
+ (<.abbr label="application programming interface">API).
+
+ """)
+ end
+
+ test "handle heredocs" do
+ assert_formatter_output(
+ """
+ <.component msg={"text"}>
+
+
+ """,
+ """
+ <.component msg="text">
+
+
+ """
+ )
+
+ assert_formatter_doesnt_change("""
+ <.component msg={\"""
+ text
+ \"""}>
+
+
+ """)
+
+ assert_formatter_output(
+ """
+ <.component id={@id} msg={\"""
+ text
+ \"""}>
+
+
+ """,
+ """
+ <.component
+ id={@id}
+ msg={\"""
+ text
+ \"""}
+ >
+
+
+ """
+ )
+ end
+
+ test "handle var <> heredocs" do
+ assert_formatter_doesnt_change("""
+ <.component id={@id} msg={@test <> \"""
+ text
+ \"""}>
+
+
+ """)
+ end
+
+ test "handle multiple HTML comments with eex vars" do
+ assert_formatter_doesnt_change("""
+
+
+ """)
+ end
+
+ # TODO: Remove this `if` after Elixir versions before than 1.14 are no
+ # longer supported.
+ if function_exported?(EEx, :tokenize, 2) do
+ test "handle EEx comments" do
+ assert_formatter_doesnt_change("""
+
+ <%!-- some --%>
+ <%!-- comment --%>
+ <%!--
+
+ <%= @user.name %>
+
+ --%>
+
+ """)
+
+ assert_formatter_doesnt_change("""
+
+ <%= # some %>
+ <%= # comment %>
+ <%= # lines %>
+
+ """)
+ end
+ end
+ end
+end
diff --git a/test/phoenix_live_view/html_tokenizer_test.exs b/test/phoenix_live_view/html_tokenizer_test.exs
deleted file mode 100644
index 9f4260cecb..0000000000
--- a/test/phoenix_live_view/html_tokenizer_test.exs
+++ /dev/null
@@ -1,584 +0,0 @@
-defmodule Phoenix.LiveView.HTMLTokenizerTest do
- use ExUnit.Case, async: true
- alias Phoenix.LiveView.HTMLTokenizer.ParseError
-
- defp tokenize(text) do
- Phoenix.LiveView.HTMLTokenizer.tokenize(text, "nofile", 0, [], [], :text)
- |> elem(0)
- |> Enum.reverse()
- end
-
- describe "text" do
- test "represented as {:text, value}" do
- assert tokenize("Hello") == [{:text, "Hello", %{line_end: 1, column_end: 6}}]
- end
-
- test "with multiple lines" do
- tokens =
- tokenize("""
- first
- second
- third
- """)
-
- assert tokens == [{:text, "first\nsecond\nthird\n", %{line_end: 4, column_end: 1}}]
- end
-
- test "keep line breaks unchanged" do
- assert tokenize("first\nsecond\r\nthird") == [
- {:text, "first\nsecond\r\nthird", %{line_end: 3, column_end: 6}}
- ]
- end
- end
-
- describe "doctype" do
- test "generated as text" do
- assert tokenize("") == [
- {:text, "", %{line_end: 1, column_end: 16}}
- ]
- end
-
- test "multiple lines" do
- assert tokenize(" ") == [
- {:text, " ", %{line_end: 3, column_end: 4}},
- {:tag_open, "br", [], %{column: 4, line: 3, self_close: true}}
- ]
- end
- end
-
- describe "comment" do
- test "generated as text" do
- assert tokenize("BeginEnd") == [
- {:text, "BeginEnd", %{line_end: 1, column_end: 25}}
- ]
- end
-
- test "multiple lines and wrapped by tags" do
- code = """
-
-
-
\
- """
-
- assert [
- {:tag_open, "p", [], %{line: 1, column: 1}},
- {:text, "\n\n", %{line_end: 5, column_end: 1}},
- {:tag_close, "p", %{line: 5, column: 1}},
- {:tag_open, "br", [], %{line: 5, column: 5}}
- ] = tokenize(code)
- end
- end
-
- describe "opening tag" do
- test "represented as {:tag_open, name, attrs, meta}" do
- tokens = tokenize("")
- assert [{:tag_open, "div", [], %{}}] = tokens
- end
-
- test "with space after name" do
- tokens = tokenize("
")
- assert [{:tag_open, "div", [], %{}}] = tokens
- end
-
- test "with line break after name" do
- tokens = tokenize("
")
- assert [{:tag_open, "div", [], %{}}] = tokens
- end
-
- test "self close" do
- tokens = tokenize("
")
- assert [{:tag_open, "div", [], %{self_close: true}}] = tokens
- end
-
- test "compute line and column" do
- tokens =
- tokenize("""
-
-
-
-
\
- """)
-
- assert [
- {:tag_open, "div", [], %{line: 1, column: 1}},
- {:text, _, %{line_end: 2, column_end: 3}},
- {:tag_open, "span", [], %{line: 2, column: 3}},
- {:text, _, %{line_end: 4, column_end: 1}},
- {:tag_open, "p", [], %{column: 1, line: 4, self_close: true}},
- {:tag_open, "br", [], %{column: 5, line: 4}}
- ] = tokens
- end
-
- test "raise on missing/incomplete tag name" do
- assert_raise ParseError, "nofile:2:4: expected tag name", fn ->
- tokenize("""
-
- <>\
- """)
- end
-
- assert_raise ParseError, "nofile:1:2: expected tag name", fn ->
- tokenize("<")
- end
-
- assert_raise ParseError, ~r"nofile:1:5: expected closing `>` or `/>`", fn ->
- tokenize("
))
-
- assert [
- {"class", {:string, "panel", %{}}},
- {"style", {:expr, "@style", %{}}},
- {"hidden", nil}
- ] = attrs
- end
-
- test "accepts space between the name and `=`" do
- attrs = tokenize_attrs(~S())
-
- assert [{"class", {:string, "panel", %{}}}] = attrs
- end
-
- test "accepts line breaks between the name and `=`" do
- attrs = tokenize_attrs("
")
-
- assert [{"class", {:string, "panel", %{}}}] = attrs
-
- attrs = tokenize_attrs("
")
-
- assert [{"class", {:string, "panel", %{}}}] = attrs
- end
-
- test "accepts space between `=` and the value" do
- attrs = tokenize_attrs(~S(
))
-
- assert [{"class", {:string, "panel", %{}}}] = attrs
- end
-
- test "accepts line breaks between `=` and the value" do
- attrs = tokenize_attrs("
")
-
- assert [{"class", {:string, "panel", %{}}}] = attrs
-
- attrs = tokenize_attrs("
")
-
- assert [{"class", {:string, "panel", %{}}}] = attrs
- end
-
- test "raise on missing value" do
- message = ~r"nofile:2:9: invalid attribute value after `=`"
-
- assert_raise ParseError, message, fn ->
- tokenize("""
-
\
- """)
- end
-
- message = ~r"nofile:1:13: invalid attribute value after `=`"
-
- assert_raise ParseError, message, fn ->
- tokenize(~S(
))
- end
-
- message = ~r"nofile:1:12: invalid attribute value after `=`"
-
- assert_raise ParseError, message, fn ->
- tokenize("
- tokenize("""
-
-
\
- """)
- end
-
- assert_raise ParseError, "nofile:1:6: expected attribute name", fn ->
- tokenize(~S(
))
- end
-
- assert_raise ParseError, "nofile:1:6: expected attribute name", fn ->
- tokenize(~S(
))
- end
- end
-
- test "raise on attribute names with quotes" do
- assert_raise ParseError, "nofile:1:5: invalid character in attribute name: '", fn ->
- tokenize(~S(
))
- end
-
- assert_raise ParseError, "nofile:1:5: invalid character in attribute name: \"", fn ->
- tokenize(~S(
))
- end
-
- assert_raise ParseError, "nofile:1:10: invalid character in attribute name: '", fn ->
- tokenize(~S(
))
- end
-
- assert_raise ParseError, "nofile:1:20: invalid character in attribute name: \"", fn ->
- tokenize(~S(
))
- end
- end
- end
-
- describe "boolean attributes" do
- test "represented as {name, nil}" do
- attrs = tokenize_attrs("
")
-
- assert [{"hidden", nil}] = attrs
- end
-
- test "multiple attributes" do
- attrs = tokenize_attrs("
")
-
- assert [{"hidden", nil}, {"selected", nil}] = attrs
- end
-
- test "with space after" do
- attrs = tokenize_attrs("
")
-
- assert [{"hidden", nil}] = attrs
- end
-
- test "in self close tag" do
- attrs = tokenize_attrs("
")
-
- assert [{"hidden", nil}] = attrs
- end
-
- test "in self close tag with space after" do
- attrs = tokenize_attrs("
")
-
- assert [{"hidden", nil}] = attrs
- end
- end
-
- describe "attributes as double quoted string" do
- test "value is represented as {:string, value, meta}}" do
- attrs = tokenize_attrs(~S(
))
-
- assert [{"class", {:string, "panel", %{delimiter: ?"}}}] = attrs
- end
-
- test "multiple attributes" do
- attrs = tokenize_attrs(~S(
))
-
- assert [
- {"class", {:string, "panel", %{delimiter: ?"}}},
- {"style", {:string, "margin: 0px;", %{delimiter: ?"}}}
- ] = attrs
- end
-
- test "value containing single quotes" do
- attrs = tokenize_attrs(~S(
))
-
- assert [{"title", {:string, "i'd love to!", %{delimiter: ?"}}}] = attrs
- end
-
- test "value containing line breaks" do
- tokens =
- tokenize("""
-
\
- """)
-
- assert [
- {:tag_open, "div", [{"title", {:string, "first\n second\nthird", _meta}}], %{}},
- {:tag_open, "span", [], %{line: 3, column: 8}}
- ] = tokens
- end
-
- test "raise on incomplete attribute value (EOF)" do
- assert_raise ParseError, ~r"nofile:2:15: expected closing `\"` for attribute value", fn ->
- tokenize("""
- ))
-
- assert [{"class", {:string, "panel", %{delimiter: ?'}}}] = attrs
- end
-
- test "multiple attributes" do
- attrs = tokenize_attrs(~S(
))
-
- assert [
- {"class", {:string, "panel", %{delimiter: ?'}}},
- {"style", {:string, "margin: 0px;", %{delimiter: ?'}}}
- ] = attrs
- end
-
- test "value containing double quotes" do
- attrs = tokenize_attrs(~S(
))
-
- assert [{"title", {:string, ~S(Say "hi!"), %{delimiter: ?'}}}] = attrs
- end
-
- test "value containing line breaks" do
- tokens =
- tokenize("""
-
\
- """)
-
- assert [
- {:tag_open, "div", [{"title", {:string, "first\n second\nthird", _meta}}], %{}},
- {:tag_open, "span", [], %{line: 3, column: 8}}
- ] = tokens
- end
-
- test "raise on incomplete attribute value (EOF)" do
- assert_raise ParseError, ~r"nofile:2:15: expected closing `\'` for attribute value", fn ->
- tokenize("""
- render_change(hello: [individual: "123"], _target: "hello[individual]")
+
+ assert last_event(view) ==
+ "
individual-changed: %{\"_target\" => [\"hello\", \"individual\"], \"hello\" => %{\"individual\" => \"123\"}}
"
+
+ assert view
+ |> form("#form", hello: [latest: "i win"])
+ |> render_change(hello: [latest: "i truly win"])
+
+ assert last_event(view) =~ ~s|"latest" => "i truly win"|
+ end
end
describe "render_submit" do
@@ -433,7 +438,7 @@ defmodule Phoenix.LiveView.ElementsTest do
test "raises if element doesn't set phx-trigger-action on the form element",
%{live: view, conn: conn} do
assert_raise ArgumentError,
- ~r"\"#empty-form\" does not have phx-trigger-action attribute",
+ ~r"\"#empty-form\" does not have a phx-trigger-action attribute",
fn -> view |> element("#empty-form") |> follow_trigger_action(conn) end
end
@@ -444,7 +449,9 @@ defmodule Phoenix.LiveView.ElementsTest do
assert conn.method == "GET"
assert conn.request_path == "/elements"
- conn = view |> form("#trigger-form-default", %{"foo" => "bar"}) |> follow_trigger_action(conn)
+ conn =
+ view |> form("#trigger-form-default", %{"foo" => "bar"}) |> follow_trigger_action(conn)
+
assert conn.method == "GET"
assert conn.request_path == "/elements"
assert conn.query_string == "foo=bar"
@@ -456,6 +463,33 @@ defmodule Phoenix.LiveView.ElementsTest do
end
end
+ describe "submit_form" do
+ test "raises if element is not a form", %{live: view, conn: conn} do
+ assert_raise ArgumentError,
+ ~r"given element did not return a form",
+ fn -> view |> element("#a-no-form") |> submit_form(conn) end
+ end
+
+ test "raises if element doesn't set action on the form element",
+ %{live: view, conn: conn} do
+ assert_raise ArgumentError,
+ ~r"\"#empty-form\" does not have an action attribute",
+ fn -> view |> element("#empty-form") |> submit_form(conn) end
+ end
+
+ test "uses default method and form action", %{live: view, conn: conn} do
+ conn = view |> element("#submit-form-default") |> submit_form(conn)
+ assert conn.method == "GET"
+ assert conn.request_path == "/not_found"
+
+ conn = view |> form("#submit-form-default", %{"foo" => "bar"}) |> submit_form(conn)
+
+ assert conn.method == "GET"
+ assert conn.request_path == "/not_found"
+ assert conn.query_string == "foo=bar"
+ end
+ end
+
describe "form" do
test "defaults", %{live: view} do
view |> form("#form") |> render_change()
@@ -489,6 +523,9 @@ defmodule Phoenix.LiveView.ElementsTest do
# Select
assert form =~ ~s|"selected" => "1"|
assert form =~ ~s|"not-selected" => "blank"|
+ assert form =~ ~s|"not-selected-treeorder" => "blank"|
+ refute form =~ ~s|"not-selected-size"|
+ assert form =~ ~s|"invalid-multiple-selected" => "3"|
# Multiple Select
assert form =~ ~s|"multiple-select" => ["2", "3"]|
@@ -496,6 +533,7 @@ defmodule Phoenix.LiveView.ElementsTest do
# Text area
assert form =~ ~s|"textarea" => "Text"|
assert form =~ ~s|"textarea_nl" => "Text"|
+ assert form =~ ~s|"textarea_empty" => ""|
# Ignore everything with no name, disabled, or submits
refute form =~ "no-name"
@@ -738,4 +776,25 @@ defmodule Phoenix.LiveView.ElementsTest do
assert element |> open_browser(open_fun) == element
end
end
+
+ describe "JS commands" do
+ test "push", %{live: view} do
+ assert view |> element("#button-js-click") |> render_click()
+ assert last_event(view) == "
button-click: %{}
"
+
+ assert view |> element("#button-js-click-value") |> render_click()
+ assert last_event(view) == "
button-click: %{\"one\" => 1}
"
+ end
+ end
+
+ describe "child component / JS commands" do
+ test "push", %{live: view} do
+ assert view |> element("#component-button-js-click-target") |> render_click()
+
+ assert last_component_event(view) ==
+ "
button-click: %{}
"
+
+ refute last_event(view) == "
button-click: %{}
"
+ end
+ end
end
diff --git a/test/phoenix_live_view/integrations/event_test.exs b/test/phoenix_live_view/integrations/event_test.exs
index 2a08272859..98f30d009d 100644
--- a/test/phoenix_live_view/integrations/event_test.exs
+++ b/test/phoenix_live_view/integrations/event_test.exs
@@ -1,19 +1,14 @@
defmodule Phoenix.LiveView.EventTest do
- use ExUnit.Case
+ use ExUnit.Case, async: false
import Phoenix.ConnTest
import Phoenix.LiveViewTest
- alias Phoenix.LiveView
+ alias Phoenix.{Component, LiveView}
alias Phoenix.LiveViewTest.{Endpoint}
@endpoint Endpoint
- setup_all do
- ExUnit.CaptureLog.capture_log(fn -> Endpoint.start_link() end)
- :ok
- end
-
setup config do
{:ok, conn: Plug.Test.init_test_session(build_conn(), config[:session] || %{})}
end
@@ -28,7 +23,7 @@ defmodule Phoenix.LiveView.EventTest do
fn socket ->
new_socket =
socket
- |> LiveView.assign(count: 123)
+ |> Component.assign(count: 123)
|> LiveView.push_event("my-event", %{one: 1})
{:reply, :ok, new_socket}
diff --git a/test/phoenix_live_view/integrations/flash_test.exs b/test/phoenix_live_view/integrations/flash_test.exs
index d867636289..1fd06b117d 100644
--- a/test/phoenix_live_view/integrations/flash_test.exs
+++ b/test/phoenix_live_view/integrations/flash_test.exs
@@ -1,5 +1,5 @@
defmodule Phoenix.LiveView.FlashIntegrationTest do
- use ExUnit.Case, async: true
+ use ExUnit.Case, async: false
import Phoenix.ConnTest
import Phoenix.LiveViewTest
@@ -27,6 +27,14 @@ defmodule Phoenix.LiveView.FlashIntegrationTest do
assert conn.resp_body =~ "root[ok!]:info"
end
+ test "returns flash as a map", %{conn: conn} do
+ {:error, {:redirect, %{flash: flash}}} =
+ conn
+ |> live("/flash-child?mount_redirect=ok!")
+
+ assert is_map(flash)
+ end
+
test "redirect with flash", %{conn: conn} do
{:ok, flash_child, _} = live(conn, "/flash-child")
@@ -86,21 +94,21 @@ defmodule Phoenix.LiveView.FlashIntegrationTest do
assert conn.resp_body =~ "root[ok!]:info"
end
- test "push_redirect with flash on mount", %{conn: conn} do
+ test "push_navigate with flash on mount", %{conn: conn} do
{:ok, _, html} =
conn
- |> live("/flash-child?mount_push_redirect=ok!")
+ |> live("/flash-child?mount_push_navigate=ok!")
|> follow_redirect(conn)
assert html =~ "root[ok!]:info"
end
- test "push_redirect with flash", %{conn: conn} do
+ test "push_navigate with flash", %{conn: conn} do
{:ok, flash_child, _} = live(conn, "/flash-child")
{:ok, root_child, disconnected_html} =
flash_child
- |> render_click("push_redirect", %{"to" => "/flash-root", "info" => "ok!"})
+ |> render_click("push_navigate", %{"to" => "/flash-root", "info" => "ok!"})
|> follow_redirect(conn, "/flash-root")
assert disconnected_html =~ "root[ok!]:info"
@@ -110,14 +118,14 @@ defmodule Phoenix.LiveView.FlashIntegrationTest do
assert flash == %{"info" => "ok!"}
end
- test "push_redirect with flash does not include previous event flash", %{conn: conn} do
+ test "push_navigate with flash does not include previous event flash", %{conn: conn} do
{:ok, flash_child, _} = live(conn, "/flash-child")
render_click(flash_child, "set_error", %{"error" => "ok!"})
{:ok, root_child, disconnected_html} =
flash_child
- |> render_click("push_redirect", %{"to" => "/flash-root", "info" => "ok!"})
+ |> render_click("push_navigate", %{"to" => "/flash-root", "info" => "ok!"})
|> follow_redirect(conn, "/flash-root")
assert disconnected_html =~ "root[ok!]:info"
@@ -183,11 +191,11 @@ defmodule Phoenix.LiveView.FlashIntegrationTest do
assert flash == %{"info" => "ok!"}
end
- test "nested push_redirect with flash", %{conn: conn} do
+ test "nested push_navigate with flash", %{conn: conn} do
{:ok, flash_live, _} = live(conn, "/flash-root")
flash_child = find_live_child(flash_live, "flash-child")
- render_click(flash_child, "push_redirect", %{"to" => "/flash-root?push", "info" => "ok!"})
+ render_click(flash_child, "push_navigate", %{"to" => "/flash-root?push", "info" => "ok!"})
flash = assert_redirect(flash_child, "/flash-root?push")
assert flash == %{"info" => "ok!"}
end
@@ -265,14 +273,14 @@ defmodule Phoenix.LiveView.FlashIntegrationTest do
assert flash == %{"info" => "ok!"}
end
- test "push_redirect with flash from component", %{conn: conn} do
+ test "push_navigate with flash from component", %{conn: conn} do
{:ok, flash_live, _} = live(conn, "/flash-root")
{:error, {:live_redirect, %{flash: _}}} =
flash_live
|> element("#flash-component")
|> render_click(%{
- "type" => "push_redirect",
+ "type" => "push_navigate",
"to" => "/flash-root",
"info" => "ok!"
})
@@ -348,25 +356,35 @@ defmodule Phoenix.LiveView.FlashIntegrationTest do
test "lv:clear-flash component", %{conn: conn} do
{:ok, flash_live, _} = live(conn, "/flash-root")
- result =
- flash_live
- |> element("#flash-component")
- |> render_click(%{"type" => "put_flash", "info" => "ok!"})
+ flash_live
+ |> element("#flash-component")
+ |> render_click(%{"type" => "put_flash", "info" => "ok!"})
- assert result =~ "component[ok!]:info"
+ flash_live
+ |> element("#flash-component")
+ |> render_click(%{"type" => "put_flash", "error" => "oops!"})
- result = flash_live |> element("#flash-component span", "Clear all") |> render_click()
- assert result =~ "component[]:info"
+ assert has_element?(flash_live, "#flash-component span[phx-value-key=info]", "component[ok!]:info")
+ assert has_element?(flash_live, "#flash-component span[phx-value-key=error]", "component[oops!]:error")
- result =
- flash_live
- |> element("#flash-component")
- |> render_click(%{"type" => "put_flash", "error" => "oops!"})
+ flash_live |> element("#flash-component span", "Clear all") |> render_click()
+
+ assert has_element?(flash_live, "#flash-component span[phx-value-key=info]", "component[]:info")
+ assert has_element?(flash_live, "#flash-component span[phx-value-key=error]", "component[]:error")
+ end
+
+ test "lv:clear-flash component with phx-value-key", %{conn: conn} do
+ {:ok, flash_live, _} = live(conn, "/flash-root")
+
+ flash_live
+ |> element("#flash-component")
+ |> render_click(%{"type" => "put_flash", "error" => "oops!"})
+
+ assert has_element?(flash_live, "#flash-component span[phx-value-key=error]", "component[oops!]:error")
- assert result =~ "component[oops!]:error"
+ flash_live |> element("#flash-component span", ":error") |> render_click()
- result = flash_live |> element("#flash-component span", ":error") |> render_click()
- assert result =~ "component[]:error"
+ assert has_element?(flash_live, "#flash-component span[phx-value-key=error]", "component[]:error")
end
test "works without session and flash", %{conn: conn} do
diff --git a/test/phoenix_live_view/integrations/lifecycle_test.exs b/test/phoenix_live_view/integrations/hooks_test.exs
similarity index 71%
rename from test/phoenix_live_view/integrations/lifecycle_test.exs
rename to test/phoenix_live_view/integrations/hooks_test.exs
index fab6717360..f77a87b3e7 100644
--- a/test/phoenix_live_view/integrations/lifecycle_test.exs
+++ b/test/phoenix_live_view/integrations/hooks_test.exs
@@ -1,10 +1,10 @@
-defmodule Phoenix.LiveView.LifecycleTest do
- use ExUnit.Case
+defmodule Phoenix.LiveView.HooksTest do
+ use ExUnit.Case, async: false
import Phoenix.ConnTest
import Phoenix.LiveViewTest
- alias Phoenix.LiveView
+ alias Phoenix.Component
alias Phoenix.LiveViewTest.{Endpoint, HooksLive}
@endpoint Endpoint
@@ -70,7 +70,7 @@ defmodule Phoenix.LiveView.LifecycleTest do
HooksLive.attach_hook(lv, :multiply_inc, :handle_event, fn
"inc", _, socket ->
- {:halt, LiveView.update(socket, :count, &(&1 * 2))}
+ {:halt, Component.update(socket, :count, &(&1 * 2))}
"dec", _, socket ->
{:cont, socket}
@@ -88,6 +88,48 @@ defmodule Phoenix.LiveView.LifecycleTest do
assert lv |> element("#inc") |> render_click() =~ "count:8"
end
+ test "handle_event/3 halts and replies", %{conn: conn} do
+ {:ok, lv, _html} = live(conn, "/lifecycle")
+
+ HooksLive.attach_hook(lv, :greet_1, :handle_event, fn "greet", %{"name" => name}, socket ->
+ {:halt, %{msg: "Hello, #{name}!"}, socket}
+ end)
+
+ HooksLive.attach_hook(lv, :greet_2, :handle_event, fn "greet", %{"name" => name}, socket ->
+ {:halt, %{msg: "Hi, #{name}!"}, socket}
+ end)
+
+ render_hook(lv, :greet, %{name: "Mike"})
+
+ assert_reply(lv, %{msg: "Hello, Mike!"})
+ end
+
+ test "only handle_event/3 error prints {:halt, map, %Socket{}}", %{conn: conn} do
+ {:ok, lv, _html} = live(conn, "/lifecycle")
+
+ HooksLive.attach_hook(lv, :boom, :handle_event, fn _, _, _ -> :boom end)
+
+ result =
+ HooksLive.exits_with(lv, ArgumentError, fn ->
+ lv |> element("#inc") |> render_click()
+ end)
+
+ assert result =~ "{:halt, map, %Socket{}}"
+ assert result =~ "Got: :boom"
+
+ {:ok, lv, _html} = live(conn, "/lifecycle")
+
+ HooksLive.attach_hook(lv, :reply, :handle_info, fn :boom, socket ->
+ {:halt, %{}, socket}
+ end)
+
+ assert ExUnit.CaptureLog.capture_log(fn ->
+ send(lv.pid, :boom)
+ ref = Process.monitor(lv.pid)
+ assert_receive {:DOWN, ^ref, _, _, _}
+ end) =~ "Got: {:halt, %{}, #Phoenix.LiveView.Socket<"
+ end
+
test "handle_params/3 raises when hook result is invalid", %{conn: conn} do
{:ok, lv, _html} = live(conn, "/lifecycle")
@@ -104,10 +146,10 @@ defmodule Phoenix.LiveView.LifecycleTest do
HooksLive.attach_hook(lv, :hook, :handle_params, fn
_params, _uri, %{assigns: %{params_hook_ref: _}} = socket ->
- {:halt, LiveView.update(socket, :params_hook_ref, &(&1 + 1))}
+ {:halt, Component.update(socket, :params_hook_ref, &(&1 + 1))}
_params, _uri, socket ->
- {:halt, LiveView.assign(socket, :params_hook_ref, 0)}
+ {:halt, Component.assign(socket, :params_hook_ref, 0)}
end)
lv |> element("#patch") |> render_click() =~ "params_hook:0"
@@ -129,13 +171,13 @@ defmodule Phoenix.LiveView.LifecycleTest do
HooksLive.attach_hook(lv, :hook, :handle_params, fn
_params, _uri, %{assigns: %{params_hook_ref: 0}} = socket ->
- {:halt, LiveView.update(socket, :params_hook_ref, &(&1 + 1))}
+ {:halt, Component.update(socket, :params_hook_ref, &(&1 + 1))}
_params, _uri, %{assigns: %{params_hook_ref: 1}} = socket ->
{:cont, socket}
_params, _uri, socket ->
- {:halt, LiveView.assign(socket, :params_hook_ref, 0)}
+ {:halt, Component.assign(socket, :params_hook_ref, 0)}
end)
lv |> element("#patch") |> render_click() =~ "params_hook:0"
@@ -197,20 +239,25 @@ defmodule Phoenix.LiveView.LifecycleTest do
refute_received {:intercepted, ^ref}
end
+ test "handle_info/3 without module callback", %{conn: conn} do
+ {:ok, lv, _html} = live(conn, "/lifecycle/handle-info-not-defined")
+ assert render(lv) =~ "data=somedata"
+ end
+
test "attach_hook raises when given a live component socket", %{conn: conn} do
{:ok, lv, _html} = live(conn, "/lifecycle/components")
assert HooksLive.exits_with(lv, ArgumentError, fn ->
- lv |> element("#attach") |> render_click()
- end) =~ "lifecycle hooks are not supported on stateful components."
+ lv |> element("#attach") |> render_click()
+ end) =~ "lifecycle hooks are not supported on stateful components."
end
test "detach_hook raises when given a live component socket", %{conn: conn} do
{:ok, lv, _html} = live(conn, "/lifecycle/components")
assert HooksLive.exits_with(lv, ArgumentError, fn ->
- lv |> element("#detach") |> render_click()
- end) =~ "lifecycle hooks are not supported on stateful components."
+ lv |> element("#detach") |> render_click()
+ end) =~ "lifecycle hooks are not supported on stateful components."
end
test "stage_info", %{conn: conn} do
@@ -255,4 +302,34 @@ defmodule Phoenix.LiveView.LifecycleTest do
exported?: false
}
end
+
+ test "afte_render hook", %{conn: conn} do
+ {:ok, lv, _html} = live(conn, "/lifecycle")
+
+ assert render(lv) =~ "count:0"
+
+ HooksLive.attach_hook(lv, :after, :after_render, fn socket ->
+ if Phoenix.Component.changed?(socket, :count) && socket.assigns.count >= 1 do
+ Phoenix.Component.assign(socket, :count, socket.assigns.count * 10)
+ else
+ socket
+ end
+ end)
+
+ assert lv |> element("#inc") |> render_click() =~ "count:1"
+
+ socket = HooksLive.run(lv, fn socket -> {:reply, socket, socket} end)
+ assert socket.assigns.count == 10
+
+ assert lv |> element("#inc") |> render_click() =~ "count:1"
+ socket = HooksLive.run(lv, fn socket -> {:reply, socket, socket} end)
+ assert socket.assigns.count == 110
+
+ HooksLive.detach_hook(lv, :after, :after_render)
+
+ assert lv |> element("#inc") |> render_click() =~ "count:111"
+ assert lv |> element("#inc") |> render_click() =~ "count:112"
+ socket = HooksLive.run(lv, fn socket -> {:reply, socket, socket} end)
+ assert socket.assigns.count == 112
+ end
end
diff --git a/test/phoenix_live_view/integrations/html_formatter_test.exs b/test/phoenix_live_view/integrations/html_formatter_test.exs
new file mode 100644
index 0000000000..f87f3f935f
--- /dev/null
+++ b/test/phoenix_live_view/integrations/html_formatter_test.exs
@@ -0,0 +1,151 @@
+if Version.match?(System.version(), ">= 1.13.0") do
+ defmodule Phoenix.LiveView.Integrations.HTMLFormatterTest do
+ use ExUnit.Case
+
+ alias Phoenix.LiveView.HTMLFormatter
+
+ defp assert_mix_format_output(input_ex, expected, dot_formatter_opts \\ []) do
+ filename = "index.html.heex"
+ ex_path = Path.join(System.tmp_dir(), filename)
+ dot_formatter_path = ex_path <> ".formatter.exs"
+ dot_formatter_opts = Keyword.put(dot_formatter_opts, :plugins, [HTMLFormatter])
+
+ on_exit(fn ->
+ File.rm(ex_path)
+ File.rm(dot_formatter_path)
+ end)
+
+ File.write!(ex_path, input_ex)
+ File.write!(dot_formatter_path, inspect(dot_formatter_opts))
+
+ # Run mix format twice to make sure the formatted file doesn't change after
+ # another mix format.
+ formatted = run_formatter(ex_path, dot_formatter_path)
+ assert formatted == expected
+ assert run_formatter(ex_path, dot_formatter_path) == formatted
+ end
+
+ defp run_formatter(ex_path, dot_formatter_path) do
+ Mix.Tasks.Format.run([ex_path, "--dot-formatter", dot_formatter_path])
+ File.read!(ex_path)
+ end
+
+ test "formats with default options" do
+ input = """
+
+ <%= live_redirect to: "url", id: "link", role: "button" do %>
+
+ <% end %>
+ <%= @user.name %>
+ <%= if true do %> good
<% else %>bad
<% end %>
+
+
+
+ <%= for value <- @values do %>
+
+ <%= case value.type do %>
+ <% :text -> %>
+ Hello
+ <% _ -> %>
+ Hello
+ <% end %>
+
+ <% end %>
+
+
+
+
+
+
+ Leave me alone
+
+
+
+ """
+
+ expected = """
+
+ <%= live_redirect to: "url", id: "link", role: "button" do %>
+
+
content 1
+
content 2
+
+ <% end %>
+ <%= @user.name %>
+ <%= if true do %>
+ good
+ <% else %>
+ bad
+ <% end %>
+
+
+
+ <%= for value <- @values do %>
+
+ <%= case value.type do %>
+ <% :text -> %>
+ Hello
+ <% _ -> %>
+ Hello
+ <% end %>
+
+ <% end %>
+
+
+
+
+
+ Leave me alone
+
+
+
+ """
+
+ assert_mix_format_output(input, expected)
+ end
+
+ test "accept line_length as option" do
+ input = """
+
+ """
+
+ expected = """
+
+
+
+ <%= @user.name %>
+
+
+
+ """
+
+ assert_mix_format_output(input, expected, line_length: 20)
+ end
+
+ test "heex_line_length overrides line_length" do
+ input = """
+
+ """
+
+ expected = """
+
+ """
+
+ assert_mix_format_output(input, expected, line_length: 20, heex_line_length: 80)
+ end
+ end
+end
diff --git a/test/phoenix_live_view/integrations/layout_test.exs b/test/phoenix_live_view/integrations/layout_test.exs
index 58857279d7..82206832eb 100644
--- a/test/phoenix_live_view/integrations/layout_test.exs
+++ b/test/phoenix_live_view/integrations/layout_test.exs
@@ -1,5 +1,5 @@
defmodule Phoenix.LiveView.LayoutTest do
- use ExUnit.Case, async: true
+ use ExUnit.Case, async: false
import Phoenix.ConnTest
import Phoenix.LiveViewTest
@@ -28,7 +28,14 @@ defmodule Phoenix.LiveView.LayoutTest do
"LIVELAYOUTSTART-246-The value is: 246-LIVELAYOUTEND\n"
end
- @tag session: %{live_layout: {LayoutView, "live-override.html"}}
+ test "is picked from config on use on first render", %{conn: conn} do
+ conn = get(conn, "/layout")
+
+ assert html_response(conn, 200) =~
+ ~r|^LAYOUT
]+>LIVELAYOUTSTART\-123\-The value is: 123\-LIVELAYOUTEND|
+ end
+
+ @tag session: %{live_layout: {LayoutView, :live_override}}
test "is picked from config on mount when given a layout", %{conn: conn} do
{:ok, view, html} = live(conn, "/layout")
@@ -54,7 +61,7 @@ defmodule Phoenix.LiveView.LayoutTest do
assert html =~ "The value is: 123
"
end
- @tag session: %{live_layout: {LayoutView, "live-override.html"}}
+ @tag session: %{live_layout: {LayoutView, :live_override}}
test "is picked from config on mount even on child live views", %{conn: conn} do
assert get(conn, "/parent_layout") |> html_response(200) =~
~r|
]+>LIVEOVERRIDESTART\-123\-The value is: 123\-LIVEOVERRIDEEND|
diff --git a/test/phoenix_live_view/integrations/live_components_test.exs b/test/phoenix_live_view/integrations/live_components_test.exs
index d49e7e8d40..b42d382d04 100644
--- a/test/phoenix_live_view/integrations/live_components_test.exs
+++ b/test/phoenix_live_view/integrations/live_components_test.exs
@@ -3,7 +3,6 @@ defmodule Phoenix.LiveView.LiveComponentsTest do
import Phoenix.ConnTest
import Phoenix.LiveViewTest
- import Phoenix.LiveView.TelemetryTestHelpers
alias Phoenix.LiveViewTest.{Endpoint, DOM, StatefulComponent}
@endpoint Endpoint
@@ -98,6 +97,18 @@ defmodule Phoenix.LiveView.LiveComponentsTest do
refute render(view) =~ "Hello World"
end
+ test "tracks removals of a nested LiveView alongside with a LiveComponent in the root view", %{conn: conn} do
+ {:ok, view, _} = live(conn, "/component_and_nested_in_live")
+ html = render(view)
+ assert html =~ "hello"
+ assert html =~ "world"
+ render_click(view, "disable", %{})
+
+ html = render(view)
+ refute html =~ "hello"
+ refute html =~ "world"
+ end
+
test "tracks removals when there is a race between server and client", %{conn: conn} do
{:ok, view, _} = live(conn, "/cids_destroyed")
@@ -247,56 +258,6 @@ defmodule Phoenix.LiveView.LiveComponentsTest do
}
] = DOM.parse(html)
end
-
- test "emits telemetry events when callback is successful", %{conn: conn} do
- attach_telemetry([:phoenix, :live_component, :handle_event])
- {:ok, view, _html} = live(conn, "/components")
-
- view |> element("#chris") |> render_click(%{"op" => "upcase"})
-
- assert_receive {:event, [:phoenix, :live_component, :handle_event, :start],
- %{system_time: _}, metadata}
-
- assert metadata.socket.transport_pid
- assert metadata.event == "transform"
- assert metadata.component == Phoenix.LiveViewTest.StatefulComponent
- assert metadata.params == %{"op" => "upcase"}
-
- assert_receive {:event, [:phoenix, :live_component, :handle_event, :stop], %{duration: _},
- metadata}
-
- assert metadata.socket.transport_pid
- assert metadata.event == "transform"
- assert metadata.component == Phoenix.LiveViewTest.StatefulComponent
- assert metadata.params == %{"op" => "upcase"}
- end
-
- test "emits telemetry events when callback fails", %{conn: conn} do
- Process.flag(:trap_exit, true)
-
- attach_telemetry([:phoenix, :live_component, :handle_event])
- {:ok, view, _html} = live(conn, "/components")
-
- assert view |> element("#chris") |> render_click(%{"op" => "boom"}) |> catch_exit
-
- assert_receive {:event, [:phoenix, :live_component, :handle_event, :start],
- %{system_time: _}, metadata}
-
- assert metadata.socket.transport_pid
- assert metadata.event == "transform"
- assert metadata.component == Phoenix.LiveViewTest.StatefulComponent
- assert metadata.params == %{"op" => "boom"}
-
- assert_receive {:event, [:phoenix, :live_component, :handle_event, :exception],
- %{duration: _}, metadata}
-
- assert metadata.kind == :error
- assert metadata.reason == {:case_clause, "boom"}
- assert metadata.socket.transport_pid
- assert metadata.event == "transform"
- assert metadata.component == Phoenix.LiveViewTest.StatefulComponent
- assert metadata.params == %{"op" => "boom"}
- end
end
describe "send_update" do
@@ -357,7 +318,7 @@ defmodule Phoenix.LiveView.LiveComponentsTest do
assert ExUnit.CaptureLog.capture_log(fn ->
send(view.pid, {:send_update, [{StatefulComponent, id: "nemo", name: "NEW-nemo"}]})
render(view)
- refute_received {:updated, _}
+ refute_receive {:updated, _}
end) =~
"send_update failed because component Phoenix.LiveViewTest.StatefulComponent with ID \"nemo\" does not exist or it has been removed"
end
@@ -380,12 +341,12 @@ defmodule Phoenix.LiveView.LiveComponentsTest do
end
describe "redirects" do
- test "push_redirect", %{conn: conn} do
+ test "push_navigate", %{conn: conn} do
{:ok, view, html} = live(conn, "/components")
assert html =~ "Redirect: none"
assert {:error, {:live_redirect, %{to: "/components?redirect=push"}}} =
- view |> element("#chris") |> render_click(%{"op" => "push_redirect"})
+ view |> element("#chris") |> render_click(%{"op" => "push_navigate"})
assert_redirect(view, "/components?redirect=push")
end
diff --git a/test/phoenix_live_view/integrations/live_navigation_test.exs b/test/phoenix_live_view/integrations/live_navigation_test.exs
deleted file mode 100644
index 2e41014ed4..0000000000
--- a/test/phoenix_live_view/integrations/live_navigation_test.exs
+++ /dev/null
@@ -1,113 +0,0 @@
-defmodule Phoenix.LiveView.LiveNavigationTest do
- use ExUnit.Case
-
- import Phoenix.ConnTest
- import Phoenix.LiveViewTest
-
- alias Phoenix.LiveViewTest.{Endpoint, DOM}
- @endpoint Endpoint
-
- setup_all do
- ExUnit.CaptureLog.capture_log(fn -> Endpoint.start_link() end)
- :ok
- end
-
- setup do
- {:ok, conn: Plug.Test.init_test_session(build_conn(), %{})}
- end
-
- describe "live_redirect" do
- test "within same live session", %{conn: conn} do
- assert {:ok, thermo_live, html} = live(conn, "/thermo-live-session")
- thermo_ref = Process.monitor(thermo_live.pid)
-
- assert [{"article", root_attrs, _}] = DOM.parse(html)
-
- %{"data-phx-session" => thermo_session, "data-phx-static" => thermo_static} =
- Enum.into(root_attrs, %{})
-
- assert {:ok, clock_live, html} = live_redirect(thermo_live, to: "/clock-live-session")
-
- for str <- [html, render(clock_live)] do
- content = DOM.parse(str)
- assert [{"section", attrs, _inner}] = content
- assert {"class", "clock"} in attrs
- assert {"data-phx-session", thermo_session} in attrs
- assert {"data-phx-static", thermo_static} in attrs
- assert str =~ "time: 12:00 NY"
- end
-
- assert_receive {:DOWN, ^thermo_ref, :process, _pid, {:shutdown, :closed}}
-
- assert {:ok, thermo_live2, html} = live_redirect(clock_live, to: "/thermo-live-session")
-
- for str <- [html, render(thermo_live2)] do
- content = DOM.parse(str)
- assert [{"article", attrs, _inner}] = content
- assert {"class", "thermo"} in attrs
- assert str =~ "The temp is"
- end
- end
-
- test "refused with mismatched live session", %{conn: conn} do
- assert {:ok, thermo_live, _html} = live(conn, "/thermo-live-session")
-
- assert {:error, {:redirect, _}} =
- live_redirect(thermo_live, to: "/clock-live-session-admin")
- end
-
- test "refused with no live session", %{conn: conn} do
- assert {:ok, thermo_live, _html} = live(conn, "/thermo")
- assert {:error, {:redirect, _}} = live_redirect(thermo_live, to: "/thermo-live-session")
-
- assert {:ok, thermo_live, _html} = live(conn, "/thermo")
-
- assert {:error, {:redirect, _}} =
- live_redirect(thermo_live, to: "/thermo-live-session-admin")
- end
-
- test "with outdated token", %{conn: conn} do
- assert {:ok, thermo_live, _html} = live(conn, "/thermo-live-session")
-
- assert {:error, {:redirect, %{to: "http://www.example.com/clock-live-session"}}} =
- Phoenix.LiveViewTest.__live_redirect__(
- thermo_live,
- [to: "/clock-live-session"],
- fn _token ->
- salt = Phoenix.LiveView.Utils.salt!(@endpoint)
- Phoenix.Token.sign(@endpoint, salt, {0, %{}})
- end
- )
- end
- end
-
- describe "live_patch" do
- test "patches on custom host with full path", %{conn: conn} do
- {:ok, live, html} = live(conn, "https://app.example.com/with-host/full")
- assert html =~ "URI: https://app.example.com/with-host/full"
- assert html =~ "LiveAction: full"
-
- html = live |> element("#path") |> render_click()
- assert html =~ "URI: https://app.example.com/with-host/path"
- assert html =~ "LiveAction: path"
-
- html = live |> element("#full") |> render_click()
- assert html =~ "URI: https://app.example.com/with-host/full"
- assert html =~ "LiveAction: full"
- end
-
- test "patches on custom host with partial path", %{conn: conn} do
- {:ok, live, html} = live(%{conn | host: "app.example.com"}, "/with-host/path")
- assert html =~ "URI: http://app.example.com/with-host/path"
- assert html =~ "LiveAction: path"
-
- html = live |> element("#full") |> render_click()
- assert html =~ "URI: https://app.example.com/with-host/full"
- assert html =~ "LiveAction: full"
-
- html = live |> element("#path") |> render_click()
- assert html =~ "URI: http://app.example.com/with-host/path"
- assert html =~ "LiveAction: path"
- end
- end
-end
diff --git a/test/phoenix_live_view/integrations/live_reload_test.exs b/test/phoenix_live_view/integrations/live_reload_test.exs
new file mode 100644
index 0000000000..4475c5694f
--- /dev/null
+++ b/test/phoenix_live_view/integrations/live_reload_test.exs
@@ -0,0 +1,51 @@
+defmodule Phoenix.LiveView.LiveReloadTest do
+ use ExUnit.Case, async: false
+
+ import Phoenix.ConnTest
+ import Phoenix.ChannelTest
+ import Phoenix.LiveViewTest
+
+ alias Phoenix.LiveReloader
+ alias Phoenix.LiveReloader.Channel
+
+ @endpoint Phoenix.LiveView.LiveReloadTestHelpers.Endpoint
+ @pubsub Phoenix.LiveView.PubSub
+
+ setup_all do
+ ExUnit.CaptureLog.capture_log(fn ->
+ start_supervised!(@endpoint)
+ start_supervised!(Phoenix.PubSub.child_spec(name: @pubsub))
+ end)
+
+ :ok
+ end
+
+ setup config do
+ {:ok, _, socket} =
+ LiveReloader.Socket |> socket() |> subscribe_and_join(Channel, "phoenix:live_reload", %{})
+
+ conn = Plug.Test.init_test_session(build_conn(), config[:session] || %{})
+ {:ok, conn: conn, socket: socket}
+ end
+
+ test "LiveView renders again when the phoenix_live_reload is received", %{
+ conn: conn,
+ socket: socket
+ } do
+ Phoenix.PubSub.subscribe(@pubsub, "live_view")
+
+ Application.put_env(:phoenix_live_view, :vsn, 1)
+ {:ok, lv, _html} = live(conn, "/live-reload")
+ assert render(lv) =~ "
Version 1
"
+
+ send(
+ socket.channel_pid,
+ {:file_event, self(), {"lib/test_auth_web/live/user_live.ex", :created}}
+ )
+
+ Application.put_env(:phoenix_live_view, :vsn, 2)
+
+ assert_receive {:phoenix_live_reload, :live_view, "lib/test_auth_web/live/user_live.ex"}
+ assert render(lv) =~ "
Version 2
"
+ end
+end
diff --git a/test/phoenix_live_view/integrations/live_view_test.exs b/test/phoenix_live_view/integrations/live_view_test.exs
index 5881c751f4..2d2f14d36a 100644
--- a/test/phoenix_live_view/integrations/live_view_test.exs
+++ b/test/phoenix_live_view/integrations/live_view_test.exs
@@ -1,18 +1,14 @@
defmodule Phoenix.LiveView.LiveViewTest do
use ExUnit.Case, async: false
- import Plug.Conn
import Phoenix.ConnTest
-
import Phoenix.LiveViewTest
- import Phoenix.LiveView.TelemetryTestHelpers
+
alias Phoenix.HTML
alias Phoenix.LiveView
- alias Phoenix.LiveView.Socket
- alias Phoenix.LiveViewTest.{Endpoint, DOM, ClockLive, ClockControlsLive, LiveInComponent}
+ alias Phoenix.LiveViewTest.{Endpoint, DOM}
@endpoint Endpoint
- @moduletag :capture_log
setup config do
{:ok, conn: Plug.Test.init_test_session(build_conn(), config[:session] || %{})}
@@ -52,50 +48,6 @@ defmodule Phoenix.LiveView.LiveViewTest do
assert html =~ "The temp is: 1"
end
- @tag session: %{current_user_id: "1"}
- test "static mount emits telemetry events are emitted on successful callback", %{conn: conn} do
- attach_telemetry([:phoenix, :live_view, :mount])
-
- conn
- |> get("/thermo?foo=bar")
- |> html_response(200)
-
- assert_receive {:event, [:phoenix, :live_view, :mount, :start], %{system_time: _},
- %{socket: %Socket{transport_pid: nil}} = metadata}
-
- assert metadata.params == %{"foo" => "bar"}
- assert metadata.session == %{"current_user_id" => "1"}
-
- assert_receive {:event, [:phoenix, :live_view, :mount, :stop], %{duration: _},
- %{socket: %Socket{transport_pid: nil}} = metadata}
-
- assert metadata.params == %{"foo" => "bar"}
- assert metadata.session == %{"current_user_id" => "1"}
- end
-
- @tag session: %{current_user_id: "1"}
- test "static mount emits telemetry events when callback raises an exception", %{conn: conn} do
- attach_telemetry([:phoenix, :live_view, :mount])
-
- assert_raise Plug.Conn.WrapperError, ~r/boom/, fn ->
- get(conn, "/errors?crash_on=disconnected_mount")
- end
-
- assert_receive {:event, [:phoenix, :live_view, :mount, :start], %{system_time: _},
- %{socket: %Socket{transport_pid: nil}} = metadata}
-
- assert metadata.params == %{"crash_on" => "disconnected_mount"}
- assert metadata.session == %{"current_user_id" => "1"}
-
- assert_receive {:event, [:phoenix, :live_view, :mount, :exception], %{duration: _},
- %{socket: %Socket{transport_pid: nil}} = metadata}
-
- assert metadata.kind == :error
- assert %RuntimeError{} = metadata.reason
- assert metadata.params == %{"crash_on" => "disconnected_mount"}
- assert metadata.session == %{"current_user_id" => "1"}
- end
-
test "live mount in single call", %{conn: conn} do
{:ok, _view, html} = live(conn, "/thermo")
assert html =~ "The temp is: 1"
@@ -120,220 +72,6 @@ defmodule Phoenix.LiveView.LiveViewTest do
|> live()
end
end
-
- @tag session: %{current_user_id: "1"}
- test "live mount emits telemetry events are emitted on successful callback", %{conn: conn} do
- attach_telemetry([:phoenix, :live_view, :mount])
-
- {:ok, _view, _html} = live(conn, "/thermo?foo=bar")
-
- assert_receive {:event, [:phoenix, :live_view, :mount, :start], %{system_time: _},
- %{socket: %{transport_pid: pid}} = metadata}
- when is_pid(pid)
-
- assert metadata.socket.transport_pid
- assert metadata.params == %{"foo" => "bar"}
- assert metadata.session == %{"current_user_id" => "1"}
-
- assert_receive {:event, [:phoenix, :live_view, :mount, :stop], %{duration: _},
- %{socket: %{transport_pid: pid}} = metadata}
- when is_pid(pid)
-
- assert metadata.socket.transport_pid
- assert metadata.params == %{"foo" => "bar"}
- assert metadata.session == %{"current_user_id" => "1"}
- end
-
- @tag session: %{current_user_id: "1"}
- test "live mount emits telemetry events when callback raises an exception", %{conn: conn} do
- attach_telemetry([:phoenix, :live_view, :mount])
-
- assert catch_exit(live(conn, "/errors?crash_on=connected_mount"))
-
- assert_receive {:event, [:phoenix, :live_view, :mount, :start], %{system_time: _},
- %{socket: %{transport_pid: pid}} = metadata}
- when is_pid(pid)
-
- assert metadata.socket.transport_pid
- assert metadata.params == %{"crash_on" => "connected_mount"}
- assert metadata.session == %{"current_user_id" => "1"}
-
- assert_receive {:event, [:phoenix, :live_view, :mount, :exception], %{duration: _},
- %{socket: %{transport_pid: pid}} = metadata}
- when is_pid(pid)
-
- assert metadata.socket.transport_pid
- assert metadata.kind == :error
- assert %RuntimeError{} = metadata.reason
- assert metadata.params == %{"crash_on" => "connected_mount"}
- assert metadata.session == %{"current_user_id" => "1"}
- end
-
- test "push_redirect when disconnected", %{conn: conn} do
- conn = get(conn, "/redir?during=disconnected&kind=push_redirect&to=/thermo")
- assert redirected_to(conn) == "/thermo"
-
- {:error, {:live_redirect, %{to: "/thermo"}}} =
- live(conn, "/redir?during=disconnected&kind=push_redirect&to=/thermo")
- end
-
- test "push_redirect when connected", %{conn: conn} do
- conn = get(conn, "/redir?during=connected&kind=push_redirect&to=/thermo")
- assert html_response(conn, 200) =~ "parent_content"
- assert {:error, {:live_redirect, %{kind: :push, to: "/thermo"}}} = live(conn)
- end
-
- test "push_patch when disconnected", %{conn: conn} do
- assert_raise Plug.Conn.WrapperError, ~r/attempted to live patch while/, fn ->
- get(conn, "/redir?during=disconnected&kind=push_patch&to=/redir?patched=true")
- end
- end
-
- test "push_patch when connected", %{conn: conn} do
- conn = get(conn, "/redir?during=connected&kind=push_patch&to=/redir?patched=true")
- assert html_response(conn, 200) =~ "parent_content"
-
- assert Exception.format(:exit, catch_exit(live(conn))) =~
- "attempted to live patch while mounting"
- end
-
- test "redirect when disconnected", %{conn: conn} do
- conn = get(conn, "/redir?during=disconnected&kind=redirect&to=/thermo")
- assert redirected_to(conn) == "/thermo"
-
- {:error, {:redirect, %{to: "/thermo"}}} =
- live(conn, "/redir?during=disconnected&kind=redirect&to=/thermo")
- end
-
- test "redirect when connected", %{conn: conn} do
- conn = get(conn, "/redir?during=connected&kind=redirect&to=/thermo")
- assert html_response(conn, 200) =~ "parent_content"
- assert {:error, {:redirect, %{to: "/thermo"}}} = live(conn)
- end
-
- test "external redirect when disconnected", %{conn: conn} do
- conn = get(conn, "/redir?during=disconnected&kind=external&to=https://phoenixframework.org")
- assert redirected_to(conn) == "https://phoenixframework.org"
-
- {:error, {:redirect, %{to: "https://phoenixframework.org"}}} =
- live(conn, "/redir?during=disconnected&kind=external&to=https://phoenixframework.org")
- end
-
- test "external redirect when connected", %{conn: conn} do
- conn = get(conn, "/redir?during=connected&kind=external&to=https://phoenixframework.org")
- assert html_response(conn, 200) =~ "parent_content"
- assert {:error, {:redirect, %{to: "https://phoenixframework.org"}}} = live(conn)
- end
-
- test "child push_redirect when disconnected", %{conn: conn} do
- conn = get(conn, "/redir?during=disconnected&kind=push_redirect&child_to=/thermo")
- assert redirected_to(conn) == "/thermo"
- end
-
- test "child push_redirect when connected", %{conn: conn} do
- conn =
- get(conn, "/redir?during=connected&kind=push_redirect&child_to=/thermo?from_child=true")
-
- assert html_response(conn, 200) =~ "child_content"
- assert {:error, {:live_redirect, %{to: "/thermo?from_child=true"}}} = live(conn)
- end
-
- test "child push_patch when disconnected", %{conn: conn} do
- assert_raise Plug.Conn.WrapperError,
- ~r/a LiveView cannot be mounted while issuing a live patch to the client/,
- fn ->
- get(
- conn,
- "/redir?during=disconnected&kind=push_patch&child_to=/redir?patched=true"
- )
- end
- end
-
- test "child push_patch when connected", %{conn: conn} do
- conn = get(conn, "/redir?during=connected&kind=push_patch&child_to=/redir?patched=true")
- assert html_response(conn, 200) =~ "child_content"
-
- assert Exception.format(:exit, catch_exit(live(conn))) =~
- "a LiveView cannot be mounted while issuing a live patch to the client"
- end
-
- test "child redirect when disconnected", %{conn: conn} do
- conn =
- get(conn, "/redir?during=disconnected&kind=redirect&child_to=/thermo?from_child=true")
-
- assert redirected_to(conn) == "/thermo?from_child=true"
- end
-
- test "child redirect when connected", %{conn: conn} do
- conn = get(conn, "/redir?during=connected&kind=redirect&child_to=/thermo?from_child=true")
- assert html_response(conn, 200) =~ "parent_content"
- assert {:error, {:redirect, %{to: "/thermo?from_child=true"}}} = live(conn)
- end
-
- test "child external redirect when disconnected", %{conn: conn} do
- conn =
- get(
- conn,
- "/redir?during=disconnected&kind=external&child_to=https://phoenixframework.org?from_child=true"
- )
-
- assert redirected_to(conn) == "https://phoenixframework.org?from_child=true"
- end
-
- test "child external redirect when connected", %{conn: conn} do
- conn =
- get(
- conn,
- "/redir?during=connected&kind=external&child_to=https://phoenixframework.org?from_child=true"
- )
-
- assert html_response(conn, 200) =~ "parent_content"
-
- assert {:error, {:redirect, %{to: "https://phoenixframework.org?from_child=true"}}} =
- live(conn)
- end
- end
-
- describe "live_isolated" do
- test "renders a live view with custom session", %{conn: conn} do
- {:ok, view, _} =
- live_isolated(conn, Phoenix.LiveViewTest.DashboardLive, session: %{"hello" => "world"})
-
- assert render(view) =~ "session: %{"hello" => "world"}"
- end
-
- test "renders a live view with custom session and a router", %{conn: conn} do
- conn = %Plug.Conn{conn | request_path: "/router/thermo_defaults/123"}
- {:ok, view, _} =
- live_isolated(conn, Phoenix.LiveViewTest.DashboardLive,
- session: %{"hello" => "world"}
- )
-
- assert render(view) =~ "session: %{"hello" => "world"}"
- end
-
- test "raises if handle_params is implemented", %{conn: conn} do
- assert_raise ArgumentError,
- ~r/it is not mounted nor accessed through the router live\/3 macro/,
- fn -> live_isolated(conn, Phoenix.LiveViewTest.ParamCounterLive) end
- end
-
- test "works without an initialized session" do
- {:ok, view, _} =
- live_isolated(Phoenix.ConnTest.build_conn(), Phoenix.LiveViewTest.DashboardLive,
- session: %{"hello" => "world"}
- )
-
- assert render(view) =~ "session: %{"hello" => "world"}"
- end
-
- test "raises on session with atom keys" do
- assert_raise ArgumentError, ~r"LiveView :session must be a map with string keys,", fn ->
- live_isolated(Phoenix.ConnTest.build_conn(), Phoenix.LiveViewTest.DashboardLive,
- session: %{hello: "world"}
- )
- end
- end
end
describe "rendering" do
@@ -394,7 +132,7 @@ defmodule Phoenix.LiveView.LiveViewTest do
end
end
- describe "event rendering" do
+ describe "render_*" do
test "render_click", %{conn: conn} do
{:ok, view, _} = live(conn, "/thermo")
assert render_click(view, :save, %{temp: 20}) =~ "The temp is: 20"
@@ -444,51 +182,6 @@ defmodule Phoenix.LiveView.LiveViewTest do
{:ok, view, _} = live(conn, "/thermo")
assert render_hook(view, :save, %{temp: 20}) =~ "The temp is: 20"
end
-
- test "render_* with a successful handle_event callback emits telemetry metrics", %{conn: conn} do
- attach_telemetry([:phoenix, :live_view, :handle_event])
-
- {:ok, view, _} = live(conn, "/thermo")
- render_submit(view, :save, %{temp: 20})
-
- assert_receive {:event, [:phoenix, :live_view, :handle_event, :start], %{system_time: _},
- metadata}
-
- assert metadata.socket.transport_pid
- assert metadata.event == "save"
- assert metadata.params == %{"temp" => "20"}
-
- assert_receive {:event, [:phoenix, :live_view, :handle_event, :stop], %{duration: _},
- metadata}
-
- assert metadata.socket.transport_pid
- assert metadata.event == "save"
- assert metadata.params == %{"temp" => "20"}
- end
-
- test "render_* with crashing handle_event callback emits telemetry metrics", %{conn: conn} do
- Process.flag(:trap_exit, true)
- attach_telemetry([:phoenix, :live_view, :handle_event])
-
- {:ok, view, _} = live(conn, "/errors")
- catch_exit(render_submit(view, :crash, %{"foo" => "bar"}))
-
- assert_receive {:event, [:phoenix, :live_view, :handle_event, :start], %{system_time: _},
- metadata}
-
- assert metadata.socket.transport_pid
- assert metadata.event == "crash"
- assert metadata.params == %{"foo" => "bar"}
-
- assert_receive {:event, [:phoenix, :live_view, :handle_event, :exception], %{duration: _},
- metadata}
-
- assert metadata.socket.transport_pid
- assert metadata.kind == :error
- assert %RuntimeError{} = metadata.reason
- assert metadata.event == "crash"
- assert metadata.params == %{"foo" => "bar"}
- end
end
describe "container" do
@@ -502,7 +195,7 @@ defmodule Phoenix.LiveView.LiveViewTest do
{:ok, view, connected_html} = live(conn)
assert static_html =~
- ~r/
]*data-phx-main=\"true\".*[^>]*>/
+ ~r/]*data-phx-main.*[^>]*>/
assert static_html =~ ~r/<\/article>/
@@ -594,293 +287,77 @@ defmodule Phoenix.LiveView.LiveViewTest do
end
end
- describe "nested live render" do
- test "nested child render on disconnected mount", %{conn: conn} do
- conn =
- conn
- |> Plug.Test.init_test_session(%{nest: []})
- |> get("/thermo")
-
- html = html_response(conn, 200)
- assert html =~ "The temp is: 0"
- assert html =~ "time: 12:00"
- assert html =~ "+ "
- end
-
- @tag session: %{nest: []}
- test "nested child render on connected mount", %{conn: conn} do
- {:ok, thermo_view, _} = live(conn, "/thermo")
-
- html = render(thermo_view)
- assert html =~ "The temp is: 1"
- assert html =~ "time: 12:00"
- assert html =~ "+ "
-
- GenServer.call(thermo_view.pid, {:set, :nest, false})
- html = render(thermo_view)
- assert html =~ "The temp is: 1"
- refute html =~ "time"
- refute html =~ "snooze"
- end
-
- test "dynamically added children", %{conn: conn} do
- {:ok, thermo_view, _html} = live(conn, "/thermo")
-
- assert render(thermo_view) =~ "The temp is: 1"
- refute render(thermo_view) =~ "time"
- refute render(thermo_view) =~ "snooze"
- GenServer.call(thermo_view.pid, {:set, :nest, []})
- assert render(thermo_view) =~ "The temp is: 1"
- assert render(thermo_view) =~ "time"
- assert render(thermo_view) =~ "snooze"
-
- assert clock_view = find_live_child(thermo_view, "clock")
- assert controls_view = find_live_child(clock_view, "NY-controls")
- assert clock_view.module == ClockLive
- assert controls_view.module == ClockControlsLive
-
- assert render_click(controls_view, :snooze) == "+ "
- assert render(clock_view) =~ "time: 12:05"
- assert render(clock_view) =~ "+ "
- assert render(controls_view) =~ "+ "
-
- :ok = GenServer.call(clock_view.pid, {:set, "12:01"})
-
- assert render(clock_view) =~ "time: 12:01"
- assert render(thermo_view) =~ "time: 12:01"
-
- assert render(thermo_view) =~ "+ "
- end
-
- @tag session: %{nest: []}
- test "nested children are removed and killed", %{conn: conn} do
- Process.flag(:trap_exit, true)
-
- html_without_nesting =
- DOM.parse("""
- Redirect: none\nThe temp is: 1
- -
- +
- """)
-
- {:ok, thermo_view, _} = live(conn, "/thermo")
-
- assert find_live_child(thermo_view, "clock")
- refute DOM.child_nodes(hd(DOM.parse(render(thermo_view)))) == html_without_nesting
-
- GenServer.call(thermo_view.pid, {:set, :nest, false})
- assert DOM.child_nodes(hd(DOM.parse(render(thermo_view)))) == html_without_nesting
- refute find_live_child(thermo_view, "clock")
- end
-
- @tag session: %{dup: false}
- test "multiple nested children of same module", %{conn: conn} do
- {:ok, parent, _} = live(conn, "/same-child")
- assert tokyo = find_live_child(parent, "Tokyo")
- assert madrid = find_live_child(parent, "Madrid")
- assert toronto = find_live_child(parent, "Toronto")
- child_ids = for view <- [tokyo, madrid, toronto], do: view.id
-
- assert Enum.uniq(child_ids) == child_ids
- assert render(parent) =~ "Tokyo"
- assert render(parent) =~ "Madrid"
- assert render(parent) =~ "Toronto"
- end
-
- @tag session: %{dup: false}
- test "multiple nested children of same module with new session", %{conn: conn} do
- {:ok, parent, _} = live(conn, "/same-child")
- assert render_click(parent, :inc) =~ "Toronto"
+ describe "title" do
+ test "sends page title updates", %{conn: conn} do
+ {:ok, view, _html} = live(conn, "/thermo")
+ GenServer.call(view.pid, {:set, :page_title, "New Title"})
+ assert page_title(view) =~ "New Title"
end
+ end
- test "nested for comprehensions", %{conn: conn} do
- users = [
- %{name: "chris", email: "chris@test"},
- %{name: "josé", email: "jose@test"}
- ]
-
- expected_users = "chris chris@test josé jose@test "
-
- {:ok, thermo_view, html} =
- conn
- |> put_session(:nest, [])
- |> put_session(:users, users)
- |> live("/thermo")
+ describe "live_isolated" do
+ test "renders a live view with custom session", %{conn: conn} do
+ {:ok, view, _} =
+ live_isolated(conn, Phoenix.LiveViewTest.DashboardLive, session: %{"hello" => "world"})
- assert html =~ expected_users
- assert render(thermo_view) =~ expected_users
+ assert render(view) =~ "session: %{"hello" => "world"}"
end
- test "raises on duplicate child LiveView id", %{conn: conn} do
- Process.flag(:trap_exit, true)
-
- {:ok, view, _html} =
- conn
- |> Plug.Conn.put_session(:user_id, 13)
- |> live("/root")
+ test "renders a live view with custom session and a router", %{conn: conn} do
+ conn = %Plug.Conn{conn | request_path: "/router/thermo_defaults/123"}
- :ok = GenServer.call(view.pid, {:dynamic_child, :static})
+ {:ok, view, _} =
+ live_isolated(conn, Phoenix.LiveViewTest.DashboardLive, session: %{"hello" => "world"})
- assert Exception.format(:exit, catch_exit(render(view))) =~
- "expected selector \"#static\" to return a single element, but got 2"
+ assert render(view) =~ "session: %{"hello" => "world"}"
end
- test "live view nested inside a live component" do
- assert {:ok, _view, _html} = live_isolated(build_conn(), LiveInComponent.Root)
+ test "raises if handle_params is implemented", %{conn: conn} do
+ assert_raise ArgumentError,
+ ~r/it is not mounted nor accessed through the router live\/3 macro/,
+ fn -> live_isolated(conn, Phoenix.LiveViewTest.ParamCounterLive) end
end
- @tag session: %{nest: []}
- test "push_redirect", %{conn: conn} do
- {:ok, thermo_view, html} = live(conn, "/thermo")
- assert html =~ "Redirect: none"
-
- assert clock_view = find_live_child(thermo_view, "clock")
-
- send(
- clock_view.pid,
- {:run,
- fn socket ->
- {:noreply, LiveView.push_redirect(socket, to: "/thermo?redirect=push")}
- end}
- )
+ test "works without an initialized session" do
+ {:ok, view, _} =
+ live_isolated(Phoenix.ConnTest.build_conn(), Phoenix.LiveViewTest.DashboardLive,
+ session: %{"hello" => "world"}
+ )
- assert_redirect(thermo_view, "/thermo?redirect=push")
+ assert render(view) =~ "session: %{"hello" => "world"}"
end
- @tag session: %{nest: []}
- test "refute_redirect", %{conn: conn} do
- {:ok, thermo_view, _html} = live(conn, "/thermo")
-
- clock_view = find_live_child(thermo_view, "clock")
-
- send(
- clock_view.pid,
- {:run,
- fn socket ->
- {:noreply, LiveView.push_redirect(socket, to: "/some_url")}
- end}
- )
-
- refute_redirected(thermo_view, "/not_going_here")
-
- send(
- clock_view.pid,
- {:run,
- fn socket ->
- {:noreply, LiveView.push_redirect(socket, to: "/another_url")}
- end}
- )
-
- try do
- refute_redirected(thermo_view, "/another_url")
- rescue
- e ->
- assert %ArgumentError{message: message} = e
- assert message =~ "not to redirect to"
+ test "raises on session with atom keys" do
+ assert_raise ArgumentError, ~r"LiveView :session must be a map with string keys,", fn ->
+ live_isolated(Phoenix.ConnTest.build_conn(), Phoenix.LiveViewTest.DashboardLive,
+ session: %{hello: "world"}
+ )
end
end
-
- @tag session: %{nest: []}
- test "push_redirect with destination that can vary", %{conn: conn} do
- {:ok, thermo_view, html} = live(conn, "/thermo")
- assert html =~ "Redirect: none"
-
- assert clock_view = find_live_child(thermo_view, "clock")
-
- id = Enum.random(1000..9999)
-
- send(
- clock_view.pid,
- {:run,
- fn socket ->
- {:noreply, LiveView.push_redirect(socket, to: "/thermo?redirect=#{id}")}
- end}
- )
-
- {path, _flash} = assert_redirect(thermo_view)
- assert path =~ ~r/\/thermo\?redirect=[0-9]+/
- end
-
- @tag session: %{nest: []}
- test "push_patch", %{conn: conn} do
- {:ok, thermo_view, html} = live(conn, "/thermo")
- assert html =~ "Redirect: none"
- assert clock_view = find_live_child(thermo_view, "clock")
-
- send(
- clock_view.pid,
- {:run,
- fn socket ->
- {:noreply, LiveView.push_patch(socket, to: "/thermo?redirect=patch")}
- end}
- )
-
- assert_patch(thermo_view, "/thermo?redirect=patch")
- assert render(thermo_view) =~ "Redirect: patch"
- end
-
- @tag session: %{nest: []}
- test "push_patch to destination which can vary", %{conn: conn} do
- {:ok, thermo_view, html} = live(conn, "/thermo")
- assert html =~ "Redirect: none"
- assert clock_view = find_live_child(thermo_view, "clock")
-
- id = Enum.random(1000..9999)
- send(
- clock_view.pid,
- {:run,
- fn socket ->
- {:noreply, LiveView.push_patch(socket, to: "/thermo?redirect=#{id}")}
- end}
- )
-
- path = assert_patch(thermo_view)
- assert path =~ ~r/\/thermo\?redirect=[0-9]+/
- assert render(thermo_view) =~ "Redirect: #{id}"
- end
-
- @tag session: %{nest: []}
- test "redirect from child", %{conn: conn} do
- {:ok, thermo_view, html} = live(conn, "/thermo")
- assert html =~ "Redirect: none"
-
- assert clock_view = find_live_child(thermo_view, "clock")
-
- send(
- clock_view.pid,
- {:run,
- fn socket ->
- {:noreply, LiveView.redirect(socket, to: "/thermo?redirect=redirect")}
- end}
- )
-
- assert_redirect(thermo_view, "/thermo?redirect=redirect")
- end
-
- @tag session: %{nest: []}
- test "external redirect from child", %{conn: conn} do
- {:ok, thermo_view, html} = live(conn, "/thermo")
- assert html =~ "Redirect: none"
-
- assert clock_view = find_live_child(thermo_view, "clock")
-
- send(
- clock_view.pid,
- {:run,
- fn socket ->
- {:noreply, LiveView.redirect(socket, external: "https://phoenixframework.org")}
- end}
- )
-
- assert_redirect(thermo_view, "https://phoenixframework.org")
- end
end
- describe "title" do
- test "sends page title updates", %{conn: conn} do
- {:ok, view, _html} = live(conn, "/thermo")
- GenServer.call(view.pid, {:set, :page_title, "New Title"})
- assert page_title(view) =~ "New Title"
+ describe "format_status/2" do
+ test "returns LiveView information", %{conn: conn} do
+ {:ok, %{pid: pid}, _html} = live(conn, "/clock")
+
+ assert {:status, ^pid, {:module, :gen_server},
+ [
+ _pdict,
+ :running,
+ _parent,
+ _dbg_opts,
+ [
+ header: 'Status for generic server ' ++ _,
+ data: _gen_server_data,
+ data: [
+ {'LiveView', Phoenix.LiveViewTest.ClockLive},
+ {'Parent pid', nil},
+ {'Transport pid', _},
+ {'Topic', <<_::binary>>},
+ {'Components count', 0}
+ ]
+ ]
+ ]} = :sys.get_status(pid)
end
end
@@ -891,7 +368,7 @@ defmodule Phoenix.LiveView.LiveViewTest do
end
end
- test "return the transport pid when connected", %{conn: conn} do
+ test "return the transport pid as the test process when connected", %{conn: conn} do
{:ok, clock_view, _html} = live(conn, "/clock")
parent = self()
ref = make_ref()
@@ -908,4 +385,26 @@ defmodule Phoenix.LiveView.LiveViewTest do
assert transport_pid == self()
end
end
+
+ describe "connected mount exceptions" do
+ test "when disconnected, raises normally per plug wrapper", %{conn: conn} do
+ assert_raise(Plug.Conn.WrapperError, ~r/Phoenix.LiveViewTest.ThermostatLive.Error/, fn ->
+ get(conn, "/thermo?raise_disconnected=500")
+ end)
+
+ assert_raise(Plug.Conn.WrapperError, ~r/Phoenix.LiveViewTest.ThermostatLive.Error/, fn ->
+ get(conn, "/thermo?raise_disconnected=404")
+ end)
+ end
+
+ test "when connected, raises and exits for 5xx", %{conn: conn} do
+ assert {{exception, _}, _} = catch_exit(live(conn, "/thermo?raise_connected=500"))
+ assert %Phoenix.LiveViewTest.ThermostatLive.Error{plug_status: 500} = exception
+ end
+
+ test "when connected, raises and wraps 4xx in client response", %{conn: conn} do
+ assert {reason, _} = catch_exit(live(conn, "/thermo?raise_connected=404"))
+ assert %{reason: "reload", status: 404} = reason
+ end
+ end
end
diff --git a/test/phoenix_live_view/integrations/navigation_test.exs b/test/phoenix_live_view/integrations/navigation_test.exs
new file mode 100644
index 0000000000..02a713e428
--- /dev/null
+++ b/test/phoenix_live_view/integrations/navigation_test.exs
@@ -0,0 +1,243 @@
+defmodule Phoenix.LiveView.NavigationTest do
+ use ExUnit.Case, async: false
+
+ import Phoenix.ConnTest
+ import Phoenix.LiveViewTest
+
+ alias Phoenix.LiveViewTest.{Endpoint, DOM}
+
+ @endpoint Endpoint
+
+ setup do
+ {:ok, conn: Plug.Test.init_test_session(build_conn(), %{})}
+ end
+
+ # Nested used of navigation helpers go to nested_test.exs
+
+ describe "push_navigate" do
+ test "when disconnected", %{conn: conn} do
+ conn = get(conn, "/redir?during=disconnected&kind=push_navigate&to=/thermo")
+ assert redirected_to(conn) == "/thermo"
+
+ {:error, {:live_redirect, %{to: "/thermo"}}} =
+ live(conn, "/redir?during=disconnected&kind=push_navigate&to=/thermo")
+ end
+
+ test "when connected", %{conn: conn} do
+ conn = get(conn, "/redir?during=connected&kind=push_navigate&to=/thermo")
+ assert html_response(conn, 200) =~ "parent_content"
+ assert {:error, {:live_redirect, %{kind: :push, to: "/thermo"}}} = live(conn)
+ end
+
+ test "child when disconnected", %{conn: conn} do
+ conn = get(conn, "/redir?during=disconnected&kind=push_navigate&child_to=/thermo")
+ assert redirected_to(conn) == "/thermo"
+ end
+
+ test "child when connected", %{conn: conn} do
+ conn =
+ get(conn, "/redir?during=connected&kind=push_navigate&child_to=/thermo?from_child=true")
+
+ assert html_response(conn, 200) =~ "child_content"
+ assert {:error, {:live_redirect, %{to: "/thermo?from_child=true"}}} = live(conn)
+ end
+ end
+
+ describe "push_patch" do
+ test "when disconnected", %{conn: conn} do
+ assert_raise Plug.Conn.WrapperError, ~r/attempted to live patch while/, fn ->
+ get(conn, "/redir?during=disconnected&kind=push_patch&to=/redir?patched=true")
+ end
+ end
+
+ test "when connected", %{conn: conn} do
+ conn = get(conn, "/redir?during=connected&kind=push_patch&to=/redir?patched=true")
+ assert html_response(conn, 200) =~ "parent_content"
+
+ assert Exception.format(:exit, catch_exit(live(conn))) =~
+ "attempted to live patch while mounting"
+ end
+
+ test "child when disconnected", %{conn: conn} do
+ assert_raise Plug.Conn.WrapperError,
+ ~r/a LiveView cannot be mounted while issuing a live patch to the client/,
+ fn ->
+ get(
+ conn,
+ "/redir?during=disconnected&kind=push_patch&child_to=/redir?patched=true"
+ )
+ end
+ end
+
+ test "child when connected", %{conn: conn} do
+ conn = get(conn, "/redir?during=connected&kind=push_patch&child_to=/redir?patched=true")
+ assert html_response(conn, 200) =~ "child_content"
+
+ assert Exception.format(:exit, catch_exit(live(conn))) =~
+ "a LiveView cannot be mounted while issuing a live patch to the client"
+ end
+ end
+
+ describe "redirect" do
+ test "when disconnected", %{conn: conn} do
+ conn = get(conn, "/redir?during=disconnected&kind=redirect&to=/thermo")
+ assert redirected_to(conn) == "/thermo"
+
+ {:error, {:redirect, %{to: "/thermo"}}} =
+ live(conn, "/redir?during=disconnected&kind=redirect&to=/thermo")
+ end
+
+ test "when connected", %{conn: conn} do
+ conn = get(conn, "/redir?during=connected&kind=redirect&to=/thermo")
+ assert html_response(conn, 200) =~ "parent_content"
+ assert {:error, {:redirect, %{to: "/thermo"}}} = live(conn)
+ end
+
+ test "child when disconnected", %{conn: conn} do
+ conn =
+ get(conn, "/redir?during=disconnected&kind=redirect&child_to=/thermo?from_child=true")
+
+ assert redirected_to(conn) == "/thermo?from_child=true"
+ end
+
+ test "child when connected", %{conn: conn} do
+ conn = get(conn, "/redir?during=connected&kind=redirect&child_to=/thermo?from_child=true")
+ assert html_response(conn, 200) =~ "parent_content"
+ assert {:error, {:redirect, %{to: "/thermo?from_child=true"}}} = live(conn)
+ end
+ end
+
+ describe "external redirect" do
+ test "when disconnected", %{conn: conn} do
+ conn = get(conn, "/redir?during=disconnected&kind=external&to=https://phoenixframework.org")
+ assert redirected_to(conn) == "https://phoenixframework.org"
+
+ {:error, {:redirect, %{to: "https://phoenixframework.org"}}} =
+ live(conn, "/redir?during=disconnected&kind=external&to=https://phoenixframework.org")
+ end
+
+ test "when connected", %{conn: conn} do
+ conn = get(conn, "/redir?during=connected&kind=external&to=https://phoenixframework.org")
+ assert html_response(conn, 200) =~ "parent_content"
+ assert {:error, {:redirect, %{to: "https://phoenixframework.org"}}} = live(conn)
+ end
+
+ test "child when disconnected", %{conn: conn} do
+ conn =
+ get(
+ conn,
+ "/redir?during=disconnected&kind=external&child_to=https://phoenixframework.org?from_child=true"
+ )
+
+ assert redirected_to(conn) == "https://phoenixframework.org?from_child=true"
+ end
+
+ test "child when connected", %{conn: conn} do
+ conn =
+ get(
+ conn,
+ "/redir?during=connected&kind=external&child_to=https://phoenixframework.org?from_child=true"
+ )
+
+ assert html_response(conn, 200) =~ "parent_content"
+
+ assert {:error, {:redirect, %{to: "https://phoenixframework.org?from_child=true"}}} =
+ live(conn)
+ end
+ end
+
+ describe "live_redirect" do
+ test "within same live session", %{conn: conn} do
+ assert {:ok, thermo_live, html} = live(conn, "/thermo-live-session")
+ thermo_ref = Process.monitor(thermo_live.pid)
+
+ assert [{"article", root_attrs, _}] = DOM.parse(html)
+
+ %{"data-phx-session" => thermo_session, "data-phx-static" => thermo_static} =
+ Enum.into(root_attrs, %{})
+
+ assert {:ok, clock_live, html} = live_redirect(thermo_live, to: "/clock-live-session")
+
+ for str <- [html, render(clock_live)] do
+ content = DOM.parse(str)
+ assert [{"section", attrs, _inner}] = content
+ assert {"class", "clock"} in attrs
+ assert {"data-phx-session", thermo_session} in attrs
+ assert {"data-phx-static", thermo_static} in attrs
+ assert str =~ "time: 12:00 NY"
+ end
+
+ assert_receive {:DOWN, ^thermo_ref, :process, _pid, {:shutdown, :closed}}
+
+ assert {:ok, thermo_live2, html} = live_redirect(clock_live, to: "/thermo-live-session")
+
+ for str <- [html, render(thermo_live2)] do
+ content = DOM.parse(str)
+ assert [{"article", attrs, _inner}] = content
+ assert {"class", "thermo"} in attrs
+ assert str =~ "The temp is"
+ end
+ end
+
+ test "refused with mismatched live session", %{conn: conn} do
+ assert {:ok, thermo_live, _html} = live(conn, "/thermo-live-session")
+
+ assert {:error, {:redirect, _}} =
+ live_redirect(thermo_live, to: "/clock-live-session-admin")
+ end
+
+ test "refused with no live session", %{conn: conn} do
+ assert {:ok, thermo_live, _html} = live(conn, "/thermo")
+ assert {:error, {:redirect, _}} = live_redirect(thermo_live, to: "/thermo-live-session")
+
+ assert {:ok, thermo_live, _html} = live(conn, "/thermo")
+
+ assert {:error, {:redirect, _}} =
+ live_redirect(thermo_live, to: "/thermo-live-session-admin")
+ end
+
+ test "with outdated token", %{conn: conn} do
+ assert {:ok, thermo_live, _html} = live(conn, "/thermo-live-session")
+
+ assert {:error, {:redirect, %{to: "http://www.example.com/clock-live-session"}}} =
+ Phoenix.LiveViewTest.__live_redirect__(
+ thermo_live,
+ [to: "/clock-live-session"],
+ fn _token ->
+ salt = Phoenix.LiveView.Utils.salt!(@endpoint)
+ Phoenix.Token.sign(@endpoint, salt, {0, %{}})
+ end
+ )
+ end
+ end
+
+ describe "live_patch" do
+ test "patches on custom host with full path", %{conn: conn} do
+ {:ok, live, html} = live(conn, "https://app.example.com/with-host/full")
+ assert html =~ "URI: https://app.example.com/with-host/full"
+ assert html =~ "LiveAction: full"
+
+ html = live |> element("#path") |> render_click()
+ assert html =~ "URI: https://app.example.com/with-host/path"
+ assert html =~ "LiveAction: path"
+
+ html = live |> element("#full") |> render_click()
+ assert html =~ "URI: https://app.example.com/with-host/full"
+ assert html =~ "LiveAction: full"
+ end
+
+ test "patches on custom host with partial path", %{conn: conn} do
+ {:ok, live, html} = live(%{conn | host: "app.example.com"}, "/with-host/path")
+ assert html =~ "URI: http://app.example.com/with-host/path"
+ assert html =~ "LiveAction: path"
+
+ html = live |> element("#full") |> render_click()
+ assert html =~ "URI: https://app.example.com/with-host/full"
+ assert html =~ "LiveAction: full"
+
+ html = live |> element("#path") |> render_click()
+ assert html =~ "URI: http://app.example.com/with-host/path"
+ assert html =~ "LiveAction: path"
+ end
+ end
+end
diff --git a/test/phoenix_live_view/integrations/nested_test.exs b/test/phoenix_live_view/integrations/nested_test.exs
new file mode 100644
index 0000000000..97f0260c1a
--- /dev/null
+++ b/test/phoenix_live_view/integrations/nested_test.exs
@@ -0,0 +1,323 @@
+defmodule Phoenix.LiveView.NestedTest do
+ use ExUnit.Case, async: false
+
+ import Plug.Conn
+ import Phoenix.ConnTest
+ import Phoenix.LiveViewTest
+
+ alias Phoenix.LiveView
+ alias Phoenix.LiveViewTest.{Endpoint, DOM, ClockLive, ClockControlsLive, LiveInComponent}
+
+ @endpoint Endpoint
+
+ setup config do
+ {:ok, conn: Plug.Test.init_test_session(build_conn(), config[:session] || %{})}
+ end
+
+ test "nested child render on disconnected mount", %{conn: conn} do
+ conn =
+ conn
+ |> Plug.Test.init_test_session(%{nest: []})
+ |> get("/thermo")
+
+ html = html_response(conn, 200)
+ assert html =~ "The temp is: 0"
+ assert html =~ "time: 12:00"
+ assert html =~ "+ "
+ end
+
+ @tag session: %{nest: []}
+ test "nested child render on connected mount", %{conn: conn} do
+ {:ok, thermo_view, _} = live(conn, "/thermo")
+
+ html = render(thermo_view)
+ assert html =~ "The temp is: 1"
+ assert html =~ "time: 12:00"
+ assert html =~ "+ "
+
+ GenServer.call(thermo_view.pid, {:set, :nest, false})
+ html = render(thermo_view)
+ assert html =~ "The temp is: 1"
+ refute html =~ "time"
+ refute html =~ "snooze"
+ end
+
+ test "dynamically added children", %{conn: conn} do
+ {:ok, thermo_view, _html} = live(conn, "/thermo")
+
+ assert render(thermo_view) =~ "The temp is: 1"
+ refute render(thermo_view) =~ "time"
+ refute render(thermo_view) =~ "snooze"
+ GenServer.call(thermo_view.pid, {:set, :nest, []})
+ assert render(thermo_view) =~ "The temp is: 1"
+ assert render(thermo_view) =~ "time"
+ assert render(thermo_view) =~ "snooze"
+
+ assert clock_view = find_live_child(thermo_view, "clock")
+ assert controls_view = find_live_child(clock_view, "NY-controls")
+ assert clock_view.module == ClockLive
+ assert controls_view.module == ClockControlsLive
+
+ assert render_click(controls_view, :snooze) == "+ "
+ assert render(clock_view) =~ "time: 12:05"
+ assert render(clock_view) =~ "+ "
+ assert render(controls_view) =~ "+ "
+
+ :ok = GenServer.call(clock_view.pid, {:set, "12:01"})
+
+ assert render(clock_view) =~ "time: 12:01"
+ assert render(thermo_view) =~ "time: 12:01"
+
+ assert render(thermo_view) =~ "+ "
+ end
+
+ @tag session: %{nest: []}
+ test "nested children are removed and killed", %{conn: conn} do
+ Process.flag(:trap_exit, true)
+
+ html_without_nesting =
+ DOM.parse("""
+ Redirect: none\nThe temp is: 1
+ -
+ +
+ """)
+
+ {:ok, thermo_view, _} = live(conn, "/thermo")
+
+ assert find_live_child(thermo_view, "clock")
+ refute DOM.child_nodes(hd(DOM.parse(render(thermo_view)))) == html_without_nesting
+
+ GenServer.call(thermo_view.pid, {:set, :nest, false})
+ assert DOM.child_nodes(hd(DOM.parse(render(thermo_view)))) == html_without_nesting
+ refute find_live_child(thermo_view, "clock")
+ end
+
+ @tag session: %{dup: false}
+ test "multiple nested children of same module", %{conn: conn} do
+ {:ok, parent, _} = live(conn, "/same-child")
+ assert tokyo = find_live_child(parent, "Tokyo")
+ assert madrid = find_live_child(parent, "Madrid")
+ assert toronto = find_live_child(parent, "Toronto")
+ child_ids = for view <- [tokyo, madrid, toronto], do: view.id
+
+ assert Enum.uniq(child_ids) == child_ids
+ assert render(parent) =~ "Tokyo"
+ assert render(parent) =~ "Madrid"
+ assert render(parent) =~ "Toronto"
+ end
+
+ @tag session: %{dup: false}
+ test "multiple nested children of same module with new session", %{conn: conn} do
+ {:ok, parent, _} = live(conn, "/same-child")
+ assert render_click(parent, :inc) =~ "Toronto"
+ end
+
+ test "nested within comprehensions", %{conn: conn} do
+ users = [
+ %{name: "chris", email: "chris@test"},
+ %{name: "josé", email: "jose@test"}
+ ]
+
+ expected_users = "chris chris@test josé jose@test "
+
+ {:ok, thermo_view, html} =
+ conn
+ |> put_session(:nest, [])
+ |> put_session(:users, users)
+ |> live("/thermo")
+
+ assert html =~ expected_users
+ assert render(thermo_view) =~ expected_users
+ end
+
+ test "nested within live component" do
+ assert {:ok, _view, _html} = live_isolated(build_conn(), LiveInComponent.Root)
+ end
+
+ test "raises on duplicate child LiveView id", %{conn: conn} do
+ Process.flag(:trap_exit, true)
+
+ {:ok, view, _html} =
+ conn
+ |> Plug.Conn.put_session(:user_id, 13)
+ |> live("/root")
+
+ :ok = GenServer.call(view.pid, {:dynamic_child, :static})
+
+ assert Exception.format(:exit, catch_exit(render(view))) =~
+ "expected selector \"#static\" to return a single element, but got 2"
+ end
+
+ describe "navigation helpers" do
+ @tag session: %{nest: []}
+ test "push_navigate", %{conn: conn} do
+ {:ok, thermo_view, html} = live(conn, "/thermo")
+ assert html =~ "Redirect: none"
+
+ assert clock_view = find_live_child(thermo_view, "clock")
+
+ send(
+ clock_view.pid,
+ {:run,
+ fn socket ->
+ {:noreply, LiveView.push_navigate(socket, to: "/thermo?redirect=push")}
+ end}
+ )
+
+ assert_redirect(thermo_view, "/thermo?redirect=push")
+ end
+
+ @tag session: %{nest: []}
+ test "refute_redirect", %{conn: conn} do
+ {:ok, thermo_view, _html} = live(conn, "/thermo")
+
+ clock_view = find_live_child(thermo_view, "clock")
+
+ send(
+ clock_view.pid,
+ {:run,
+ fn socket ->
+ {:noreply, LiveView.push_navigate(socket, to: "/some_url")}
+ end}
+ )
+
+ refute_redirected(thermo_view, "/not_going_here")
+
+ send(
+ clock_view.pid,
+ {:run,
+ fn socket ->
+ {:noreply, LiveView.push_navigate(socket, to: "/another_url")}
+ end}
+ )
+
+ try do
+ refute_redirected(thermo_view, "/another_url")
+ rescue
+ e ->
+ assert %ArgumentError{message: message} = e
+ assert message =~ "not to redirect to"
+ end
+ end
+
+ @tag session: %{nest: []}
+ test "push_navigate with destination that can vary", %{conn: conn} do
+ {:ok, thermo_view, html} = live(conn, "/thermo")
+ assert html =~ "Redirect: none"
+
+ assert clock_view = find_live_child(thermo_view, "clock")
+
+ id = Enum.random(1000..9999)
+
+ send(
+ clock_view.pid,
+ {:run,
+ fn socket ->
+ {:noreply, LiveView.push_navigate(socket, to: "/thermo?redirect=#{id}")}
+ end}
+ )
+
+ {path, _flash} = assert_redirect(thermo_view)
+ assert path =~ ~r/\/thermo\?redirect=[0-9]+/
+ end
+
+ @tag session: %{nest: []}
+ test "push_patch", %{conn: conn} do
+ {:ok, thermo_view, html} = live(conn, "/thermo")
+ assert html =~ "Redirect: none"
+ assert clock_view = find_live_child(thermo_view, "clock")
+
+ send(
+ clock_view.pid,
+ {:run,
+ fn socket ->
+ {:noreply, LiveView.push_patch(socket, to: "/thermo?redirect=patch")}
+ end}
+ )
+
+ assert_patch(thermo_view, "/thermo?redirect=patch")
+ assert render(thermo_view) =~ "Redirect: patch"
+ end
+
+ @tag session: %{nest: []}
+ test "push_patch to destination which can vary", %{conn: conn} do
+ {:ok, thermo_view, html} = live(conn, "/thermo")
+ assert html =~ "Redirect: none"
+ assert clock_view = find_live_child(thermo_view, "clock")
+
+ id = Enum.random(1000..9999)
+
+ send(
+ clock_view.pid,
+ {:run,
+ fn socket ->
+ {:noreply, LiveView.push_patch(socket, to: "/thermo?redirect=#{id}")}
+ end}
+ )
+
+ path = assert_patch(thermo_view)
+ assert path =~ ~r/\/thermo\?redirect=[0-9]+/
+ assert render(thermo_view) =~ "Redirect: #{id}"
+ end
+
+ @tag session: %{nest: []}
+ test "redirect from child", %{conn: conn} do
+ {:ok, thermo_view, html} = live(conn, "/thermo")
+ assert html =~ "Redirect: none"
+
+ assert clock_view = find_live_child(thermo_view, "clock")
+
+ send(
+ clock_view.pid,
+ {:run,
+ fn socket ->
+ {:noreply, LiveView.redirect(socket, to: "/thermo?redirect=redirect")}
+ end}
+ )
+
+ assert_redirect(thermo_view, "/thermo?redirect=redirect")
+ end
+
+ @tag session: %{nest: []}
+ test "external redirect from child", %{conn: conn} do
+ {:ok, thermo_view, html} = live(conn, "/thermo")
+ assert html =~ "Redirect: none"
+
+ assert clock_view = find_live_child(thermo_view, "clock")
+
+ send(
+ clock_view.pid,
+ {:run,
+ fn socket ->
+ {:noreply, LiveView.redirect(socket, external: "https://phoenixframework.org")}
+ end}
+ )
+
+ assert_redirect(thermo_view, "https://phoenixframework.org")
+ end
+ end
+
+ describe "sticky" do
+ @tag session: %{name: "ny"}
+ test "process does not go down with parent", %{conn: conn} do
+ {:ok, clock_view, _html} = live(conn, "/clock?sticky=true")
+ %Phoenix.LiveViewTest.View{} = sticky_child = find_live_child(clock_view, "ny-controls")
+ child_pid = sticky_child.pid
+ assert Process.alive?(child_pid)
+ Process.monitor(child_pid)
+
+ send(
+ clock_view.pid,
+ {:run,
+ fn socket ->
+ {:noreply, LiveView.push_navigate(socket, to: "/clock?sticky=true&redirected=true")}
+ end}
+ )
+
+ assert_redirect(clock_view, "/clock?sticky=true&redirected=true")
+ refute_receive {:DOWN, _ref, :process, ^child_pid, {:shutdown, :parent_exited}}
+ # client proxy transport
+ assert_receive {:DOWN, _ref, :process, ^child_pid, {:shutdown, :closed}}
+ end
+ end
+end
diff --git a/test/phoenix_live_view/integrations/params_test.exs b/test/phoenix_live_view/integrations/params_test.exs
index 951c115eb1..39284c777e 100644
--- a/test/phoenix_live_view/integrations/params_test.exs
+++ b/test/phoenix_live_view/integrations/params_test.exs
@@ -1,21 +1,18 @@
defmodule Phoenix.LiveView.ParamsTest do
- use ExUnit.Case
+ # Telemetry events need to run asynchronously
+ use ExUnit.Case, async: false
+
import Plug.Conn
import Phoenix.ConnTest
import Phoenix.LiveViewTest
import Phoenix.LiveView.TelemetryTestHelpers
- alias Phoenix.LiveView
+ alias Phoenix.{Component, LiveView}
alias Phoenix.LiveViewTest.{Endpoint, DOM}
@endpoint Endpoint
- setup_all do
- ExUnit.CaptureLog.capture_log(fn -> Endpoint.start_link() end)
- :ok
- end
-
setup do
conn =
Phoenix.ConnTest.build_conn(:get, "http://www.example.com/", nil)
@@ -36,10 +33,10 @@ defmodule Phoenix.LiveView.ParamsTest do
response = html_response(conn, 200)
assert response =~
- escape(~s|params: %{"id" => "123", "query1" => "query1", "query2" => "query2"}|)
+ rendered_to_string(~s|params: %{"id" => "123", "query1" => "query1", "query2" => "query2"}|)
assert response =~
- escape(~s|mount: %{"id" => "123", "query1" => "query1", "query2" => "query2"}|)
+ rendered_to_string(~s|mount: %{"id" => "123", "query1" => "query1", "query2" => "query2"}|)
end
test "telemetry events are emitted on success", %{conn: conn} do
@@ -103,6 +100,7 @@ defmodule Phoenix.LiveView.ParamsTest do
|> get("/counter/123?from=handle_params")
assert redirected_to(conn) == "/"
+ # TODO use Phoenix.Flash.get when 1.7 is released
assert get_flash(conn, :info) == "msg"
end
@@ -115,10 +113,10 @@ defmodule Phoenix.LiveView.ParamsTest do
|> redirected_to() == "/counter/123?from=rehandled_params"
end
- test "push_redirect", %{conn: conn} do
+ test "push_navigate", %{conn: conn} do
assert conn
|> put_serialized_session(:on_handle_params, fn socket ->
- {:noreply, LiveView.push_redirect(socket, to: "/thermo/456")}
+ {:noreply, LiveView.push_navigate(socket, to: "/thermo/456")}
end)
|> get("/counter/123?from=handle_params")
|> redirected_to() == "/thermo/456"
@@ -139,8 +137,8 @@ defmodule Phoenix.LiveView.ParamsTest do
|> get("/counter/123?q1=1", q2: "2")
|> live()
- assert html =~ escape(~s|params: %{"id" => "123", "q1" => "1", "q2" => "2"}|)
- assert html =~ escape(~s|mount: %{"id" => "123", "q1" => "1", "q2" => "2"}|)
+ assert html =~ rendered_to_string(~s|params: %{"id" => "123", "q1" => "1", "q2" => "2"}|)
+ assert html =~ rendered_to_string(~s|mount: %{"id" => "123", "q1" => "1", "q2" => "2"}|)
end
test "is called on connected mount with query string params from live", %{conn: conn} do
@@ -148,7 +146,7 @@ defmodule Phoenix.LiveView.ParamsTest do
conn
|> live("/counter/123?q1=1")
- assert html =~ escape(~s|%{"id" => "123", "q1" => "1"}|)
+ assert html =~ rendered_to_string(~s|%{"id" => "123", "q1" => "1"}|)
end
test "telemetry events are emitted on success", %{conn: conn} do
@@ -213,16 +211,16 @@ defmodule Phoenix.LiveView.ParamsTest do
|> live()
response = render(counter_live)
- assert response =~ escape(~s|params: %{"from" => "rehandled_params", "id" => "123"}|)
- assert response =~ escape(~s|mount: %{"from" => "handle_params", "id" => "123"}|)
+ assert response =~ rendered_to_string(~s|params: %{"from" => "rehandled_params", "id" => "123"}|)
+ assert response =~ rendered_to_string(~s|mount: %{"from" => "handle_params", "id" => "123"}|)
end
- test "push_redirect", %{conn: conn} do
+ test "push_navigate", %{conn: conn} do
{:error, {:live_redirect, %{to: "/thermo/456"}}} =
conn
|> put_serialized_session(:on_handle_params, fn socket ->
if LiveView.connected?(socket) do
- {:noreply, LiveView.push_redirect(socket, to: "/thermo/456")}
+ {:noreply, LiveView.push_navigate(socket, to: "/thermo/456")}
else
{:noreply, socket}
end
@@ -251,7 +249,7 @@ defmodule Phoenix.LiveView.ParamsTest do
assert {
"div",
[
- {"data-phx-main", "true"},
+ {"data-phx-main", _},
{"data-phx-session", _},
{"data-phx-static", _},
{"id", "phx-" <> _}
@@ -264,14 +262,14 @@ defmodule Phoenix.LiveView.ParamsTest do
{:ok, counter_live, _html} = live(conn, "/counter/123")
assert render_patch(counter_live, "/counter/123?filter=true") =~
- escape(~s|%{"filter" => "true", "id" => "123"}|)
+ rendered_to_string(~s|%{"filter" => "true", "id" => "123"}|)
end
test "with encoded URL", %{conn: conn} do
{:ok, counter_live, _html} = live(conn, "/counter/123")
assert render_patch(counter_live, "/counter/Wm9uZTozNzYxOa%3d%3d?foo=bar+15%26") =~
- escape(~s|%{"foo" => "bar 15&", "id" => "Wm9uZTozNzYxOa=="}|)
+ rendered_to_string(~s|%{"foo" => "bar 15&", "id" => "Wm9uZTozNzYxOa=="}|)
end
end
@@ -280,7 +278,7 @@ defmodule Phoenix.LiveView.ParamsTest do
{:ok, counter_live, _html} = live(conn, "/counter/123")
assert render_click(counter_live, :push_patch, %{to: "/counter/123?from=event_ack"}) =~
- escape(~s|%{"from" => "event_ack", "id" => "123"}|)
+ rendered_to_string(~s|%{"from" => "event_ack", "id" => "123"}|)
assert_patch(counter_live, "/counter/123?from=event_ack")
end
@@ -289,14 +287,14 @@ defmodule Phoenix.LiveView.ParamsTest do
{:ok, counter_live, _html} = live(conn, "/counter/123")
send(counter_live.pid, {:push_patch, "/counter/123?from=handle_info"})
- assert render(counter_live) =~ escape(~s|%{"from" => "handle_info", "id" => "123"}|)
+ assert render(counter_live) =~ rendered_to_string(~s|%{"from" => "handle_info", "id" => "123"}|)
end
test "from handle_cast", %{conn: conn} do
{:ok, counter_live, _html} = live(conn, "/counter/123")
:ok = GenServer.cast(counter_live.pid, {:push_patch, "/counter/123?from=handle_cast"})
- assert render(counter_live) =~ escape(~s|%{"from" => "handle_cast", "id" => "123"}|)
+ assert render(counter_live) =~ rendered_to_string(~s|%{"from" => "handle_cast", "id" => "123"}|)
end
test "from handle_call", %{conn: conn} do
@@ -307,7 +305,7 @@ defmodule Phoenix.LiveView.ParamsTest do
end
:ok = GenServer.call(counter_live.pid, {:push_patch, next})
- assert render(counter_live) =~ escape(~s|%{"from" => "handle_call", "id" => "123"}|)
+ assert render(counter_live) =~ rendered_to_string(~s|%{"from" => "handle_call", "id" => "123"}|)
end
test "from handle_params", %{conn: conn} do
@@ -317,7 +315,7 @@ defmodule Phoenix.LiveView.ParamsTest do
send(self(), {:set, :val, 1000})
new_socket =
- LiveView.assign(socket, :on_handle_params, fn socket ->
+ Component.assign(socket, :on_handle_params, fn socket ->
{:noreply, LiveView.push_patch(socket, to: "/counter/123?from=rehandled_params")}
end)
@@ -327,7 +325,7 @@ defmodule Phoenix.LiveView.ParamsTest do
:ok = GenServer.call(counter_live.pid, {:push_patch, next})
html = render(counter_live)
- assert html =~ escape(~s|%{"from" => "rehandled_params", "id" => "123"}|)
+ assert html =~ rendered_to_string(~s|%{"from" => "rehandled_params", "id" => "123"}|)
assert html =~ "The value is: 1000"
assert_receive {:handle_params, "http://www.example.com/counter/123?from=rehandled_params",
@@ -335,12 +333,12 @@ defmodule Phoenix.LiveView.ParamsTest do
end
end
- describe "push_redirect" do
+ describe "push_navigate" do
test "from event callback", %{conn: conn} do
{:ok, counter_live, _html} = live(conn, "/counter/123")
assert {:error, {:live_redirect, %{to: "/thermo/123"}}} =
- render_click(counter_live, :push_redirect, %{to: "/thermo/123"})
+ render_click(counter_live, :push_navigate, %{to: "/thermo/123"})
assert_redirect(counter_live, "/thermo/123")
end
@@ -350,8 +348,8 @@ defmodule Phoenix.LiveView.ParamsTest do
next = fn socket ->
new_socket =
- LiveView.assign(socket, :on_handle_params, fn socket ->
- {:noreply, LiveView.push_redirect(socket, to: "/thermo/123")}
+ Component.assign(socket, :on_handle_params, fn socket ->
+ {:noreply, LiveView.push_navigate(socket, to: "/thermo/123")}
end)
{:reply, :ok, LiveView.push_patch(new_socket, to: "/counter/123?from=handle_params")}
@@ -363,35 +361,15 @@ defmodule Phoenix.LiveView.ParamsTest do
%{val: 1}, %{"from" => "handle_params", "id" => "123"}}
end
- test "shuts down with push_redirect", %{conn: conn} do
+ test "shuts down with push_navigate", %{conn: conn} do
{:ok, counter_live, _html} = live(conn, "/counter/123")
next = fn socket ->
- {:noreply, LiveView.push_redirect(socket, to: "/thermo/123")}
+ {:noreply, LiveView.push_navigate(socket, to: "/thermo/123")}
end
assert {{:shutdown, {:live_redirect, %{to: "/thermo/123"}}}, _} =
- catch_exit(GenServer.call(counter_live.pid, {:push_redirect, next}))
- end
- end
-
- describe "connect_params" do
- test "connect_params can be read on mount", %{conn: conn} do
- {:ok, counter_live, _html} =
- conn |> put_connect_params(%{"connect1" => "1"}) |> live("/counter/123")
-
- assert render(counter_live) =~ escape(~s|connect: %{"_mounts" => 0, "connect1" => "1"}|)
- end
- end
-
- describe "connect_info" do
- test "connect_params can be read on mount", %{conn: conn} do
- {:ok, counter_live, _html} =
- conn
- |> put_connect_info(%{x_headers: [{"x-forwarded-for", "1.2.3.4"}]})
- |> live("/counter/123")
-
- assert render(counter_live) =~ escape(~s|x_headers: [{"x-forwarded-for", "1.2.3.4"}]|)
+ catch_exit(GenServer.call(counter_live.pid, {:push_navigate, next}))
end
end
@@ -430,10 +408,4 @@ defmodule Phoenix.LiveView.ParamsTest do
assert html =~ "Params: %{"id" => "1"}"
end
end
-
- defp escape(str) do
- str
- |> Phoenix.HTML.html_escape()
- |> Phoenix.HTML.safe_to_string()
- end
end
diff --git a/test/phoenix_live_view/integrations/stream_test.exs b/test/phoenix_live_view/integrations/stream_test.exs
new file mode 100644
index 0000000000..b5c2b903e1
--- /dev/null
+++ b/test/phoenix_live_view/integrations/stream_test.exs
@@ -0,0 +1,157 @@
+defmodule Phoenix.LiveView.StreamTest do
+ use ExUnit.Case, async: true
+
+ import Phoenix.ConnTest
+ import Phoenix.LiveViewTest
+
+ alias Phoenix.LiveViewTest.{StreamLive, DOM, Endpoint}
+
+ @endpoint Endpoint
+
+ setup do
+ {:ok, conn: Plug.Test.init_test_session(build_conn(), %{})}
+ end
+
+ test "stream is pruned after render", %{conn: conn} do
+ {:ok, lv, html} = live(conn, "/stream")
+
+ users = [{"users-1", "chris"}, {"users-2", "callan"}]
+
+ for {id, name} <- users do
+ assert html =~ ~s|id="#{id}"|
+ assert html =~ name
+ end
+
+ stream = StreamLive.run(lv, fn socket -> {:reply, socket.assigns.streams.users, socket} end)
+
+ assert stream.inserts == []
+ assert stream.deletes == []
+
+ assert lv |> render() |> users_in_dom("users") == [
+ {"users-1", "chris"},
+ {"users-2", "callan"}
+ ]
+
+ assert lv
+ |> element(~S|#users-1 button[phx-click="update"]|)
+ |> render_click()
+ |> users_in_dom("users") ==
+ [{"users-1", "updated"}, {"users-2", "callan"}]
+
+ assert_pruned_stream(lv)
+
+ assert lv
+ |> element(~S|#users-2 button[phx-click="move-to-first"]|)
+ |> render_click()
+ |> users_in_dom("users") ==
+ [{"users-2", "updated"}, {"users-1", "updated"}]
+
+ assert lv
+ |> element(~S|#users-2 button[phx-click="move-to-last"]|)
+ |> render_click()
+ |> users_in_dom("users") ==
+ [{"users-1", "updated"}, {"users-2", "updated"}]
+
+ assert lv
+ |> element(~S|#users-1 button[phx-click="delete"]|)
+ |> render_click()
+ |> users_in_dom("users") ==
+ [{"users-2", "updated"}]
+
+ assert_pruned_stream(lv)
+
+ # second stream in LiveView
+ assert lv |> render() |> users_in_dom("admins") == [
+ {"admins-1", "chris-admin"},
+ {"admins-2", "callan-admin"}
+ ]
+
+ assert lv
+ |> element(~S|#admins-1 button[phx-click="admin-update"]|)
+ |> render_click()
+ |> users_in_dom("admins") ==
+ [{"admins-1", "updated"}, {"admins-2", "callan-admin"}]
+
+ assert_pruned_stream(lv)
+
+ assert lv
+ |> element(~S|#admins-2 button[phx-click="admin-move-to-first"]|)
+ |> render_click()
+ |> users_in_dom("admins") ==
+ [{"admins-2", "updated"}, {"admins-1", "updated"}]
+
+ assert lv
+ |> element(~S|#admins-2 button[phx-click="admin-move-to-last"]|)
+ |> render_click()
+ |> users_in_dom("admins") ==
+ [{"admins-1", "updated"}, {"admins-2", "updated"}]
+
+ assert lv
+ |> element(~S|#admins-1 button[phx-click="admin-delete"]|)
+ |> render_click()
+ |> users_in_dom("admins") ==
+ [{"admins-2", "updated"}]
+ end
+
+ describe "within live component" do
+ test "stream operations", %{conn: conn} do
+ {:ok, lv, _html} = live(conn, "/stream")
+
+ assert lv |> render() |> users_in_dom("c_users") == [
+ {"c_users-1", "chris"},
+ {"c_users-2", "callan"}
+ ]
+
+ assert lv
+ |> element(~S|#c_users-1 button[phx-click="update"]|)
+ |> render_click()
+ |> users_in_dom("c_users") ==
+ [{"c_users-1", "updated"}, {"c_users-2", "callan"}]
+
+ assert_pruned_stream(lv)
+
+ assert lv
+ |> element(~S|#c_users-2 button[phx-click="move-to-first"]|)
+ |> render_click()
+ |> users_in_dom("c_users") ==
+ [{"c_users-2", "updated"}, {"c_users-1", "updated"}]
+
+ assert lv
+ |> element(~S|#c_users-2 button[phx-click="move-to-last"]|)
+ |> render_click()
+ |> users_in_dom("c_users") ==
+ [{"c_users-1", "updated"}, {"c_users-2", "updated"}]
+
+ assert lv
+ |> element(~S|#c_users-1 button[phx-click="delete"]|)
+ |> render_click()
+ |> users_in_dom("c_users") ==
+ [{"c_users-2", "updated"}]
+
+ Phoenix.LiveView.send_update(lv.pid, Phoenix.LiveViewTest.StreamComponent,
+ id: "stream-component",
+ send_assigns_to: self()
+ )
+
+ assert_receive {:assigns, %{streams: streams}}
+ assert streams.c_users.inserts == []
+ assert streams.c_users.deletes == []
+ assert_pruned_stream(lv)
+ end
+ end
+
+ defp assert_pruned_stream(lv) do
+ stream = StreamLive.run(lv, fn socket -> {:reply, socket.assigns.streams.users, socket} end)
+ assert stream.inserts == []
+ assert stream.deletes == []
+ end
+
+ defp users_in_dom(html, parent_id) do
+ html
+ |> DOM.parse()
+ |> DOM.all("##{parent_id} > *")
+ |> Enum.map(fn {_tag, [{"id", id}], [text | _children]} ->
+ {id, String.trim(text)}
+ end)
+ end
+end
diff --git a/test/phoenix_live_view/integrations/telemetry_test.exs b/test/phoenix_live_view/integrations/telemetry_test.exs
new file mode 100644
index 0000000000..7cc9e3dad8
--- /dev/null
+++ b/test/phoenix_live_view/integrations/telemetry_test.exs
@@ -0,0 +1,285 @@
+defmodule Phoenix.LiveView.TelemtryTest do
+ # Telemetry tests need to run synchronously
+ use ExUnit.Case, async: false
+
+ import ExUnit.CaptureLog
+
+ import Phoenix.ConnTest
+ import Phoenix.LiveViewTest
+ import Phoenix.LiveView.TelemetryTestHelpers
+
+ alias Phoenix.LiveView.Socket
+ alias Phoenix.LiveViewTest.Endpoint
+
+ @endpoint Endpoint
+ @moduletag session: %{names: ["chris", "jose"], from: nil}
+
+ setup config do
+ {:ok, conn: Plug.Test.init_test_session(build_conn(), config[:session] || %{})}
+ end
+
+ describe "live views" do
+ @tag session: %{current_user_id: "1"}
+ test "static mount emits telemetry events are emitted on successful callback", %{conn: conn} do
+ attach_telemetry([:phoenix, :live_view, :mount])
+
+ log =
+ capture_log(fn ->
+ conn
+ |> get("/thermo?foo=bar")
+ |> html_response(200)
+
+ assert_receive {:event, [:phoenix, :live_view, :mount, :start], %{system_time: _},
+ %{socket: %Socket{transport_pid: nil}} = metadata}
+
+ assert metadata.params == %{"foo" => "bar"}
+ assert metadata.session == %{"current_user_id" => "1"}
+ assert metadata.uri == "http://www.example.com/thermo?foo=bar"
+
+ assert_receive {:event, [:phoenix, :live_view, :mount, :stop], %{duration: _},
+ %{socket: %Socket{transport_pid: nil}} = metadata}
+
+ assert metadata.params == %{"foo" => "bar"}
+ assert metadata.session == %{"current_user_id" => "1"}
+ assert metadata.uri == "http://www.example.com/thermo?foo=bar"
+ end)
+
+ refute log =~ "MOUNT Phoenix.LiveViewTest.ThermostatLive"
+ refute log =~ "Replied in "
+
+ refute log =~ "HANDLE PARAMS"
+ refute log =~ "Replied in "
+ end
+
+ @tag session: %{current_user_id: "1"}
+ test "static mount emits telemetry events when callback raises an exception", %{conn: conn} do
+ attach_telemetry([:phoenix, :live_view, :mount])
+
+ assert_raise Plug.Conn.WrapperError, ~r/boom/, fn ->
+ get(conn, "/errors?crash_on=disconnected_mount")
+ end
+
+ assert_receive {:event, [:phoenix, :live_view, :mount, :start], %{system_time: _},
+ %{socket: %Socket{transport_pid: nil}} = metadata}
+
+ assert metadata.params == %{"crash_on" => "disconnected_mount"}
+ assert metadata.session == %{"current_user_id" => "1"}
+ assert metadata.uri == "http://www.example.com/errors?crash_on=disconnected_mount"
+
+ assert_receive {:event, [:phoenix, :live_view, :mount, :exception], %{duration: _},
+ %{socket: %Socket{transport_pid: nil}} = metadata}
+
+ assert metadata.kind == :error
+ assert %RuntimeError{} = metadata.reason
+ assert metadata.params == %{"crash_on" => "disconnected_mount"}
+ assert metadata.session == %{"current_user_id" => "1"}
+ assert metadata.uri == "http://www.example.com/errors?crash_on=disconnected_mount"
+ end
+
+ @tag session: %{current_user_id: "1"}
+ test "live mount emits telemetry events are emitted on successful callback", %{conn: conn} do
+ attach_telemetry([:phoenix, :live_view, :mount])
+
+ log =
+ capture_log(fn ->
+ {:ok, _view, _html} = live(conn, "/thermo?foo=bar")
+
+ assert_receive {:event, [:phoenix, :live_view, :mount, :start], %{system_time: _},
+ %{socket: %{transport_pid: pid}} = metadata}
+ when is_pid(pid)
+
+ assert metadata.socket.transport_pid
+ assert metadata.params == %{"foo" => "bar"}
+ assert metadata.session == %{"current_user_id" => "1"}
+ assert metadata.uri == "http://www.example.com/thermo?foo=bar"
+
+ assert_receive {:event, [:phoenix, :live_view, :mount, :stop], %{duration: _},
+ %{socket: %{transport_pid: pid}} = metadata}
+ when is_pid(pid)
+
+ assert metadata.socket.transport_pid
+ assert metadata.params == %{"foo" => "bar"}
+ assert metadata.session == %{"current_user_id" => "1"}
+ assert metadata.uri == "http://www.example.com/thermo?foo=bar"
+ end)
+
+ assert log =~ "MOUNT Phoenix.LiveViewTest.ThermostatLive"
+ assert log =~ " Parameters: %{\"foo\" => \"bar\"}"
+ assert log =~ " Session: %{\"current_user_id\" => \"1\"}"
+ assert log =~ "Replied in"
+
+ assert log =~ "HANDLE PARAMS"
+ assert log =~ " View: Phoenix.LiveViewTest.ThermostatLive"
+ assert log =~ " Parameters: %{\"foo\" => \"bar\"}"
+ assert log =~ "Replied in"
+ end
+
+ @tag session: %{current_user_id: "1"}
+ test "live mount emits telemetry events when callback raises an exception", %{conn: conn} do
+ attach_telemetry([:phoenix, :live_view, :mount])
+
+ assert catch_exit(live(conn, "/errors?crash_on=connected_mount"))
+
+ assert_receive {:event, [:phoenix, :live_view, :mount, :start], %{system_time: _},
+ %{socket: %{transport_pid: pid}} = metadata}
+ when is_pid(pid)
+
+ assert metadata.socket.transport_pid
+ assert metadata.params == %{"crash_on" => "connected_mount"}
+ assert metadata.session == %{"current_user_id" => "1"}
+ assert metadata.uri == "http://www.example.com/errors?crash_on=connected_mount"
+
+ assert_receive {:event, [:phoenix, :live_view, :mount, :exception], %{duration: _},
+ %{socket: %{transport_pid: pid}} = metadata}
+ when is_pid(pid)
+
+ assert metadata.socket.transport_pid
+ assert metadata.kind == :error
+ assert %RuntimeError{} = metadata.reason
+ assert metadata.params == %{"crash_on" => "connected_mount"}
+ assert metadata.session == %{"current_user_id" => "1"}
+ assert metadata.uri == "http://www.example.com/errors?crash_on=connected_mount"
+ end
+
+ test "render_* with a successful handle_event callback emits telemetry metrics", %{conn: conn} do
+ attach_telemetry([:phoenix, :live_view, :handle_event])
+
+ log =
+ capture_log(fn ->
+ {:ok, view, _} = live(conn, "/thermo")
+ render_submit(view, :save, %{temp: 20})
+
+ assert_receive {:event, [:phoenix, :live_view, :handle_event, :start],
+ %{system_time: _}, metadata}
+
+ assert metadata.socket.transport_pid
+ assert metadata.event == "save"
+ assert metadata.params == %{"temp" => "20"}
+
+ assert_receive {:event, [:phoenix, :live_view, :handle_event, :stop], %{duration: _},
+ metadata}
+
+ assert metadata.socket.transport_pid
+ assert metadata.event == "save"
+ assert metadata.params == %{"temp" => "20"}
+ end)
+
+ assert log =~ "HANDLE EVENT"
+ assert log =~ " View: Phoenix.LiveViewTest.ThermostatLive"
+ assert log =~ " Event: \"save\""
+ assert log =~ " Parameters: %{\"temp\" => \"20\"}"
+ assert log =~ "Replied in"
+ end
+
+ test "render_* with crashing handle_event callback emits telemetry metrics", %{conn: conn} do
+ Process.flag(:trap_exit, true)
+ attach_telemetry([:phoenix, :live_view, :handle_event])
+
+ {:ok, view, _} = live(conn, "/errors")
+ catch_exit(render_submit(view, :crash, %{"foo" => "bar"}))
+
+ assert_receive {:event, [:phoenix, :live_view, :handle_event, :start], %{system_time: _},
+ metadata}
+
+ assert metadata.socket.transport_pid
+ assert metadata.event == "crash"
+ assert metadata.params == %{"foo" => "bar"}
+
+ assert_receive {:event, [:phoenix, :live_view, :handle_event, :exception], %{duration: _},
+ metadata}
+
+ assert metadata.socket.transport_pid
+ assert metadata.kind == :error
+ assert %RuntimeError{} = metadata.reason
+ assert metadata.event == "crash"
+ assert metadata.params == %{"foo" => "bar"}
+ end
+ end
+
+ describe "live components" do
+ test "emits telemetry events when callback is successful", %{conn: conn} do
+ attach_telemetry([:phoenix, :live_component, :handle_event])
+
+ log =
+ capture_log(fn ->
+ {:ok, view, _html} = live(conn, "/components")
+
+ view |> element("#chris") |> render_click(%{"op" => "upcase"})
+
+ assert_receive {:event, [:phoenix, :live_component, :handle_event, :start],
+ %{system_time: _}, metadata}
+
+ assert metadata.socket.transport_pid
+ assert metadata.event == "transform"
+ assert metadata.component == Phoenix.LiveViewTest.StatefulComponent
+ assert metadata.params == %{"op" => "upcase"}
+
+ assert_receive {:event, [:phoenix, :live_component, :handle_event, :stop],
+ %{duration: _}, metadata}
+
+ assert metadata.socket.transport_pid
+ assert metadata.event == "transform"
+ assert metadata.component == Phoenix.LiveViewTest.StatefulComponent
+ assert metadata.params == %{"op" => "upcase"}
+ end)
+
+ assert log =~ "HANDLE EVENT"
+ assert log =~ " Component: Phoenix.LiveViewTest.StatefulComponent"
+ assert log =~ " View: Phoenix.LiveViewTest.WithComponentLive"
+ assert log =~ " Event: \"transform\""
+ assert log =~ " Parameters: %{\"op\" => \"upcase\"}"
+ assert log =~ "Replied in"
+ end
+
+ test "emits telemetry events when callback fails", %{conn: conn} do
+ Process.flag(:trap_exit, true)
+
+ attach_telemetry([:phoenix, :live_component, :handle_event])
+ {:ok, view, _html} = live(conn, "/components")
+
+ assert view |> element("#chris") |> render_click(%{"op" => "boom"}) |> catch_exit
+
+ assert_receive {:event, [:phoenix, :live_component, :handle_event, :start],
+ %{system_time: _}, metadata}
+
+ assert metadata.socket.transport_pid
+ assert metadata.event == "transform"
+ assert metadata.component == Phoenix.LiveViewTest.StatefulComponent
+ assert metadata.params == %{"op" => "boom"}
+
+ assert_receive {:event, [:phoenix, :live_component, :handle_event, :exception],
+ %{duration: _}, metadata}
+
+ assert metadata.kind == :error
+ assert metadata.reason == {:case_clause, "boom"}
+ assert metadata.socket.transport_pid
+ assert metadata.event == "transform"
+ assert metadata.component == Phoenix.LiveViewTest.StatefulComponent
+ assert metadata.params == %{"op" => "boom"}
+ end
+ end
+
+ describe "logging configuration" do
+ @tag session: %{current_user_id: "1"}
+ test "log level can be overridden for an individual Live View module", %{conn: conn} do
+ log =
+ capture_log([level: :warning], fn ->
+ {:ok, _view, _html} = live(conn, "/log-override")
+ end)
+
+ assert log =~ "MOUNT Phoenix.LiveViewTest.WithLogOverride"
+ assert log =~ "Replied in "
+ end
+
+ @tag session: %{current_user_id: "1"}
+ test "logging can be disabled for an individual Live View module", %{conn: conn} do
+ log =
+ capture_log(fn ->
+ {:ok, _view, _html} = live(conn, "/log-disabled")
+ end)
+
+ refute log =~ "MOUNT Phoenix.LiveViewTest.WithLogDisabled"
+ refute log =~ "Replied in "
+ end
+ end
+end
diff --git a/test/phoenix_live_view/integrations/update_test.exs b/test/phoenix_live_view/integrations/update_test.exs
index be68eceaaf..1345e28b78 100644
--- a/test/phoenix_live_view/integrations/update_test.exs
+++ b/test/phoenix_live_view/integrations/update_test.exs
@@ -1,12 +1,11 @@
defmodule Phoenix.LiveView.UpdateTest do
- use ExUnit.Case, async: true
+ use ExUnit.Case, async: false
import Phoenix.ConnTest
import Phoenix.LiveViewTest
alias Phoenix.LiveViewTest.{Endpoint, DOM}
@endpoint Endpoint
- @moduletag :capture_log
setup config do
{:ok,
diff --git a/test/phoenix_live_view/js_test.exs b/test/phoenix_live_view/js_test.exs
new file mode 100644
index 0000000000..7344bd1a63
--- /dev/null
+++ b/test/phoenix_live_view/js_test.exs
@@ -0,0 +1,842 @@
+defmodule Phoenix.LiveView.JSTest do
+ use ExUnit.Case, async: true
+
+ alias Phoenix.LiveView.JS
+
+ describe "push" do
+ test "with defaults" do
+ assert JS.push("inc") == %JS{ops: [["push", %{event: "inc"}]]}
+ end
+
+ test "target" do
+ assert JS.push("inc", target: "#modal") == %JS{
+ ops: [["push", %{event: "inc", target: "#modal"}]]
+ }
+
+ assert JS.push("inc", target: 1) == %JS{
+ ops: [["push", %{event: "inc", target: 1}]]
+ }
+ end
+
+ test "loading" do
+ assert JS.push("inc", loading: "#modal") == %JS{
+ ops: [["push", %{event: "inc", loading: "#modal"}]]
+ }
+ end
+
+ test "page_loading" do
+ assert JS.push("inc", page_loading: true) == %JS{
+ ops: [["push", %{event: "inc", page_loading: true}]]
+ }
+ end
+
+ test "value" do
+ assert JS.push("inc", value: %{one: 1, two: 2}) == %JS{
+ ops: [["push", %{event: "inc", value: %{one: 1, two: 2}}]]
+ }
+
+ assert_raise ArgumentError, ~r/push :value expected to be a map/, fn ->
+ JS.push("inc", value: "not-a-map")
+ end
+ end
+
+ test "raises with unknown options" do
+ assert_raise ArgumentError, ~r/invalid option for push/, fn ->
+ JS.push("inc", to: "#modal", bad: :opt)
+ end
+ end
+
+ test "composability" do
+ js = JS.push("inc") |> JS.push("dec", loading: ".foo")
+
+ assert js == %JS{
+ ops: [["push", %{event: "inc"}], ["push", %{event: "dec", loading: ".foo"}]]
+ }
+ end
+
+ test "encoding" do
+ assert js_to_string(JS.push("inc", value: %{one: 1, two: 2})) ==
+ "[["push",{"event":"inc","value":{"one":1,"two":2}}]]"
+ end
+ end
+
+ describe "add_class" do
+ test "with defaults" do
+ assert JS.add_class("show") == %JS{
+ ops: [
+ ["add_class", %{names: ["show"], time: 200, to: nil, transition: [[], [], []]}]
+ ]
+ }
+
+ assert JS.add_class("show", to: "#modal") == %JS{
+ ops: [
+ [
+ "add_class",
+ %{names: ["show"], time: 200, to: "#modal", transition: [[], [], []]}
+ ]
+ ]
+ }
+ end
+
+ test "multiple classes" do
+ assert JS.add_class("show hl") == %JS{
+ ops: [
+ [
+ "add_class",
+ %{names: ["show", "hl"], time: 200, to: nil, transition: [[], [], []]}
+ ]
+ ]
+ }
+ end
+
+ test "custom time" do
+ assert JS.add_class("show", time: 543) == %JS{
+ ops: [
+ ["add_class", %{names: ["show"], time: 543, to: nil, transition: [[], [], []]}]
+ ]
+ }
+ end
+
+ test "transition" do
+ assert JS.add_class("show", transition: "fade") == %JS{
+ ops: [
+ [
+ "add_class",
+ %{names: ["show"], time: 200, to: nil, transition: [["fade"], [], []]}
+ ]
+ ]
+ }
+
+ assert JS.add_class("c", transition: "a b") == %JS{
+ ops: [
+ [
+ "add_class",
+ %{names: ["c"], time: 200, to: nil, transition: [["a", "b"], [], []]}
+ ]
+ ]
+ }
+
+ assert JS.add_class("show", transition: {"fade", "opacity-0", "opacity-100"}) == %JS{
+ ops: [
+ [
+ "add_class",
+ %{
+ names: ["show"],
+ time: 200,
+ to: nil,
+ transition: [["fade"], ["opacity-0"], ["opacity-100"]]
+ }
+ ]
+ ]
+ }
+ end
+
+ test "composability" do
+ js = JS.add_class("show", to: "#modal", time: 100) |> JS.add_class("hl")
+
+ assert js == %JS{
+ ops: [
+ [
+ "add_class",
+ %{names: ["show"], time: 100, to: "#modal", transition: [[], [], []]}
+ ],
+ ["add_class", %{names: ["hl"], time: 200, to: nil, transition: [[], [], []]}]
+ ]
+ }
+ end
+
+ test "raises with unknown options" do
+ assert_raise ArgumentError, ~r/invalid option for add_class/, fn ->
+ JS.add_class("show", bad: :opt)
+ end
+ end
+
+ test "encoding" do
+ assert js_to_string(JS.add_class("show")) ==
+ "[["add_class",{"names":["show"],"time":200,"to":null,"transition":[[],[],[]]}]]"
+ end
+ end
+
+ describe "remove_class" do
+ test "with defaults" do
+ assert JS.remove_class("show") == %JS{
+ ops: [
+ [
+ "remove_class",
+ %{names: ["show"], time: 200, to: nil, transition: [[], [], []]}
+ ]
+ ]
+ }
+
+ assert JS.remove_class("show", to: "#modal") == %JS{
+ ops: [
+ [
+ "remove_class",
+ %{names: ["show"], time: 200, to: "#modal", transition: [[], [], []]}
+ ]
+ ]
+ }
+ end
+
+ test "multiple classes" do
+ assert JS.remove_class("show hl") == %JS{
+ ops: [
+ [
+ "remove_class",
+ %{names: ["show", "hl"], time: 200, to: nil, transition: [[], [], []]}
+ ]
+ ]
+ }
+ end
+
+ test "custom time" do
+ assert JS.remove_class("show", time: 543) == %JS{
+ ops: [
+ [
+ "remove_class",
+ %{names: ["show"], time: 543, to: nil, transition: [[], [], []]}
+ ]
+ ]
+ }
+ end
+
+ test "transition" do
+ assert JS.remove_class("show", transition: "fade") == %JS{
+ ops: [
+ [
+ "remove_class",
+ %{names: ["show"], time: 200, to: nil, transition: [["fade"], [], []]}
+ ]
+ ]
+ }
+
+ assert JS.remove_class("c", transition: "a b") == %JS{
+ ops: [
+ [
+ "remove_class",
+ %{names: ["c"], time: 200, to: nil, transition: [["a", "b"], [], []]}
+ ]
+ ]
+ }
+
+ assert JS.remove_class("show", transition: {"fade", "opacity-0", "opacity-100"}) == %JS{
+ ops: [
+ [
+ "remove_class",
+ %{
+ names: ["show"],
+ time: 200,
+ to: nil,
+ transition: [["fade"], ["opacity-0"], ["opacity-100"]]
+ }
+ ]
+ ]
+ }
+ end
+
+ test "composability" do
+ js = JS.remove_class("show", to: "#modal", time: 100) |> JS.remove_class("hl")
+
+ assert js == %JS{
+ ops: [
+ [
+ "remove_class",
+ %{names: ["show"], time: 100, to: "#modal", transition: [[], [], []]}
+ ],
+ ["remove_class", %{names: ["hl"], time: 200, to: nil, transition: [[], [], []]}]
+ ]
+ }
+ end
+
+ test "raises with unknown options" do
+ assert_raise ArgumentError, ~r/invalid option for remove_class/, fn ->
+ JS.remove_class("show", bad: :opt)
+ end
+ end
+
+ test "encoding" do
+ assert js_to_string(JS.remove_class("show")) ==
+ "[["remove_class",{"names":["show"],"time":200,"to":null,"transition":[[],[],[]]}]]"
+ end
+ end
+
+ describe "dispatch" do
+ test "with defaults" do
+ assert JS.dispatch("click", to: "#modal") == %JS{
+ ops: [["dispatch", %{to: "#modal", event: "click"}]]
+ }
+
+ assert JS.dispatch("click") == %JS{
+ ops: [["dispatch", %{to: nil, event: "click"}]]
+ }
+ end
+
+ test "with optional flags" do
+ assert JS.dispatch("click", bubbles: false) == %JS{
+ ops: [["dispatch", %{to: nil, event: "click", bubbles: false}]]
+ }
+ end
+
+ test "raises with unknown options" do
+ assert_raise ArgumentError, ~r/invalid option for dispatch/, fn ->
+ JS.dispatch("click", to: ".foo", bad: :opt)
+ end
+ end
+
+ test "raises with click details" do
+ assert_raise ArgumentError, ~r/click events cannot be dispatched with details/, fn ->
+ JS.dispatch("click", to: ".foo", detail: %{id: 123})
+ end
+ end
+
+ test "composability" do
+ js =
+ JS.dispatch("click", to: "#modal")
+ |> JS.dispatch("keydown", to: "#keyboard")
+ |> JS.dispatch("keyup")
+
+ assert js == %JS{
+ ops: [
+ ["dispatch", %{to: "#modal", event: "click"}],
+ ["dispatch", %{to: "#keyboard", event: "keydown"}],
+ ["dispatch", %{to: nil, event: "keyup"}]
+ ]
+ }
+ end
+
+ test "encoding" do
+ assert js_to_string(JS.dispatch("click", to: ".foo")) ==
+ "[["dispatch",{"event":"click","to":".foo"}]]"
+ end
+ end
+
+ describe "toggle" do
+ test "with defaults" do
+ assert JS.toggle(to: "#modal") == %JS{
+ ops: [
+ [
+ "toggle",
+ %{display: nil, ins: [[], [], []], outs: [[], [], []], time: 200, to: "#modal"}
+ ]
+ ]
+ }
+ end
+
+ test "in and out classes" do
+ assert JS.toggle(to: "#modal", in: "fade-in d-block", out: "fade-out d-block") ==
+ %JS{
+ ops: [
+ [
+ "toggle",
+ %{
+ display: nil,
+ ins: [["fade-in", "d-block"], [], []],
+ outs: [["fade-out", "d-block"], [], []],
+ time: 200,
+ to: "#modal"
+ }
+ ]
+ ]
+ }
+
+ assert JS.toggle(
+ to: "#modal",
+ in: {"fade-in", "opacity-0", "opacity-100"},
+ out: {"fade-out", "opacity-100", "opacity-0"}
+ ) ==
+ %JS{
+ ops: [
+ [
+ "toggle",
+ %{
+ display: nil,
+ ins: [["fade-in"], ["opacity-0"], ["opacity-100"]],
+ outs: [["fade-out"], ["opacity-100"], ["opacity-0"]],
+ time: 200,
+ to: "#modal"
+ }
+ ]
+ ]
+ }
+ end
+
+ test "custom time" do
+ assert JS.toggle(to: "#modal", time: 123) == %JS{
+ ops: [
+ [
+ "toggle",
+ %{display: nil, ins: [[], [], []], outs: [[], [], []], time: 123, to: "#modal"}
+ ]
+ ]
+ }
+ end
+
+ test "custom display" do
+ assert JS.toggle(to: "#modal", display: "block") == %JS{
+ ops: [
+ [
+ "toggle",
+ %{
+ display: "block",
+ ins: [[], [], []],
+ outs: [[], [], []],
+ time: 200,
+ to: "#modal"
+ }
+ ]
+ ]
+ }
+ end
+
+ test "raises with unknown options" do
+ assert_raise ArgumentError, ~r/invalid option for toggle/, fn ->
+ JS.toggle(to: "#modal", bad: :opt)
+ end
+ end
+
+ test "composability" do
+ js = JS.toggle(to: "#modal") |> JS.toggle(to: "#keyboard", time: 123)
+
+ assert js == %JS{
+ ops: [
+ [
+ "toggle",
+ %{to: "#modal", display: nil, ins: [[], [], []], outs: [[], [], []], time: 200}
+ ],
+ [
+ "toggle",
+ %{
+ to: "#keyboard",
+ display: nil,
+ ins: [[], [], []],
+ outs: [[], [], []],
+ time: 123
+ }
+ ]
+ ]
+ }
+ end
+
+ test "encoding" do
+ assert js_to_string(JS.toggle(to: "#modal")) ==
+ "[["toggle",{"display":null,"ins":[[],[],[]],"outs":[[],[],[]],"time":200,"to":"#modal"}]]"
+ end
+ end
+
+ describe "show" do
+ test "with defaults" do
+ assert JS.show(to: "#modal") == %JS{
+ ops: [["show", %{display: nil, transition: [[], [], []], time: 200, to: "#modal"}]]
+ }
+ end
+
+ test "transition classes" do
+ assert JS.show(to: "#modal", transition: "fade-in d-block") ==
+ %JS{
+ ops: [
+ [
+ "show",
+ %{
+ display: nil,
+ transition: [["fade-in", "d-block"], [], []],
+ time: 200,
+ to: "#modal"
+ }
+ ]
+ ]
+ }
+
+ assert JS.show(
+ to: "#modal",
+ transition:
+ {"fade-in d-block", "opacity-0 -translate-x-full", "opacity-100 translate-x-0"}
+ ) ==
+ %JS{
+ ops: [
+ [
+ "show",
+ %{
+ display: nil,
+ transition: [
+ ["fade-in", "d-block"],
+ ["opacity-0", "-translate-x-full"],
+ ["opacity-100", "translate-x-0"]
+ ],
+ time: 200,
+ to: "#modal"
+ }
+ ]
+ ]
+ }
+ end
+
+ test "custom time" do
+ assert JS.show(to: "#modal", time: 123) == %JS{
+ ops: [["show", %{display: nil, transition: [[], [], []], time: 123, to: "#modal"}]]
+ }
+ end
+
+ test "custom display" do
+ assert JS.show(to: "#modal", display: "block") == %JS{
+ ops: [
+ ["show", %{display: "block", transition: [[], [], []], time: 200, to: "#modal"}]
+ ]
+ }
+ end
+
+ test "raises with unknown options" do
+ assert_raise ArgumentError, ~r/invalid option for show/, fn ->
+ JS.show(to: "#modal", bad: :opt)
+ end
+ end
+
+ test "composability" do
+ js = JS.show(to: "#modal") |> JS.show(to: "#keyboard", time: 123)
+
+ assert js == %JS{
+ ops: [
+ ["show", %{to: "#modal", display: nil, transition: [[], [], []], time: 200}],
+ ["show", %{to: "#keyboard", display: nil, transition: [[], [], []], time: 123}]
+ ]
+ }
+ end
+
+ test "encoding" do
+ assert js_to_string(JS.show(to: "#modal")) ==
+ "[["show",{"display":null,"time":200,"to":"#modal","transition":[[],[],[]]}]]"
+ end
+ end
+
+ describe "hide" do
+ test "with defaults" do
+ assert JS.hide(to: "#modal") == %JS{
+ ops: [["hide", %{transition: [[], [], []], time: 200, to: "#modal"}]]
+ }
+ end
+
+ test "transition classes" do
+ assert JS.hide(to: "#modal", transition: "fade-out d-block") ==
+ %JS{
+ ops: [
+ [
+ "hide",
+ %{
+ transition: [["fade-out", "d-block"], [], []],
+ time: 200,
+ to: "#modal"
+ }
+ ]
+ ]
+ }
+
+ assert JS.hide(
+ to: "#modal",
+ transition:
+ {"fade-in d-block", "opacity-0 -translate-x-full", "opacity-100 translate-x-0"}
+ ) ==
+ %JS{
+ ops: [
+ [
+ "hide",
+ %{
+ transition: [
+ ["fade-in", "d-block"],
+ ["opacity-0", "-translate-x-full"],
+ ["opacity-100", "translate-x-0"]
+ ],
+ time: 200,
+ to: "#modal"
+ }
+ ]
+ ]
+ }
+ end
+
+ test "custom time" do
+ assert JS.hide(to: "#modal", time: 123) == %JS{
+ ops: [["hide", %{transition: [[], [], []], time: 123, to: "#modal"}]]
+ }
+ end
+
+ test "raises with unknown options" do
+ assert_raise ArgumentError, ~r/invalid option for hide/, fn ->
+ JS.hide(to: "#modal", bad: :opt)
+ end
+ end
+
+ test "composability" do
+ js = JS.hide(to: "#modal") |> JS.hide(to: "#keyboard", time: 123)
+
+ assert js == %JS{
+ ops: [
+ ["hide", %{to: "#modal", transition: [[], [], []], time: 200}],
+ ["hide", %{to: "#keyboard", transition: [[], [], []], time: 123}]
+ ]
+ }
+ end
+
+ test "encoding" do
+ assert js_to_string(JS.hide(to: "#modal")) ==
+ "[["hide",{"time":200,"to":"#modal","transition":[[],[],[]]}]]"
+ end
+ end
+
+ describe "transition" do
+ test "with defaults" do
+ assert JS.transition("shake") == %JS{
+ ops: [["transition", %{transition: [["shake"], [], []], time: 200, to: nil}]]
+ }
+
+ assert JS.transition("shake", to: "#modal") == %JS{
+ ops: [["transition", %{transition: [["shake"], [], []], time: 200, to: "#modal"}]]
+ }
+
+ assert JS.transition("shake swirl", to: "#modal") == %JS{
+ ops: [
+ [
+ "transition",
+ %{transition: [["shake", "swirl"], [], []], time: 200, to: "#modal"}
+ ]
+ ]
+ }
+
+ assert JS.transition({"shake swirl", "opacity-0 a", "opacity-100 b"}, to: "#modal") == %JS{
+ ops: [
+ [
+ "transition",
+ %{
+ transition: [["shake", "swirl"], ["opacity-0", "a"], ["opacity-100", "b"]],
+ time: 200,
+ to: "#modal"
+ }
+ ]
+ ]
+ }
+ end
+
+ test "custom time" do
+ assert JS.transition("shake", to: "#modal", time: 123) == %JS{
+ ops: [["transition", %{transition: [["shake"], [], []], time: 123, to: "#modal"}]]
+ }
+ end
+
+ test "raises with unknown options" do
+ assert_raise ArgumentError, ~r/invalid option for transition/, fn ->
+ JS.transition("shake", to: "#modal", bad: :opt)
+ end
+ end
+
+ test "composability" do
+ js = JS.transition("shake", to: "#modal") |> JS.transition("hl", to: "#keyboard", time: 123)
+
+ assert js == %JS{
+ ops: [
+ ["transition", %{to: "#modal", transition: [["shake"], [], []], time: 200}],
+ ["transition", %{to: "#keyboard", transition: [["hl"], [], []], time: 123}]
+ ]
+ }
+ end
+
+ test "encoding" do
+ assert js_to_string(JS.transition("shake", to: "#modal")) ==
+ "[["transition",{"time":200,"to":"#modal","transition":[["shake"],[],[]]}]]"
+ end
+ end
+
+ describe "set_attribute" do
+ test "with defaults" do
+ assert JS.set_attribute({"aria-expanded", "true"}) == %JS{
+ ops: [
+ ["set_attr", %{attr: ["aria-expanded", "true"], to: nil}]
+ ]
+ }
+
+ assert JS.set_attribute({"aria-expanded", "true"}, to: "#dropdown") == %JS{
+ ops: [
+ ["set_attr", %{attr: ["aria-expanded", "true"], to: "#dropdown"}]
+ ]
+ }
+ end
+
+ test "composability" do
+ js =
+ JS.set_attribute({"expanded", "true"})
+ |> JS.set_attribute({"has-popup", "true"})
+ |> JS.set_attribute({"has-popup", "true"}, to: "#dropdown")
+
+ assert js == %JS{
+ ops: [
+ ["set_attr", %{to: nil, attr: ["expanded", "true"]}],
+ ["set_attr", %{to: nil, attr: ["has-popup", "true"]}],
+ ["set_attr", %{to: "#dropdown", attr: ["has-popup", "true"]}]
+ ]
+ }
+ end
+
+ test "raises with unknown options" do
+ assert_raise ArgumentError, ~r/invalid option for set_attribute/, fn ->
+ JS.set_attribute({"disabled", ""}, bad: :opt)
+ end
+ end
+
+ test "encoding" do
+ assert js_to_string(JS.set_attribute({"disabled", "true"})) ==
+ "[["set_attr",{"attr":["disabled","true"],"to":null}]]"
+ end
+ end
+
+ describe "remove_attribute" do
+ test "with defaults" do
+ assert JS.remove_attribute("aria-expanded") == %JS{
+ ops: [
+ ["remove_attr", %{attr: "aria-expanded", to: nil}]
+ ]
+ }
+
+ assert JS.remove_attribute("aria-expanded", to: "#dropdown") == %JS{
+ ops: [
+ ["remove_attr", %{attr: "aria-expanded", to: "#dropdown"}]
+ ]
+ }
+ end
+
+ test "composability" do
+ js =
+ JS.remove_attribute("expanded")
+ |> JS.remove_attribute("has-popup")
+ |> JS.remove_attribute("has-popup", to: "#dropdown")
+
+ assert js == %JS{
+ ops: [
+ ["remove_attr", %{to: nil, attr: "expanded"}],
+ ["remove_attr", %{to: nil, attr: "has-popup"}],
+ ["remove_attr", %{to: "#dropdown", attr: "has-popup"}]
+ ]
+ }
+ end
+
+ test "raises with unknown options" do
+ assert_raise ArgumentError, ~r/invalid option for remove_attribute/, fn ->
+ JS.remove_attribute("disabled", bad: :opt)
+ end
+ end
+
+ test "encoding" do
+ assert js_to_string(JS.remove_attribute("disabled")) ==
+ "[["remove_attr",{"attr":"disabled","to":null}]]"
+ end
+ end
+
+ describe "focus" do
+ test "with defaults" do
+ assert JS.focus() == %JS{ops: [["focus", %{to: nil}]]}
+ assert JS.focus(to: "input") == %JS{ops: [["focus", %{to: "input"}]]}
+ end
+
+ test "composability" do
+ js =
+ JS.set_attribute({"expanded", "true"})
+ |> JS.focus()
+
+ assert js == %JS{
+ ops: [["set_attr", %{attr: ["expanded", "true"], to: nil}], ["focus", %{to: nil}]]
+ }
+ end
+
+ test "raises with unknown options" do
+ assert_raise ArgumentError, ~r/invalid option for focus/, fn ->
+ JS.focus(bad: :opt)
+ end
+ end
+
+ test "encoding" do
+ assert js_to_string(JS.focus()) == "[["focus",{"to":null}]]"
+ end
+ end
+
+ describe "focus_first" do
+ test "with defaults" do
+ assert JS.focus_first() == %JS{ops: [["focus_first", %{to: nil}]]}
+ assert JS.focus_first(to: "input") == %JS{ops: [["focus_first", %{to: "input"}]]}
+ end
+
+ test "composability" do
+ js =
+ JS.set_attribute({"expanded", "true"})
+ |> JS.focus_first()
+
+ assert js == %JS{
+ ops: [
+ ["set_attr", %{attr: ["expanded", "true"], to: nil}],
+ ["focus_first", %{to: nil}]
+ ]
+ }
+ end
+
+ test "raises with unknown options" do
+ assert_raise ArgumentError, ~r/invalid option for focus_first/, fn ->
+ JS.focus_first(bad: :opt)
+ end
+ end
+
+ test "encoding" do
+ assert js_to_string(JS.focus_first()) == "[["focus_first",{"to":null}]]"
+ end
+ end
+
+ describe "push_focus" do
+ test "with defaults" do
+ assert JS.push_focus() == %JS{ops: [["push_focus", %{to: nil}]]}
+ assert JS.push_focus(to: "input") == %JS{ops: [["push_focus", %{to: "input"}]]}
+ end
+
+ test "composability" do
+ js =
+ JS.set_attribute({"expanded", "true"})
+ |> JS.push_focus()
+
+ assert js == %JS{
+ ops: [
+ ["set_attr", %{attr: ["expanded", "true"], to: nil}],
+ ["push_focus", %{to: nil}]
+ ]
+ }
+ end
+
+ test "raises with unknown options" do
+ assert_raise ArgumentError, ~r/invalid option for push_focus/, fn ->
+ JS.push_focus(bad: :opt)
+ end
+ end
+
+ test "encoding" do
+ assert js_to_string(JS.push_focus()) == "[["push_focus",{"to":null}]]"
+ end
+ end
+
+ describe "pop_focus" do
+ test "with defaults" do
+ assert JS.pop_focus() == %JS{ops: [["pop_focus", %{}]]}
+ end
+
+ test "composability" do
+ js =
+ JS.set_attribute({"expanded", "true"})
+ |> JS.pop_focus()
+
+ assert js == %JS{
+ ops: [["set_attr", %{attr: ["expanded", "true"], to: nil}], ["pop_focus", %{}]]
+ }
+ end
+
+ test "encoding" do
+ assert js_to_string(JS.pop_focus()) == "[["pop_focus",{}]]"
+ end
+ end
+
+ defp js_to_string(%JS{} = js) do
+ js
+ |> Phoenix.HTML.Safe.to_iodata()
+ |> IO.iodata_to_binary()
+ end
+end
diff --git a/test/phoenix_live_view/leex_extension_test.exs b/test/phoenix_live_view/leex_extension_test.exs
index 7a627a8918..377b64a41b 100644
--- a/test/phoenix_live_view/leex_extension_test.exs
+++ b/test/phoenix_live_view/leex_extension_test.exs
@@ -1,7 +1,7 @@
defmodule Phoenix.LiveView.LEEXTest do
use ExUnit.Case, async: true
- alias Phoenix.LiveView.{Engine, Rendered, Component}
+ alias Phoenix.LiveView.Rendered
defmodule View do
use Phoenix.View, root: "test/support/templates/leex", path: ""
@@ -12,10 +12,6 @@ defmodule Phoenix.LiveView.LEEXTest do
def render(assigns), do: ~L"FROM COMPONENT"
end
- defmacrop compile(string) do
- EEx.compile_string(string, engine: Engine, file: __CALLER__.file, line: __CALLER__.line + 1)
- end
-
@assigns %{
pre: "pre",
inner_content: "inner",
@@ -74,20 +70,6 @@ defmodule Phoenix.LiveView.LEEXTest do
{:safe, ["pre: ", "pre", "\n", ["live: ", "inner", ""], "\npost: ", "post"]}
end
- test "renders inside render_layout/4" do
- import Phoenix.View
- assigns = @assigns
-
- assert %Rendered{} =
- compile("""
- <%= render_layout(View, "inner_live.html", %{}) do %>
- WITH COMPONENT:
- <%= %Component{assigns: %{}, component: SampleComponent} %>
- <% end %>
- """)
- |> expand_rendered(true)
- end
-
defp expand_dynamic(dynamic, track_changes?) do
Enum.map(dynamic.(track_changes?), &expand_rendered(&1, track_changes?))
end
diff --git a/test/phoenix_live_view/live_stream_test.exs b/test/phoenix_live_view/live_stream_test.exs
new file mode 100644
index 0000000000..f038a9aed7
--- /dev/null
+++ b/test/phoenix_live_view/live_stream_test.exs
@@ -0,0 +1,31 @@
+defmodule Phoenix.LiveView.LiveStreamTest do
+ use ExUnit.Case, async: true
+
+ alias Phoenix.LiveView.LiveStream
+
+ test "new raises on invalid options" do
+ msg = ~r/stream :dom_id must return a function which accepts each item, got: false/
+
+ assert_raise ArgumentError, msg, fn ->
+ LiveStream.new(:numbers, [1, 2, 3], dom_id: false)
+ end
+ end
+
+ test "default dom_id" do
+ stream = LiveStream.new(:users, [%{id: 1}, %{id: 2}], [])
+ assert stream.inserts == [{"users-1", -1, %{id: 1}}, {"users-2", -1, %{id: 2}}]
+ end
+
+ test "custom dom_id" do
+ stream = LiveStream.new(:users, [%{name: "u1"}, %{name: "u2"}], dom_id: &"u-#{&1.name}")
+ assert stream.inserts == [{"u-u1", -1, %{name: "u1"}}, {"u-u2", -1, %{name: "u2"}}]
+ end
+
+ test "default dom_id without struct or map with :id" do
+ msg = ~r/expected stream :users to be a struct or map with :id key/
+
+ assert_raise ArgumentError, msg, fn ->
+ LiveStream.new(:users, [%{user_id: 1}, %{user_id: 2}], [])
+ end
+ end
+end
diff --git a/test/phoenix_live_view/router_test.exs b/test/phoenix_live_view/router_test.exs
index 07804ce51d..5fc4170a44 100644
--- a/test/phoenix_live_view/router_test.exs
+++ b/test/phoenix_live_view/router_test.exs
@@ -1,5 +1,5 @@
defmodule Phoenix.LiveView.RouterTest do
- use ExUnit.Case, async: true
+ use ExUnit.Case, async: false
import Phoenix.ConnTest
import Phoenix.LiveViewTest
@@ -30,7 +30,7 @@ defmodule Phoenix.LiveView.RouterTest do
test "routing with empty session", %{conn: conn} do
conn = get(conn, "/router/thermo_defaults/123")
- assert conn.resp_body =~ ~s(session: %{})
+ assert conn.resp_body =~ ~s()
end
@tag plug_session: %{user_id: "chris"}
@@ -126,8 +126,7 @@ defmodule Phoenix.LiveView.RouterTest do
function:
Function.capture(Phoenix.LiveViewTest.HaltConnectedMount, :on_mount, 4)
}
- ],
- session: %{}
+ ]
}
assert conn |> get(path) |> html_response(200) =~
@@ -137,7 +136,7 @@ defmodule Phoenix.LiveView.RouterTest do
end
test "with on_mount {Module, arg}", %{conn: conn} do
- path = "/lifecycle/mount-args"
+ path = "/lifecycle/mount-mod-arg"
assert {:internal, route} =
Route.live_link_info(@endpoint, Phoenix.LiveViewTest.Router, path)
@@ -149,14 +148,61 @@ defmodule Phoenix.LiveView.RouterTest do
stage: :mount,
function: Function.capture(Phoenix.LiveViewTest.MountArgs, :on_mount, 4)
}
- ],
- session: %{}
+ ]
}
assert {:error, {:live_redirect, %{to: "/lifecycle?called=true&inlined=true"}}} =
live(conn, path)
end
+ test "with on_mount [Module, ...]", %{conn: conn} do
+ path = "/lifecycle/mount-mods"
+
+ assert {:internal, route} =
+ Route.live_link_info(@endpoint, Phoenix.LiveViewTest.Router, path)
+
+ assert route.live_session.extra == %{
+ on_mount: [
+ %{
+ id: {Phoenix.LiveViewTest.OnMount, :default},
+ stage: :mount,
+ function: Function.capture(Phoenix.LiveViewTest.OnMount, :on_mount, 4)
+ },
+ %{
+ id: {Phoenix.LiveViewTest.OtherOnMount, :default},
+ stage: :mount,
+ function: Function.capture(Phoenix.LiveViewTest.OtherOnMount, :on_mount, 4)
+ }
+ ]
+ }
+
+ assert {:ok, _, _} = live(conn, path)
+ end
+
+ test "with on_mount [{Module, arg}, ...]", %{conn: conn} do
+ path = "/lifecycle/mount-mods-args"
+
+ assert {:internal, route} =
+ Route.live_link_info(@endpoint, Phoenix.LiveViewTest.Router, path)
+
+ assert route.live_session.extra == %{
+ on_mount: [
+ %{
+ id: {Phoenix.LiveViewTest.OnMount, :other},
+ stage: :mount,
+ function: Function.capture(Phoenix.LiveViewTest.OnMount, :on_mount, 4)
+ },
+ %{
+ id: {Phoenix.LiveViewTest.OtherOnMount, :other},
+ stage: :mount,
+ function: Function.capture(Phoenix.LiveViewTest.OtherOnMount, :on_mount, 4)
+ }
+ ]
+ }
+
+ assert {:ok, _, _} = live(conn, path)
+ end
+
test "raises when nesting" do
assert_raise(RuntimeError, ~r"attempting to define live_session :invalid inside :ok", fn ->
Code.eval_quoted(
@@ -194,5 +240,40 @@ defmodule Phoenix.LiveView.RouterTest do
)
end)
end
+
+ test "with layout override", %{conn: conn} do
+ path = "/dashboard-live-session-layout"
+
+ assert {:internal, route} =
+ Route.live_link_info(@endpoint, Phoenix.LiveViewTest.Router, path)
+
+ assert route.live_session.extra == %{
+ layout: {Phoenix.LiveViewTest.LayoutView, :live_override}
+ }
+
+ {:ok, view, html} = live(conn, path)
+
+ assert html =~
+ ~r|]+>LIVEOVERRIDESTART\-123\-The value is: 123\-LIVEOVERRIDEEND|
+
+ assert render(view) =~
+ ~r|
]+>LIVEOVERRIDESTART\-123\-The value is: 123\-LIVEOVERRIDEEND|
+ end
+
+ test "with layout override on disconnected render", %{conn: conn} do
+ path = "/dashboard-live-session-layout"
+
+ assert {:internal, route} =
+ Route.live_link_info(@endpoint, Phoenix.LiveViewTest.Router, path)
+
+ assert route.live_session.extra == %{
+ layout: {Phoenix.LiveViewTest.LayoutView, :live_override}
+ }
+
+ conn = get(conn, path)
+
+ assert html_response(conn, 200) =~
+ ~r|
]+>LIVEOVERRIDESTART\-123\-The value is: 123\-LIVEOVERRIDEEND|
+ end
end
end
diff --git a/test/phoenix_live_view/socket_test.exs b/test/phoenix_live_view/socket_test.exs
new file mode 100644
index 0000000000..b26ab301e6
--- /dev/null
+++ b/test/phoenix_live_view/socket_test.exs
@@ -0,0 +1,37 @@
+defmodule Phoenix.LiveView.SocketTest do
+ use ExUnit.Case, async: true
+
+ test "use with no override" do
+ defmodule MySocket do
+ use Phoenix.LiveView.Socket
+ end
+
+ info = %{peer_data: %{}}
+ assert {:ok, %Phoenix.Socket{} = socket} = MySocket.connect(%{}, %Phoenix.Socket{}, info)
+ assert socket.private.connect_info == info
+ assert MySocket.id(socket) == nil
+ end
+
+ test "use with overrides" do
+ defmodule MyOverrides do
+ use Phoenix.LiveView.Socket
+
+ def connect(%{"error" => "true"}, _socket, _info) do
+ :error
+ end
+
+ def connect(_params, socket, info) do
+ {:ok, assign(socket, :info, info)}
+ end
+
+ def id(_socket), do: "my-id"
+ end
+
+ info = %{peer_data: %{}}
+ assert :error = MyOverrides.connect(%{"error" => "true"}, %Phoenix.Socket{}, info)
+ assert {:ok, %Phoenix.Socket{} = socket} = MyOverrides.connect(%{}, %Phoenix.Socket{}, info)
+ assert socket.private.connect_info == info
+ assert socket.assigns.info == info
+ assert MyOverrides.id(socket) == "my-id"
+ end
+end
diff --git a/test/phoenix_live_view/test/dom_test.exs b/test/phoenix_live_view/test/dom_test.exs
index b234de4ed2..a9b27f0c66 100644
--- a/test/phoenix_live_view/test/dom_test.exs
+++ b/test/phoenix_live_view/test/dom_test.exs
@@ -5,7 +5,7 @@ defmodule Phoenix.LiveViewTest.DOMTest do
describe "find_live_views" do
# >= 4432 characters
- @too_big_session Enum.map(1..4432, fn _ -> "t" end) |> Enum.join()
+ @too_big_session Enum.map_join(1..4432, fn _ -> "t" end)
test "finds views given html" do
assert DOM.find_live_views(
@@ -132,7 +132,7 @@ defmodule Phoenix.LiveViewTest.DOMTest do
"""
- {new_html, _removed_cids} = DOM.patch_id("phx-458", DOM.parse(html), DOM.parse(inner_html))
+ {new_html, _removed_cids} = DOM.patch_id("phx-458", DOM.parse(html), DOM.parse(inner_html), [])
new_html = DOM.to_html(new_html)
@@ -161,7 +161,7 @@ defmodule Phoenix.LiveViewTest.DOMTest do
"""
- {new_html, _removed_cids} = DOM.patch_id("phx-458", DOM.parse(html), DOM.parse(inner_html))
+ {new_html, _removed_cids} = DOM.patch_id("phx-458", DOM.parse(html), DOM.parse(inner_html), [])
new_html = DOM.to_html(new_html)
@@ -189,7 +189,7 @@ defmodule Phoenix.LiveViewTest.DOMTest do
"""
- {new_html, _removed_cids} = DOM.patch_id("phx-458", DOM.parse(html), DOM.parse(inner_html))
+ {new_html, _removed_cids} = DOM.patch_id("phx-458", DOM.parse(html), DOM.parse(inner_html), [])
new_html = DOM.to_html(new_html)
@@ -216,7 +216,7 @@ defmodule Phoenix.LiveViewTest.DOMTest do
"""
- {new_html, _removed_cids} = DOM.patch_id("phx-458", DOM.parse(html), DOM.parse(inner_html))
+ {new_html, _removed_cids} = DOM.patch_id("phx-458", DOM.parse(html), DOM.parse(inner_html), [])
new_html = DOM.to_html(new_html)
@@ -229,10 +229,10 @@ defmodule Phoenix.LiveViewTest.DOMTest do
describe "merge_diff" do
test "merges unless static" do
assert DOM.merge_diff(%{0 => "bar", s: "foo"}, %{0 => "baz"}) ==
- %{0 => "baz", s: "foo"}
+ %{0 => "baz", s: "foo", streams: []}
assert DOM.merge_diff(%{s: "foo", d: []}, %{s: "bar"}) ==
- %{s: "bar"}
+ %{s: "bar", streams: []}
end
end
end
diff --git a/test/phoenix_live_view/tokenizer_test.exs b/test/phoenix_live_view/tokenizer_test.exs
new file mode 100644
index 0000000000..ffa062752f
--- /dev/null
+++ b/test/phoenix_live_view/tokenizer_test.exs
@@ -0,0 +1,833 @@
+defmodule Phoenix.LiveView.TokenizerTest do
+ use ExUnit.Case, async: true
+ alias Phoenix.LiveView.Tokenizer.ParseError
+ alias Phoenix.LiveView.Tokenizer
+
+ defp tokenizer_state(text), do: Tokenizer.init(0, "nofile", text, Phoenix.LiveView.HTMLEngine)
+
+ defp tokenize(text) do
+ Tokenizer.tokenize(text, [], [], :text, tokenizer_state(text))
+ |> elem(0)
+ |> Enum.reverse()
+ end
+
+ describe "text" do
+ test "represented as {:text, value}" do
+ assert tokenize("Hello") == [{:text, "Hello", %{line_end: 1, column_end: 6}}]
+ end
+
+ test "with multiple lines" do
+ tokens =
+ tokenize("""
+ first
+ second
+ third
+ """)
+
+ assert tokens == [{:text, "first\nsecond\nthird\n", %{line_end: 4, column_end: 1}}]
+ end
+
+ test "keep line breaks unchanged" do
+ assert tokenize("first\nsecond\r\nthird") == [
+ {:text, "first\nsecond\r\nthird", %{line_end: 3, column_end: 6}}
+ ]
+ end
+ end
+
+ describe "doctype" do
+ test "generated as text" do
+ assert tokenize("") == [
+ {:text, "", %{line_end: 1, column_end: 16}}
+ ]
+ end
+
+ test "multiple lines" do
+ assert tokenize("
") == [
+ {:text, " ", %{line_end: 3, column_end: 4}},
+ {:tag, "br", [],
+ %{column: 4, line: 3, self_close: true, tag_name: "br", inner_location: {3, 10}}}
+ ]
+ end
+ end
+
+ describe "comment" do
+ test "generated as text" do
+ assert tokenize("BeginEnd") == [
+ {:text, "BeginEnd",
+ %{line_end: 1, column_end: 25, context: [:comment_start, :comment_end]}}
+ ]
+ end
+
+ test "multiple lines and wrapped by tags" do
+ code = """
+
+
+
\
+ """
+
+ assert [
+ {:tag, "p", [], %{line: 1, column: 1}},
+ {:text, "\n\n", %{line_end: 5, column_end: 1}},
+ {:close, :tag, "p", %{line: 5, column: 1}},
+ {:tag, "br", [], %{line: 5, column: 5}}
+ ] = tokenize(code)
+ end
+
+ test "adds comment_start and comment_end" do
+ first_part = """
+
+
+
+
+ """
+
+ {tokens, :text} =
+ Tokenizer.tokenize(second_part, [], first_tokens, cont, tokenizer_state(second_part))
+
+ assert Enum.reverse(tokens) == [
+ {:tag, "p", [], %{column: 1, line: 1, inner_location: {1, 4}, tag_name: "p"}},
+ {:text, "\n\n", %{column_end: 1, context: [:comment_end], line_end: 3}},
+ {:close, :tag, "p", %{column: 1, line: 3, inner_location: {3, 1}, tag_name: "p"}},
+ {:text, "\n", %{column_end: 1, line_end: 4}},
+ {:tag, "div", [], %{column: 1, line: 4, inner_location: {4, 6}, tag_name: "div"}},
+ {:text, "\n ", %{column_end: 3, line_end: 5}},
+ {:tag, "p", [], %{column: 3, line: 5, inner_location: {5, 6}, tag_name: "p"}},
+ {:text, "Hello", %{column_end: 11, line_end: 5}},
+ {:close, :tag, "p",
+ %{column: 11, line: 5, inner_location: {5, 11}, tag_name: "p"}},
+ {:text, "\n", %{column_end: 1, line_end: 6}},
+ {:close, :tag, "div",
+ %{column: 1, line: 6, inner_location: {6, 1}, tag_name: "div"}},
+ {:text, "\n", %{column_end: 1, line_end: 7}}
+ ]
+ end
+
+ test "two comments in a row" do
+ first_part = """
+
+
+
+
+
Hi
+
+ """
+
+ {tokens, :text} =
+ Tokenizer.tokenize(third_part, [], second_tokens, cont, tokenizer_state(third_part))
+
+ assert Enum.reverse(tokens) == [
+ {:tag, "p", [], %{column: 1, line: 1, inner_location: {1, 4}, tag_name: "p"}},
+ {:text, "\n\n\n", %{column_end: 1, context: [:comment_end], line_end: 2}},
+ {:tag, "div", [], %{column: 1, line: 2, inner_location: {2, 6}, tag_name: "div"}},
+ {:text, "\n ", %{column_end: 3, line_end: 3}},
+ {:tag, "p", [], %{column: 3, line: 3, inner_location: {3, 6}, tag_name: "p"}},
+ {:text, "Hi", %{column_end: 8, line_end: 3}},
+ {:close, :tag, "p", %{column: 8, line: 3, inner_location: {3, 8}, tag_name: "p"}},
+ {:text, "\n", %{column_end: 1, line_end: 4}},
+ {:close, :tag, "p", %{column: 1, line: 4, inner_location: {4, 1}, tag_name: "p"}},
+ {:text, "\n", %{column_end: 1, line_end: 5}}
+ ]
+ end
+ end
+
+ describe "opening tag" do
+ test "represented as {:tag, name, attrs, meta}" do
+ tokens = tokenize("
")
+ assert [{:tag, "div", [], %{}}] = tokens
+ end
+
+ test "with space after name" do
+ tokens = tokenize("
")
+ assert [{:tag, "div", [], %{}}] = tokens
+ end
+
+ test "with line break after name" do
+ tokens = tokenize("
")
+ assert [{:tag, "div", [], %{}}] = tokens
+ end
+
+ test "self close" do
+ tokens = tokenize("
")
+ assert [{:tag, "div", [], %{self_close: true}}] = tokens
+ end
+
+ test "compute line and column" do
+ tokens =
+ tokenize("""
+
+
+
+
\
+ """)
+
+ assert [
+ {:tag, "div", [], %{line: 1, column: 1}},
+ {:text, _, %{line_end: 2, column_end: 3}},
+ {:tag, "span", [], %{line: 2, column: 3}},
+ {:text, _, %{line_end: 4, column_end: 1}},
+ {:tag, "p", [], %{column: 1, line: 4, self_close: true}},
+ {:tag, "br", [], %{column: 5, line: 4}}
+ ] = tokens
+ end
+
+ test "raise on missing/incomplete tag name" do
+ message = """
+ nofile:2:4: expected tag name after <. If you meant to use < as part of a text, use < instead
+ |
+ 1 |
+ 2 | <>
+ | ^\
+ """
+
+ assert_raise ParseError, message, fn ->
+ tokenize("""
+
+ <>\
+ """)
+ end
+
+ message = """
+ nofile:1:2: expected tag name after <. If you meant to use < as part of a text, use < instead
+ |
+ 1 | <
+ | ^\
+ """
+
+ assert_raise ParseError, message, fn ->
+ tokenize("<")
+ end
+
+ assert_raise ParseError, ~r"nofile:1:5: expected closing `>` or `/>`", fn ->
+ tokenize("
))
+
+ assert [
+ {"class", {:string, "panel", %{}}, %{column: 6, line: 1}},
+ {"style", {:expr, "@style", %{}}, %{column: 20, line: 1}},
+ {"hidden", nil, %{column: 35, line: 1}}
+ ] = attrs
+ end
+
+ test "accepts space between the name and `=`" do
+ attrs = tokenize_attrs(~S())
+
+ assert [{"class", {:string, "panel", %{}}, %{}}] = attrs
+ end
+
+ test "accepts line breaks between the name and `=`" do
+ attrs = tokenize_attrs("
")
+
+ assert [{"class", {:string, "panel", %{}}, %{}}] = attrs
+
+ attrs = tokenize_attrs("
")
+
+ assert [{"class", {:string, "panel", %{}}, %{}}] = attrs
+ end
+
+ test "accepts space between `=` and the value" do
+ attrs = tokenize_attrs(~S(
))
+
+ assert [{"class", {:string, "panel", %{}}, %{}}] = attrs
+ end
+
+ test "accepts line breaks between `=` and the value" do
+ attrs = tokenize_attrs("
")
+
+ assert [{"class", {:string, "panel", %{}}, %{}}] = attrs
+
+ attrs = tokenize_attrs("
")
+
+ assert [{"class", {:string, "panel", %{}}, %{}}] = attrs
+ end
+
+ test "raise on missing value" do
+ message = """
+ nofile:2:9: invalid attribute value after `=`. Expected either a value between quotes (such as \"value\" or 'value') or an Elixir expression between curly brackets (such as `{expr}`)
+ |
+ 1 |
+ | ^\
+ """
+
+ assert_raise ParseError, message, fn ->
+ tokenize("""
+
\
+ """)
+ end
+
+ message = """
+ nofile:1:13: invalid attribute value after `=`. Expected either a value between quotes (such as \"value\" or 'value') or an Elixir expression between curly brackets (such as `{expr}`)
+ |
+ 1 |
+ | ^\
+ """
+
+ assert_raise ParseError, message, fn ->
+ tokenize(~S(
))
+ end
+
+ message = """
+ nofile:1:12: invalid attribute value after `=`. Expected either a value between quotes (such as \"value\" or 'value') or an Elixir expression between curly brackets (such as `{expr}`)
+ |
+ 1 |
+ tokenize("
+ 2 |
+ | ^\
+ """
+
+ assert_raise ParseError, message, fn ->
+ tokenize("""
+
+
\
+ """)
+ end
+
+ message = """
+ nofile:1:6: expected attribute name
+ |
+ 1 |
+ | ^\
+ """
+
+ assert_raise ParseError, message, fn ->
+ tokenize(~S(
))
+ end
+
+ message = """
+ nofile:1:6: expected attribute name
+ |
+ 1 |
+ | ^\
+ """
+
+ assert_raise ParseError, message, fn ->
+ tokenize(~S(
))
+ end
+ end
+
+ test "raise on attribute names with quotes" do
+ message = """
+ nofile:1:5: invalid character in attribute name: '
+ |
+ 1 |
+ | ^\
+ """
+
+ assert_raise ParseError, message, fn ->
+ tokenize(~S(
))
+ end
+
+ message = """
+ nofile:1:5: invalid character in attribute name: \"
+ |
+ 1 |
+ | ^\
+ """
+
+ assert_raise ParseError, message, fn ->
+ tokenize(~S(
))
+ end
+
+ message = """
+ nofile:1:10: invalid character in attribute name: '
+ |
+ 1 |
+ | ^\
+ """
+
+ assert_raise ParseError, message, fn ->
+ tokenize(~S(
))
+ end
+
+ message = """
+ nofile:1:20: invalid character in attribute name: \"
+ |
+ 1 |
+ | ^\
+ """
+
+ assert_raise ParseError, message, fn ->
+ tokenize(~S(
))
+ end
+ end
+ end
+
+ describe "boolean attributes" do
+ test "represented as {name, nil, meta}" do
+ attrs = tokenize_attrs("
")
+
+ assert [{"hidden", nil, %{}}] = attrs
+ end
+
+ test "multiple attributes" do
+ attrs = tokenize_attrs("
")
+
+ assert [{"hidden", nil, %{}}, {"selected", nil, %{}}] = attrs
+ end
+
+ test "with space after" do
+ attrs = tokenize_attrs("
")
+
+ assert [{"hidden", nil, %{}}] = attrs
+ end
+
+ test "in self close tag" do
+ attrs = tokenize_attrs("
")
+
+ assert [{"hidden", nil, %{}}] = attrs
+ end
+
+ test "in self close tag with space after" do
+ attrs = tokenize_attrs("
")
+
+ assert [{"hidden", nil, %{}}] = attrs
+ end
+ end
+
+ describe "attributes as double quoted string" do
+ test "value is represented as {:string, value, meta}}" do
+ attrs = tokenize_attrs(~S(
))
+
+ assert [{"class", {:string, "panel", %{delimiter: ?"}}, %{}}] = attrs
+ end
+
+ test "multiple attributes" do
+ attrs = tokenize_attrs(~S(
))
+
+ assert [
+ {"class", {:string, "panel", %{delimiter: ?"}}, %{}},
+ {"style", {:string, "margin: 0px;", %{delimiter: ?"}}, %{}}
+ ] = attrs
+ end
+
+ test "value containing single quotes" do
+ attrs = tokenize_attrs(~S(
))
+
+ assert [{"title", {:string, "i'd love to!", %{delimiter: ?"}}, %{}}] = attrs
+ end
+
+ test "value containing line breaks" do
+ tokens =
+ tokenize("""
+
\
+ """)
+
+ assert [
+ {:tag, "div", [{"title", {:string, "first\n second\nthird", _meta}, %{}}], %{}},
+ {:tag, "span", [], %{line: 3, column: 8}}
+ ] = tokens
+ end
+
+ test "raise on incomplete attribute value (EOF)" do
+ assert_raise ParseError, ~r"nofile:2:15: expected closing `\"` for attribute value", fn ->
+ tokenize("""
+ ))
+
+ assert [{"class", {:string, "panel", %{delimiter: ?'}}, %{}}] = attrs
+ end
+
+ test "multiple attributes" do
+ attrs = tokenize_attrs(~S(
))
+
+ assert [
+ {"class", {:string, "panel", %{delimiter: ?'}}, %{}},
+ {"style", {:string, "margin: 0px;", %{delimiter: ?'}}, %{}}
+ ] = attrs
+ end
+
+ test "value containing double quotes" do
+ attrs = tokenize_attrs(~S(
))
+
+ assert [{"title", {:string, ~S(Say "hi!"), %{delimiter: ?'}}, %{}}] = attrs
+ end
+
+ test "value containing line breaks" do
+ tokens =
+ tokenize("""
+
\
+ """)
+
+ assert [
+ {:tag, "div", [{"title", {:string, "first\n second\nthird", _meta}, %{}}], %{}},
+ {:tag, "span", [], %{line: 3, column: 8}}
+ ] = tokens
+ end
+
+ test "raise on incomplete attribute value (EOF)" do
+ assert_raise ParseError, ~r"nofile:2:15: expected closing `\'` for attribute value", fn ->
+ tokenize("""
+ LiveView.assign(socket, uploads_count: 2) end})
+ GenServer.call(lv.pid, {:setup, fn socket -> Component.assign(socket, uploads_count: 2) end})
GenServer.call(
lv.pid,
diff --git a/test/phoenix_live_view/upload/config_test.exs b/test/phoenix_live_view/upload/config_test.exs
index f2f05ffa7d..7cf2adefc8 100644
--- a/test/phoenix_live_view/upload/config_test.exs
+++ b/test/phoenix_live_view/upload/config_test.exs
@@ -183,6 +183,7 @@ defmodule Phoenix.LiveView.UploadConfigTest do
%{
"name" => name,
+ "relative_path" => relative_path,
"size" => size,
"ref" => ref,
"type" => type
@@ -194,6 +195,7 @@ defmodule Phoenix.LiveView.UploadConfigTest do
assert [
%Phoenix.LiveView.UploadEntry{
client_name: ^name,
+ client_relative_path: ^relative_path,
client_size: ^size,
client_type: ^type,
ref: ^ref
@@ -210,6 +212,7 @@ defmodule Phoenix.LiveView.UploadConfigTest do
%{
"name" => name,
+ "relative_path" => relative_path,
"size" => size,
"ref" => ref,
"type" => type
@@ -221,6 +224,7 @@ defmodule Phoenix.LiveView.UploadConfigTest do
assert [
%Phoenix.LiveView.UploadEntry{
client_name: ^name,
+ client_relative_path: ^relative_path,
client_size: ^size,
client_type: ^type,
ref: ^ref
@@ -383,10 +387,17 @@ defmodule Phoenix.LiveView.UploadConfigTest do
end
end
+ test "supports binary upload name" do
+ assert LiveView.allow_upload(build_socket(), "avatar", accept: ~w(image/png .jpeg))
+ end
+
defp build_client_entry(name, attrs \\ %{}) do
+ name = "#{name}_#{System.unique_integer([:positive, :monotonic])}"
+
attrs
|> Enum.into(%{
- "name" => "#{name}_#{System.unique_integer([:positive, :monotonic])}",
+ "name" => name,
+ "relative_path" => "./#{name}",
"last_modified" => DateTime.utc_now() |> DateTime.to_unix(),
"size" => 1024,
"type" => "application/octet-stream"
diff --git a/test/phoenix_live_view/upload/external_test.exs b/test/phoenix_live_view/upload/external_test.exs
index d27af5d936..6ac780be78 100644
--- a/test/phoenix_live_view/upload/external_test.exs
+++ b/test/phoenix_live_view/upload/external_test.exs
@@ -1,5 +1,5 @@
defmodule Phoenix.LiveView.UploadExternalTest do
- use ExUnit.Case, async: true
+ use ExUnit.Case, async: false
@endpoint Phoenix.LiveViewTest.Endpoint
@@ -52,7 +52,7 @@ defmodule Phoenix.LiveView.UploadExternalTest do
def preflight(%LiveView.UploadEntry{} = entry, socket) do
new_socket =
- Phoenix.LiveView.update(socket, :preflights, fn preflights ->
+ Phoenix.Component.update(socket, :preflights, fn preflights ->
[entry.client_name | preflights]
end)
@@ -61,7 +61,7 @@ defmodule Phoenix.LiveView.UploadExternalTest do
def consume(%LiveView.UploadEntry{} = entry, socket) do
if entry.done? do
- Phoenix.LiveView.consume_uploaded_entry(socket, entry, fn _ -> :ok end)
+ Phoenix.LiveView.consume_uploaded_entry(socket, entry, fn _ -> {:ok, :ok} end)
end
{:noreply, socket}
@@ -128,7 +128,7 @@ defmodule Phoenix.LiveView.UploadExternalTest do
run(lv, fn socket ->
Phoenix.LiveView.consume_uploaded_entries(socket, :avatar, fn meta, entry ->
- send(parent, {:consume, meta, entry.client_name})
+ {:ok, send(parent, {:consume, meta, entry.client_name})}
end)
{:reply, :ok, socket}
end)
@@ -149,7 +149,7 @@ defmodule Phoenix.LiveView.UploadExternalTest do
run(lv, fn socket ->
{[entry], []} = Phoenix.LiveView.uploaded_entries(socket, :avatar)
Phoenix.LiveView.consume_uploaded_entry(socket, entry, fn meta ->
- send(parent, {:individual_consume, meta, entry.client_name})
+ {:ok, send(parent, {:individual_consume, meta, entry.client_name})}
end)
{:reply, :ok, socket}
end)
diff --git a/test/phoenix_live_view_test.exs b/test/phoenix_live_view_test.exs
index ecf92a026f..ee676a6fdd 100644
--- a/test/phoenix_live_view_test.exs
+++ b/test/phoenix_live_view_test.exs
@@ -23,9 +23,6 @@ defmodule Phoenix.LiveViewUnitTest do
URI.parse("https://www.example.com")
)
- @assigns_changes %{key: "value", map: %{foo: :bar}, __changed__: %{}}
- @assigns_nil_changes %{key: "value", map: %{foo: :bar}, __changed__: nil}
-
describe "flash" do
test "get and put" do
assert put_flash(@socket, :hello, "world").assigns.flash == %{"hello" => "world"}
@@ -74,7 +71,7 @@ defmodule Phoenix.LiveViewUnitTest do
socket = Utils.post_mount_prune(%{@socket | transport_pid: self()})
assert_raise RuntimeError, ~r/attempted to read connect_info/, fn ->
- get_connect_info(socket)
+ get_connect_info(socket, :uri)
end
end
@@ -82,18 +79,40 @@ defmodule Phoenix.LiveViewUnitTest do
socket = Utils.post_mount_prune(%{@socket | transport_pid: nil})
assert_raise RuntimeError, ~r/attempted to read connect_info/, fn ->
- get_connect_info(socket)
+ get_connect_info(socket, :uri)
end
end
- test "returns nil when disconnected" do
- socket = %{@socket | transport_pid: nil}
- assert get_connect_info(socket) == nil
+ test "returns params when connected" do
+ socket = %{@socket | transport_pid: self(), private: %{connect_info: %{user_agent: "foo"}}}
+ assert get_connect_info(socket, :user_agent) == "foo"
end
- test "returns params connected and mounting" do
- socket = %{@socket | transport_pid: self()}
- assert get_connect_info(socket) == %{}
+ test "returns params when disconnected" do
+ conn =
+ Plug.Test.conn(:get, "/")
+ |> Plug.Conn.put_req_header("user-agent", "custom-client")
+ |> Plug.Conn.put_req_header("x-foo", "bar")
+ |> Plug.Conn.put_req_header("x-bar", "baz")
+ |> Plug.Conn.put_req_header("tracestate", "one")
+ |> Plug.Conn.put_req_header("traceparent", "two")
+
+ socket = %{@socket | private: %{connect_info: conn}}
+
+ assert get_connect_info(socket, :user_agent) ==
+ "custom-client"
+
+ assert get_connect_info(socket, :x_headers) ==
+ [{"x-foo", "bar"}, {"x-bar", "baz"}]
+
+ assert get_connect_info(socket, :trace_context_headers) ==
+ [{"tracestate", "one"}, {"traceparent", "two"}]
+
+ assert get_connect_info(socket, :peer_data) ==
+ %{address: {127, 0, 0, 1}, port: 111_317, ssl_cert: nil}
+
+ assert get_connect_info(socket, :uri) ==
+ %URI{host: "www.example.com", path: "/", port: 80, query: "", scheme: "http"}
end
end
@@ -180,146 +199,6 @@ defmodule Phoenix.LiveViewUnitTest do
end
end
- describe "assign with socket" do
- test "tracks changes" do
- socket = assign(@socket, existing: "foo")
- assert changed?(socket, :existing)
-
- socket = Utils.clear_changed(socket)
- socket = assign(socket, existing: "foo")
- refute changed?(socket, :existing)
- end
-
- test "keeps whole maps in changes" do
- socket = assign(@socket, existing: %{foo: :bar})
- socket = Utils.clear_changed(socket)
-
- socket = assign(socket, existing: %{foo: :baz})
- assert socket.assigns.existing == %{foo: :baz}
- assert socket.assigns.__changed__.existing == %{foo: :bar}
-
- socket = assign(socket, existing: %{foo: :bat})
- assert socket.assigns.existing == %{foo: :bat}
- assert socket.assigns.__changed__.existing == %{foo: :bar}
-
- socket = assign(socket, %{existing: %{foo: :bam}})
- assert socket.assigns.existing == %{foo: :bam}
- assert socket.assigns.__changed__.existing == %{foo: :bar}
- end
- end
-
- describe "assign with assigns" do
- test "tracks changes" do
- assigns = assign(@assigns_changes, key: "value")
- assert assigns.key == "value"
- refute changed?(assigns, :key)
-
- assigns = assign(@assigns_changes, key: "changed")
- assert assigns.key == "changed"
- assert changed?(assigns, :key)
-
- assigns = assign(@assigns_nil_changes, key: "changed")
- assert assigns.key == "changed"
- assert assigns.__changed__ == nil
- assert changed?(assigns, :key)
- end
-
- test "keeps whole maps in changes" do
- assigns = assign(@assigns_changes, map: %{foo: :baz})
- assert assigns.map == %{foo: :baz}
- assert assigns.__changed__[:map] == %{foo: :bar}
-
- assigns = assign(@assigns_nil_changes, map: %{foo: :baz})
- assert assigns.map == %{foo: :baz}
- assert assigns.__changed__ == nil
- end
- end
-
- describe "assign_new with socket" do
- test "uses socket assigns if no parent assigns are present" do
- socket =
- @socket
- |> assign(existing: "existing")
- |> assign_new(:existing, fn -> "new-existing" end)
- |> assign_new(:notexisting, fn -> "new-notexisting" end)
-
- assert socket.assigns == %{
- existing: "existing",
- notexisting: "new-notexisting",
- live_action: nil,
- flash: %{},
- __changed__: %{existing: true, notexisting: true}
- }
- end
-
- test "uses parent assigns when present and falls back to socket assigns" do
- socket =
- put_in(@socket.private[:assign_new], {%{existing: "existing-parent"}, []})
- |> assign(existing2: "existing2")
- |> assign_new(:existing, fn -> "new-existing" end)
- |> assign_new(:existing2, fn -> "new-existing2" end)
- |> assign_new(:notexisting, fn -> "new-notexisting" end)
-
- assert socket.assigns == %{
- existing: "existing-parent",
- existing2: "existing2",
- notexisting: "new-notexisting",
- live_action: nil,
- flash: %{},
- __changed__: %{existing: true, notexisting: true, existing2: true}
- }
- end
- end
-
- describe "assign_new with assigns" do
- test "tracks changes" do
- assigns = assign_new(@assigns_changes, :key, fn -> raise "wont be invoked" end)
- assert assigns.key == "value"
- refute changed?(assigns, :key)
- refute assigns.__changed__[:key]
-
- assigns = assign_new(@assigns_changes, :another, fn -> "changed" end)
- assert assigns.another == "changed"
- assert changed?(assigns, :another)
-
- assigns = assign_new(@assigns_nil_changes, :another, fn -> "changed" end)
- assert assigns.another == "changed"
- assert changed?(assigns, :another)
- assert assigns.__changed__ == nil
- end
- end
-
- describe "update with socket" do
- test "tracks changes" do
- socket = @socket |> assign(key: "value") |> Utils.clear_changed()
-
- socket = update(socket, :key, fn "value" -> "value" end)
- assert socket.assigns.key == "value"
- refute changed?(socket, :key)
-
- socket = update(socket, :key, fn "value" -> "changed" end)
- assert socket.assigns.key == "changed"
- assert changed?(socket, :key)
- end
- end
-
- describe "update with assigns" do
- test "tracks changes" do
- assigns = update(@assigns_changes, :key, fn "value" -> "value" end)
- assert assigns.key == "value"
- refute changed?(assigns, :key)
-
- assigns = update(@assigns_changes, :key, fn "value" -> "changed" end)
- assert assigns.key == "changed"
- assert changed?(assigns, :key)
-
- assigns = update(@assigns_nil_changes, :key, fn "value" -> "changed" end)
- assert assigns.key == "changed"
- assert changed?(assigns, :key)
- assert assigns.__changed__ == nil
- end
- end
-
describe "redirect/2" do
test "requires local path on to" do
assert_raise ArgumentError, ~r"the :to option in redirect/2 expects a path", fn ->
@@ -336,20 +215,29 @@ defmodule Phoenix.LiveViewUnitTest do
test "allows external paths" do
assert redirect(@socket, external: "http://foo.com/bar").redirected ==
{:redirect, %{external: "http://foo.com/bar"}}
+
+ assert redirect(@socket, external: {:javascript, "alert"}).redirected ==
+ {:redirect, %{external: "javascript:alert"}}
+ end
+
+ test "disallows insecure external paths" do
+ assert_raise ArgumentError, ~r/unsupported scheme given to redirect\/2/, fn ->
+ redirect(@socket, external: "javascript:alert('xss');")
+ end
end
end
- describe "push_redirect/2" do
+ describe "push_navigate/2" do
test "requires local path on to" do
- assert_raise ArgumentError, ~r"the :to option in push_redirect/2 expects a path", fn ->
- push_redirect(@socket, to: "http://foo.com")
+ assert_raise ArgumentError, ~r"the :to option in push_navigate/2 expects a path", fn ->
+ push_navigate(@socket, to: "http://foo.com")
end
- assert_raise ArgumentError, ~r"the :to option in push_redirect/2 expects a path", fn ->
- push_redirect(@socket, to: "//foo.com")
+ assert_raise ArgumentError, ~r"the :to option in push_navigate/2 expects a path", fn ->
+ push_navigate(@socket, to: "//foo.com")
end
- assert push_redirect(@socket, to: "/counter/123").redirected ==
+ assert push_navigate(@socket, to: "/counter/123").redirected ==
{:live, :redirect, %{kind: :push, to: "/counter/123"}}
end
end
@@ -364,16 +252,10 @@ defmodule Phoenix.LiveViewUnitTest do
push_patch(@socket, to: "//foo.com")
end
- assert_raise ArgumentError,
- ~r"cannot push_patch/2 to \"/counter/123\" because the given path does not point to the current root view",
- fn ->
- push_patch(put_in(@socket.private.root_view, __MODULE__), to: "/counter/123")
- end
-
socket = %{@socket | view: Phoenix.LiveViewTest.ParamCounterLive}
assert push_patch(socket, to: "/counter/123").redirected ==
- {:live, {%{"id" => "123"}, nil}, %{kind: :push, to: "/counter/123"}}
+ {:live, :patch, %{kind: :push, to: "/counter/123"}}
end
end
end
diff --git a/test/support/layout_view.ex b/test/support/layout_view.ex
index db1454ff76..ab4cd2f378 100644
--- a/test/support/layout_view.ex
+++ b/test/support/layout_view.ex
@@ -1,7 +1,8 @@
defmodule Phoenix.LiveViewTest.LayoutView do
use Phoenix.View, root: ""
+ use Phoenix.Component
+
alias Phoenix.LiveViewTest.Router.Helpers, as: Routes
- import Phoenix.LiveView.Helpers
def render("app.html", assigns) do
# Assert those assigns are always available
@@ -17,7 +18,7 @@ defmodule Phoenix.LiveViewTest.LayoutView do
"""
end
- def render("live-override.html", assigns) do
+ def render("live_override.html", assigns) do
~H"""
LIVEOVERRIDESTART-<%= @val %>-<%= @inner_content %>-LIVEOVERRIDEEND
"""
@@ -31,13 +32,13 @@ defmodule Phoenix.LiveViewTest.LayoutView do
def render("with-function-component.html", assigns) do
~H"""
- RENDER:<%= component(&Phoenix.LiveViewTest.FunctionComponent.render/1, value: "from component") %>
+ RENDER:
"""
end
def render("layout-with-function-component.html", assigns) do
~H"""
- LAYOUT:<%= component(&Phoenix.LiveViewTest.FunctionComponent.render/1, value: "from layout") %>
+ LAYOUT:
<%= @inner_content %>
"""
end
diff --git a/test/support/live_reload_test_helpers.ex b/test/support/live_reload_test_helpers.ex
new file mode 100644
index 0000000000..6a77df8834
--- /dev/null
+++ b/test/support/live_reload_test_helpers.ex
@@ -0,0 +1,38 @@
+defmodule Phoenix.LiveView.LiveReloadTestHelpers.Endpoint do
+ use Phoenix.Endpoint, otp_app: :phoenix_live_view
+
+ @before_compile Phoenix.LiveViewTest.EndpointOverridable
+
+ socket "/live", Phoenix.LiveView.Socket
+ socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
+
+ plug Phoenix.CodeReloader
+
+ defoverridable url: 0, script_name: 0, config: 1, config: 2, static_path: 1
+ def url(), do: "http://localhost:4000"
+ def script_name(), do: []
+ def static_path(path), do: "/static" <> path
+ def config(:live_view), do: [signing_salt: "112345678212345678312345678412"]
+ def config(:secret_key_base), do: String.duplicate("57689", 50)
+ def config(:cache_static_manifest_latest), do: Process.get(:cache_static_manifest_latest)
+ def config(:otp_app), do: :phoenix_live_view
+ def config(:pubsub_server), do: Phoenix.LiveView.PubSub
+ def config(:live_reload), do:
+ [
+ url: "ws://localhost:4000",
+ patterns: [
+ ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
+ ~r"priv/gettext/.*(po)$",
+ ~r"lib/test_auth_web/views/.*(ex)$",
+ ~r"lib/test_auth_web/templates/.*(eex)$"
+ ],
+ notify: [
+ live_view: [
+ ~r"lib/test_auth_web/components.ex$",
+ ~r"lib/test_auth_web/live/.*(ex)$"
+ ]
+ ]
+ ]
+ def config(which), do: super(which)
+ def config(which, default), do: super(which, default)
+end
diff --git a/test/support/live_views/component_and_nested_in_live.ex b/test/support/live_views/component_and_nested_in_live.ex
new file mode 100644
index 0000000000..82bce758ab
--- /dev/null
+++ b/test/support/live_views/component_and_nested_in_live.ex
@@ -0,0 +1,50 @@
+defmodule Phoenix.LiveViewTest.ComponentAndNestedInLive do
+ use Phoenix.LiveView
+
+ defmodule NestedLive do
+ use Phoenix.LiveView
+
+ def mount(_params, _session, socket) do
+ {:ok, assign(socket, :hello, "hello")}
+ end
+
+ def render(assigns) do
+ ~H"
<%= @hello %>
"
+ end
+
+ def handle_event("disable", _params, socket) do
+ send(socket.parent_pid, :disable)
+ {:noreply, socket}
+ end
+ end
+
+ defmodule NestedComponent do
+ use Phoenix.LiveComponent
+
+ def mount(socket) do
+ {:ok, assign(socket, :world, "world")}
+ end
+
+ def render(assigns) do
+ ~H"
<%= @world %>
"
+ end
+ end
+
+ def mount(_params, _session, socket) do
+ {:ok, assign(socket, :enabled, true)}
+ end
+
+ def render(assigns) do
+ ~H"""
+ <%= if @enabled do %>
+ <%= live_render @socket, NestedLive, id: :nested_live %>
+ <%= live_component NestedComponent, id: :_component %>
+ <% end %>
+ """
+ end
+
+ def handle_event("disable", _, socket) do
+ {:noreply, assign(socket, :enabled, false)}
+ end
+end
+
diff --git a/test/support/live_views/components.ex b/test/support/live_views/components.ex
index bb85340063..df0d332165 100644
--- a/test/support/live_views/components.ex
+++ b/test/support/live_views/components.ex
@@ -1,5 +1,5 @@
defmodule Phoenix.LiveViewTest.FunctionComponent do
- import Phoenix.LiveView.Helpers
+ use Phoenix.Component
def render(assigns) do
~H"""
@@ -14,6 +14,161 @@ defmodule Phoenix.LiveViewTest.FunctionComponent do
end
end
+defmodule Phoenix.LiveViewTest.FunctionComponentWithAttrs do
+ use Phoenix.Component
+
+ defmodule Struct do
+ defstruct []
+ end
+
+ def identity(var), do: var
+ def map_identity(%{} = map), do: map
+
+ attr :attr, :any
+ def fun_attr_any(assigns), do: ~H[]
+
+ attr :attr, :string
+ def fun_attr_string(assigns), do: ~H[]
+
+ attr :attr, :atom
+ def fun_attr_atom(assigns), do: ~H[]
+
+ attr :attr, :boolean
+ def fun_attr_boolean(assigns), do: ~H[]
+
+ attr :attr, :integer
+ def fun_attr_integer(assigns), do: ~H[]
+
+ attr :attr, :float
+ def fun_attr_float(assigns), do: ~H[]
+
+ attr :attr, :map
+ def fun_attr_map(assigns), do: ~H[]
+
+ attr :attr, :list
+ def fun_attr_list(assigns), do: ~H[]
+
+ attr :attr, :global
+ def fun_attr_global(assigns), do: ~H[]
+
+ attr :attr, Struct
+ def fun_attr_struct(assigns), do: ~H[]
+
+ attr :attr, :any, required: true
+ def fun_attr_required(assigns), do: ~H[]
+
+ attr :attr, :any, default: %{}
+ def fun_attr_default(assigns), do: ~H[]
+
+ attr :attr1, :any
+ attr :attr2, :any
+ def fun_multiple_attr(assigns), do: ~H[]
+
+ attr :attr, :any, doc: "attr docs"
+ def fun_with_attr_doc(assigns), do: ~H[]
+
+ attr :attr, :any, default: "foo", doc: "attr docs."
+ def fun_with_attr_doc_period(assigns), do: ~H[]
+
+ attr :attr, :any,
+ default: "foo",
+ doc: """
+ attr docs with bullets:
+
+ * foo
+ * bar
+
+ and that's it.
+ """
+
+ def fun_with_attr_doc_multiline(assigns), do: ~H[]
+
+ attr :attr1, :any
+ attr :attr2, :any, doc: false
+ def fun_with_hidden_attr(assigns), do: ~H[]
+
+ attr :attr, :any
+ @doc "fun docs"
+ def fun_with_doc(assigns), do: ~H[]
+
+ attr :attr, :any
+
+ @doc """
+ fun docs
+ [INSERT LVATTRDOCS]
+ fun docs
+ """
+ def fun_doc_injection(assigns), do: ~H[]
+
+ attr :attr, :any
+ @doc false
+ def fun_doc_false(assigns), do: ~H[]
+
+ attr :attr, :any
+ defp private_fun(assigns), do: ~H[]
+
+ slot :inner_block
+ def fun_slot(assigns), do: ~H[]
+
+ slot :inner_block, doc: "slot docs"
+ def fun_slot_doc(assigns), do: ~H[]
+
+ slot :inner_block, required: true
+ def fun_slot_required(assigns), do: ~H[]
+
+ slot :named, required: true, doc: "a named slot" do
+ attr :attr1, :any, required: true, doc: "a slot attr doc"
+ attr :attr2, :any, doc: "a slot attr doc"
+ end
+
+ def fun_slot_with_attrs(assigns), do: ~H[]
+
+ slot :named, required: true do
+ attr :attr1, :any, required: true, doc: "a slot attr doc"
+ attr :attr2, :any, doc: "a slot attr doc"
+ end
+
+ def fun_slot_no_doc_with_attrs(assigns), do: ~H[]
+
+ slot :named,
+ required: true,
+ doc: """
+ Important slot:
+
+ * for a
+ * for b
+ """ do
+ attr :attr1, :any, required: true, doc: "a slot attr doc"
+ attr :attr2, :any, doc: "a slot attr doc"
+ end
+
+ def fun_slot_doc_multiline_with_attrs(assigns), do: ~H[]
+
+ slot :named, required: true do
+ attr :attr1, :any,
+ required: true,
+ doc: """
+ attr docs with bullets:
+
+ * foo
+ * bar
+
+ and that's it.
+ """
+
+ attr :attr2, :any, doc: "a slot attr doc"
+ end
+
+ def fun_slot_doc_with_attrs_multiline(assigns), do: ~H[]
+
+ attr :attr1, :atom, values: [:foo, :bar, :baz]
+ attr :attr2, :atom, examples: [:foo, :bar, :baz]
+ attr :attr3, :list, values: [[60, 40]]
+ attr :attr4, :list, examples: [[60, 40]]
+
+ def fun_attr_values_examples(assigns), do: ~H[]
+end
+
defmodule Phoenix.LiveViewTest.StatefulComponent do
use Phoenix.LiveComponent
@@ -49,7 +204,7 @@ defmodule Phoenix.LiveViewTest.StatefulComponent do
~H"""
@id <> include_parent_id(@parent_id)}>
<%= @name %> says hi
- <%= if @dup_name, do: live_component __MODULE__, id: @dup_name, name: @dup_name %>
+ <%= if @dup_name, do: live_component(__MODULE__, id: @dup_name, name: @dup_name) %>
"""
end
@@ -71,8 +226,8 @@ defmodule Phoenix.LiveViewTest.StatefulComponent do
"dup" ->
{:noreply, assign(socket, :dup_name, socket.assigns.name <> "-dup")}
- "push_redirect" ->
- {:noreply, push_redirect(socket, to: "/components?redirect=push")}
+ "push_navigate" ->
+ {:noreply, push_navigate(socket, to: "/components?redirect=push")}
"push_patch" ->
{:noreply, push_patch(socket, to: "/components?redirect=patch")}
@@ -136,13 +291,13 @@ defmodule Phoenix.LiveViewTest.WithMultipleTargets do
def mount(_params, %{"names" => names, "from" => from} = session, socket) do
{
:ok,
- assign(socket, [
+ assign(socket,
names: names,
from: from,
disabled: [],
message: nil,
parent_selector: Map.get(session, "parent_selector", "#parent_id")
- ])
+ )
}
end
@@ -162,3 +317,23 @@ defmodule Phoenix.LiveViewTest.WithMultipleTargets do
{:noreply, assign(socket, :message, "Parent was updated")}
end
end
+
+defmodule Phoenix.LiveViewTest.WithLogOverride do
+ use Phoenix.LiveView, log: :warning
+
+ def mount(_params, _session, socket) do
+ {:ok, socket}
+ end
+
+ def render(assigns), do: ~H[]
+end
+
+defmodule Phoenix.LiveViewTest.WithLogDisabled do
+ use Phoenix.LiveView, log: false
+
+ def mount(_params, _session, socket) do
+ {:ok, socket}
+ end
+
+ def render(assigns), do: ~H[]
+end
diff --git a/test/support/live_views/connect.ex b/test/support/live_views/connect.ex
new file mode 100644
index 0000000000..6f6a08c988
--- /dev/null
+++ b/test/support/live_views/connect.ex
@@ -0,0 +1,27 @@
+defmodule Phoenix.LiveViewTest.ConnectLive do
+ use Phoenix.LiveView
+
+ def render(assigns) do
+ ~H"""
+ params: <%= inspect(@params) %>
+ uri: <%= URI.to_string(@uri) %>
+ trace: <%= inspect(@trace) %>
+ peer: <%= inspect(@peer) %>
+ x-headers: <%= inspect(@x_headers) %>
+ user-agent: <%= inspect(@user_agent) %>
+ """
+ end
+
+ def mount(_params, _session, socket) do
+ {:ok,
+ assign(
+ socket,
+ params: get_connect_params(socket),
+ uri: get_connect_info(socket, :uri),
+ trace: get_connect_info(socket, :trace_context_headers),
+ peer: get_connect_info(socket, :peer_data),
+ x_headers: get_connect_info(socket, :x_headers),
+ user_agent: get_connect_info(socket, :user_agent)
+ )}
+ end
+end
diff --git a/test/support/live_views/elements.ex b/test/support/live_views/elements.ex
index e8c935cb12..310d0b4b85 100644
--- a/test/support/live_views/elements.ex
+++ b/test/support/live_views/elements.ex
@@ -1,12 +1,15 @@
defmodule Phoenix.LiveViewTest.ElementsLive do
use Phoenix.LiveView
+ alias Phoenix.LiveView.JS
+
def render(assigns) do
~H"""
<%# lookups %>
<%= @event %>
This is a div
This
+
<%= live_component Phoenix.LiveViewTest.ElementsComponent, id: 1 %>
<%# basic render_* %>
This is a span
@@ -29,6 +32,8 @@ defmodule Phoenix.LiveViewTest.ElementsLive do
This is a span
This is a span
+
This is a JS button
+
This is a JS button with a value
This is a button
This is a span
This is a span
@@ -44,7 +49,7 @@ defmodule Phoenix.LiveViewTest.ElementsLive do
<%# hooks %>
-
+
<%# forms %>
Change
@@ -75,6 +80,20 @@ defmodule Phoenix.LiveViewTest.ElementsLive do
+ Disabled Prompt
+ None
+ One
+ Two
+
+
+ Disabled Prompt
+
+ None
+ One
+
+ Two
+
+
None
One
Two
@@ -84,12 +103,18 @@ defmodule Phoenix.LiveViewTest.ElementsLive do
One
Two
+
+ One
+ Two
+ Three
+
One
Two
Three
+
@@ -103,12 +128,15 @@ defmodule Phoenix.LiveViewTest.ElementsLive do
<%= Phoenix.HTML.Form.datetime_select :hello, :naive_select %>
<%= Phoenix.HTML.Form.datetime_select :hello, :utc_select, second: [] %>
+
+
+
@@ -136,3 +164,33 @@ defmodule Phoenix.LiveViewTest.ElementsLive do
{:noreply, assign(socket, :event, "#{event}: #{inspect(value)}")}
end
end
+
+defmodule Phoenix.LiveViewTest.ElementsComponent do
+ use Phoenix.LiveComponent
+
+ alias Phoenix.LiveView.JS
+
+ def render(assigns) do
+ ~H"""
+
+
<%= @event %>
+
+
button
+
+ """
+ end
+
+ def mount(socket) do
+ socket = assign(socket, :event, nil)
+
+ {:ok, socket}
+ end
+
+ def handle_params(params, _uri, socket) do
+ {:noreply, assign(socket, :event, "handle_params: #{inspect(params)}")}
+ end
+
+ def handle_event(event, value, socket) do
+ {:noreply, assign(socket, :event, "#{event}: #{inspect(value)}")}
+ end
+end
diff --git a/test/support/live_views/flash.ex b/test/support/live_views/flash.ex
index e9c816ca09..62042189e7 100644
--- a/test/support/live_views/flash.ex
+++ b/test/support/live_views/flash.ex
@@ -30,8 +30,8 @@ defmodule Phoenix.LiveViewTest.FlashLive do
{:noreply, socket |> put_flash(:info, info) |> redirect(to: to)}
end
- def handle_event("push_redirect", %{"to" => to, "info" => info}, socket) do
- {:noreply, socket |> put_flash(:info, info) |> push_redirect(to: to)}
+ def handle_event("push_navigate", %{"to" => to, "info" => info}, socket) do
+ {:noreply, socket |> put_flash(:info, info) |> push_navigate(to: to)}
end
def handle_event("push_patch", %{"to" => to, "info" => info}, socket) do
@@ -49,9 +49,9 @@ defmodule Phoenix.LiveViewTest.FlashComponent do
def render(assigns) do
~H"""
- Clear all
- component[<%= live_flash(@flash, :info) %>]:info
- component[<%= live_flash(@flash, :error) %>]:error
+ Clear all
+ component[<%= live_flash(@flash, :info) %>]:info
+ component[<%= live_flash(@flash, :error) %>]:error
"""
end
@@ -60,8 +60,8 @@ defmodule Phoenix.LiveViewTest.FlashComponent do
{:noreply, socket |> put_flash(:info, info) |> redirect(to: to)}
end
- def handle_event("click", %{"type" => "push_redirect", "to" => to, "info" => info}, socket) do
- {:noreply, socket |> put_flash(:info, info) |> push_redirect(to: to)}
+ def handle_event("click", %{"type" => "push_navigate", "to" => to, "info" => info}, socket) do
+ {:noreply, socket |> put_flash(:info, info) |> push_navigate(to: to)}
end
def handle_event("click", %{"type" => "push_patch", "to" => to, "info" => info}, socket) do
@@ -104,8 +104,8 @@ defmodule Phoenix.LiveViewTest.FlashChildLive do
{:ok, socket |> redirect(to: "/flash-root") |> put_flash(:info, message)}
end
- def mount(%{"mount_push_redirect" => message}, _uri, socket) do
- {:ok, socket |> push_redirect(to: "/flash-root") |> put_flash(:info, message)}
+ def mount(%{"mount_push_navigate" => message}, _uri, socket) do
+ {:ok, socket |> push_navigate(to: "/flash-root") |> put_flash(:info, message)}
end
def mount(_params, _session, socket), do: {:ok, socket}
@@ -118,8 +118,8 @@ defmodule Phoenix.LiveViewTest.FlashChildLive do
{:noreply, socket |> put_flash(:info, info) |> redirect(to: to)}
end
- def handle_event("push_redirect", %{"to" => to, "info" => info}, socket) do
- {:noreply, socket |> put_flash(:info, info) |> push_redirect(to: to)}
+ def handle_event("push_navigate", %{"to" => to, "info" => info}, socket) do
+ {:noreply, socket |> put_flash(:info, info) |> push_navigate(to: to)}
end
def handle_event("push_patch", %{"to" => to, "info" => info}, socket) do
diff --git a/test/support/live_views/general.ex b/test/support/live_views/general.ex
index ee09d8dd70..eecb16d554 100644
--- a/test/support/live_views/general.ex
+++ b/test/support/live_views/general.ex
@@ -3,6 +3,11 @@ alias Phoenix.LiveViewTest.{ClockLive, ClockControlsLive}
defmodule Phoenix.LiveViewTest.ThermostatLive do
use Phoenix.LiveView, container: {:article, class: "thermo"}, namespace: Phoenix.LiveViewTest
+ defmodule Error do
+ defexception [:plug_status]
+ def message(%{plug_status: status}), do: "error #{status}"
+ end
+
def render(assigns) do
~H"""
Redirect: <%= @redirect %>
@@ -18,6 +23,22 @@ defmodule Phoenix.LiveViewTest.ThermostatLive do
"""
end
+ def mount(%{"raise_connected" => status}, session, socket) do
+ if connected?(socket) do
+ raise Error, plug_status: String.to_integer(status)
+ else
+ mount(%{}, session, socket)
+ end
+ end
+
+ def mount(%{"raise_disconnected" => status}, session, socket) do
+ if connected?(socket) do
+ mount(%{}, session, socket)
+ else
+ raise Error, plug_status: String.to_integer(status)
+ end
+ end
+
def mount(_params, session, socket) do
nest = Map.get(session, "nest", false)
users = session["users"] || []
@@ -71,12 +92,17 @@ defmodule Phoenix.LiveViewTest.ClockLive do
def render(assigns) do
~H"""
time: <%= @time %> <%= @name %>
- <%= live_render(@socket, ClockControlsLive, id: :"#{String.replace(@name, " ", "-")}-controls") %>
+ <%= live_render(@socket, ClockControlsLive, id: :"#{String.replace(@name, " ", "-")}-controls", sticky: @sticky) %>
"""
end
- def mount(_params, session, socket) do
- {:ok, assign(socket, time: "12:00", name: session["name"] || "NY")}
+ def mount(:not_mounted_at_router, session, socket) do
+ {:ok, assign(socket, time: "12:00", name: session["name"] || "NY", sticky: false)}
+ end
+
+ def mount(%{} = params, session, socket) do
+ {:ok,
+ assign(socket, time: "12:00", name: session["name"] || "NY", sticky: !!params["sticky"])}
end
def handle_info(:snooze, socket) do
@@ -264,7 +290,7 @@ defmodule Phoenix.LiveViewTest.RedirLive do
end
end
- defp do_redirect(socket, "push_redirect", opts), do: push_redirect(socket, opts)
+ defp do_redirect(socket, "push_navigate", opts), do: push_navigate(socket, opts)
defp do_redirect(socket, "redirect", opts), do: redirect(socket, opts)
defp do_redirect(socket, "external", to: url), do: redirect(socket, external: url)
defp do_redirect(socket, "push_patch", opts), do: push_patch(socket, opts)
diff --git a/test/support/live_views/layout.ex b/test/support/live_views/layout.ex
index 16f85608b8..80b61978df 100644
--- a/test/support/live_views/layout.ex
+++ b/test/support/live_views/layout.ex
@@ -13,7 +13,7 @@ defmodule Phoenix.LiveViewTest.ParentLayoutLive do
end
defmodule Phoenix.LiveViewTest.LayoutLive do
- use Phoenix.LiveView, layout: {Phoenix.LiveViewTest.LayoutView, "live.html"}
+ use Phoenix.LiveView, layout: {Phoenix.LiveViewTest.LayoutView, :live}
def render(assigns), do: ~H|The value is: <%= @val %>|
diff --git a/test/support/live_views/lifecycle.ex b/test/support/live_views/lifecycle.ex
index f6e80cc836..d88c843061 100644
--- a/test/support/live_views/lifecycle.ex
+++ b/test/support/live_views/lifecycle.ex
@@ -1,27 +1,47 @@
defmodule Phoenix.LiveViewTest.InitAssigns do
- alias Phoenix.LiveView
+ alias Phoenix.Component
def on_mount(:default, _params, _session, socket) do
{:cont,
socket
- |> LiveView.assign(:init_assigns_mount, true)
- |> LiveView.assign(:last_on_mount, :init_assigns_mount)}
+ |> Component.assign(:init_assigns_mount, true)
+ |> Component.assign(:last_on_mount, :init_assigns_mount)}
end
def on_mount(:other, _params, _session, socket) do
{:cont,
socket
- |> LiveView.assign(:init_assigns_other_mount, true)
- |> LiveView.assign(:last_on_mount, :init_assigns_other_mount)}
+ |> Component.assign(:init_assigns_other_mount, true)
+ |> Component.assign(:last_on_mount, :init_assigns_other_mount)}
end
end
defmodule Phoenix.LiveViewTest.MountArgs do
- use Phoenix.Component
+ import Phoenix.LiveView
def on_mount(:inlined, _params, _session, socket) do
qs = URI.encode_query(%{called: true, inlined: true})
- {:halt, push_redirect(socket, to: "/lifecycle?#{qs}")}
+ {:halt, push_navigate(socket, to: "/lifecycle?#{qs}")}
+ end
+end
+
+defmodule Phoenix.LiveViewTest.OnMount do
+ def on_mount(:default, _params, _session, socket) do
+ {:cont, socket}
+ end
+
+ def on_mount(:other, _params, _session, socket) do
+ {:cont, socket}
+ end
+end
+
+defmodule Phoenix.LiveViewTest.OtherOnMount do
+ def on_mount(:default, _params, _session, socket) do
+ {:cont, socket}
+ end
+
+ def on_mount(:other, _params, _session, socket) do
+ {:cont, socket}
end
end
@@ -144,7 +164,7 @@ defmodule Phoenix.LiveViewTest.HooksLive.RedirectMount do
on_mount __MODULE__
def on_mount(:default, _, _, %{assigns: %{live_action: action}} = socket) do
- {action, push_redirect(socket, to: "/lifecycle")}
+ {action, push_navigate(socket, to: "/lifecycle")}
end
def render(assigns), do: ~H"
"
@@ -162,13 +182,13 @@ defmodule Phoenix.LiveViewTest.HooksLive.Noop do
end
defmodule Phoenix.LiveViewTest.HaltConnectedMount do
- alias Phoenix.LiveView
+ alias Phoenix.{Component, LiveView}
def on_mount(_arg, _params, _session, socket) do
if LiveView.connected?(socket) do
- {:halt, LiveView.push_redirect(socket, to: "/lifecycle")}
+ {:halt, LiveView.push_navigate(socket, to: "/lifecycle")}
else
- {:cont, LiveView.assign(socket, :last_on_mount, __MODULE__)}
+ {:cont, Component.assign(socket, :last_on_mount, __MODULE__)}
end
end
end
@@ -244,3 +264,18 @@ defmodule Phoenix.LiveViewTest.HooksLive.HandleParamsNotDefined do
def render(assigns), do: ~H"url=<%= assigns[:url] %>"
end
+
+defmodule Phoenix.LiveViewTest.HooksLive.HandleInfoNotDefined do
+ use Phoenix.LiveView, namespace: Phoenix.LiveViewTest
+
+ def mount(_, _, socket) do
+ send(self(), {:data, "somedata"})
+
+ {:ok, attach_hook(socket, :assign_url, :handle_info, fn message, socket ->
+ {:data, data} = message
+ {:cont, assign(socket, :data, data)}
+ end)}
+ end
+
+ def render(assigns), do: ~H"data=<%= assigns[:data] %>"
+end
diff --git a/test/support/live_views/params.ex b/test/support/live_views/params.ex
index 5af2c57476..99bb94652e 100644
--- a/test/support/live_views/params.ex
+++ b/test/support/live_views/params.ex
@@ -4,8 +4,6 @@ defmodule Phoenix.LiveViewTest.ParamCounterLive do
def render(assigns) do
~H"""
The value is: <%= @val %>
- connect: <%= inspect(@connect_params) %>
- info: <%= inspect(@connect_info) %>
mount: <%= inspect(@mount_params) %>
params: <%= inspect(@params) %>
"""
@@ -19,8 +17,6 @@ defmodule Phoenix.LiveViewTest.ParamCounterLive do
socket,
val: 1,
mount_params: params,
- connect_params: get_connect_params(socket) || %{},
- connect_info: get_connect_info(socket) || %{},
test_pid: session["test_pid"],
connected?: connected?(socket),
on_handle_params: on_handle_params && :erlang.binary_to_term(on_handle_params)
@@ -43,15 +39,15 @@ defmodule Phoenix.LiveViewTest.ParamCounterLive do
{:noreply, push_patch(socket, to: to)}
end
- def handle_info({:push_redirect, to}, socket) do
- {:noreply, push_redirect(socket, to: to)}
+ def handle_info({:push_navigate, to}, socket) do
+ {:noreply, push_navigate(socket, to: to)}
end
def handle_call({:push_patch, func}, _from, socket) do
func.(socket)
end
- def handle_call({:push_redirect, func}, _from, socket) do
+ def handle_call({:push_navigate, func}, _from, socket) do
func.(socket)
end
@@ -59,16 +55,16 @@ defmodule Phoenix.LiveViewTest.ParamCounterLive do
{:noreply, push_patch(socket, to: to)}
end
- def handle_cast({:push_redirect, to}, socket) do
- {:noreply, push_redirect(socket, to: to)}
+ def handle_cast({:push_navigate, to}, socket) do
+ {:noreply, push_navigate(socket, to: to)}
end
def handle_event("push_patch", %{"to" => to}, socket) do
{:noreply, push_patch(socket, to: to)}
end
- def handle_event("push_redirect", %{"to" => to}, socket) do
- {:noreply, push_redirect(socket, to: to)}
+ def handle_event("push_navigate", %{"to" => to}, socket) do
+ {:noreply, push_navigate(socket, to: to)}
end
end
diff --git a/test/support/live_views/reload_live.ex b/test/support/live_views/reload_live.ex
new file mode 100644
index 0000000000..2e7d99cc8d
--- /dev/null
+++ b/test/support/live_views/reload_live.ex
@@ -0,0 +1,21 @@
+defmodule Phoenix.LiveViewTest.ReloadLive do
+ use Phoenix.LiveView
+
+ def mount(_params, _session, socket) do
+ {:ok, socket}
+ end
+
+ def render(assigns) do
+ case Application.fetch_env(:phoenix_live_view, :vsn) do
+ {:ok, 1} ->
+ ~H"""
+
Version 1
+ """
+
+ {:ok, 2} ->
+ ~H"""
+
Version 2
+ """
+ end
+ end
+end
diff --git a/test/support/live_views/streams.ex b/test/support/live_views/streams.ex
new file mode 100644
index 0000000000..1820fe58db
--- /dev/null
+++ b/test/support/live_views/streams.ex
@@ -0,0 +1,143 @@
+defmodule Phoenix.LiveViewTest.StreamLive do
+ use Phoenix.LiveView
+
+ def run(lv, func) do
+ GenServer.call(lv.pid, {:run, func})
+ end
+
+ def render(assigns) do
+ ~H"""
+
+
+ <%= user.name %>
+ delete
+ update
+ make first
+ make last
+
+
+
+
+ <%= user.name %>
+ delete
+ update
+ make first
+ make last
+
+
+ <.live_component id="stream-component" module={Phoenix.LiveViewTest.StreamComponent} />
+ """
+ end
+
+ def mount(_params, _session, socket) do
+ {:ok,
+ socket
+ |> stream(:users, [user(1, "chris"), user(2, "callan")])
+ |> stream(:admins, [user(1, "chris-admin"), user(2, "callan-admin")])}
+ end
+
+ def handle_event("delete", %{"id" => dom_id}, socket) do
+ {:noreply, stream_delete_by_dom_id(socket, :users, dom_id)}
+ end
+
+ def handle_event("update", %{"id" => "users-" <> id}, socket) do
+ {:noreply, stream_insert(socket, :users, user(id, "updated"))}
+ end
+
+ def handle_event("move-to-first", %{"id" => "users-" <> id}, socket) do
+ {:noreply, stream_insert(socket, :users, user(id, "updated"), at: 0)}
+ end
+
+ def handle_event("move-to-last", %{"id" => "users-" <> id = dom_id}, socket) do
+ user = user(id, "updated")
+
+ {:noreply,
+ socket
+ |> stream_delete_by_dom_id(:users, dom_id)
+ |> stream_insert(:users, user, at: -1)}
+ end
+
+ def handle_event("admin-delete", %{"id" => dom_id}, socket) do
+ {:noreply, stream_delete_by_dom_id(socket, :admins, dom_id)}
+ end
+
+ def handle_event("admin-update", %{"id" => "admins-" <> id}, socket) do
+ {:noreply, stream_insert(socket, :admins, user(id, "updated"))}
+ end
+
+ def handle_event("admin-move-to-first", %{"id" => "admins-" <> id}, socket) do
+ {:noreply, stream_insert(socket, :admins, user(id, "updated"), at: 0)}
+ end
+
+ def handle_event("admin-move-to-last", %{"id" => "admins-" <> id = dom_id}, socket) do
+ user = user(id, "updated")
+
+ {:noreply,
+ socket
+ |> stream_delete_by_dom_id(:admins, dom_id)
+ |> stream_insert(:admins, user, at: -1)}
+ end
+
+ def handle_call({:run, func}, _, socket), do: func.(socket)
+
+ defp user(id, name) do
+ %{id: id, name: name}
+ end
+end
+
+defmodule Phoenix.LiveViewTest.StreamComponent do
+ use Phoenix.LiveComponent
+
+ def run(lv, func) do
+ GenServer.call(lv.pid, {:run, func})
+ end
+
+ def render(assigns) do
+ ~H"""
+
+
+ <%= user.name %>
+ delete
+ update
+ make first
+ make last
+
+
+ """
+ end
+
+ def update(%{send_assigns_to: test_pid}, socket) when is_pid(test_pid) do
+ send(test_pid, {:assigns, socket.assigns})
+ {:ok, socket}
+ end
+
+ def update(_assigns, socket) do
+ users = [user(1, "chris"), user(2, "callan")]
+ {:ok, stream(socket, :c_users, users)}
+ end
+
+ def handle_event("delete", %{"id" => dom_id}, socket) do
+ {:noreply, stream_delete_by_dom_id(socket, :c_users, dom_id)}
+ end
+
+ def handle_event("update", %{"id" => "c_users-" <> id}, socket) do
+ {:noreply, stream_insert(socket, :c_users, user(id, "updated"))}
+ end
+
+ def handle_event("move-to-first", %{"id" => "c_users-" <> id}, socket) do
+ {:noreply, stream_insert(socket, :c_users, user(id, "updated"), at: 0)}
+ end
+
+ def handle_event("move-to-last", %{"id" => "c_users-" <> id = dom_id}, socket) do
+ user = user(id, "updated")
+
+ {:noreply,
+ socket
+ |> stream_delete_by_dom_id(:c_users, dom_id)
+ |> stream_insert(:c_users, user, at: -1)}
+ end
+
+ defp user(id, name) do
+ %{id: id, name: name}
+ end
+end
diff --git a/test/support/live_views/upload_live.ex b/test/support/live_views/upload_live.ex
index 9ef4ed71bd..2465d657d6 100644
--- a/test/support/live_views/upload_live.ex
+++ b/test/support/live_views/upload_live.ex
@@ -17,7 +17,7 @@ defmodule Phoenix.LiveViewTest.UploadLive do
error:<%= inspect(msg) %>
<% end %>
<% end %>
- <%= live_file_input @uploads.avatar %>
+ <%= live_file_input @uploads.avatar, [] %>
save
"""
@@ -93,7 +93,7 @@ defmodule Phoenix.LiveViewTest.UploadComponent do
error:<%= inspect(msg) %>
<% end %>
<% end %>
- <%= live_file_input @uploads.avatar %>
+ <%= live_file_input @uploads.avatar, [] %>
save
diff --git a/test/support/router.ex b/test/support/router.ex
index aee9ad0d39..2cbf493b25 100644
--- a/test/support/router.ex
+++ b/test/support/router.ex
@@ -41,7 +41,10 @@ defmodule Phoenix.LiveViewTest.Router do
live "/components", WithComponentLive
live "/multi-targets", WithMultipleTargets
live "/assigns-not-in-socket", AssignsNotInSocketLive
+ live "/log-override", WithLogOverride
+ live "/log-disabled", WithLogDisabled
live "/errors", ErrorsLive
+ live "/live-reload", ReloadLive
# controller test
get "/controller/:type", Controller, :incoming
@@ -64,7 +67,7 @@ defmodule Phoenix.LiveViewTest.Router do
live "/router/foobarbaz/nosuffix", NoSuffix, :index, as: :custom_route
# integration layout
- live_session :styled_layout, root_layout: {Phoenix.LiveViewTest.LayoutView, "styled.html"} do
+ live_session :styled_layout, root_layout: {Phoenix.LiveViewTest.LayoutView, :styled} do
live "/styled-elements", ElementsLive
end
@@ -101,6 +104,7 @@ defmodule Phoenix.LiveViewTest.Router do
# integration components
live "/component_in_live", ComponentInLive.Root
live "/cids_destroyed", CidsDestroyedLive
+ live "/component_and_nested_in_live", ComponentAndNestedInLive
# integration lifecycle
live "/lifecycle", HooksLive
@@ -111,6 +115,13 @@ defmodule Phoenix.LiveViewTest.Router do
live "/lifecycle/redirect-halt-mount", HooksLive.RedirectMount, :halt
live "/lifecycle/components", HooksLive.WithComponent
live "/lifecycle/handle-params-not-defined", HooksLive.HandleParamsNotDefined
+ live "/lifecycle/handle-info-not-defined", HooksLive.HandleInfoNotDefined
+
+ # integration stream
+ live "/stream", StreamLive
+
+ # integration connect
+ live "/connect", ConnectLive
# live_patch
scope host: "app.example.com" do
@@ -141,8 +152,25 @@ defmodule Phoenix.LiveViewTest.Router do
live "/lifecycle/halt-connected-mount", HooksLive.Noop
end
- live_session :mount_mfa, on_mount: {Phoenix.LiveViewTest.MountArgs, :inlined} do
- live "/lifecycle/mount-args", HooksLive.Noop
+ live_session :mount_mod_arg, on_mount: {Phoenix.LiveViewTest.MountArgs, :inlined} do
+ live "/lifecycle/mount-mod-arg", HooksLive.Noop
+ end
+
+ live_session :mount_mods,
+ on_mount: [Phoenix.LiveViewTest.OnMount, Phoenix.LiveViewTest.OtherOnMount] do
+ live "/lifecycle/mount-mods", HooksLive.Noop
+ end
+
+ live_session :mount_mod_args,
+ on_mount: [
+ {Phoenix.LiveViewTest.OnMount, :other},
+ {Phoenix.LiveViewTest.OtherOnMount, :other}
+ ] do
+ live "/lifecycle/mount-mods-args", HooksLive.Noop
+ end
+
+ live_session :layout, layout: {Phoenix.LiveViewTest.LayoutView, :live_override} do
+ live "/dashboard-live-session-layout", LayoutLive
end
end
diff --git a/test/test_helper.exs b/test/test_helper.exs
index 869559e709..c6b7aca2a0 100644
--- a/test/test_helper.exs
+++ b/test/test_helper.exs
@@ -1 +1,5 @@
-ExUnit.start()
+after_verify_exclude =
+ if Version.match?(System.version(), ">= 1.14.0-dev"), do: [], else: [:after_verify]
+
+{:ok, _} = Phoenix.LiveViewTest.Endpoint.start_link()
+ExUnit.start(exclude: after_verify_exclude)