diff --git a/assets/js/phoenix_live_view/dom_patch.js b/assets/js/phoenix_live_view/dom_patch.js index 18c6a6a396..fe93a977a6 100644 --- a/assets/js/phoenix_live_view/dom_patch.js +++ b/assets/js/phoenix_live_view/dom_patch.js @@ -28,7 +28,8 @@ import morphdom from "morphdom" export default class DOMPatch { static patchWithClonedTree(container, clonedTree, liveSocket){ - let activeElement = liveSocket.getActiveElement() + let focused = liveSocket.getActiveElement() + let {selectionStart, selectionEnd} = focused && DOM.hasSelectionRange(focused) ? focused : {} let phxUpdate = liveSocket.binding(PHX_UPDATE) morphdom(container, clonedTree, { @@ -38,12 +39,14 @@ export default class DOMPatch { // we cannot morph locked children if(!container.isSameNode(fromEl) && fromEl.hasAttribute(PHX_REF_LOCK)){ return false } if(DOM.isIgnored(fromEl, phxUpdate)){ return false } - if(activeElement && activeElement.isSameNode(fromEl) && DOM.isFormInput(fromEl)){ + if(focused && focused.isSameNode(fromEl) && DOM.isFormInput(fromEl)){ DOM.mergeFocusedInput(fromEl, toEl) return false } } }) + + liveSocket.silenceEvents(() => DOM.restoreFocus(focused, selectionStart, selectionEnd)) } constructor(view, container, id, html, streams, targetCID){ diff --git a/test/e2e/support/issues/issue_3448.ex b/test/e2e/support/issues/issue_3448.ex new file mode 100644 index 0000000000..2ddb878169 --- /dev/null +++ b/test/e2e/support/issues/issue_3448.ex @@ -0,0 +1,65 @@ +defmodule Phoenix.LiveViewTest.E2E.Issue3448Live do + # https://github.com/phoenixframework/phoenix_live_view/issues/3448 + + use Phoenix.LiveView + + alias Phoenix.LiveView.JS + + def mount(_params, _session, socket) do + form = to_form(%{"a" => []}) + + {:ok, assign_new(socket, :form, fn -> form end)} + end + + def render(assigns) do + ~H""" + <.form for={@form} id="my_form" phx-change="validate" class="flex flex-col gap-2"> + <.my_component> + <:left_content :for={value <- @form[:a].value || []}> +