From d2a24b65cc47addc652a553e56e131dffaedeaee Mon Sep 17 00:00:00 2001 From: HawkForge <75696486+HawkForge@users.noreply.github.com> Date: Wed, 14 Oct 2026 16:13:44 +0600 Subject: [PATCH] Skip x-modelable when cloning during a morph Alpine.morph() clones every element it patches. On the clone, x-modelable still queued its entangle microtask, which ran after cloneNode() had returned and so registered a real effect against the live component's data. That effect copied the outer value back over any inner change made in the meantime, and since the clone is thrown away without cleanup, one entanglement leaked per morph. Wrap the directive in skipDuringClone() like x-init, x-effect and x-on. Fixes #4928 --- .../alpinejs/src/directives/x-modelable.js | 5 +- .../cypress/integration/plugins/morph.spec.js | 61 +++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/packages/alpinejs/src/directives/x-modelable.js b/packages/alpinejs/src/directives/x-modelable.js index 543e66827..583de85bd 100644 --- a/packages/alpinejs/src/directives/x-modelable.js +++ b/packages/alpinejs/src/directives/x-modelable.js @@ -1,7 +1,8 @@ import { directive } from '../directives' import { entangle } from '../entangle'; +import { skipDuringClone } from '../clone' -directive('modelable', (el, { expression }, { effect, evaluateLater, cleanup }) => { +directive('modelable', skipDuringClone((el, { expression }, { effect, evaluateLater, cleanup }) => { let func = evaluateLater(expression) let innerGet = () => { let result; func(i => result = i); return result; } let evaluateInnerSet = evaluateLater(`${expression} = __placeholder`) @@ -36,4 +37,4 @@ directive('modelable', (el, { expression }, { effect, evaluateLater, cleanup }) cleanup(releaseEntanglement) }) -}) +})) diff --git a/tests/cypress/integration/plugins/morph.spec.js b/tests/cypress/integration/plugins/morph.spec.js index 65b0eed69..7d17d6826 100644 --- a/tests/cypress/integration/plugins/morph.spec.js +++ b/tests/cypress/integration/plugins/morph.spec.js @@ -1387,3 +1387,64 @@ test('morphing does not re-apply x-cloak to an already initialized element', cy.window().its('heightsDuringMorph').should('have.length.greaterThan', 0).and('not.include', 0) }, ) + +test('x-modelable keeps a change made right after a morph', + [html` +