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);