Skip to content

Commit

Permalink
Fix x-id when used with morph (#3919)
Browse files Browse the repository at this point in the history
* fix x-id when used with morph

* fix $id caching mechanism

* remove .only

* fix tests
  • Loading branch information
calebporzio authored Dec 12, 2023
1 parent 3d75916 commit 0effdae
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 10 deletions.
13 changes: 12 additions & 1 deletion packages/alpinejs/src/directives/x-id.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import { interceptClone } from "../clone"
import { directive } from "../directives"
import { setIdRoot } from '../ids'

directive('id', (el, { expression }, { evaluate }) => {
let names = evaluate(expression)

names.forEach(name => setIdRoot(el, name))
})

interceptClone((from, to) => {
// Transfer over existing ID registrations from
// the existing dom tree over to the new one
// so that there aren't ID mismatches...
if (from._x_ids) {
to._x_ids = from._x_ids
}
})

48 changes: 40 additions & 8 deletions packages/alpinejs/src/magics/$id.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,46 @@
import { magic } from '../magics'
import { closestIdRoot, findAndIncrementId } from '../ids'
import { interceptClone } from '../clone'

magic('id', el => (name, key = null) => {
let root = closestIdRoot(el, name)
magic('id', (el, { cleanup }) => (name, key = null) => {
let cacheKey = `${name}${key ? `-${key}` : ''}`

let id = root
? root._x_ids[name]
: findAndIncrementId(name)
return cacheIdByNameOnElement(el, cacheKey, cleanup, () => {
let root = closestIdRoot(el, name)

return key
? `${name}-${id}-${key}`
: `${name}-${id}`
let id = root
? root._x_ids[name]
: findAndIncrementId(name)

return key
? `${name}-${id}-${key}`
: `${name}-${id}`
})
})

interceptClone((from, to) => {
// Transfer over existing ID registrations from
// the existing dom tree over to the new one
// so that there aren't ID mismatches...
if (from._x_id) {
to._x_id = from._x_id
}
})

function cacheIdByNameOnElement(el, cacheKey, cleanup, callback)
{
if (! el._x_id) el._x_id = {}

// We only want $id to run once per an element's lifecycle...
if (el._x_id[cacheKey]) return el._x_id[cacheKey]

let output = callback()

el._x_id[cacheKey] = output

cleanup(() => {
delete el._x_id[cacheKey]
})

return output
}
33 changes: 32 additions & 1 deletion tests/cypress/integration/magics/$id.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ test('$id scopes can be reset',
<div x-data>
<h1 :id="$id('foo')"></h1>
<h5 :id="$id('bar')"></h5>
<div x-id="['foo']">
<h2 :aria-labelledby="$id('foo')"></h2>
<h6 :aria-labelledby="$id('bar')"></h6>
Expand All @@ -127,3 +127,34 @@ test('$id scopes can be reset',
get('h6').should(haveAttribute('aria-labelledby', 'bar-1'))
}
)

test('can be used with morph without losing track',
[html`
<div x-data>
<p x-id="['foo']">
<span :id="$id('foo')">bob</span>
</p>
<h1 :id="$id('bar')">lob</h1>
</div>
`],
({ get }, reload, window, document) => {
let toHtml = html`
<div x-data>
<p x-id="['foo']">
<span :id="$id('foo')">bob</span>
</p>
<h1 :id="$id('bar')">lob</h1>
</div>
`

get('span').should(haveAttribute('id', 'foo-1'))
get('h1').should(haveAttribute('id', 'bar-1'))

get('div').then(([el]) => window.Alpine.morph(el, toHtml))

get('span').should(haveAttribute('id', 'foo-1'))
get('h1').should(haveAttribute('id', 'bar-1'))
},
)

0 comments on commit 0effdae

Please sign in to comment.