Browse Source

Cleanup nested removed element's attributes

Caleb Porzio 3 years ago
parent
commit
3825a13659

+ 2 - 16
packages/alpinejs/src/lifecycle.js

@@ -1,4 +1,4 @@
-import { startObservingMutations, onAttributesAdded, onElAdded, onElRemoved } from "./mutation"
+import { startObservingMutations, onAttributesAdded, onElAdded, onElRemoved, cleanupAttributes } from "./mutation"
 import { deferHandlingDirectives, directives } from "./directives"
 import { dispatch } from './utils/dispatch'
 import { nextTick } from "./nextTick"
@@ -61,20 +61,6 @@ export function initTree(el, walker = walk) {
     })
 }
 
-let onDestroys = new WeakMap
-
-export function onDestroy(el, callback) {
-    if (! onDestroys.get(el)) onDestroys.set(el, [])
-
-    onDestroys.get(el).push(callback)
-}
-
 function destroyTree(root) {
-    walk(root, el => {
-        let callbacks = onDestroys.get(el)
-
-        callbacks && callbacks.forEach(callback => callback())
-
-        onDestroys.delete(el)
-    })
+    walk(root, el => cleanupAttributes(el))
 }

+ 16 - 33
packages/alpinejs/src/mutation.js

@@ -1,6 +1,4 @@
-let onAttributeRemoveds = new WeakMap
 let onAttributeAddeds = []
-let onElRemovedByEl = new WeakMap
 let onElRemoveds = []
 let onElAddeds = []
 
@@ -8,14 +6,8 @@ export function onElAdded(callback) {
     onElAddeds.push(callback)
 }
 
-export function onElRemoved(el, callback) {
-    if (typeof el === 'function' && callback === undefined) {
-        onElRemoveds.push(el)
-    } else {
-        if (! onElRemovedByEl.has(el)) onElRemovedByEl.set(el, [])
-
-        onElRemovedByEl.get(el).push(callback)
-    }
+export function onElRemoved(callback) {
+    onElRemoveds.push(callback)
 }
 
 export function onAttributesAdded(callback) {
@@ -23,10 +15,20 @@ export function onAttributesAdded(callback) {
 }
 
 export function onAttributeRemoved(el, name, callback) {
-    if (! onAttributeRemoveds.has(el)) onAttributeRemoveds.set(el, {})
-    if (! onAttributeRemoveds.get(el)[name]) onAttributeRemoveds.get(el)[name] = []
+    if (! el._x_attributeCleanups) el._x_attributeCleanups = {}
+    if (! el._x_attributeCleanups[name]) el._x_attributeCleanups[name] = []
+
+    el._x_attributeCleanups[name].push(callback)
+}
 
-    onAttributeRemoveds.get(el)[name].push(callback)
+export function cleanupAttributes(el, names) {
+    if (! el._x_attributeCleanups) return
+
+    Object.entries(el._x_attributeCleanups).forEach(([name, value]) => {
+        (names === undefined || names.includes(name)) && value.forEach(i => i())
+
+        delete el._x_attributeCleanups[name]
+    })
 }
 
 let observer = new MutationObserver(onMutate)
@@ -128,13 +130,7 @@ function onMutate(mutations) {
     }
 
     removedAttributes.forEach((attrs, el) => {
-        if (onAttributeRemoveds.get(el)) {
-            attrs.forEach(name => {
-                if (onAttributeRemoveds.get(el)[name]) {
-                    onAttributeRemoveds.get(el)[name].forEach(i => i())
-                }
-            })
-        }
+        cleanupAttributes(el, attrs)
     })
 
     addedAttributes.forEach((attrs, el) => {
@@ -154,19 +150,6 @@ function onMutate(mutations) {
         // as both an "add" and "remove", so we want to skip those.
         if (addedNodes.includes(node)) continue
 
-
-        if (onAttributeRemoveds.has(node)) {
-            Object.entries(onAttributeRemoveds.get(node)).forEach(([key, value]) => {
-                value.forEach(i => i())
-            })
-            onAttributeRemoveds.delete(node)
-        }
-
-        if (onElRemovedByEl.has(node)) {
-            onElRemovedByEl.get(node).forEach(i => i())
-            onElRemovedByEl.delete(node)
-        }
-
         onElRemoveds.forEach(i => i(node))
     }
 

+ 27 - 0
tests/cypress/integration/mutation.spec.js

@@ -25,6 +25,33 @@ test('element side effects are cleaned up after the elements are removed',
     }
 )
 
+test('nested element side effects are cleaned up after the parent is removed',
+    html`
+        <div x-data="{ foo: 1, bar: 1 }">
+            <button @click="bar++">bar</button>
+            <a href="#" @click.prevent="$refs.article.remove()">remove</a>
+
+            <article x-ref="article">
+                <span x-text="(() => { foo = foo + 1; return bar })"></span>
+            </article>
+
+            <h1 x-text="foo"></h1>
+            <h2 x-text="bar"></h2>
+        </div>
+    `,
+    ({ get }) => {
+        get('h1').should(haveText('2'))
+        get('h2').should(haveText('1'))
+        get('button').click()
+        get('h1').should(haveText('3'))
+        get('h2').should(haveText('2'))
+        get('a').click()
+        get('button').click()
+        get('h1').should(haveText('3'))
+        get('h2').should(haveText('3'))
+    }
+)
+
 test('can mutate directive value',
     html`
         <div x-data="{ foo: 'bar', bar: 'baz' }">