diff --git a/resources/js/partials.js b/resources/js/partials.js index 0e79c96..4127d94 100644 --- a/resources/js/partials.js +++ b/resources/js/partials.js @@ -2,8 +2,8 @@ import {findClosestLivewireComponent, isComponentRootEl, isntElement} from "./ut document.addEventListener('livewire:init', () => { Livewire.interceptMessage(({message, onSuccess}) => { - onSuccess(({payload}) => { - queueMicrotask(() => { + onSuccess(({payload, onMorph}) => { + const applyPartials = () => { if (payload.effects?.html) { return } @@ -11,156 +11,189 @@ document.addEventListener('livewire:init', () => { const partialFragments = payload.effects?.partialFragments ?? {} for (const [name, html] of Object.entries(partialFragments)) { - - let els = Array.from( - message.component.el.querySelectorAll( - `[wire\\:partial="${name}"]`, - ), - ).filter( - (el) => - findClosestLivewireComponent(el).id === - message.component.id, - ) - - if (!els.length) { - continue - } - - let el = els[0] - - const getIgnoreKey = (node) => { - return node.getAttribute('wire:partial.ignore') || - node.getAttribute('wire:key') || - node.id - } - - const applyMarkers = (node) => { - if (node.hasAttribute('wire:partial.ignore')) { - let key = getIgnoreKey(node) - if (key && !node.previousSibling?.nodeValue?.includes(`START:PARTIAL-IGNORE:${key}`)) { - node.before(document.createComment(`START:PARTIAL-IGNORE:${key}`)) - node.after(document.createComment(`END:PARTIAL-IGNORE:${key}`)) - } - } - node.querySelectorAll('[wire\\:partial\\.ignore]').forEach(child => { - let key = getIgnoreKey(child) - if (key && !child.previousSibling?.nodeValue?.includes(`START:PARTIAL-IGNORE:${key}`)) { - child.before(document.createComment(`START:PARTIAL-IGNORE:${key}`)) - child.after(document.createComment(`END:PARTIAL-IGNORE:${key}`)) - } - }) + try { + morphPartial(message, name, html) + } catch (error) { + console.error(`[powergrid-partials] Failed to morph "${name}"`, error) } + } + } - const capturedContent = new Map() - - const captureContent = (node) => { - if (node.hasAttribute('wire:partial.ignore')) { - let key = getIgnoreKey(node) - if (key) { - capturedContent.set(key, node.innerHTML) - } - } - node.querySelectorAll('[wire\\:partial\\.ignore]').forEach(child => { - let key = getIgnoreKey(child) - if (key) { - capturedContent.set(key, child.innerHTML) - } - }) - } + if (typeof onMorph === 'function') { + onMorph(applyPartials) + } else { + queueMicrotask(applyPartials) + } + }) + }) +}) - captureContent(el) - applyMarkers(el) +function wrapperTagName(el) { + const parent = el.parentElement + const tag = parent ? parent.tagName.toLowerCase() : 'div' + const customElement = window.customElements?.get(tag) + + return customElement?.name || tag +} + +function morphPartial(message, name, html) { + let els = Array.from( + message.component.el.querySelectorAll( + `[wire\\:partial="${name}"]`, + ), + ).filter( + (el) => + findClosestLivewireComponent(el).id === + message.component.id, + ) + + if (!els.length) { + return + } + + let el = els[0] + + const getIgnoreKey = (node) => { + return node.getAttribute('wire:partial.ignore') || + node.getAttribute('wire:key') || + node.id + } + + const applyMarkers = (node) => { + if (node.hasAttribute('wire:partial.ignore')) { + let key = getIgnoreKey(node) + if (key && !node.previousSibling?.nodeValue?.includes(`START:PARTIAL-IGNORE:${key}`)) { + node.before(document.createComment(`START:PARTIAL-IGNORE:${key}`)) + node.after(document.createComment(`END:PARTIAL-IGNORE:${key}`)) + } + } + node.querySelectorAll('[wire\\:partial\\.ignore]').forEach(child => { + let key = getIgnoreKey(child) + if (key && !child.previousSibling?.nodeValue?.includes(`START:PARTIAL-IGNORE:${key}`)) { + child.before(document.createComment(`START:PARTIAL-IGNORE:${key}`)) + child.after(document.createComment(`END:PARTIAL-IGNORE:${key}`)) + } + }) + } + + const capturedContent = new Map() + + const captureContent = (node) => { + if (node.hasAttribute('wire:partial.ignore')) { + let key = getIgnoreKey(node) + if (key) { + capturedContent.set(key, node.innerHTML) + } + } + node.querySelectorAll('[wire\\:partial\\.ignore]').forEach(child => { + let key = getIgnoreKey(child) + if (key) { + capturedContent.set(key, child.innerHTML) + } + }) + } - let wrapperTag = el.parentElement - ? el.parentElement.tagName.toLowerCase() - : 'div' + captureContent(el) + applyMarkers(el) - let wrapper = document.createElement(wrapperTag) + let wrapper = document.createElement(wrapperTagName(el)) - wrapper.innerHTML = html - wrapper.__livewire = message.component + wrapper.innerHTML = html + wrapper.__livewire = message.component - let to = wrapper.firstElementChild + let to = wrapper.firstElementChild - to.__livewire = message.component + if (!to) { + return + } - applyMarkers(to) + to.__livewire = message.component - to.querySelectorAll('[wire\\:partial\\.ignore]').forEach(node => { - let key = getIgnoreKey(node) - if (key && node.innerHTML.includes(``)) { - node.innerHTML = capturedContent.get(key) - } - }) + applyMarkers(to) - if (to.hasAttribute('wire:partial.ignore')) { - let key = getIgnoreKey(to) - if (key && to.innerHTML.includes(``)) { - to.innerHTML = capturedContent.get(key) - } - } + to.querySelectorAll('[wire\\:partial\\.ignore]').forEach(node => { + let key = getIgnoreKey(node) + if (key && node.innerHTML.includes(``)) { + node.innerHTML = capturedContent.get(key) + } + }) - window.Alpine.morph(el, to, { - updating: (el, toEl, childrenOnly, skip) => { - if (isntElement(el)) { - return - } - - if (el.__livewire_replace === true) { - el.innerHTML = toEl.innerHTML - } - - if (el.__livewire_replace_self === true) { - el.outerHTML = toEl.outerHTML - - return skip() - } - - if (el.hasAttribute('wire:partial.ignore')) { - return skip() - } - - if (el.__livewire_ignore === true) { - return skip() - } - - if (el.__livewire_ignore_self === true) { - childrenOnly() - } - - if ( - isComponentRootEl(el) && - el.getAttribute('wire:id') !== - message.component.id - ) { - return skip() - } - - if (isComponentRootEl(el)) { - toEl.__livewire = message.component - } - }, - - key: (el) => { - if (isntElement(el)) { - return - } - - if (el.hasAttribute(`wire:key`)) { - return el.getAttribute(`wire:key`) - } - - if (el.hasAttribute(`wire:id`)) { - return el.getAttribute(`wire:id`) - } - - return el.id - }, - - lookahead: false, - }) - } - }) - }) + if (to.hasAttribute('wire:partial.ignore')) { + let key = getIgnoreKey(to) + if (key && to.innerHTML.includes(``)) { + to.innerHTML = capturedContent.get(key) + } + } + + Livewire.trigger?.('morph', {el, toEl: to, component: message.component}) + + window.Alpine.morph(el, to, { + updating: (el, toEl, childrenOnly, skip) => { + if (isntElement(el)) { + return + } + + if (el.__livewire_replace === true) { + el.innerHTML = toEl.innerHTML + } + + if (el.__livewire_replace_self === true) { + el.outerHTML = toEl.outerHTML + + return skip() + } + + if (el.hasAttribute('wire:partial.ignore')) { + return skip() + } + + if (el.__livewire_ignore === true || el.hasAttribute('wire:ignore')) { + return skip() + } + + if (el.__livewire_ignore_self === true || el.hasAttribute('wire:ignore.self')) { + childrenOnly() + } + + if ( + isComponentRootEl(el) && + el.getAttribute('wire:id') !== + message.component.id + ) { + return skip() + } + + if (isComponentRootEl(el)) { + toEl.__livewire = message.component + } + }, + + added: (addedEl) => { + if (isntElement(addedEl)) { + return + } + + Livewire.trigger?.('morph.added', {el: addedEl}) + }, + + key: (el) => { + if (isntElement(el)) { + return + } + + if (el.hasAttribute(`wire:key`)) { + return el.getAttribute(`wire:key`) + } + + if (el.hasAttribute(`wire:id`)) { + return el.getAttribute(`wire:id`) + } + + return el.id + }, + + lookahead: false, }) -}) + + Livewire.trigger?.('morphed', {el, component: message.component}) +} diff --git a/tests/Browser/TableLoadingBrowserTest.php b/tests/Browser/TableLoadingBrowserTest.php index 9797140..1ab0619 100644 --- a/tests/Browser/TableLoadingBrowserTest.php +++ b/tests/Browser/TableLoadingBrowserTest.php @@ -177,9 +177,43 @@ public function render() } } +class TableInlineFiltersBrowserTest extends Component +{ + public int $pageNumber = 1; + + public string $filterUniqid; + + public function mount(): void + { + $this->filterUniqid = uniqid('filter-'); + } + + public function nextPage(): void + { + $this->pageNumber++; + + partials($this)->partial('table-body', 'table-inline-filters-body'); + } + + public function render() + { + return <<<'BLADE' +