Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'))
},
)