"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=syncTree;var _transaction=_interopRequireDefault(require("../transaction"));var _process=_interopRequireDefault(require("../util/process"));var _types=require("../util/types");function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}const{max}=Math;const keyNames=["old","new"];const textName="#text";/**
 * Compares how the new state should look to the old state and mutates it,
 * while recording the changes along the way.
 *
 * @param {Partial<VTree> | null} oldTree
 * @param {Partial<VTree> | null=} newTree
 * @param {any[]} patches
 * @param {Partial<TransactionState>} state
 * @param {Transaction} transaction
 * @param {boolean=} attributesOnly
 *
 * @return {any[] | false | null}
 */function syncTree(oldTree,newTree,patches=[],state={},transaction=_types.EMPTY.OBJ,attributesOnly){if(!oldTree)oldTree=/** @type {VTree} */_types.EMPTY.OBJ;if(!newTree)newTree=/** @type {VTree} */_types.EMPTY.OBJ;const{svgElements=new Set}=state;const oldNodeName=oldTree.nodeName;const newNodeName=newTree.nodeName;const isEmpty=oldTree===_types.EMPTY.OBJ||attributesOnly;// Check for SVG in parent.
const isSVG=newNodeName==="svg"||svgElements.has(/** @type {VTree} */newTree);let shortCircuit=null;// Invoke the SyncTree hooks which allow middleware to do their own custom
// sync logic. This may mean short-circuiting the sync completely by
// returning the previous tree. This effectively sandboxes an element in the
// DOM from changes or diffs. Another useful way to use these hooks are to
// take the new tree, and convert it into something else. This is how
// components are implemented.
if(_types.SyncTreeHookCache.size){_types.SyncTreeHookCache.forEach(fn=>{// Call the user provided middleware function for a single root node.
// Allow the consumer to specify a return value of a different VTree
// (useful for components).
const entry=fn(oldTree,newTree,transaction);// If the value returned matches the original element, then short circuit
// and do not dig further.
if(entry&&entry===oldTree){shortCircuit=patches}// Allow skipping an element for diffing. It will be preserved and kept
// in the DOM.
else if(entry===false){shortCircuit=false}// If the user has returned a value, treat it as the new tree.
else if(entry){newTree=entry}})}if(shortCircuit!==null||!newTree){return shortCircuit}// Text nodes are low level and frequently change, so this path is accounted
// for first.
if(newNodeName===textName){if(oldNodeName===textName&&oldTree.nodeValue!==newTree.nodeValue){// If both VTrees are text nodes and the values are different, change the
// `Element#nodeValue`.
patches.push(_types.PATCH_TYPE.NODE_VALUE,oldTree,newTree.nodeValue,oldTree.nodeValue);oldTree.nodeValue=newTree.nodeValue;return patches}// Ensure new text nodes have decoded entities.
else if(isEmpty){patches.push(_types.PATCH_TYPE.NODE_VALUE,newTree,newTree.nodeValue,null);return patches}}const newChildNodes=newTree.childNodes||[];// Seek out attribute changes first, but only from element Nodes.
if(newTree.nodeType===_types.NODE_TYPE.ELEMENT){const oldAttributes=isEmpty?_types.EMPTY.OBJ:oldTree.attributes;const newAttributes=newTree.attributes||{};// Search for sets and changes.
for(let key in newAttributes){const value=newAttributes[key];if(key in oldAttributes&&oldAttributes[key]===newAttributes[key]){continue}if(!isEmpty){oldAttributes[key]=value}// Skip script types as these are handled special by the transaction
// patch and end tasks.
if(// Do not block existing scripts being modified.
(!oldTree||oldTree.nodeName!=="script")&&newTree.nodeName==="script"&&key==="type"){continue}patches.push(_types.PATCH_TYPE.SET_ATTRIBUTE,isEmpty?newTree:oldTree,key,value)}// Search for removals.
if(!isEmpty){for(let key in oldAttributes){if(key in newAttributes){continue}patches.push(_types.PATCH_TYPE.REMOVE_ATTRIBUTE,oldTree,key);delete oldAttributes[key]}}}// Scan all childNodes for attribute changes.
if(attributesOnly){// Do a single pass over the new child nodes.
for(let i=0;i<newChildNodes.length;i++){// Ensure all SVG elements are tracked.
isSVG&&svgElements.add(newChildNodes[i]);syncTree(null,newChildNodes[i],patches,state,transaction,true)}return patches}/** @type {any} */const keysLookup={old:new Map,new:new Map};// Reduce duplicate logic by condensing old and new operations in a loop.
for(let i=0;i<keyNames.length;i++){const keyName=keyNames[i];const map=keysLookup[keyName];const vTree=arguments[i];const nodes=vTree&&vTree.childNodes;if(nodes&&nodes.length){for(let i=0;i<nodes.length;i++){const vTree=nodes[i];if(vTree.key){if(_process.default.env.NODE_ENV!=="production"){if(map.has(vTree.key)){throw new Error(`Key: ${vTree.key} cannot be duplicated`)}}map.set(vTree.key,vTree)}}}}/** @type {VTree[]} */const oldChildNodes=oldTree.childNodes||[];// Used when skipping over nodes during syncTreeHook.
let maxLength=max(newChildNodes.length,oldChildNodes.length);// Always perform a full diff based on the largest set of nodes. The end user
// may want to do something with any of them, so while it may be more
// performant to only loop over the new nodes, we loop over all instead.
for(let i=0;i<maxLength;i++){const oldChildNode=oldChildNodes&&oldChildNodes[i];const newChildNode=newChildNodes[i];// Check for SVG in child as well.
if(isSVG||newChildNode&&newChildNode.nodeName==="svg"){svgElements.add(newChildNode)}// If there is no new child node, we will skip this comparison. If the old
// node is something we want to preserve, we can migrate it to the new
// nodes.
if(!newChildNode){if(syncTree(oldChildNode,null,patches,state,transaction,true)===false){newChildNodes.splice(i,0,oldChildNode)}continue}// If there is no old element to compare to, this is a simple addition.
if(!oldChildNode){oldChildNodes.push(newChildNode);// Crawl this Node for any changes to apply.
syncTree(null,newChildNode,patches,state,transaction,true);patches.push(_types.PATCH_TYPE.INSERT_BEFORE,oldTree,newChildNode,null);continue}const newKey=newChildNode.key;const oldKey=oldChildNode.key;const oldInNew=keysLookup.new.has(oldKey);const newInOld=keysLookup.old.has(newKey);// If we are working with keys, we can follow an optimized path.
if(oldKey||newKey){// Remove the old node instead of replacing.
if(!oldInNew&&!newInOld){oldChildNodes.splice(oldChildNodes.indexOf(oldChildNode),1,newChildNode);syncTree(null,newChildNode,patches,state,transaction,true);patches.push(_types.PATCH_TYPE.REPLACE_CHILD,newChildNode,oldChildNode);i=i-1;continue}else if(!oldInNew){patches.push(_types.PATCH_TYPE.REMOVE_CHILD,oldChildNode);oldChildNodes.splice(oldChildNodes.indexOf(oldChildNode),1);i=i-1;continue}// If there is a key set for this new element, use that to figure out
// which element to use.
if(newKey!==oldKey){let optimalNewNode=newChildNode;// Prefer existing to new and remove from old position.
if(newKey&&newInOld){optimalNewNode=keysLookup.old.get(newKey);oldChildNodes.splice(oldChildNodes.indexOf(optimalNewNode),1)}else{optimalNewNode=newChildNode}// Crawl this Node for any changes to apply.
syncTree(null,optimalNewNode,patches,state,transaction,true);patches.push(_types.PATCH_TYPE.INSERT_BEFORE,oldTree,optimalNewNode,oldChildNode);oldChildNodes.splice(i,0,optimalNewNode);continue}}const sameType=oldChildNode.nodeName===newChildNode.nodeName;const retVal=syncTree(oldChildNode,newChildNode,patches,state,transaction,!sameType);if(retVal===false){newChildNodes.splice(i,0,oldChildNode);maxLength+=1;continue}// If the element we're replacing is totally different from the previous
// replace the entire element, don't bother investigating children.
if(!sameType){oldChildNodes[i]=newChildNode;// This only works if VTrees are identical.
const lookupIndex=oldChildNodes.lastIndexOf(newChildNode);if(lookupIndex>i){oldChildNodes.splice(lookupIndex,1)}patches.push(_types.PATCH_TYPE.REPLACE_CHILD,newChildNode,oldChildNode)}}// We've reconciled new changes, so we can remove any old nodes and adjust
// lengths to be equal.
if(oldChildNodes.length!==newChildNodes.length){for(let i=newChildNodes.length;i<oldChildNodes.length;i++){patches.push(_types.PATCH_TYPE.REMOVE_CHILD,oldChildNodes[i])}oldChildNodes.length=newChildNodes.length}return patches}module.exports=exports.default;