/****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol */ var __assign = function() { __assign = Object.assign || function __assign(t) { var arguments$1 = arguments; for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments$1[i]; for (var p in s) { if (Object.prototype.hasOwnProperty.call(s, p)) { t[p] = s[p]; } } } return t; }; return __assign.apply(this, arguments); }; function __spreadArray(to, from, pack) { if (pack || arguments.length === 2) { for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) { ar = Array.prototype.slice.call(from, 0, i); } ar[i] = from[i]; } } } return to.concat(ar || Array.prototype.slice.call(from)); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var Diff = /** @class */ (function () { function Diff(options) { if (options === void 0) { options = {}; } var _this = this; Object.entries(options).forEach(function (_a) { var key = _a[0], value = _a[1]; return (_this[key] = value); }); } Diff.prototype.toString = function () { return JSON.stringify(this); }; Diff.prototype.setValue = function (aKey, aValue) { this[aKey] = aValue; return this; }; return Diff; }()); function checkElementType(element) { var arguments$1 = arguments; var elementTypeNames = []; for (var _i = 1; _i < arguments.length; _i++) { elementTypeNames[_i - 1] = arguments$1[_i]; } if (typeof element === "undefined" || element === null) { return false; } return elementTypeNames.some(function (elementTypeName) { var _a, _b; // We need to check if the specified type is defined // because otherwise instanceof throws an exception. return typeof ((_b = (_a = element === null || element === void 0 ? void 0 : element.ownerDocument) === null || _a === void 0 ? void 0 : _a.defaultView) === null || _b === void 0 ? void 0 : _b[elementTypeName]) === "function" && element instanceof element.ownerDocument.defaultView[elementTypeName]; }); } function objToNode(objNode, insideSvg, options) { var node; if (objNode.nodeName === "#text") { node = options.document.createTextNode(objNode.data); } else if (objNode.nodeName === "#comment") { node = options.document.createComment(objNode.data); } else { if (insideSvg) { node = options.document.createElementNS("http://www.w3.org/2000/svg", objNode.nodeName); if (objNode.nodeName === "foreignObject") { insideSvg = false; } } else if (objNode.nodeName.toLowerCase() === "svg") { node = options.document.createElementNS("http://www.w3.org/2000/svg", "svg"); insideSvg = true; } else { node = options.document.createElement(objNode.nodeName); } if (objNode.attributes) { Object.entries(objNode.attributes).forEach(function (_a) { var key = _a[0], value = _a[1]; return node.setAttribute(key, value); }); } if (objNode.childNodes) { node = node; objNode.childNodes.forEach(function (childNode) { return node.appendChild(objToNode(childNode, insideSvg, options)); }); } if (options.valueDiffing) { if (objNode.value && checkElementType(node, "HTMLButtonElement", "HTMLDataElement", "HTMLInputElement", "HTMLLIElement", "HTMLMeterElement", "HTMLOptionElement", "HTMLProgressElement", "HTMLParamElement")) { node.value = objNode.value; } if (objNode.checked && checkElementType(node, "HTMLInputElement")) { node.checked = objNode.checked; } if (objNode.selected && checkElementType(node, "HTMLOptionElement")) { node.selected = objNode.selected; } } } return node; } // ===== Apply a diff ===== var getFromRoute = function (node, route) { route = route.slice(); while (route.length > 0) { var c = route.splice(0, 1)[0]; node = node.childNodes[c]; } return node; }; function applyDiff(tree, diff, options) { var action = diff[options._const.action]; var route = diff[options._const.route]; var node; if (![options._const.addElement, options._const.addTextElement].includes(action)) { // For adding nodes, we calculate the route later on. It's different because it includes the position of the newly added item. node = getFromRoute(tree, route); } var newNode; var reference; var nodeArray; // pre-diff hook var info = { diff: diff, node: node }; if (options.preDiffApply(info)) { return true; } switch (action) { case options._const.addAttribute: if (!node || !checkElementType(node, "Element")) { return false; } node.setAttribute(diff[options._const.name], diff[options._const.value]); break; case options._const.modifyAttribute: if (!node || !checkElementType(node, "Element")) { return false; } node.setAttribute(diff[options._const.name], diff[options._const.newValue]); if (checkElementType(node, "HTMLInputElement") && diff[options._const.name] === "value") { node.value = diff[options._const.newValue]; } break; case options._const.removeAttribute: if (!node || !checkElementType(node, "Element")) { return false; } node.removeAttribute(diff[options._const.name]); break; case options._const.modifyTextElement: if (!node || !checkElementType(node, "Text")) { return false; } options.textDiff(node, node.data, diff[options._const.oldValue], diff[options._const.newValue]); if (checkElementType(node.parentNode, "HTMLTextAreaElement")) { node.parentNode.value = diff[options._const.newValue]; } break; case options._const.modifyValue: if (!node || typeof node.value === "undefined") { return false; } node.value = diff[options._const.newValue]; break; case options._const.modifyComment: if (!node || !checkElementType(node, "Comment")) { return false; } options.textDiff(node, node.data, diff[options._const.oldValue], diff[options._const.newValue]); break; case options._const.modifyChecked: if (!node || typeof node.checked === "undefined") { return false; } node.checked = diff[options._const.newValue]; break; case options._const.modifySelected: if (!node || typeof node.selected === "undefined") { return false; } node.selected = diff[options._const.newValue]; break; case options._const.replaceElement: { var insideSvg = diff[options._const.newValue].nodeName.toLowerCase() === "svg" || node.parentNode.namespaceURI === "http://www.w3.org/2000/svg"; node.parentNode.replaceChild(objToNode(diff[options._const.newValue], insideSvg, options), node); break; } case options._const.relocateGroup: nodeArray = __spreadArray([], new Array(diff[options._const.groupLength]), true).map(function () { return node.removeChild(node.childNodes[diff[options._const.from]]); }); nodeArray.forEach(function (childNode, index) { if (index === 0) { reference = node.childNodes[diff[options._const.to]]; } node.insertBefore(childNode, reference || null); }); break; case options._const.removeElement: node.parentNode.removeChild(node); break; case options._const.addElement: { var parentRoute = route.slice(); var c = parentRoute.splice(parentRoute.length - 1, 1)[0]; node = getFromRoute(tree, parentRoute); if (!checkElementType(node, "Element")) { return false; } node.insertBefore(objToNode(diff[options._const.element], node.namespaceURI === "http://www.w3.org/2000/svg", options), node.childNodes[c] || null); break; } case options._const.removeTextElement: { if (!node || node.nodeType !== 3) { return false; } var parentNode = node.parentNode; parentNode.removeChild(node); if (checkElementType(parentNode, "HTMLTextAreaElement")) { parentNode.value = ""; } break; } case options._const.addTextElement: { var parentRoute = route.slice(); var c = parentRoute.splice(parentRoute.length - 1, 1)[0]; newNode = options.document.createTextNode(diff[options._const.value]); node = getFromRoute(tree, parentRoute); if (!node.childNodes) { return false; } node.insertBefore(newNode, node.childNodes[c] || null); if (checkElementType(node.parentNode, "HTMLTextAreaElement")) { node.parentNode.value = diff[options._const.value]; } break; } default: console.log("unknown action"); } // if a new node was created, we might be interested in its // post diff hook options.postDiffApply({ diff: info.diff, node: info.node, newNode: newNode }); return true; } function applyDOM(tree, diffs, options) { return diffs.every(function (diff) { return applyDiff(tree, diff, options); }); } // ===== Undo a diff ===== function swap(obj, p1, p2) { var tmp = obj[p1]; obj[p1] = obj[p2]; obj[p2] = tmp; } function undoDiff(tree, diff, options) { switch (diff[options._const.action]) { case options._const.addAttribute: diff[options._const.action] = options._const.removeAttribute; applyDiff(tree, diff, options); break; case options._const.modifyAttribute: swap(diff, options._const.oldValue, options._const.newValue); applyDiff(tree, diff, options); break; case options._const.removeAttribute: diff[options._const.action] = options._const.addAttribute; applyDiff(tree, diff, options); break; case options._const.modifyTextElement: swap(diff, options._const.oldValue, options._const.newValue); applyDiff(tree, diff, options); break; case options._const.modifyValue: swap(diff, options._const.oldValue, options._const.newValue); applyDiff(tree, diff, options); break; case options._const.modifyComment: swap(diff, options._const.oldValue, options._const.newValue); applyDiff(tree, diff, options); break; case options._const.modifyChecked: swap(diff, options._const.oldValue, options._const.newValue); applyDiff(tree, diff, options); break; case options._const.modifySelected: swap(diff, options._const.oldValue, options._const.newValue); applyDiff(tree, diff, options); break; case options._const.replaceElement: swap(diff, options._const.oldValue, options._const.newValue); applyDiff(tree, diff, options); break; case options._const.relocateGroup: swap(diff, options._const.from, options._const.to); applyDiff(tree, diff, options); break; case options._const.removeElement: diff[options._const.action] = options._const.addElement; applyDiff(tree, diff, options); break; case options._const.addElement: diff[options._const.action] = options._const.removeElement; applyDiff(tree, diff, options); break; case options._const.removeTextElement: diff[options._const.action] = options._const.addTextElement; applyDiff(tree, diff, options); break; case options._const.addTextElement: diff[options._const.action] = options._const.removeTextElement; applyDiff(tree, diff, options); break; default: console.log("unknown action"); } } function undoDOM(tree, diffs, options) { diffs = diffs.slice(); diffs.reverse(); diffs.forEach(function (diff) { undoDiff(tree, diff, options); }); } var elementDescriptors = function (el) { var output = []; output.push(el.nodeName); if (el.nodeName !== "#text" && el.nodeName !== "#comment") { el = el; if (el.attributes) { if (el.attributes["class"]) { output.push("".concat(el.nodeName, ".").concat(el.attributes["class"].replace(/ /g, "."))); } if (el.attributes.id) { output.push("".concat(el.nodeName, "#").concat(el.attributes.id)); } } } return output; }; var findUniqueDescriptors = function (li) { var uniqueDescriptors = {}; var duplicateDescriptors = {}; li.forEach(function (node) { elementDescriptors(node).forEach(function (descriptor) { var inUnique = descriptor in uniqueDescriptors; var inDupes = descriptor in duplicateDescriptors; if (!inUnique && !inDupes) { uniqueDescriptors[descriptor] = true; } else if (inUnique) { delete uniqueDescriptors[descriptor]; duplicateDescriptors[descriptor] = true; } }); }); return uniqueDescriptors; }; var uniqueInBoth = function (l1, l2) { var l1Unique = findUniqueDescriptors(l1); var l2Unique = findUniqueDescriptors(l2); var inBoth = {}; Object.keys(l1Unique).forEach(function (key) { if (l2Unique[key]) { inBoth[key] = true; } }); return inBoth; }; var removeDone = function (tree) { delete tree.outerDone; delete tree.innerDone; delete tree.valueDone; if (tree.childNodes) { return tree.childNodes.every(removeDone); } else { return true; } }; var cleanNode = function (diffNode) { if (Object.prototype.hasOwnProperty.call(diffNode, "data")) { var textNode = { nodeName: diffNode.nodeName === "#text" ? "#text" : "#comment", data: diffNode.data }; return textNode; } else { var elementNode = { nodeName: diffNode.nodeName }; diffNode = diffNode; if (Object.prototype.hasOwnProperty.call(diffNode, "attributes")) { elementNode.attributes = __assign({}, diffNode.attributes); } if (Object.prototype.hasOwnProperty.call(diffNode, "checked")) { elementNode.checked = diffNode.checked; } if (Object.prototype.hasOwnProperty.call(diffNode, "value")) { elementNode.value = diffNode.value; } if (Object.prototype.hasOwnProperty.call(diffNode, "selected")) { elementNode.selected = diffNode.selected; } if (Object.prototype.hasOwnProperty.call(diffNode, "childNodes")) { elementNode.childNodes = diffNode.childNodes.map(function (diffChildNode) { return cleanNode(diffChildNode); }); } return elementNode; } }; var isEqual = function (e1, e2) { if (!["nodeName", "value", "checked", "selected", "data"].every(function (element) { if (e1[element] !== e2[element]) { return false; } return true; })) { return false; } if (Object.prototype.hasOwnProperty.call(e1, "data")) { // Comment or Text return true; } e1 = e1; e2 = e2; if (Boolean(e1.attributes) !== Boolean(e2.attributes)) { return false; } if (Boolean(e1.childNodes) !== Boolean(e2.childNodes)) { return false; } if (e1.attributes) { var e1Attributes = Object.keys(e1.attributes); var e2Attributes = Object.keys(e2.attributes); if (e1Attributes.length !== e2Attributes.length) { return false; } if (!e1Attributes.every(function (attribute) { if (e1.attributes[attribute] !== e2.attributes[attribute]) { return false; } return true; })) { return false; } } if (e1.childNodes) { if (e1.childNodes.length !== e2.childNodes.length) { return false; } if (!e1.childNodes.every(function (childNode, index) { return isEqual(childNode, e2.childNodes[index]); })) { return false; } } return true; }; var roughlyEqual = function (e1, e2, uniqueDescriptors, sameSiblings, preventRecursion) { if (preventRecursion === void 0) { preventRecursion = false; } if (!e1 || !e2) { return false; } if (e1.nodeName !== e2.nodeName) { return false; } if (["#text", "#comment"].includes(e1.nodeName)) { // Note that we initially don't care what the text content of a node is, // the mere fact that it's the same tag and "has text" means it's roughly // equal, and then we can find out the true text difference later. return preventRecursion ? true : e1.data === e2.data; } e1 = e1; e2 = e2; if (e1.nodeName in uniqueDescriptors) { return true; } if (e1.attributes && e2.attributes) { if (e1.attributes.id) { if (e1.attributes.id !== e2.attributes.id) { return false; } else { var idDescriptor = "".concat(e1.nodeName, "#").concat(e1.attributes.id); if (idDescriptor in uniqueDescriptors) { return true; } } } if (e1.attributes["class"] && e1.attributes["class"] === e2.attributes["class"]) { var classDescriptor = "".concat(e1.nodeName, ".").concat(e1.attributes["class"].replace(/ /g, ".")); if (classDescriptor in uniqueDescriptors) { return true; } } } if (sameSiblings) { return true; } var nodeList1 = e1.childNodes ? e1.childNodes.slice().reverse() : []; var nodeList2 = e2.childNodes ? e2.childNodes.slice().reverse() : []; if (nodeList1.length !== nodeList2.length) { return false; } if (preventRecursion) { return nodeList1.every(function (element, index) { return element.nodeName === nodeList2[index].nodeName; }); } else { // note: we only allow one level of recursion at any depth. If 'preventRecursion' // was not set, we must explicitly force it to true for child iterations. var childUniqueDescriptors_1 = uniqueInBoth(nodeList1, nodeList2); return nodeList1.every(function (element, index) { return roughlyEqual(element, nodeList2[index], childUniqueDescriptors_1, true, true); }); } }; /** * based on https://en.wikibooks.org/wiki/Algorithm_implementation/Strings/Longest_common_substring#JavaScript */ var findCommonSubsets = function (c1, c2, marked1, marked2) { var lcsSize = 0; var index = []; var c1Length = c1.length; var c2Length = c2.length; var // set up the matching table matches = __spreadArray([], new Array(c1Length + 1), true).map(function () { return []; }); var uniqueDescriptors = uniqueInBoth(c1, c2); var // If all of the elements are the same tag, id and class, then we can // consider them roughly the same even if they have a different number of // children. This will reduce removing and re-adding similar elements. subsetsSame = c1Length === c2Length; if (subsetsSame) { c1.some(function (element, i) { var c1Desc = elementDescriptors(element); var c2Desc = elementDescriptors(c2[i]); if (c1Desc.length !== c2Desc.length) { subsetsSame = false; return true; } c1Desc.some(function (description, i) { if (description !== c2Desc[i]) { subsetsSame = false; return true; } }); if (!subsetsSame) { return true; } }); } // fill the matches with distance values for (var c1Index = 0; c1Index < c1Length; c1Index++) { var c1Element = c1[c1Index]; for (var c2Index = 0; c2Index < c2Length; c2Index++) { var c2Element = c2[c2Index]; if (!marked1[c1Index] && !marked2[c2Index] && roughlyEqual(c1Element, c2Element, uniqueDescriptors, subsetsSame)) { matches[c1Index + 1][c2Index + 1] = matches[c1Index][c2Index] ? matches[c1Index][c2Index] + 1 : 1; if (matches[c1Index + 1][c2Index + 1] >= lcsSize) { lcsSize = matches[c1Index + 1][c2Index + 1]; index = [c1Index + 1, c2Index + 1]; } } else { matches[c1Index + 1][c2Index + 1] = 0; } } } if (lcsSize === 0) { return false; } return { oldValue: index[0] - lcsSize, newValue: index[1] - lcsSize, length: lcsSize }; }; var makeBooleanArray = function (n, v) { return __spreadArray([], new Array(n), true).map(function () { return v; }); }; /** * Generate arrays that indicate which node belongs to which subset, * or whether it's actually an orphan node, existing in only one * of the two trees, rather than somewhere in both. * * So if t1 =
, t2 =
. * The longest subset is "
" (length 2), so it will group 0. * The second longest is "" (length 1), so it will be group 1. * gaps1 will therefore be [1,0,0] and gaps2 [0,0,1]. * * If an element is not part of any group, it will stay being 'true', which * is the initial value. For example: * t1 =


, t2 =
* * The "

" and "" do only show up in one of the two and will * therefore be marked by "true". The remaining parts are parts of the * groups 0 and 1: * gaps1 = [1, true, 0, 0], gaps2 = [true, 0, 0, 1] * */ var getGapInformation = function (t1, t2, stable) { var gaps1 = t1.childNodes ? makeBooleanArray(t1.childNodes.length, true) : []; var gaps2 = t2.childNodes ? makeBooleanArray(t2.childNodes.length, true) : []; var group = 0; // give elements from the same subset the same group number stable.forEach(function (subset) { var endOld = subset.oldValue + subset.length; var endNew = subset.newValue + subset.length; for (var j = subset.oldValue; j < endOld; j += 1) { gaps1[j] = group; } for (var j = subset.newValue; j < endNew; j += 1) { gaps2[j] = group; } group += 1; }); return { gaps1: gaps1, gaps2: gaps2 }; }; /** * Find all matching subsets, based on immediate child differences only. */ var markBoth = function (marked1, marked2, subset, i) { marked1[subset.oldValue + i] = true; marked2[subset.newValue + i] = true; }; var markSubTrees = function (oldTree, newTree) { // note: the child lists are views, and so update as we update old/newTree var oldChildren = oldTree.childNodes ? oldTree.childNodes : []; var newChildren = newTree.childNodes ? newTree.childNodes : []; var marked1 = makeBooleanArray(oldChildren.length, false); var marked2 = makeBooleanArray(newChildren.length, false); var subsets = []; var returnIndex = function () { return arguments[1]; }; var foundAllSubsets = false; var _loop_1 = function () { var subset = findCommonSubsets(oldChildren, newChildren, marked1, marked2); if (subset) { subsets.push(subset); var subsetArray = __spreadArray([], new Array(subset.length), true).map(returnIndex); subsetArray.forEach(function (item) { return markBoth(marked1, marked2, subset, item); }); } else { foundAllSubsets = true; } }; while (!foundAllSubsets) { _loop_1(); } oldTree.subsets = subsets; oldTree.subsetsAge = 100; return subsets; }; var DiffTracker = /** @class */ (function () { function DiffTracker() { this.list = []; } DiffTracker.prototype.add = function (diffs) { var _a; (_a = this.list).push.apply(_a, diffs); }; DiffTracker.prototype.forEach = function (fn) { this.list.forEach(function (li) { return fn(li); }); }; return DiffTracker; }()); // ===== Apply a virtual diff ===== function getFromVirtualRoute(tree, route) { var node = tree; var parentNode; var nodeIndex; route = route.slice(); while (route.length > 0) { nodeIndex = route.splice(0, 1)[0]; parentNode = node; node = node.childNodes ? node.childNodes[nodeIndex] : undefined; } return { node: node, parentNode: parentNode, nodeIndex: nodeIndex }; } function applyVirtualDiff(tree, diff, options) { var _a; var node, parentNode, nodeIndex; if (![options._const.addElement, options._const.addTextElement].includes(diff[options._const.action])) { // For adding nodes, we calculate the route later on. It's different because it includes the position of the newly added item. var routeInfo = getFromVirtualRoute(tree, diff[options._const.route]); node = routeInfo.node; parentNode = routeInfo.parentNode; nodeIndex = routeInfo.nodeIndex; } var newSubsets = []; // pre-diff hook var info = { diff: diff, node: node }; if (options.preVirtualDiffApply(info)) { return true; } var newNode; var nodeArray; var route; switch (diff[options._const.action]) { case options._const.addAttribute: if (!node.attributes) { node.attributes = {}; } node.attributes[diff[options._const.name]] = diff[options._const.value]; if (diff[options._const.name] === "checked") { node.checked = true; } else if (diff[options._const.name] === "selected") { node.selected = true; } else if (node.nodeName === "INPUT" && diff[options._const.name] === "value") { node.value = diff[options._const.value]; } break; case options._const.modifyAttribute: node.attributes[diff[options._const.name]] = diff[options._const.newValue]; break; case options._const.removeAttribute: delete node.attributes[diff[options._const.name]]; if (Object.keys(node.attributes).length === 0) { delete node.attributes; } if (diff[options._const.name] === "checked") { node.checked = false; } else if (diff[options._const.name] === "selected") { delete node.selected; } else if (node.nodeName === "INPUT" && diff[options._const.name] === "value") { delete node.value; } break; case options._const.modifyTextElement: node.data = diff[options._const.newValue]; if (parentNode.nodeName === "TEXTAREA") { parentNode.value = diff[options._const.newValue]; } break; case options._const.modifyValue: node.value = diff[options._const.newValue]; break; case options._const.modifyComment: node.data = diff[options._const.newValue]; break; case options._const.modifyChecked: node.checked = diff[options._const.newValue]; break; case options._const.modifySelected: node.selected = diff[options._const.newValue]; break; case options._const.replaceElement: newNode = cleanNode(diff[options._const.newValue]); parentNode.childNodes[nodeIndex] = newNode; break; case options._const.relocateGroup: nodeArray = node.childNodes .splice(diff[options._const.from], diff[options._const.groupLength]) .reverse(); nodeArray.forEach(function (movedNode) { return node.childNodes.splice(diff[options._const.to], 0, movedNode); }); if (node.subsets) { node.subsets.forEach(function (map) { if (diff[options._const.from] < diff[options._const.to] && map.oldValue <= diff[options._const.to] && map.oldValue > diff[options._const.from]) { map.oldValue -= diff[options._const.groupLength]; var splitLength = map.oldValue + map.length - diff[options._const.to]; if (splitLength > 0) { // new insertion splits map. newSubsets.push({ oldValue: diff[options._const.to] + diff[options._const.groupLength], newValue: map.newValue + map.length - splitLength, length: splitLength }); map.length -= splitLength; } } else if (diff[options._const.from] > diff[options._const.to] && map.oldValue > diff[options._const.to] && map.oldValue < diff[options._const.from]) { map.oldValue += diff[options._const.groupLength]; var splitLength = map.oldValue + map.length - diff[options._const.to]; if (splitLength > 0) { // new insertion splits map. newSubsets.push({ oldValue: diff[options._const.to] + diff[options._const.groupLength], newValue: map.newValue + map.length - splitLength, length: splitLength }); map.length -= splitLength; } } else if (map.oldValue === diff[options._const.from]) { map.oldValue = diff[options._const.to]; } }); } break; case options._const.removeElement: parentNode.childNodes.splice(nodeIndex, 1); if (parentNode.subsets) { parentNode.subsets.forEach(function (map) { if (map.oldValue > nodeIndex) { map.oldValue -= 1; } else if (map.oldValue === nodeIndex) { map["delete"] = true; } else if (map.oldValue < nodeIndex && map.oldValue + map.length > nodeIndex) { if (map.oldValue + map.length - 1 === nodeIndex) { map.length--; } else { newSubsets.push({ newValue: map.newValue + nodeIndex - map.oldValue, oldValue: nodeIndex, length: map.length - nodeIndex + map.oldValue - 1 }); map.length = nodeIndex - map.oldValue; } } }); } node = parentNode; break; case options._const.addElement: { route = diff[options._const.route].slice(); var c_1 = route.splice(route.length - 1, 1)[0]; node = (_a = getFromVirtualRoute(tree, route)) === null || _a === void 0 ? void 0 : _a.node; newNode = cleanNode(diff[options._const.element]); if (!node.childNodes) { node.childNodes = []; } if (c_1 >= node.childNodes.length) { node.childNodes.push(newNode); } else { node.childNodes.splice(c_1, 0, newNode); } if (node.subsets) { node.subsets.forEach(function (map) { if (map.oldValue >= c_1) { map.oldValue += 1; } else if (map.oldValue < c_1 && map.oldValue + map.length > c_1) { var splitLength = map.oldValue + map.length - c_1; newSubsets.push({ newValue: map.newValue + map.length - splitLength, oldValue: c_1 + 1, length: splitLength }); map.length -= splitLength; } }); } break; } case options._const.removeTextElement: parentNode.childNodes.splice(nodeIndex, 1); if (parentNode.nodeName === "TEXTAREA") { delete parentNode.value; } if (parentNode.subsets) { parentNode.subsets.forEach(function (map) { if (map.oldValue > nodeIndex) { map.oldValue -= 1; } else if (map.oldValue === nodeIndex) { map["delete"] = true; } else if (map.oldValue < nodeIndex && map.oldValue + map.length > nodeIndex) { if (map.oldValue + map.length - 1 === nodeIndex) { map.length--; } else { newSubsets.push({ newValue: map.newValue + nodeIndex - map.oldValue, oldValue: nodeIndex, length: map.length - nodeIndex + map.oldValue - 1 }); map.length = nodeIndex - map.oldValue; } } }); } node = parentNode; break; case options._const.addTextElement: { route = diff[options._const.route].slice(); var c_2 = route.splice(route.length - 1, 1)[0]; newNode = { nodeName: "#text", data: diff[options._const.value] }; node = getFromVirtualRoute(tree, route).node; if (!node.childNodes) { node.childNodes = []; } if (c_2 >= node.childNodes.length) { node.childNodes.push(newNode); } else { node.childNodes.splice(c_2, 0, newNode); } if (node.nodeName === "TEXTAREA") { node.value = diff[options._const.newValue]; } if (node.subsets) { node.subsets.forEach(function (map) { if (map.oldValue >= c_2) { map.oldValue += 1; } if (map.oldValue < c_2 && map.oldValue + map.length > c_2) { var splitLength = map.oldValue + map.length - c_2; newSubsets.push({ newValue: map.newValue + map.length - splitLength, oldValue: c_2 + 1, length: splitLength }); map.length -= splitLength; } }); } break; } default: console.log("unknown action"); } if (node.subsets) { node.subsets = node.subsets.filter(function (map) { return !map["delete"] && map.oldValue !== map.newValue; }); if (newSubsets.length) { node.subsets = node.subsets.concat(newSubsets); } } options.postVirtualDiffApply({ node: info.node, diff: info.diff, newNode: newNode }); return; } function applyVirtual(tree, diffs, options) { diffs.forEach(function (diff) { applyVirtualDiff(tree, diff, options); }); return true; } function nodeToObj(aNode, options) { if (options === void 0) { options = { valueDiffing: true }; } var objNode = { nodeName: aNode.nodeName }; if (checkElementType(aNode, "Text", "Comment")) { objNode.data = aNode.data; } else { if (aNode.attributes && aNode.attributes.length > 0) { objNode.attributes = {}; var nodeArray = Array.prototype.slice.call(aNode.attributes); nodeArray.forEach(function (attribute) { return (objNode.attributes[attribute.name] = attribute.value); }); } if (aNode.childNodes && aNode.childNodes.length > 0) { objNode.childNodes = []; var nodeArray = Array.prototype.slice.call(aNode.childNodes); nodeArray.forEach(function (childNode) { return objNode.childNodes.push(nodeToObj(childNode, options)); }); } if (options.valueDiffing) { if (checkElementType(aNode, "HTMLTextAreaElement")) { objNode.value = aNode.value; } if (checkElementType(aNode, "HTMLInputElement") && ["radio", "checkbox"].includes(aNode.type.toLowerCase()) && aNode.checked !== undefined) { objNode.checked = aNode.checked; } else if (checkElementType(aNode, "HTMLButtonElement", "HTMLDataElement", "HTMLInputElement", "HTMLLIElement", "HTMLMeterElement", "HTMLOptionElement", "HTMLProgressElement", "HTMLParamElement")) { objNode.value = aNode.value; } if (checkElementType(aNode, "HTMLOptionElement")) { objNode.selected = aNode.selected; } } } return objNode; } // from html-parse-stringify (MIT) var tagRE = /<\s*\/*[a-zA-Z:_][a-zA-Z0-9:_\-.]*\s*(?:"[^"]*"['"]*|'[^']*'['"]*|[^'"/>])*\/*\s*>|/g; var attrRE = /\s([^'"/\s><]+?)[\s/>]|([^\s=]+)=\s?(".*?"|'.*?')/g; function unescape(string) { return string .replace(/</g, "<") .replace(/>/g, ">") .replace(/&/g, "&"); } // create optimized lookup object for // void elements as listed here: // https://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements var lookup = { area: true, base: true, br: true, col: true, embed: true, hr: true, img: true, input: true, keygen: true, link: true, menuItem: true, meta: true, param: true, source: true, track: true, wbr: true }; var parseTag = function (tag, caseSensitive) { var res = { nodeName: "", attributes: {} }; var voidElement = false; var type = "tag"; var tagMatch = tag.match(/<\/?([^\s]+?)[/\s>]/); if (tagMatch) { res.nodeName = caseSensitive || tagMatch[1] === "svg" ? tagMatch[1] : tagMatch[1].toUpperCase(); if (lookup[tagMatch[1]] || tag.charAt(tag.length - 2) === "/") { voidElement = true; } // handle comment tag if (res.nodeName.startsWith("!--")) { var endIndex = tag.indexOf("-->"); return { type: "comment", node: { nodeName: "#comment", data: endIndex !== -1 ? tag.slice(4, endIndex) : "" }, voidElement: voidElement }; } } var reg = new RegExp(attrRE); var result = null; var done = false; while (!done) { result = reg.exec(tag); if (result === null) { done = true; } else if (result[0].trim()) { if (result[1]) { var attr = result[1].trim(); var arr = [attr, ""]; if (attr.indexOf("=") > -1) { arr = attr.split("="); } res.attributes[arr[0]] = arr[1]; reg.lastIndex--; } else if (result[2]) { res.attributes[result[2]] = result[3] .trim() .substring(1, result[3].length - 1); } } } return { type: type, node: res, voidElement: voidElement }; }; var stringToObj = function (html, options) { if (options === void 0) { options = { valueDiffing: true, caseSensitive: false }; } var result = []; var current; var level = -1; var arr = []; var inComponent = false, insideSvg = false; // handle text at top level if (html.indexOf("<") !== 0) { var end = html.indexOf("<"); result.push({ nodeName: "#text", data: end === -1 ? html : html.substring(0, end) }); } html.replace(tagRE, function (tag, index) { var isOpen = tag.charAt(1) !== "/"; var isComment = tag.startsWith("