From 7d9d373cedaa21ea0ca111da6bf0021c696a81c8 Mon Sep 17 00:00:00 2001 From: edun17 Date: Sat, 1 Aug 2026 04:00:01 +0000 Subject: [PATCH] [LLV] Fix phx-hook mounting inside LocalLiveView Fake views omit data-phx-session, so stock ownsElement never claims the subtree and addHook skips child hooks. Own nodes by container containment. Co-authored-by: Cursor --- local-live-view/assets/local_live_view/types.ts | 1 + local-live-view/assets/local_live_view/view_setup.ts | 7 +++++++ local-live-view/priv/static/local_live_view.js | 6 ++++++ 3 files changed, 14 insertions(+) diff --git a/local-live-view/assets/local_live_view/types.ts b/local-live-view/assets/local_live_view/types.ts index 9f400a76..a1017551 100644 --- a/local-live-view/assets/local_live_view/types.ts +++ b/local-live-view/assets/local_live_view/types.ts @@ -68,6 +68,7 @@ export interface LLVView { opts: object, ): void; addHook: (el: Element) => unknown; + ownsElement?: (el: Element) => boolean; destroy?: (callback?: () => void) => void; } diff --git a/local-live-view/assets/local_live_view/view_setup.ts b/local-live-view/assets/local_live_view/view_setup.ts index 0afb607a..41d2f56c 100644 --- a/local-live-view/assets/local_live_view/view_setup.ts +++ b/local-live-view/assets/local_live_view/view_setup.ts @@ -20,6 +20,13 @@ export function setupFakeView( // stock Channel/Push machinery over the PopcornTransport. view.channel = popcornSocket.channel(`lv:${llvId}`); + // LLV roots have no data-phx-session, so stock ownsElement (closestViewEl) would + // assign children to the host LiveView or reject them entirely — and addHook + // would skip every phx-hook under this container. Own the subtree by containment. + view.ownsElement = function (this: LLVView, el: Element) { + return this.el === el || this.el.contains(el); + }; + // addHook: skip the root element to prevent Phoenix from trying to register it // as a hook within this view's scope — hooks on children are still processed normally. const origAddHook = view.addHook.bind(view); diff --git a/local-live-view/priv/static/local_live_view.js b/local-live-view/priv/static/local_live_view.js index 38eecaf7..299e5073 100644 --- a/local-live-view/priv/static/local_live_view.js +++ b/local-live-view/priv/static/local_live_view.js @@ -524,6 +524,12 @@ function setupFakeView(socket, views, popcornSocket, pop_view_el) { // diffs, out-of-band "diff" frames, ref bookkeeping — runs through the // stock Channel/Push machinery over the PopcornTransport. view.channel = popcornSocket.channel(`lv:${llvId}`); + // LLV roots have no data-phx-session, so stock ownsElement (closestViewEl) would + // assign children to the host LiveView or reject them entirely — and addHook + // would skip every phx-hook under this container. Own the subtree by containment. + view.ownsElement = function (el) { + return this.el === el || this.el.contains(el); + }; // addHook: skip the root element to prevent Phoenix from trying to register it // as a hook within this view's scope — hooks on children are still processed normally. const origAddHook = view.addHook.bind(view);