diff --git a/morph.html b/morph.html index bf68136f7..e1738670b 100644 --- a/morph.html +++ b/morph.html @@ -5,44 +5,21 @@
-
-
-
- - - -
- - Child -
- -
-
-
+
+

@@ -69,7 +46,7 @@
             Alpine.morph(
                 document.querySelector('#before').firstElementChild,
                 document.querySelector('#after').firstElementChild.outerHTML,
-                { debug: true }
+                { debug: true, key(el) { return el.dataset.key } }
             )
         }
 
diff --git a/packages/alpinejs/src/alpine.js b/packages/alpinejs/src/alpine.js
index 39331ffc0..afd8e7c39 100644
--- a/packages/alpinejs/src/alpine.js
+++ b/packages/alpinejs/src/alpine.js
@@ -5,7 +5,7 @@ import { onElRemoved, onAttributeRemoved, onAttributesAdded, mutateDom, deferMut
 import { mergeProxies, closestDataStack, addScopeToNode, scope as $data } from './scope'
 import { setEvaluator, evaluate, evaluateLater, dontAutoEvaluateFunctions } from './evaluator'
 import { transition } from './directives/x-transition'
-import { clone, skipDuringClone, onlyDuringClone } from './clone'
+import { clone, cloneNode, skipDuringClone, onlyDuringClone } from './clone'
 import { interceptor } from './interceptor'
 import { getBinding as bound, extractProp } from './utils/bind'
 import { debounce } from './utils/debounce'
@@ -68,7 +68,8 @@ let Alpine = {
     magic,
     store,
     start,
-    clone,
+    clone, // INTERNAL
+    cloneNode, // INTERNAL
     bound,
     $data,
     walk,
diff --git a/packages/alpinejs/src/clone.js b/packages/alpinejs/src/clone.js
index 3e870c8c3..0089bbd73 100644
--- a/packages/alpinejs/src/clone.js
+++ b/packages/alpinejs/src/clone.js
@@ -12,22 +12,53 @@ export function onlyDuringClone(callback) {
     return (...args) => isCloning && callback(...args)
 }
 
-export function interuptCrawl(callback) {
-    return (...args) => isCloning || callback(...args)
+export function cloneNode(from, to)
+{
+    // Transfer over existing runtime Alpine state from
+    // the existing dom tree over to the new one...
+    if (from._x_dataStack) {
+        to._x_dataStack = from._x_dataStack
+
+        // Set a flag to signify the new tree is using
+        // pre-seeded state (used so x-data knows when
+        // and when not to initialize state)...
+        to.setAttribute('data-has-alpine-state', true)
+    }
+
+    isCloning = true
+
+    // We don't need reactive effects in the new tree.
+    // Cloning is just used to seed new server HTML with
+    // Alpine before "morphing" it onto live Alpine...
+    dontRegisterReactiveSideEffects(() => {
+        initTree(to, (el, callback) => {
+            // We're hijacking the "walker" so that we
+            // only initialize the element we're cloning...
+            callback(el, () => {})
+        })
+    })
+
+    isCloning = false
 }
 
+let isCloningLegacy = false
+
+/** deprecated */
 export function clone(oldEl, newEl) {
     if (! newEl._x_dataStack) newEl._x_dataStack = oldEl._x_dataStack
 
     isCloning = true
+    isCloningLegacy = true
 
     dontRegisterReactiveSideEffects(() => {
         cloneTree(newEl)
     })
 
     isCloning = false
+    isCloningLegacy = false
 }
 
+/** deprecated */
 export function cloneTree(el) {
     let hasRunThroughFirstEl = false
 
@@ -59,3 +90,15 @@ function dontRegisterReactiveSideEffects(callback) {
 
     overrideEffect(cache)
 }
+
+// If we are cloning a tree, we only want to evaluate x-data if another
+// x-data context DOESN'T exist on the component.
+// The reason a data context WOULD exist is that we graft root x-data state over
+// from the live tree before hydrating the clone tree.
+export function shouldSkipRegisteringDataDuringClone(el) {
+    if (! isCloning) return false
+    if (isCloningLegacy) return true
+
+    return el.hasAttribute('data-has-alpine-state')
+}
+
diff --git a/packages/alpinejs/src/directives/x-data.js b/packages/alpinejs/src/directives/x-data.js
index 734b6b29e..c5723b3b4 100644
--- a/packages/alpinejs/src/directives/x-data.js
+++ b/packages/alpinejs/src/directives/x-data.js
@@ -2,7 +2,7 @@ import { directive, prefix } from '../directives'
 import { initInterceptors } from '../interceptor'
 import { injectDataProviders } from '../datas'
 import { addRootSelector } from '../lifecycle'
-import { isCloning } from '../clone'
+import { shouldSkipRegisteringDataDuringClone } from '../clone'
 import { addScopeToNode } from '../scope'
 import { injectMagics, magic } from '../magics'
 import { reactive } from '../reactivity'
@@ -11,11 +11,7 @@ import { evaluate } from '../evaluator'
 addRootSelector(() => `[${prefix('data')}]`)
 
 directive('data', ((el, { expression }, { cleanup }) => {
-    // If we are cloning a tree, we only want to evaluate x-data if another
-    // x-data context DOESN'T exist on the component.
-    // The reason a data context WOULD exist is that we graft root x-data state over
-    // from the live tree before hydrating the clone tree.
-    if (isCloning && el._x_dataStack) return;
+    if (shouldSkipRegisteringDataDuringClone(el)) return
 
     expression = expression === '' ? '{}' : expression
 
diff --git a/packages/morph/src/dom.js b/packages/morph/src/dom.js
deleted file mode 100644
index e6dc656d4..000000000
--- a/packages/morph/src/dom.js
+++ /dev/null
@@ -1,85 +0,0 @@
-
-export function createElement(html) {
-    const template = document.createElement('template')
-    template.innerHTML = html
-    return template.content.firstElementChild
-}
-
-export function textOrComment(el) {
-    return el.nodeType === 3
-        || el.nodeType === 8
-}
-
-export let dom = {
-    replace(children, old, replacement) {
-        // Here's what's happening here:
-        // First, we're swapping the actual dom element with the new one
-        // Then, we're replaceing the old one with the new one in the children array
-        // Finally, because the old has been replaced by the new, we can remove the previous new element in it's old position...
-        let index = children.indexOf(old)
-
-        let replacementIndex = children.indexOf(replacement)
-
-        if (index === -1) throw 'Cant find element in children'
-
-        old.replaceWith(replacement)
-
-        children[index] = replacement
-
-        if (replacementIndex) {
-            children.splice(replacementIndex, 1)
-        }
-
-        return children
-    },
-    before(children, reference, subject) {
-        let index = children.indexOf(reference)
-
-        if (index === -1) throw 'Cant find element in children'
-
-        reference.before(subject)
-
-        children.splice(index, 0, subject)
-
-        return children
-    },
-    append(children, subject, appendFn) {
-        let last = children[children.length - 1]
-
-        appendFn(subject)
-
-        children.push(subject)
-
-        return children
-    },
-    remove(children, subject) {
-        let index = children.indexOf(subject)
-
-        if (index === -1) throw 'Cant find element in children'
-
-        subject.remove()
-
-        return children.filter(i => i !== subject)
-    },
-    first(children) {
-        return this.teleportTo(children[0])
-    },
-    next(children, reference) {
-        let index = children.indexOf(reference)
-
-        if (index === -1) return
-
-        return this.teleportTo(this.teleportBack(children[index + 1]))
-    },
-    teleportTo(el) {
-        if (! el) return el
-        if (el._x_teleport) return el._x_teleport
-        return el
-    },
-    teleportBack(el) {
-        if (! el) return el
-        if (el._x_teleportBack) return el._x_teleportBack
-        return el
-    }
-}
-
diff --git a/packages/morph/src/morph.js b/packages/morph/src/morph.js
index 5dfd7e6f2..0bf2908e8 100644
--- a/packages/morph/src/morph.js
+++ b/packages/morph/src/morph.js
@@ -1,4 +1,3 @@
-import { dom, createElement, textOrComment} from './dom.js'
 
 let resolveStep = () => {}
 
@@ -13,19 +12,13 @@ export function morph(from, toHtml, options) {
 
     let fromEl
     let toEl
-    let key
-        ,lookahead
-        ,updating
-        ,updated
-        ,removing
-        ,removed
-        ,adding
-        ,added
+    let key, lookahead, updating, updated, removing, removed, adding, added
 
     function assignOptions(options = {}) {
         let defaultGetKey = el => el.getAttribute('key')
         let noop = () => {}
 
+        console.log(options.key)
         updating = options.updating || noop
         updated = options.updated || noop
         removing = options.removing || noop
@@ -37,25 +30,22 @@ export function morph(from, toHtml, options) {
     }
 
     function patch(from, to) {
-        // This is a time saver, however, it won't catch differences in nested