/** * Check is item is object */ const isObject = (val) => Object.prototype.toString.call(val) === "[object Object]"; /** * Check for valid JSON string */ const isJson = (str) => { let t = !1; try { t = JSON.parse(str); } catch (e) { return !1; } return !(null === t || (!Array.isArray(t) && !isObject(t))) && t; }; /** * Create DOM element node */ const createElement = (nodeName, attrs) => { const dom = document.createElement(nodeName); if (attrs && "object" == typeof attrs) { for (const attr in attrs) { if ("html" === attr) { dom.innerHTML = attrs[attr]; } else { dom.setAttribute(attr, attrs[attr]); } } } return dom; }; const objToText = (obj) => { if (["#text", "#comment"].includes(obj.nodeName)) { return obj.data; } if (obj.childNodes) { return obj.childNodes.map((childNode) => objToText(childNode)).join(""); } return ""; }; const escapeText = function (text) { return text .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); }; const visibleToColumnIndex = function (visibleIndex, columns) { let counter = 0; let columnIndex = 0; while (counter < (visibleIndex + 1)) { const columnSettings = columns[columnIndex]; if (!columnSettings.hidden) { counter += 1; } columnIndex += 1; } return columnIndex - 1; }; const columnToVisibleIndex = function (columnIndex, columns) { let visibleIndex = columnIndex; let counter = 0; while (counter < columnIndex) { const columnSettings = columns[counter]; if (columnSettings.hidden) { visibleIndex -= 1; } counter++; } return visibleIndex; }; 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); } 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 && (node instanceof HTMLButtonElement || node instanceof HTMLDataElement || node instanceof HTMLInputElement || node instanceof HTMLLIElement || node instanceof HTMLMeterElement || node instanceof HTMLOptionElement || node instanceof HTMLProgressElement || node instanceof HTMLParamElement)) { node.value = objNode.value; } if (objNode.checked && node instanceof HTMLInputElement) { node.checked = objNode.checked; } if (objNode.selected && node instanceof 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 // {preDiffApply, postDiffApply, textDiff, valueDiffing, _const} ) { 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 || !(node instanceof Element)) { return false; } node.setAttribute(diff[options._const.name], diff[options._const.value]); break; case options._const.modifyAttribute: if (!node || !(node instanceof Element)) { return false; } node.setAttribute(diff[options._const.name], diff[options._const.newValue]); if (node instanceof HTMLInputElement && diff[options._const.name] === "value") { node.value = diff[options._const.newValue]; } break; case options._const.removeAttribute: if (!node || !(node instanceof Element)) { return false; } node.removeAttribute(diff[options._const.name]); break; case options._const.modifyTextElement: if (!node || !(node instanceof Text)) { return false; } options.textDiff(node, node.data, diff[options._const.oldValue], diff[options._const.newValue]); if (node.parentNode instanceof 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 || !(node instanceof 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 = Array.apply(void 0, new Array(diff[options._const.groupLength])).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 (!(node instanceof 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 (parentNode instanceof 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 (node.parentNode instanceof 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 // {preDiffApply, postDiffApply, textDiff, valueDiffing, _const} ) { 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 = 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 = Array.apply(void 0, new Array(c1Length + 1)).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 Array.apply(void 0, new Array(n)).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 = Array.apply(void 0, new Array(subset.length)).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; }()); //export const elementHasValue = (element: Element) : boolean => element instanceof HTMLButtonElement || element instanceof HTMLDataElement || element instanceof HTMLInputElement || element instanceof HTMLLIElement || element instanceof HTMLMeterElement || element instanceof HTMLOptionElement || element instanceof HTMLProgressElement || element instanceof HTMLParamElement 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; }()); // ===== 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 // {preVirtualDiffApply, postVirtualDiffApply, _const} ) { 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 = 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 = 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 = {}; newNode.nodeName = "#text"; newNode.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 (aNode instanceof Text || aNode instanceof 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 (aNode instanceof HTMLTextAreaElement) { objNode.value = aNode.value; } if (aNode instanceof HTMLInputElement && ["radio", "checkbox"].includes(aNode.type.toLowerCase()) && aNode.checked !== undefined) { objNode.checked = aNode.checked; } else if (aNode instanceof HTMLButtonElement || aNode instanceof HTMLDataElement || aNode instanceof HTMLInputElement || aNode instanceof HTMLLIElement || aNode instanceof HTMLMeterElement || aNode instanceof HTMLOptionElement || aNode instanceof HTMLProgressElement || aNode instanceof HTMLParamElement) { objNode.value = aNode.value; } if (aNode instanceof 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) { var res = { nodeName: "", attributes: {} }; var voidElement = false; var type = "tag"; var tagMatch = tag.match(/<\/?([^\s]+?)[/\s>]/); if (tagMatch) { res.nodeName = tagMatch[1]; 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 }; } var result = []; var current; var level = -1; var arr = []; var inComponent = 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("