Skip to content

Commit da60871

Browse files
calebporzioclaude
andauthored
Don't re-apply x-cloak to live elements during morph (#4922)
Morph seeds each incoming element with Alpine.cloneNode() before patching attributes. x-cloak only removes itself in a microtask, so the incoming element still has x-cloak when attributes are patched, and it gets copied back onto the already-initialized live element. The element is then display: none until the microtask runs, so anything measuring it in that window (e.g. a MutationObserver callback) reads 0px. Strip x-cloak synchronously while cloning, like wire:cloak's interceptor already does. The cloned element is detached, so there's nothing to hide. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 20ee0e9 commit da60871

2 files changed

Lines changed: 38 additions & 2 deletions

File tree

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
import { directive, prefix } from '../directives'
22
import { mutateDom } from '../mutation'
3+
import { isCloning } from '../clone'
34

4-
directive('cloak', el => queueMicrotask(() => mutateDom(() => el.removeAttribute(prefix('cloak')))))
5+
directive('cloak', el => {
6+
// When morph seeds incoming HTML, strip x-cloak right away so it
7+
// isn't patched back onto the live element, hiding it mid-morph...
8+
if (isCloning) return el.removeAttribute(prefix('cloak'))
9+
10+
queueMicrotask(() => mutateDom(() => el.removeAttribute(prefix('cloak'))))
11+
})

‎tests/cypress/integration/plugins/morph.spec.js‎

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { haveAttribute, haveFocus, haveLength, haveText, haveValue, haveHtml, html, test } from '../../utils'
1+
import { haveAttribute, haveFocus, haveLength, haveText, haveValue, haveHtml, html, notHaveAttribute, test } from '../../utils'
22

33
test('can morph components and preserve Alpine state',
44
[html`
@@ -1358,3 +1358,32 @@ test('wont run x-data init() method twice when morph patches a node to add x-dat
13581358
cy.wrap(window).its('timesInitRan').should('equal', 1)
13591359
},
13601360
)
1361+
1362+
test('morphing does not re-apply x-cloak to an already initialized element',
1363+
[html`
1364+
<style>[x-cloak] { display: none; }</style>
1365+
1366+
<div x-data>
1367+
<p x-cloak>foo</p>
1368+
</div>
1369+
`],
1370+
({ get }, reload, window, document) => {
1371+
get('p').should(notHaveAttribute('x-cloak'))
1372+
1373+
get('div').then(([el]) => {
1374+
let toHtml = el.outerHTML.replace('<p>', '<p x-cloak>')
1375+
1376+
window.heightsDuringMorph = []
1377+
1378+
new MutationObserver(() => {
1379+
window.heightsDuringMorph.push(el.querySelector('p').offsetHeight)
1380+
}).observe(el, { subtree: true, childList: true, attributes: true, characterData: true })
1381+
1382+
window.Alpine.morph(el, toHtml.replace('foo', 'bar'))
1383+
})
1384+
1385+
get('p').should(haveText('bar'))
1386+
get('p').should(notHaveAttribute('x-cloak'))
1387+
cy.window().its('heightsDuringMorph').should('have.length.greaterThan', 0).and('not.include', 0)
1388+
},
1389+
)

0 commit comments

Comments
 (0)