From cbfb6655a8545c9492dd7df5e4143e2e782bc822 Mon Sep 17 00:00:00 2001 From: Will Rowe Date: Mon, 15 Sep 2025 21:42:37 +0000 Subject: [PATCH 1/3] Add failing tests --- .../directives/x-modelable.spec.js | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/cypress/integration/directives/x-modelable.spec.js b/tests/cypress/integration/directives/x-modelable.spec.js index 690e42264..424af22fb 100644 --- a/tests/cypress/integration/directives/x-modelable.spec.js +++ b/tests/cypress/integration/directives/x-modelable.spec.js @@ -68,3 +68,78 @@ test('x-modelable removes the event listener used by corresponding x-model', get('h2').should(haveText('foo')) } ) + +test('works when inside x-teleport', + html` +
+ +
+ `, + ({ get }) => { + get('h1').should(haveText('foo')) + get('h2').should(haveText('foo')) + get('#1').click() + get('h1').should(haveText('bob')) + get('h2').should(haveText('bob')) + get('#2').click() + get('h1').should(haveText('lob')) + get('h2').should(haveText('lob')) + } +) + +test('works when inside x-teleport when targeting parent', + html` +
+

+ + +
+ `, + ({ get }) => { + get('h1').should(haveText('foo')) + get('h2').should(haveText('foo')) + get('#1').click() + get('h1').should(haveText('bob')) + get('h2').should(haveText('bob')) + get('#2').click() + get('h1').should(haveText('lob')) + get('h2').should(haveText('lob')) + } +) + +test('works when inside x-teleport with x-data directly on it', + html` +
+ +
+ `, + ({ get }) => { + get('h1').should(haveText('foo')) + get('#1').click() + get('h1').should(haveText('bob')) + get('#2').click() + get('h1').should(haveText('lob')) + } +) \ No newline at end of file From e7f69a9a62965be0b140cad755595ba29bace600 Mon Sep 17 00:00:00 2001 From: Will Rowe Date: Mon, 15 Sep 2025 21:43:23 +0000 Subject: [PATCH 2/3] Simplify `findClosest` and always include the teleport element itself --- packages/alpinejs/src/lifecycle.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/alpinejs/src/lifecycle.js b/packages/alpinejs/src/lifecycle.js index 66b792468..dc55c78a6 100644 --- a/packages/alpinejs/src/lifecycle.js +++ b/packages/alpinejs/src/lifecycle.js @@ -66,12 +66,7 @@ export function findClosest(el, callback) { if (callback(el)) return el - // Support crawling up teleports. - if (el._x_teleportBack) el = el._x_teleportBack - - if (! el.parentElement) return - - return findClosest(el.parentElement, callback) + return findClosest(el._x_teleportBack ?? el.parentElement, callback) } export function isRoot(el) { From 06764e77ff1c22e66183e97186d98652df1c520e Mon Sep 17 00:00:00 2001 From: Will Rowe Date: Mon, 15 Sep 2025 21:44:41 +0000 Subject: [PATCH 3/3] Use `findClosest` for the `parent` modifier on `x-model` --- packages/alpinejs/src/directives/x-model.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/alpinejs/src/directives/x-model.js b/packages/alpinejs/src/directives/x-model.js index 488004c2a..ebe8bf85b 100644 --- a/packages/alpinejs/src/directives/x-model.js +++ b/packages/alpinejs/src/directives/x-model.js @@ -5,12 +5,13 @@ import { mutateDom } from '../mutation' import { nextTick } from '../nextTick' import { isCloning } from '../clone' import on from '../utils/on' +import { findClosest } from '../lifecycle' directive('model', (el, { modifiers, expression }, { effect, cleanup }) => { let scopeTarget = el if (modifiers.includes('parent')) { - scopeTarget = el.parentNode + scopeTarget = findClosest(el, (element) => element !== el) } let evaluateGet = evaluateLater(scopeTarget, expression)