777 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			777 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
'use strict';
 | 
						|
 | 
						|
var DOCUMENT_FRAGMENT_NODE = 11;
 | 
						|
 | 
						|
function morphAttrs(fromNode, toNode) {
 | 
						|
    var toNodeAttrs = toNode.attributes;
 | 
						|
    var attr;
 | 
						|
    var attrName;
 | 
						|
    var attrNamespaceURI;
 | 
						|
    var attrValue;
 | 
						|
    var fromValue;
 | 
						|
 | 
						|
    // document-fragments dont have attributes so lets not do anything
 | 
						|
    if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE || fromNode.nodeType === DOCUMENT_FRAGMENT_NODE) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // update attributes on original DOM element
 | 
						|
    for (var i = toNodeAttrs.length - 1; i >= 0; i--) {
 | 
						|
        attr = toNodeAttrs[i];
 | 
						|
        attrName = attr.name;
 | 
						|
        attrNamespaceURI = attr.namespaceURI;
 | 
						|
        attrValue = attr.value;
 | 
						|
 | 
						|
        if (attrNamespaceURI) {
 | 
						|
            attrName = attr.localName || attrName;
 | 
						|
            fromValue = fromNode.getAttributeNS(attrNamespaceURI, attrName);
 | 
						|
 | 
						|
            if (fromValue !== attrValue) {
 | 
						|
                if (attr.prefix === 'xmlns'){
 | 
						|
                    attrName = attr.name; // It's not allowed to set an attribute with the XMLNS namespace without specifying the `xmlns` prefix
 | 
						|
                }
 | 
						|
                fromNode.setAttributeNS(attrNamespaceURI, attrName, attrValue);
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            fromValue = fromNode.getAttribute(attrName);
 | 
						|
 | 
						|
            if (fromValue !== attrValue) {
 | 
						|
                fromNode.setAttribute(attrName, attrValue);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // Remove any extra attributes found on the original DOM element that
 | 
						|
    // weren't found on the target element.
 | 
						|
    var fromNodeAttrs = fromNode.attributes;
 | 
						|
 | 
						|
    for (var d = fromNodeAttrs.length - 1; d >= 0; d--) {
 | 
						|
        attr = fromNodeAttrs[d];
 | 
						|
        attrName = attr.name;
 | 
						|
        attrNamespaceURI = attr.namespaceURI;
 | 
						|
 | 
						|
        if (attrNamespaceURI) {
 | 
						|
            attrName = attr.localName || attrName;
 | 
						|
 | 
						|
            if (!toNode.hasAttributeNS(attrNamespaceURI, attrName)) {
 | 
						|
                fromNode.removeAttributeNS(attrNamespaceURI, attrName);
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            if (!toNode.hasAttribute(attrName)) {
 | 
						|
                fromNode.removeAttribute(attrName);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
var range; // Create a range object for efficently rendering strings to elements.
 | 
						|
var NS_XHTML = 'http://www.w3.org/1999/xhtml';
 | 
						|
 | 
						|
var doc = typeof document === 'undefined' ? undefined : document;
 | 
						|
var HAS_TEMPLATE_SUPPORT = !!doc && 'content' in doc.createElement('template');
 | 
						|
var HAS_RANGE_SUPPORT = !!doc && doc.createRange && 'createContextualFragment' in doc.createRange();
 | 
						|
 | 
						|
function createFragmentFromTemplate(str) {
 | 
						|
    var template = doc.createElement('template');
 | 
						|
    template.innerHTML = str;
 | 
						|
    return template.content.childNodes[0];
 | 
						|
}
 | 
						|
 | 
						|
function createFragmentFromRange(str) {
 | 
						|
    if (!range) {
 | 
						|
        range = doc.createRange();
 | 
						|
        range.selectNode(doc.body);
 | 
						|
    }
 | 
						|
 | 
						|
    var fragment = range.createContextualFragment(str);
 | 
						|
    return fragment.childNodes[0];
 | 
						|
}
 | 
						|
 | 
						|
function createFragmentFromWrap(str) {
 | 
						|
    var fragment = doc.createElement('body');
 | 
						|
    fragment.innerHTML = str;
 | 
						|
    return fragment.childNodes[0];
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * This is about the same
 | 
						|
 * var html = new DOMParser().parseFromString(str, 'text/html');
 | 
						|
 * return html.body.firstChild;
 | 
						|
 *
 | 
						|
 * @method toElement
 | 
						|
 * @param {String} str
 | 
						|
 */
 | 
						|
function toElement(str) {
 | 
						|
    str = str.trim();
 | 
						|
    if (HAS_TEMPLATE_SUPPORT) {
 | 
						|
      // avoid restrictions on content for things like `<tr><th>Hi</th></tr>` which
 | 
						|
      // createContextualFragment doesn't support
 | 
						|
      // <template> support not available in IE
 | 
						|
      return createFragmentFromTemplate(str);
 | 
						|
    } else if (HAS_RANGE_SUPPORT) {
 | 
						|
      return createFragmentFromRange(str);
 | 
						|
    }
 | 
						|
 | 
						|
    return createFragmentFromWrap(str);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Returns true if two node's names are the same.
 | 
						|
 *
 | 
						|
 * NOTE: We don't bother checking `namespaceURI` because you will never find two HTML elements with the same
 | 
						|
 *       nodeName and different namespace URIs.
 | 
						|
 *
 | 
						|
 * @param {Element} a
 | 
						|
 * @param {Element} b The target element
 | 
						|
 * @return {boolean}
 | 
						|
 */
 | 
						|
function compareNodeNames(fromEl, toEl) {
 | 
						|
    var fromNodeName = fromEl.nodeName;
 | 
						|
    var toNodeName = toEl.nodeName;
 | 
						|
    var fromCodeStart, toCodeStart;
 | 
						|
 | 
						|
    if (fromNodeName === toNodeName) {
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    fromCodeStart = fromNodeName.charCodeAt(0);
 | 
						|
    toCodeStart = toNodeName.charCodeAt(0);
 | 
						|
 | 
						|
    // If the target element is a virtual DOM node or SVG node then we may
 | 
						|
    // need to normalize the tag name before comparing. Normal HTML elements that are
 | 
						|
    // in the "http://www.w3.org/1999/xhtml"
 | 
						|
    // are converted to upper case
 | 
						|
    if (fromCodeStart <= 90 && toCodeStart >= 97) { // from is upper and to is lower
 | 
						|
        return fromNodeName === toNodeName.toUpperCase();
 | 
						|
    } else if (toCodeStart <= 90 && fromCodeStart >= 97) { // to is upper and from is lower
 | 
						|
        return toNodeName === fromNodeName.toUpperCase();
 | 
						|
    } else {
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Create an element, optionally with a known namespace URI.
 | 
						|
 *
 | 
						|
 * @param {string} name the element name, e.g. 'div' or 'svg'
 | 
						|
 * @param {string} [namespaceURI] the element's namespace URI, i.e. the value of
 | 
						|
 * its `xmlns` attribute or its inferred namespace.
 | 
						|
 *
 | 
						|
 * @return {Element}
 | 
						|
 */
 | 
						|
function createElementNS(name, namespaceURI) {
 | 
						|
    return !namespaceURI || namespaceURI === NS_XHTML ?
 | 
						|
        doc.createElement(name) :
 | 
						|
        doc.createElementNS(namespaceURI, name);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Copies the children of one DOM element to another DOM element
 | 
						|
 */
 | 
						|
function moveChildren(fromEl, toEl) {
 | 
						|
    var curChild = fromEl.firstChild;
 | 
						|
    while (curChild) {
 | 
						|
        var nextChild = curChild.nextSibling;
 | 
						|
        toEl.appendChild(curChild);
 | 
						|
        curChild = nextChild;
 | 
						|
    }
 | 
						|
    return toEl;
 | 
						|
}
 | 
						|
 | 
						|
function syncBooleanAttrProp(fromEl, toEl, name) {
 | 
						|
    if (fromEl[name] !== toEl[name]) {
 | 
						|
        fromEl[name] = toEl[name];
 | 
						|
        if (fromEl[name]) {
 | 
						|
            fromEl.setAttribute(name, '');
 | 
						|
        } else {
 | 
						|
            fromEl.removeAttribute(name);
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
var specialElHandlers = {
 | 
						|
    OPTION: function(fromEl, toEl) {
 | 
						|
        var parentNode = fromEl.parentNode;
 | 
						|
        if (parentNode) {
 | 
						|
            var parentName = parentNode.nodeName.toUpperCase();
 | 
						|
            if (parentName === 'OPTGROUP') {
 | 
						|
                parentNode = parentNode.parentNode;
 | 
						|
                parentName = parentNode && parentNode.nodeName.toUpperCase();
 | 
						|
            }
 | 
						|
            if (parentName === 'SELECT' && !parentNode.hasAttribute('multiple')) {
 | 
						|
                if (fromEl.hasAttribute('selected') && !toEl.selected) {
 | 
						|
                    // Workaround for MS Edge bug where the 'selected' attribute can only be
 | 
						|
                    // removed if set to a non-empty value:
 | 
						|
                    // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12087679/
 | 
						|
                    fromEl.setAttribute('selected', 'selected');
 | 
						|
                    fromEl.removeAttribute('selected');
 | 
						|
                }
 | 
						|
                // We have to reset select element's selectedIndex to -1, otherwise setting
 | 
						|
                // fromEl.selected using the syncBooleanAttrProp below has no effect.
 | 
						|
                // The correct selectedIndex will be set in the SELECT special handler below.
 | 
						|
                parentNode.selectedIndex = -1;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        syncBooleanAttrProp(fromEl, toEl, 'selected');
 | 
						|
    },
 | 
						|
    /**
 | 
						|
     * The "value" attribute is special for the <input> element since it sets
 | 
						|
     * the initial value. Changing the "value" attribute without changing the
 | 
						|
     * "value" property will have no effect since it is only used to the set the
 | 
						|
     * initial value.  Similar for the "checked" attribute, and "disabled".
 | 
						|
     */
 | 
						|
    INPUT: function(fromEl, toEl) {
 | 
						|
        syncBooleanAttrProp(fromEl, toEl, 'checked');
 | 
						|
        syncBooleanAttrProp(fromEl, toEl, 'disabled');
 | 
						|
 | 
						|
        if (fromEl.value !== toEl.value) {
 | 
						|
            fromEl.value = toEl.value;
 | 
						|
        }
 | 
						|
 | 
						|
        if (!toEl.hasAttribute('value')) {
 | 
						|
            fromEl.removeAttribute('value');
 | 
						|
        }
 | 
						|
    },
 | 
						|
 | 
						|
    TEXTAREA: function(fromEl, toEl) {
 | 
						|
        var newValue = toEl.value;
 | 
						|
        if (fromEl.value !== newValue) {
 | 
						|
            fromEl.value = newValue;
 | 
						|
        }
 | 
						|
 | 
						|
        var firstChild = fromEl.firstChild;
 | 
						|
        if (firstChild) {
 | 
						|
            // Needed for IE. Apparently IE sets the placeholder as the
 | 
						|
            // node value and vise versa. This ignores an empty update.
 | 
						|
            var oldValue = firstChild.nodeValue;
 | 
						|
 | 
						|
            if (oldValue == newValue || (!newValue && oldValue == fromEl.placeholder)) {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            firstChild.nodeValue = newValue;
 | 
						|
        }
 | 
						|
    },
 | 
						|
    SELECT: function(fromEl, toEl) {
 | 
						|
        if (!toEl.hasAttribute('multiple')) {
 | 
						|
            var selectedIndex = -1;
 | 
						|
            var i = 0;
 | 
						|
            // We have to loop through children of fromEl, not toEl since nodes can be moved
 | 
						|
            // from toEl to fromEl directly when morphing.
 | 
						|
            // At the time this special handler is invoked, all children have already been morphed
 | 
						|
            // and appended to / removed from fromEl, so using fromEl here is safe and correct.
 | 
						|
            var curChild = fromEl.firstChild;
 | 
						|
            var optgroup;
 | 
						|
            var nodeName;
 | 
						|
            while(curChild) {
 | 
						|
                nodeName = curChild.nodeName && curChild.nodeName.toUpperCase();
 | 
						|
                if (nodeName === 'OPTGROUP') {
 | 
						|
                    optgroup = curChild;
 | 
						|
                    curChild = optgroup.firstChild;
 | 
						|
                    // handle empty optgroups
 | 
						|
                    if (!curChild) {
 | 
						|
                        curChild = optgroup.nextSibling;
 | 
						|
                        optgroup = null;
 | 
						|
                    }
 | 
						|
                } else {
 | 
						|
                    if (nodeName === 'OPTION') {
 | 
						|
                        if (curChild.hasAttribute('selected')) {
 | 
						|
                            selectedIndex = i;
 | 
						|
                            break;
 | 
						|
                        }
 | 
						|
                        i++;
 | 
						|
                    }
 | 
						|
                    curChild = curChild.nextSibling;
 | 
						|
                    if (!curChild && optgroup) {
 | 
						|
                        curChild = optgroup.nextSibling;
 | 
						|
                        optgroup = null;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            fromEl.selectedIndex = selectedIndex;
 | 
						|
        }
 | 
						|
    }
 | 
						|
};
 | 
						|
 | 
						|
var ELEMENT_NODE = 1;
 | 
						|
var DOCUMENT_FRAGMENT_NODE$1 = 11;
 | 
						|
var TEXT_NODE = 3;
 | 
						|
var COMMENT_NODE = 8;
 | 
						|
 | 
						|
function noop() {}
 | 
						|
 | 
						|
function defaultGetNodeKey(node) {
 | 
						|
  if (node) {
 | 
						|
    return (node.getAttribute && node.getAttribute('id')) || node.id;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function morphdomFactory(morphAttrs) {
 | 
						|
 | 
						|
  return function morphdom(fromNode, toNode, options) {
 | 
						|
    if (!options) {
 | 
						|
      options = {};
 | 
						|
    }
 | 
						|
 | 
						|
    if (typeof toNode === 'string') {
 | 
						|
      if (fromNode.nodeName === '#document' || fromNode.nodeName === 'HTML' || fromNode.nodeName === 'BODY') {
 | 
						|
        var toNodeHtml = toNode;
 | 
						|
        toNode = doc.createElement('html');
 | 
						|
        toNode.innerHTML = toNodeHtml;
 | 
						|
      } else {
 | 
						|
        toNode = toElement(toNode);
 | 
						|
      }
 | 
						|
    } else if (toNode.nodeType === DOCUMENT_FRAGMENT_NODE$1) {
 | 
						|
      toNode = toNode.firstElementChild;
 | 
						|
    }
 | 
						|
 | 
						|
    var getNodeKey = options.getNodeKey || defaultGetNodeKey;
 | 
						|
    var onBeforeNodeAdded = options.onBeforeNodeAdded || noop;
 | 
						|
    var onNodeAdded = options.onNodeAdded || noop;
 | 
						|
    var onBeforeElUpdated = options.onBeforeElUpdated || noop;
 | 
						|
    var onElUpdated = options.onElUpdated || noop;
 | 
						|
    var onBeforeNodeDiscarded = options.onBeforeNodeDiscarded || noop;
 | 
						|
    var onNodeDiscarded = options.onNodeDiscarded || noop;
 | 
						|
    var onBeforeElChildrenUpdated = options.onBeforeElChildrenUpdated || noop;
 | 
						|
    var skipFromChildren = options.skipFromChildren || noop;
 | 
						|
    var addChild = options.addChild || function(parent, child){ return parent.appendChild(child); };
 | 
						|
    var childrenOnly = options.childrenOnly === true;
 | 
						|
 | 
						|
    // This object is used as a lookup to quickly find all keyed elements in the original DOM tree.
 | 
						|
    var fromNodesLookup = Object.create(null);
 | 
						|
    var keyedRemovalList = [];
 | 
						|
 | 
						|
    function addKeyedRemoval(key) {
 | 
						|
      keyedRemovalList.push(key);
 | 
						|
    }
 | 
						|
 | 
						|
    function walkDiscardedChildNodes(node, skipKeyedNodes) {
 | 
						|
      if (node.nodeType === ELEMENT_NODE) {
 | 
						|
        var curChild = node.firstChild;
 | 
						|
        while (curChild) {
 | 
						|
 | 
						|
          var key = undefined;
 | 
						|
 | 
						|
          if (skipKeyedNodes && (key = getNodeKey(curChild))) {
 | 
						|
            // If we are skipping keyed nodes then we add the key
 | 
						|
            // to a list so that it can be handled at the very end.
 | 
						|
            addKeyedRemoval(key);
 | 
						|
          } else {
 | 
						|
            // Only report the node as discarded if it is not keyed. We do this because
 | 
						|
            // at the end we loop through all keyed elements that were unmatched
 | 
						|
            // and then discard them in one final pass.
 | 
						|
            onNodeDiscarded(curChild);
 | 
						|
            if (curChild.firstChild) {
 | 
						|
              walkDiscardedChildNodes(curChild, skipKeyedNodes);
 | 
						|
            }
 | 
						|
          }
 | 
						|
 | 
						|
          curChild = curChild.nextSibling;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
    * Removes a DOM node out of the original DOM
 | 
						|
    *
 | 
						|
    * @param  {Node} node The node to remove
 | 
						|
    * @param  {Node} parentNode The nodes parent
 | 
						|
    * @param  {Boolean} skipKeyedNodes If true then elements with keys will be skipped and not discarded.
 | 
						|
    * @return {undefined}
 | 
						|
    */
 | 
						|
    function removeNode(node, parentNode, skipKeyedNodes) {
 | 
						|
      if (onBeforeNodeDiscarded(node) === false) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      if (parentNode) {
 | 
						|
        parentNode.removeChild(node);
 | 
						|
      }
 | 
						|
 | 
						|
      onNodeDiscarded(node);
 | 
						|
      walkDiscardedChildNodes(node, skipKeyedNodes);
 | 
						|
    }
 | 
						|
 | 
						|
    // // TreeWalker implementation is no faster, but keeping this around in case this changes in the future
 | 
						|
    // function indexTree(root) {
 | 
						|
    //     var treeWalker = document.createTreeWalker(
 | 
						|
    //         root,
 | 
						|
    //         NodeFilter.SHOW_ELEMENT);
 | 
						|
    //
 | 
						|
    //     var el;
 | 
						|
    //     while((el = treeWalker.nextNode())) {
 | 
						|
    //         var key = getNodeKey(el);
 | 
						|
    //         if (key) {
 | 
						|
    //             fromNodesLookup[key] = el;
 | 
						|
    //         }
 | 
						|
    //     }
 | 
						|
    // }
 | 
						|
 | 
						|
    // // NodeIterator implementation is no faster, but keeping this around in case this changes in the future
 | 
						|
    //
 | 
						|
    // function indexTree(node) {
 | 
						|
    //     var nodeIterator = document.createNodeIterator(node, NodeFilter.SHOW_ELEMENT);
 | 
						|
    //     var el;
 | 
						|
    //     while((el = nodeIterator.nextNode())) {
 | 
						|
    //         var key = getNodeKey(el);
 | 
						|
    //         if (key) {
 | 
						|
    //             fromNodesLookup[key] = el;
 | 
						|
    //         }
 | 
						|
    //     }
 | 
						|
    // }
 | 
						|
 | 
						|
    function indexTree(node) {
 | 
						|
      if (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE$1) {
 | 
						|
        var curChild = node.firstChild;
 | 
						|
        while (curChild) {
 | 
						|
          var key = getNodeKey(curChild);
 | 
						|
          if (key) {
 | 
						|
            fromNodesLookup[key] = curChild;
 | 
						|
          }
 | 
						|
 | 
						|
          // Walk recursively
 | 
						|
          indexTree(curChild);
 | 
						|
 | 
						|
          curChild = curChild.nextSibling;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    indexTree(fromNode);
 | 
						|
 | 
						|
    function handleNodeAdded(el) {
 | 
						|
      onNodeAdded(el);
 | 
						|
 | 
						|
      var curChild = el.firstChild;
 | 
						|
      while (curChild) {
 | 
						|
        var nextSibling = curChild.nextSibling;
 | 
						|
 | 
						|
        var key = getNodeKey(curChild);
 | 
						|
        if (key) {
 | 
						|
          var unmatchedFromEl = fromNodesLookup[key];
 | 
						|
          // if we find a duplicate #id node in cache, replace `el` with cache value
 | 
						|
          // and morph it to the child node.
 | 
						|
          if (unmatchedFromEl && compareNodeNames(curChild, unmatchedFromEl)) {
 | 
						|
            curChild.parentNode.replaceChild(unmatchedFromEl, curChild);
 | 
						|
            morphEl(unmatchedFromEl, curChild);
 | 
						|
          } else {
 | 
						|
            handleNodeAdded(curChild);
 | 
						|
          }
 | 
						|
        } else {
 | 
						|
          // recursively call for curChild and it's children to see if we find something in
 | 
						|
          // fromNodesLookup
 | 
						|
          handleNodeAdded(curChild);
 | 
						|
        }
 | 
						|
 | 
						|
        curChild = nextSibling;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    function cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey) {
 | 
						|
      // We have processed all of the "to nodes". If curFromNodeChild is
 | 
						|
      // non-null then we still have some from nodes left over that need
 | 
						|
      // to be removed
 | 
						|
      while (curFromNodeChild) {
 | 
						|
        var fromNextSibling = curFromNodeChild.nextSibling;
 | 
						|
        if ((curFromNodeKey = getNodeKey(curFromNodeChild))) {
 | 
						|
          // Since the node is keyed it might be matched up later so we defer
 | 
						|
          // the actual removal to later
 | 
						|
          addKeyedRemoval(curFromNodeKey);
 | 
						|
        } else {
 | 
						|
          // NOTE: we skip nested keyed nodes from being removed since there is
 | 
						|
          //       still a chance they will be matched up later
 | 
						|
          removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
 | 
						|
        }
 | 
						|
        curFromNodeChild = fromNextSibling;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    function morphEl(fromEl, toEl, childrenOnly) {
 | 
						|
      var toElKey = getNodeKey(toEl);
 | 
						|
 | 
						|
      if (toElKey) {
 | 
						|
        // If an element with an ID is being morphed then it will be in the final
 | 
						|
        // DOM so clear it out of the saved elements collection
 | 
						|
        delete fromNodesLookup[toElKey];
 | 
						|
      }
 | 
						|
 | 
						|
      if (!childrenOnly) {
 | 
						|
        // optional
 | 
						|
        var beforeUpdateResult = onBeforeElUpdated(fromEl, toEl);
 | 
						|
        if (beforeUpdateResult === false) {
 | 
						|
          return;
 | 
						|
        } else if (beforeUpdateResult instanceof HTMLElement) {
 | 
						|
          fromEl = beforeUpdateResult;
 | 
						|
          // reindex the new fromEl in case it's not in the same
 | 
						|
          // tree as the original fromEl
 | 
						|
          // (Phoenix LiveView sometimes returns a cloned tree,
 | 
						|
          //  but keyed lookups would still point to the original tree)
 | 
						|
          indexTree(fromEl);
 | 
						|
        }
 | 
						|
 | 
						|
        // update attributes on original DOM element first
 | 
						|
        morphAttrs(fromEl, toEl);
 | 
						|
        // optional
 | 
						|
        onElUpdated(fromEl);
 | 
						|
 | 
						|
        if (onBeforeElChildrenUpdated(fromEl, toEl) === false) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      if (fromEl.nodeName !== 'TEXTAREA') {
 | 
						|
        morphChildren(fromEl, toEl);
 | 
						|
      } else {
 | 
						|
        specialElHandlers.TEXTAREA(fromEl, toEl);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    function morphChildren(fromEl, toEl) {
 | 
						|
      var skipFrom = skipFromChildren(fromEl, toEl);
 | 
						|
      var curToNodeChild = toEl.firstChild;
 | 
						|
      var curFromNodeChild = fromEl.firstChild;
 | 
						|
      var curToNodeKey;
 | 
						|
      var curFromNodeKey;
 | 
						|
 | 
						|
      var fromNextSibling;
 | 
						|
      var toNextSibling;
 | 
						|
      var matchingFromEl;
 | 
						|
 | 
						|
      // walk the children
 | 
						|
      outer: while (curToNodeChild) {
 | 
						|
        toNextSibling = curToNodeChild.nextSibling;
 | 
						|
        curToNodeKey = getNodeKey(curToNodeChild);
 | 
						|
 | 
						|
        // walk the fromNode children all the way through
 | 
						|
        while (!skipFrom && curFromNodeChild) {
 | 
						|
          fromNextSibling = curFromNodeChild.nextSibling;
 | 
						|
 | 
						|
          if (curToNodeChild.isSameNode && curToNodeChild.isSameNode(curFromNodeChild)) {
 | 
						|
            curToNodeChild = toNextSibling;
 | 
						|
            curFromNodeChild = fromNextSibling;
 | 
						|
            continue outer;
 | 
						|
          }
 | 
						|
 | 
						|
          curFromNodeKey = getNodeKey(curFromNodeChild);
 | 
						|
 | 
						|
          var curFromNodeType = curFromNodeChild.nodeType;
 | 
						|
 | 
						|
          // this means if the curFromNodeChild doesnt have a match with the curToNodeChild
 | 
						|
          var isCompatible = undefined;
 | 
						|
 | 
						|
          if (curFromNodeType === curToNodeChild.nodeType) {
 | 
						|
            if (curFromNodeType === ELEMENT_NODE) {
 | 
						|
              // Both nodes being compared are Element nodes
 | 
						|
 | 
						|
              if (curToNodeKey) {
 | 
						|
                // The target node has a key so we want to match it up with the correct element
 | 
						|
                // in the original DOM tree
 | 
						|
                if (curToNodeKey !== curFromNodeKey) {
 | 
						|
                  // The current element in the original DOM tree does not have a matching key so
 | 
						|
                  // let's check our lookup to see if there is a matching element in the original
 | 
						|
                  // DOM tree
 | 
						|
                  if ((matchingFromEl = fromNodesLookup[curToNodeKey])) {
 | 
						|
                    if (fromNextSibling === matchingFromEl) {
 | 
						|
                      // Special case for single element removals. To avoid removing the original
 | 
						|
                      // DOM node out of the tree (since that can break CSS transitions, etc.),
 | 
						|
                      // we will instead discard the current node and wait until the next
 | 
						|
                      // iteration to properly match up the keyed target element with its matching
 | 
						|
                      // element in the original tree
 | 
						|
                      isCompatible = false;
 | 
						|
                    } else {
 | 
						|
                      // We found a matching keyed element somewhere in the original DOM tree.
 | 
						|
                      // Let's move the original DOM node into the current position and morph
 | 
						|
                      // it.
 | 
						|
 | 
						|
                      // NOTE: We use insertBefore instead of replaceChild because we want to go through
 | 
						|
                      // the `removeNode()` function for the node that is being discarded so that
 | 
						|
                      // all lifecycle hooks are correctly invoked
 | 
						|
                      fromEl.insertBefore(matchingFromEl, curFromNodeChild);
 | 
						|
 | 
						|
                      // fromNextSibling = curFromNodeChild.nextSibling;
 | 
						|
 | 
						|
                      if (curFromNodeKey) {
 | 
						|
                        // Since the node is keyed it might be matched up later so we defer
 | 
						|
                        // the actual removal to later
 | 
						|
                        addKeyedRemoval(curFromNodeKey);
 | 
						|
                      } else {
 | 
						|
                        // NOTE: we skip nested keyed nodes from being removed since there is
 | 
						|
                        //       still a chance they will be matched up later
 | 
						|
                        removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
 | 
						|
                      }
 | 
						|
 | 
						|
                      curFromNodeChild = matchingFromEl;
 | 
						|
                      curFromNodeKey = getNodeKey(curFromNodeChild);
 | 
						|
                    }
 | 
						|
                  } else {
 | 
						|
                    // The nodes are not compatible since the "to" node has a key and there
 | 
						|
                    // is no matching keyed node in the source tree
 | 
						|
                    isCompatible = false;
 | 
						|
                  }
 | 
						|
                }
 | 
						|
              } else if (curFromNodeKey) {
 | 
						|
                // The original has a key
 | 
						|
                isCompatible = false;
 | 
						|
              }
 | 
						|
 | 
						|
              isCompatible = isCompatible !== false && compareNodeNames(curFromNodeChild, curToNodeChild);
 | 
						|
              if (isCompatible) {
 | 
						|
                // We found compatible DOM elements so transform
 | 
						|
                // the current "from" node to match the current
 | 
						|
                // target DOM node.
 | 
						|
                // MORPH
 | 
						|
                morphEl(curFromNodeChild, curToNodeChild);
 | 
						|
              }
 | 
						|
 | 
						|
            } else if (curFromNodeType === TEXT_NODE || curFromNodeType == COMMENT_NODE) {
 | 
						|
              // Both nodes being compared are Text or Comment nodes
 | 
						|
              isCompatible = true;
 | 
						|
              // Simply update nodeValue on the original node to
 | 
						|
              // change the text value
 | 
						|
              if (curFromNodeChild.nodeValue !== curToNodeChild.nodeValue) {
 | 
						|
                curFromNodeChild.nodeValue = curToNodeChild.nodeValue;
 | 
						|
              }
 | 
						|
 | 
						|
            }
 | 
						|
          }
 | 
						|
 | 
						|
          if (isCompatible) {
 | 
						|
            // Advance both the "to" child and the "from" child since we found a match
 | 
						|
            // Nothing else to do as we already recursively called morphChildren above
 | 
						|
            curToNodeChild = toNextSibling;
 | 
						|
            curFromNodeChild = fromNextSibling;
 | 
						|
            continue outer;
 | 
						|
          }
 | 
						|
 | 
						|
          // No compatible match so remove the old node from the DOM and continue trying to find a
 | 
						|
          // match in the original DOM. However, we only do this if the from node is not keyed
 | 
						|
          // since it is possible that a keyed node might match up with a node somewhere else in the
 | 
						|
          // target tree and we don't want to discard it just yet since it still might find a
 | 
						|
          // home in the final DOM tree. After everything is done we will remove any keyed nodes
 | 
						|
          // that didn't find a home
 | 
						|
          if (curFromNodeKey) {
 | 
						|
            // Since the node is keyed it might be matched up later so we defer
 | 
						|
            // the actual removal to later
 | 
						|
            addKeyedRemoval(curFromNodeKey);
 | 
						|
          } else {
 | 
						|
            // NOTE: we skip nested keyed nodes from being removed since there is
 | 
						|
            //       still a chance they will be matched up later
 | 
						|
            removeNode(curFromNodeChild, fromEl, true /* skip keyed nodes */);
 | 
						|
          }
 | 
						|
 | 
						|
          curFromNodeChild = fromNextSibling;
 | 
						|
        } // END: while(curFromNodeChild) {}
 | 
						|
 | 
						|
        // If we got this far then we did not find a candidate match for
 | 
						|
        // our "to node" and we exhausted all of the children "from"
 | 
						|
        // nodes. Therefore, we will just append the current "to" node
 | 
						|
        // to the end
 | 
						|
        if (curToNodeKey && (matchingFromEl = fromNodesLookup[curToNodeKey]) && compareNodeNames(matchingFromEl, curToNodeChild)) {
 | 
						|
          // MORPH
 | 
						|
          if(!skipFrom){ addChild(fromEl, matchingFromEl); }
 | 
						|
          morphEl(matchingFromEl, curToNodeChild);
 | 
						|
        } else {
 | 
						|
          var onBeforeNodeAddedResult = onBeforeNodeAdded(curToNodeChild);
 | 
						|
          if (onBeforeNodeAddedResult !== false) {
 | 
						|
            if (onBeforeNodeAddedResult) {
 | 
						|
              curToNodeChild = onBeforeNodeAddedResult;
 | 
						|
            }
 | 
						|
 | 
						|
            if (curToNodeChild.actualize) {
 | 
						|
              curToNodeChild = curToNodeChild.actualize(fromEl.ownerDocument || doc);
 | 
						|
            }
 | 
						|
            addChild(fromEl, curToNodeChild);
 | 
						|
            handleNodeAdded(curToNodeChild);
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        curToNodeChild = toNextSibling;
 | 
						|
        curFromNodeChild = fromNextSibling;
 | 
						|
      }
 | 
						|
 | 
						|
      cleanupFromEl(fromEl, curFromNodeChild, curFromNodeKey);
 | 
						|
 | 
						|
      var specialElHandler = specialElHandlers[fromEl.nodeName];
 | 
						|
      if (specialElHandler) {
 | 
						|
        specialElHandler(fromEl, toEl);
 | 
						|
      }
 | 
						|
    } // END: morphChildren(...)
 | 
						|
 | 
						|
    var morphedNode = fromNode;
 | 
						|
    var morphedNodeType = morphedNode.nodeType;
 | 
						|
    var toNodeType = toNode.nodeType;
 | 
						|
 | 
						|
    if (!childrenOnly) {
 | 
						|
      // Handle the case where we are given two DOM nodes that are not
 | 
						|
      // compatible (e.g. <div> --> <span> or <div> --> TEXT)
 | 
						|
      if (morphedNodeType === ELEMENT_NODE) {
 | 
						|
        if (toNodeType === ELEMENT_NODE) {
 | 
						|
          if (!compareNodeNames(fromNode, toNode)) {
 | 
						|
            onNodeDiscarded(fromNode);
 | 
						|
            morphedNode = moveChildren(fromNode, createElementNS(toNode.nodeName, toNode.namespaceURI));
 | 
						|
          }
 | 
						|
        } else {
 | 
						|
          // Going from an element node to a text node
 | 
						|
          morphedNode = toNode;
 | 
						|
        }
 | 
						|
      } else if (morphedNodeType === TEXT_NODE || morphedNodeType === COMMENT_NODE) { // Text or comment node
 | 
						|
        if (toNodeType === morphedNodeType) {
 | 
						|
          if (morphedNode.nodeValue !== toNode.nodeValue) {
 | 
						|
            morphedNode.nodeValue = toNode.nodeValue;
 | 
						|
          }
 | 
						|
 | 
						|
          return morphedNode;
 | 
						|
        } else {
 | 
						|
          // Text node to something else
 | 
						|
          morphedNode = toNode;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (morphedNode === toNode) {
 | 
						|
      // The "to node" was not compatible with the "from node" so we had to
 | 
						|
      // toss out the "from node" and use the "to node"
 | 
						|
      onNodeDiscarded(fromNode);
 | 
						|
    } else {
 | 
						|
      if (toNode.isSameNode && toNode.isSameNode(morphedNode)) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      morphEl(morphedNode, toNode, childrenOnly);
 | 
						|
 | 
						|
      // We now need to loop over any keyed nodes that might need to be
 | 
						|
      // removed. We only do the removal if we know that the keyed node
 | 
						|
      // never found a match. When a keyed node is matched up we remove
 | 
						|
      // it out of fromNodesLookup and we use fromNodesLookup to determine
 | 
						|
      // if a keyed node has been matched up or not
 | 
						|
      if (keyedRemovalList) {
 | 
						|
        for (var i=0, len=keyedRemovalList.length; i<len; i++) {
 | 
						|
          var elToRemove = fromNodesLookup[keyedRemovalList[i]];
 | 
						|
          if (elToRemove) {
 | 
						|
            removeNode(elToRemove, elToRemove.parentNode, false);
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (!childrenOnly && morphedNode !== fromNode && fromNode.parentNode) {
 | 
						|
      if (morphedNode.actualize) {
 | 
						|
        morphedNode = morphedNode.actualize(fromNode.ownerDocument || doc);
 | 
						|
      }
 | 
						|
      // If we had to swap out the from node with a new node because the old
 | 
						|
      // node was not compatible with the target node then we need to
 | 
						|
      // replace the old DOM node in the original DOM tree. This is only
 | 
						|
      // possible if the original DOM node was part of a DOM tree which
 | 
						|
      // we know is the case if it has a parent node.
 | 
						|
      fromNode.parentNode.replaceChild(morphedNode, fromNode);
 | 
						|
    }
 | 
						|
 | 
						|
    return morphedNode;
 | 
						|
  };
 | 
						|
}
 | 
						|
 | 
						|
var morphdom = morphdomFactory(morphAttrs);
 | 
						|
 | 
						|
module.exports = morphdom;
 |