Kaynağa Gözat

Fix x-id when used with morph (#3919)

* fix x-id when used with morph

* fix $id caching mechanism

* remove .only

* fix tests
Caleb Porzio 1 yıl önce
ebeveyn
işleme
0effdaedbf

+ 12 - 1
packages/alpinejs/src/directives/x-id.js

@@ -1,8 +1,19 @@
+import { interceptClone } from "../clone"
 import { directive } from "../directives"
 import { directive } from "../directives"
 import { setIdRoot } from '../ids'
 import { setIdRoot } from '../ids'
 
 
 directive('id', (el, { expression }, { evaluate }) => {
 directive('id', (el, { expression }, { evaluate }) => {
     let names = evaluate(expression)
     let names = evaluate(expression)
-    
+
     names.forEach(name => setIdRoot(el, name))
     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
+    }
+})
+

+ 40 - 8
packages/alpinejs/src/magics/$id.js

@@ -1,14 +1,46 @@
 import { magic } from '../magics'
 import { magic } from '../magics'
 import { closestIdRoot, findAndIncrementId } from '../ids'
 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
+}

+ 32 - 1
tests/cypress/integration/magics/$id.spec.js

@@ -103,7 +103,7 @@ test('$id scopes can be reset',
             <div x-data>
             <div x-data>
                 <h1 :id="$id('foo')"></h1>
                 <h1 :id="$id('foo')"></h1>
                 <h5 :id="$id('bar')"></h5>
                 <h5 :id="$id('bar')"></h5>
-                
+
                 <div x-id="['foo']">
                 <div x-id="['foo']">
                     <h2 :aria-labelledby="$id('foo')"></h2>
                     <h2 :aria-labelledby="$id('foo')"></h2>
                     <h6 :aria-labelledby="$id('bar')"></h6>
                     <h6 :aria-labelledby="$id('bar')"></h6>
@@ -127,3 +127,34 @@ test('$id scopes can be reset',
         get('h6').should(haveAttribute('aria-labelledby', 'bar-1'))
         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'))
+    },
+)