Skip to content

Commit 2cfc2ba

Browse files
maximbelyayevcalebporzioclaude
authored
feat(x-anchor): allow dynamic reference to be used with x-anchor (#4735)
* Add cypress anchor utils and update test spec * feat(x-anchor): make reference dynamic by evaluating in effect API * Include forgotten vertical anchoring assertion in util * Review fixes: style cleanup, simplify test - Remove semicolons to match project style - Fix trailing whitespace - Add missing newline at EOF - Simplify test to check style.left changes instead of complex bounding rect math - Remove unused beAnchoredTo/getBoundingRect utilities from utils.js - Remove unused haveProperty import Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Caleb Porzio <calebporzio@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 776c719 commit 2cfc2ba

File tree

2 files changed

+56
-20
lines changed

2 files changed

+56
-20
lines changed

packages/anchor/src/index.js

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,37 +13,50 @@ export default function (Alpine) {
1313
}
1414
})
1515

16-
Alpine.directive('anchor', Alpine.skipDuringClone((el, { expression, modifiers, value }, { cleanup, evaluate }) => {
16+
Alpine.directive('anchor', Alpine.skipDuringClone((el, { expression, modifiers, value }, { evaluate, effect, cleanup }) => {
1717
let { placement, offsetValue, unstyled } = getOptions(modifiers)
1818

1919
el._x_anchor = Alpine.reactive({ x: 0, y: 0 })
2020

21-
let reference = evaluate(expression)
21+
let previousReference = null
22+
let release = null
2223

23-
if (! reference) throw 'Alpine: no element provided to x-anchor...'
24+
let effector = effect(() => {
25+
let reference = evaluate(expression)
26+
if (! reference) throw 'Alpine: no element provided to x-anchor...'
2427

25-
let compute = () => {
26-
let previousValue
28+
if (previousReference !== reference) {
29+
if (release) release()
2730

28-
computePosition(reference, el, {
29-
placement,
30-
middleware: [flip(), shift({padding: 5}), offset(offsetValue)],
31-
}).then(({ x, y }) => {
32-
unstyled || setStyles(el, x, y)
31+
previousReference = reference
3332

34-
// Only trigger Alpine reactivity when the value actually changes...
35-
if (JSON.stringify({ x, y }) !== previousValue) {
36-
el._x_anchor.x = x
37-
el._x_anchor.y = y
38-
}
33+
let compute = () => {
34+
let previousValue
3935

40-
previousValue = JSON.stringify({ x, y })
41-
})
42-
}
36+
computePosition(reference, el, {
37+
placement,
38+
middleware: [flip(), shift({padding: 5}), offset(offsetValue)],
39+
}).then(({ x, y }) => {
40+
unstyled || setStyles(el, x, y)
41+
42+
// Only trigger Alpine reactivity when the value actually changes...
43+
if (JSON.stringify({ x, y }) !== previousValue) {
44+
el._x_anchor.x = x
45+
el._x_anchor.y = y
46+
}
47+
48+
previousValue = JSON.stringify({ x, y })
49+
})
50+
}
4351

44-
let release = autoUpdate(reference, el, () => compute())
52+
release = autoUpdate(reference, el, () => compute())
53+
}
54+
})
4555

46-
cleanup(() => release())
56+
cleanup(() => {
57+
effector()
58+
if (release) release()
59+
})
4760
},
4861

4962
// When cloning (or "morphing"), we will graft the style and position data from the live tree...

tests/cypress/integration/plugins/anchor.spec.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,26 @@ test('can anchor an element',
1111
get('h1').should(haveComputedStyle('position', 'absolute'))
1212
},
1313
)
14+
15+
test('can anchor to dynamic reference',
16+
[html`
17+
<div x-data="{ reference: null }" x-init="reference = document.getElementById('foo')">
18+
<button id="foo">toggle foo</button>
19+
<button id="bar" @click="reference = $el">toggle bar</button>
20+
<h1 id="baz" x-anchor="reference">contents</h1>
21+
</div>
22+
`],
23+
({ get }) => {
24+
let originalLeft
25+
26+
get('#baz').then($el => {
27+
originalLeft = $el[0].style.left
28+
})
29+
30+
get('#bar').click()
31+
32+
get('#baz').should($el => {
33+
expect($el[0].style.left).not.to.eq(originalLeft)
34+
})
35+
}
36+
)

0 commit comments

Comments
 (0)